Mach-Oラむブラリでの関数のリダむレクト

前の蚘事では、共有ELFラむブラリのコヌルピックアップ方法に぀いお説明したした。 次に、 Mach-O-圢匏のラむブラリで同じこずを行う方法を芋おいきたす。



状況を簡単に思い出したす。 Mac OS X甚のプログラムがありたす。これは、倚くのサヌドパヌティのダむナミックリンクラむブラリを䜿甚し、そのラむブラリは互いの機胜も䜿甚したす。



タスクは次のずおりです。あるラむブラリから別のラむブラリぞの関数の呌び出しをむンタヌセプトし、ハンドラヌで元の関数を呌び出したす。



い぀ものように、せっかちな人は今すぐすべおをダりンロヌドしお詊すこずができたす 。



わかりやすくするために、架空の䟋を瀺したす。C蚀語の「test」ず呌ばれるプログラムファむルtest.cず、事前にコンパむルされた内容が倉曎されおいない共有ラむブラリファむルlibtest.cがありたす。 このラむブラリは、1぀の関数libtestを提䟛したす。 実装では、それぞれが暙準のC蚀語ラむブラリlibSystem.B.dylibに含たれるMac OSに付属のputs関数を䜿甚したす。 説明した状況の抂略図を芋おみたしょう。



タスクは次のずおりです。

  1. libtest.dylibラむブラリのputs関数呌び出しを、メむンプログラムtest.cファむルに実装されたhooked_puts関数呌び出しに眮き換える必芁がありたす。この関数呌び出しは、元のputsを䜿甚できたす。

  2. 行われた倉曎を砎棄したす。぀たり、libtestの繰り返し呌び出しが元のputの呌び出しに぀ながるこずを確認したす。

同時に、コヌドの倉曎たたはラむブラリ自䜓の再コンパむルは蚱可されず、メむンプログラムのみが蚱可されたす。 呌び出しのリダむレクト自䜓は、プログラムを再起動せずに、特定のラむブラリに察しおのみ、オンザフラむで実行する必芁がありたす。



Mach-Oに぀いお簡単に



Mach-Oを理解するための最良の方法は、以䞋の画像を芋るこずです。



人類はただその構造をより明確に描写できおいないようです。 最初の近䌌では、すべおが次のようになりたす。

  1. タむトル-タヌゲットアヌキテクチャに関する情報ず、ファむルの内容をさらに解釈するためのさたざたなオプションがここに保存されたす。
  2. ダりンロヌドコマンド-最初にロヌドするために、Mach-Oパヌツをダりンロヌドする方法ず堎所、セグメント以䞋を参照、シンボルテヌブル、およびこのファむルが䟝存するラむブラリを瀺したす。
  3. セグメント-セクションにコヌドたたはデヌタをロヌドするメモリ領域を蚘述したす。
パヌサヌナヌティリティ


2番目の近䌌に぀いおは、いく぀かのナヌティリティに粟通する必芁がありたす。





抂しお、䞀般ナヌザヌにずっおMach-Oを理解するには、さたざたな䟋でMachOViewを詊しおください。 しかし、ヘッダヌ、ロヌドコマンド、セグメント、セクション、シンボルテヌブル、およびそれらのフィヌルドの正確な説明の正確な構造は䞍明であるため、これはMach-Oプログラミングには十分ではありたせん。 しかし、これは仕様に関する倧きな問題ではありたせん。 そしお、公匏のApple Webサむトでい぀でも利甚できたす 。 たた、開発ツヌルをむンストヌルしおいる堎合は、/ usr / include / mach-o特にloader.hからヘッダヌファむルを確認できたす。



さらに、ファむルの内容はディスク䞊ずたったく同じ順序でメモリ内にありたすが、リンカはシンボルテヌブルの䞀郚、行のテヌブル党䜓を削陀し、ブヌト時にメモリ内の実際のオフセットの倀を眮くこずができたす。必芁に応じお、ファむル内でこれらの倀を通垞れロにするか、ディスク䞊のオフセットに察応させるこずができたす。



ヘッダヌ構造は単玔です32ビットアヌキテクチャの堎合は匕甚したすが、64ビットはそれほど倉わりたせん。



struct mach_header { uint32_t magic; cpu_type_t cputype; cpu_subtype_t cpusubtype; uint32_t filetype; uint32_t ncmds; uint32_t sizeofcmds; uint32_t flags; };
      
      





すべおはマゞック倀で始たりたす機械語のバむト順序に関する合意に応じお、0xFEEDFACEたたはその逆。 次に、プロセッサアヌキテクチャのタむプ、ブヌトコマンドの数ずサむズ、および他の機胜を説明するフラグが衚瀺されたす。



䟋





必須のブヌトコマンドは次のずおりです。

䟋それぞれ32ビットバヌゞョンず64ビットバヌゞョン



最も重芁なセグメントは次のずおりです。

ダりンロヌドコマンドは、次のフィヌルドで始たりたす。

 struct load_command { uint32_t cmd; //   uint32_t cmdsize; //     };
      
      





その埌、コマンドのタむプに応じお、さらに倚くの異なるフィヌルドがありたす。



䟋





これらのセグメントで最も興味深いセクションは次のずおりです。

将来的には、1぀のMach-Oでは、むンポヌトテヌブルは__IMPORT、__ jump_table32ビット、Mac OS 10.5、たたは__DATA、__ la_symbol_ptr64ビット、たたはMac OS 10.6以前のいずれかになりたす。



セグメント内のセクションの構造は次のずおりです。

 struct section { char sectname[16]; char segname[16]; uint32_t addr; uint32_t size; uint32_t offset; uint32_t align; uint32_t reloff; uint32_t nreloc; uint32_t flags; uint32_t reserved1; uint32_t reserved2; };
      
      





セグメントの名前ずセクション自䜓、サむズ、ファむル内のオフセット、ダむナミックロヌダヌが配眮したメモリ内のアドレスがありたす。 さらに、特定のセクションに固有の他の情報がありたす。



䟋





ファットバむナリ



もちろん、AppleがタヌゲットアヌキテクチャMotorola-> IBM-> Intelのスムヌズな移行を繰り返した結果、実行可胜ファむルずラむブラリは、耇数のバヌゞョンの実行可胜コヌドを䞀床に栌玍するために「孊習」したこずに蚀及する䟡倀がありたす。 䞀般に、これらのファむルはfat binaryず呌ばれたす。 実際、これらは1぀のファむルに集められたいく぀かのMach-Oですが、そのヘッダヌは特別です。 サポヌトされおいるアヌキテクチャの数ず皮類、および各アヌキテクチャぞのオフセットに関する情報が含たれおいたす。 このオフセットには、䞊蚘の構造を持぀通垞のMach-Oがありたす。



Cでは次のようになりたす。

 struct fat_header { uint32_t magic; uint32_t nfat_arch; };
      
      





0xCAFEBABEが魔法の䞋に隠されおいる堎合たたはその逆-異なるプロセッサ䞊のマシン語の異なるバむト順に぀いお芚えおおいおください。 そしおその埌、その型のちょうどnfat_arch構造がすぐに続きたす

 struct fat_arch { cpu_type_t cputype; cpu_subtype_t cpusubtype; uint32_t offset; uint32_t size; uint32_t align; };
      
      





実際には、フィヌルド名は、プロセッサの皮類、特定のMach-Oのファむル内のオフセット、サむズ、および配眮を衚しおいたす。



実隓プログラム



むンポヌトされた関数を呌び出す操䜜を調べるために、Cで次のファむルを取埗したす。



ファむルtest.c
 void libtest(); //from libtest.dylib int main() { libtest(); //calls puts() from libSystem.B.dylib return 0; }
      
      







libtest.cファむル
 #include <stdio.h> void libtest() //just a simple library function { puts("libtest: calls the original puts()"); }
      
      





動的レむアりトを探玢する



Intelプロセッサに限定されおいたす。 Mac OS 10.5を入手したしょう。 これらのファむルを新しいXcodeプロゞェクトに远加し、コンパむル32ビットバヌゞョンしおデバッグモヌドで実行し、libtest.dylibラむブラリのlibtest関数でputs関数が呌び出される行で停止したす。 libtestのアセンブラリストを次に瀺したす。







もう1぀の呜什を実行したしょう。







そしお圌女の蚘憶を芋おください







これは、むンポヌトテヌブルのセルこの堎合、セル__IMPORT、__ jump_tableであり、レむトバむンディング遅延バむンディングが䜿甚されおいる堎合、たたはタヌゲット関数にすぐにゞャンプする堎合、ダむナミックロヌダヌ関数__dyld_stub_binding_helper_interfaceを呌び出すためのスプリングボヌドずしお機胜したす。 これは、その埌のputsの呌び出しによっお確認されたす。







そしおメモリ内







そのため、ダむナミックロヌダヌがCALL間接呌び出し呜什0xE8をJMP間接ゞャンプ呜什0xE9に眮き換えたこずがわかりたす。 したがっお、__ jump_table芁玠をリダむレクトするには、初期コンテンツの代わりに、眮換関数の先頭ぞの間接遷移の呜什を芏定するだけで十分です。



もう䞀぀の興味深い点。 JMPがダむナミックロヌダヌ別名リンカヌぞの切り替えに䜿甚されないのはなぜですか はい。スタックに戻りアドレスを栌玍するCALLは、むンポヌトテヌブルのどの芁玠が呌び出し元を呌び出したかをリンカが刀断するのに圹立぀ためです。 したがっお、必芁な機胜の間接JMPを䜿甚しおCALLをそれ自䜓に倉曎するこずにより、どのようなシンボルであるかを蚈算し、有効にしたす。



次に、プロゞェクトをMac OS 10.6に転送し、32ビットおよび64ビットアヌキテクチャ甚のファットバむナリをコンパむルしたす。 念のため、Xcodeでは次のようにしたす。







コンパむルし、64ビットバヌゞョンを実行しおたずえば、Snow Leopardのむンポヌトテヌブルは32ビットず同じになりたす、puts呌び出しで再び停止したす。







繰り返したすが、単玔なCALLです。 さらに調査したす。







ここで、通垞の__IMPORT、__ jump_tableずの違いはすでに顕著です。



__TEXT、__ symbol_stub1ぞようこそ。 この衚は、むンポヌトされた各関数のJMP呜什のセットです。 私たちの堎合、䞊蚘のような指瀺は1぀しかありたせん。 このような各呜什は、__ DATA、__ la_symbol_ptrテヌブルの察応するセルで指定されたアドレスに移行したす。 埌者は、このMach-Oのむンポヌトテヌブルです。



しかし、研究を続けたしょう。 移行が行われるアドレスを芋るず







次に、以䞋が衚瀺されたす。







__TEXT、__ stub_helperセクションに入りたす。 これは基本的に、Mach-OのPLTプロシヌゞャリンケヌゞテヌブルです。 最初の呜什この堎合、R11ず組み合わせたLEA、たたは単玔なPUSHがあった可胜性がありたす、動的リンカヌは再割り圓おが必芁なシンボルを蚘憶し、2番目の呜什は垞に同じアドレスに぀ながりたす-バむンディングを凊理する__dyld_stub_binding_helper関数の開始







動的リンカヌがputsの再配眮を実行するず、__ DATA、__ la_symbol_ptrの察応するセルは次のようになりたす。







そしお、これはlibSystem.B.dylibモゞュヌルのputs関数のアドレスです。 ぀たり、アドレスに眮き換えるず、コヌルをリダむレクトするずいう望たしい効果が埗られたす。



だから。 この段階で、動的リンクの発生方法、Mach-Oのむンポヌトテヌブルの皮類、およびそれらがどの芁玠で構成されおいるかを特定の䟋を䜿甚しお確認したした。 それでは、Mach-Oの解析に取りかかりたしょう



むンポヌトテヌブルでアむテムを怜玢する



むンポヌトテヌブルで、シンボルの名前で察応するセルを芋぀ける必芁がありたす。 このアクションのアルゎリズムはやや重芁です。



たず、キャラクタヌテヌブルでキャラクタヌ自䜓を芋぀ける必芁がありたす。 埌者は、次の構造の配列です。

 struct nlist { union { int32_t n_strx; } n_un; uint8_t n_type; uint8_t n_sect; int16_t n_desc; uint32_t n_value; };
      
      





n_un.n_strxは、この文字の名前の文字列テヌブルの先頭からのバむト単䜍のオフセットです。 残りは、シンボルのタむプ、シンボルが配眮されおいるセクションなどに関係したす。 ぀たり、実隓ラむブラリlibtest.dylib32ビットバヌゞョンの最埌の芁玠の䞀郚を次に瀺したす。







行テヌブルは、それぞれがれロで終わる名前のチェヌンです。 ただし、コンパむラは各名前の先頭にアンダヌスコア「_」を远加するため、たずえば、文字列テヌブルでは「puts」ずいう名前は「_puts」のようになりたす。



以䞋に䟋を瀺したす。





察応するロヌドコマンドLC_SYMTABから文字ず文字列のテヌブルの堎所を芋぀けるこずができたす。







ただし、シンボルテヌブルは均䞀ではありたせん。 いく぀かのセクションがありたす。 そのうちの1぀は特に興味深いものです。これらは未定矩未定矩の文字、぀たり動的にリンクされおいる文字です。 ずころで、MachOViewは背景が青みがかったものを匷調衚瀺したす。 シンボルテヌブルのどの郚分が未定矩のシンボルのサブセットを反映しおいるかを刀断するには、動的シンボルロヌドコマンドLC_DYSYMTABを調べる必芁がありたす。







Cでの圌女のプレれンテヌションは次のずおりです。

 struct dysymtab_command { uint32_t cmd; uint32_t cmdsize; uint32_t ilocalsym; uint32_t nlocalsym; uint32_t iextdefsym; uint32_t nextdefsym; uint32_t iundefsym; uint32_t nundefsym; uint32_t tocoff; uint32_t ntoc; uint32_t modtaboff; uint32_t nmodtab; uint32_t extrefsymoff; uint32_t nextrefsyms; uint32_t indirectsymoff; uint32_t nindirectsyms; uint32_t extreloff; uint32_t nextrel; uint32_t locreloff; uint32_t nlocrel; };
      
      



ここで、dysymtab_command.iundefsymは、未定矩の文字のサブセットで始たる文字テヌブルのむンデックスです。 dysymtab_command.nundefsym-未定矩の文字の数。 探しおいるのは意図的に䞍定のシンボルなので、このサブセットのシンボルテヌブルでのみ怜玢する必芁がありたす。



そしお今、非垞に重芁な点名前でシンボルを芋぀けるこず、私たちにずっお最も重芁なこずは、シンボルテヌブルのむンデックスを最初から芚えるこずです。 これらのむンデックスの数倀は別の重芁なテヌブルであるため、間接シンボルのテヌブルです。 dysymtab_command.indirectsymoffの倀で芋぀けるこずができ、むンデックスの数によっおdysymtab_command.nindirectsymsが決たりたす。



些现なケヌスでは、このテヌブルは1぀の芁玠のみで構成されおいたす実際にはさらに倚くの芁玠がありたす。







最埌に、最埌に芋぀ける必芁がある芁玠の__IMPORT、__ jump_tableセクションを芋おみたしょう。 次のようになりたす。







このセクションのsection.reserved1フィヌルドは非垞に重芁ですMachOViewはIndirect Sym Indexず呌ばれたす。 これは、__ jump_table芁玠で1察1の察応が始たる間接シンボルテヌブルのむンデックスを意味したす。 たた、間接シンボルテヌブルの芁玠は、シンボルテヌブルのむンデックスであるこずを芚えおいたす。 私が埗おいるものをキャッチしたすか



しかし、最終的にすべおの知識を収集する前に、画像を完成させるために、__ DATA、__ la_symbol_ptrがむンポヌトテヌブルの圹割を果たすSnow Leopardの状況を簡単に芋おみたしょう。 実際、違いはあたり目立ちたせん。



以䞋は、キャラクタヌの読み蟌みコマンドです。







そしお、ここに圌女の最埌の芁玠がありたす







ダむナミックシンボルロヌドコマンドLC_DYSYMTABからのデヌタに察応する2぀のあいたいな文字が青みがかった背景に衚瀺されたす。







たた、間接シンボルの衚には、すでに1぀の芁玠ではなく、4぀の芁玠がありたす。







ただし、秘蔵セクション__la_symbol_ptrのreserved1フィヌルドを芋るず、間接シンボルのテヌブルでの芁玠の1察1の反映は、最埌の芁玠の先頭からではなく、4番目の芁玠むンデックスは3から始たるこずがわかりたす。







__la_symbol_ptrセクションで説明されおいるむンポヌトテヌブルの内容は次のずおりです。







これらすべおのMach-Oの埮劙さを知ったので、むンポヌトテヌブルで目的のアむテムを芋぀けるためのアルゎリズムを定匏化できたす。



リダむレクトアルゎリズム



すべおのアクションを蚀葉で説明したす。コメントが豊富にあるにもかかわらず、コヌドはそれほど明確ではない可胜性があるためです。
  1. LC_SYMTAB loadコマンドからのデヌタに埓っお、文字ず行のテヌブルを芋぀けたす。
  2. LC_DYSYMTABブヌトコマンドから、未定矩のシンボルのサブセットが開始するシンボルテヌブルの芁玠iundefsymフィヌルドから孊習したす。
  3. 文字テヌブル内の未定矩文字のサブセットの䞭から、名前でタヌゲット文字を探しおいたす。
  4. シンボルテヌブルの先頭からタヌゲットシンボルのむンデックスを芚えおいたす。
  5. LC_DYSYMTABロヌドコマンドindirectsymoffフィヌルドからのデヌタを䜿甚しお、間接シンボルのテヌブルを芋぀けたす。
  6. 間接シンボルテヌブルreserved1フィヌルドで、むンポヌトテヌブルの衚瀺を開始するむンデックス__DATA、__ la_symbol_ptrセクションの内容たたは__IMPORT、__ jump_table-1぀ありたすを芋぀けたす。
  7. このむンデックスから始めお、間接シンボルテヌブルを芋お、シンボルテヌブル内のタヌゲットシンボルのむンデックスに察応する倀を探したす。
  8. むンポヌトテヌブルが間接シンボルテヌブルに衚瀺されたずきに、最初からタヌゲットシンボルがキャッチされた方法を芚えおいたす。 栌玍されおいる倀は、むンポヌトテヌブル内の目的のアむテムのむンデックスです。
  9. __la_symbol_ptrたたは__jump_tableセクションのデヌタによるず、むンポヌトテヌブルオフセットフィヌルドが芋぀かりたす。
  10. タヌゲット芁玠のむンデックスが含たれおいるので、アドレス__la_symbol_ptrの堎合を必芁な倀に曞き換えたすたたはオペランドを䜿甚しおCALL / JMP呜什をJMPに倉曎したす-必芁な関数のアドレス__jump_tableの堎合。
ファむルから読み蟌むこずによっおのみ、文字、文字列、および間接文字のテヌブルを操䜜する必芁があるこずに泚意しおください。 そしお、むンポヌトテヌブルを説明するセクションの内容を読み、そしおもちろん、既にメモリにある自身をリダむレクトしたす。 これは、文字テヌブルず文字列テヌブルがタヌゲットMach-Oの実際の状態を衚瀺する堎合ず衚瀺しない堎合があるためです。 結局、ダむナミックロヌダヌは私たちの前でそこで働き、テヌブル自䜓を配眮するこずなく、シンボルに関する必芁なすべおのデヌタを安党に保存したした。



リダむレクトの実装



あなたの考えをコヌドに倉える時です。 リダむレクトごずに必芁なMach-O芁玠の怜玢を最適化するために、操䜜党䜓を3぀の段階に分割したす。

  1.  void *mach_hook_init(char const *library_filename, void const *library_address);
          
          



    Mach-Oファむル自䜓ずメモリ内の衚瀺に基づいお、この関数は、むンポヌトテヌブルぞのオフセット、文字、行のテヌブル、および動的文字のテヌブルからの間接文字の衚瀺である䞍透明な蚘述子を返したす。このモゞュヌルの。 この蚘述子は次のずおりです。

     struct mach_hook_handle { void const *library_address; //base address of a library in memory char const *string_table; //buffer to read string_table table from file struct nlist const *symbol_table; //buffer to read symbol table from file uint32_t const *indirect_table; //buffer to read the indirect symbol table in dynamic symbol table from file uint32_t undefined_symbols_count; //number of undefined symbols in the symbol table uint32_t undefined_symbols_index; //position of undefined symbols in the symbol table uint32_t indirect_symbols_count; //number of indirect symbols in the indirect symbol table of DYSYMTAB uint32_t indirect_symbols_index; //index of the first imported symbol in the indirect symbol table of DYSYMTAB uint32_t import_table_offset; //the offset of (__DATA, __la_symbol_ptr) or (__IMPORT, __jump_table) uint32_t jump_table_present; //special flag to show if we work with (__IMPORT, __jump_table) };
          
          



  2.  mach_substitution mach_hook(void const *handle, char const *function_name, mach_substitution substitution);
          
          



    この関数は、䜿甚可胜なラむブラリ蚘述子、タヌゲットシンボルの名前、むンタヌセプタヌのアドレスに埓っお、䞊蚘のアルゎリズムに埓っおリダむレクト自䜓を実行したす。

  3.  void mach_hook_free(void *handle);
          
          



    これは、mach_hook_initを返したハンドルをクリヌンアップしたす。


これらのプロトタむプを考えるず、テストプログラムを曞き盎す必芁がありたす。

 #include <stdio.h> #include <dlfcn.h> #include "mach_hook.h" #define LIBTEST_PATH "libtest.dylib" void libtest(); //from libtest.dylib int hooked_puts(char const *s) { puts(s); //calls the original puts() from libSystem.B.dylib, because our main executable module called "test" remains intact return puts("HOOKED!"); } int main() { void *handle = 0; //handle to store hook-related info mach_substitution original; //original data for restoration Dl_info info; if (!dladdr((void const *)libtest, &info)) //gets an address of a library which contains libtest() function { fprintf(stderr, "Failed to get the base address of a library!\n", LIBTEST_PATH); goto end; } handle = mach_hook_init(LIBTEST_PATH, info.dli_fbase); if (!handle) { fprintf(stderr, "Redirection init failed!\n"); goto end; } libtest(); //calls puts() from libSystem.B.dylib puts("-----------------------------"); original = mach_hook(handle, "puts", (mach_substitution)hooked_puts); if (!original) { fprintf(stderr, "Redirection failed!\n"); goto end; } libtest(); //calls hooked_puts() puts("-----------------------------"); original = mach_hook(handle, "puts", original); //restores the original relocation if (!original) { fprintf(stderr, "Restoration failed!\n"); goto end; } libtest(); //again calls puts() from libSystem.B.dylib end: mach_hook_free(handle); handle = 0; //no effect here, but just a good advice to prevent double freeing return 0; }
      
      





テストケヌスの完党な実装は、リダむレクトアルゎリズムずプロゞェクトファむルず共にダりンロヌドできたす 。



詊運転



次のようなものを詊しおください

 user@mac$ arch -i386 ./test libtest: calls the original puts() ----------------------------- libtest: calls the original puts() HOOKED! ----------------------------- libtest: calls the original puts()
      
      





 user@mac$ arch -x86_64 ./test libtest: calls the original puts() ----------------------------- libtest: calls the original puts() HOOKED! ----------------------------- libtest: calls the original puts()
      
      





プログラムの結論は、最初に蚭定されたタスクが完党に実行されたこずを瀺しおいたす。



䟿利なリンク

頑匵っお



All Articles