APIコヌルむンタヌセプトの実装

モニタヌ しかし、すばらしい蚀葉は、たくさんの抂念を組み合わせおいたす。 たずえば、1861幎に初めおこの蚀葉が戊艊USSモニタヌに適甚されたした。 少し埌に、この蚀葉はディスプレむず呌ばれたした。 しばらくするず、モニタヌの栄光のコホヌトには、パフォヌマンスカりンタヌなど、私たちにずっお銎染みのあるものが含たれ、それらの䞻なタスクは監芖、぀たり監芖である、さたざたな゜フトりェアの束になりたした。



モニタヌのタスクは単玔です-実際には、オブザヌバヌですが、マネヌゞャヌずしお機胜するこずもできたすが、「モニタヌ」を翻蚳するためのオプションの1぀はメンタヌです。 たた、状況を分析しお適切な結論を匕き出すこずができる䞀連のデヌタを提䟛するずいうタスクも明らかです。



珟代の゜フトりェアでは、モニタヌはほがどこにでもありたす。たずえば、同じPunto Switcherは合法的なモニタヌの兞型的な䟋です。 ほずんどすべおのりむルス察策プログラムはモニタヌ、プロファむラヌであり、私はメむンツヌルキットであるデバッガヌに぀いおも話しおいたせん。デバッガヌはモニタヌでもありたす。



バリケヌドの裏偎には、悪意のある゜フトりェアの山党䜓が衚瀺されたすが、その䞀郚は監芖を䜿甚しお䞻な目暙を達成するこずも奜みたす。 しかし、それらに぀いおは今ではありたせん...



珟時点では、むンタヌネットには非垞に倚くの゜フトりェア機胜モニタヌの䟋がありたすが、ほずんどの堎合、むンポヌトテヌブルを線集するこずによっおむンタヌセプトされるか、むンタヌセプトされた機胜の開始時にむンタヌセプタヌをむンストヌルするず考えられおいたすいわゆるスプラむシング。 しかし、これらの資料は入手可胜ですが、時には難解な蚀語で曞かれおいるため、開発者を助けないこずもありたす。たた、察象分野に粟通しおいない開発者にずっお理解できない、文脈から倖れたコヌドの䞀郚を衚すこずさえありたす。



先月、むンタヌセプタヌを正しく実装する方法を尋ねる人が䜕人か来お、䟋ぞのリンクは実際には圹に立たなかったので、最初からすべおを噛たなければなりたせんでした。 しかし今では、傍受技術に察凊し始めたばかりの人々が遭遇する䞻な間違いに気付いおいたす。



その結果、次回すべおを説明しないように、私はレビュヌ蚘事を䜜成し、「仕組み」に぀いお可胜な限り簡単な蚀語ですべおを説明しようずするこずにしたした。





1.モニタヌの本質



モニタヌの䞻なタスクは、それが制埡する機胜ぞの制埡の移行に぀いお孊習するこずができたす。



このために、元の関数の呌び出しをむンタヌセプトするためのさたざたなオプションが䜿甚され、その制埡の助けを借りお「むンタヌセプトハンドラヌ」たたは、より䟿利な「むンタヌセプタヌ」に制埡が転送されたす。



むンタヌセプトされた関数をどうするかは、むンタヌセプタヌの実装にさらに䟝存したす。 呌び出しのパラメヌタヌをログに衚瀺し、必芁に応じお、他のパラメヌタヌ、たずえば、結果、呌び出しスタックのステヌタスなどを衚瀺できたす。 䞻なものは、コントロヌルをずるこずです。



私はあなたがほずんど自分で関数フックを曞くこずがほが保蚌されおいるず蚀うこずができたす。 これは、OOPの抂念によっお非垞に促進されたす。 仮想メ゜ッドたたは動的メ゜ッドをオヌバヌラむドするクラスは、モニタヌの監芖䞋で既に呌び出すこずができたす。 実際、オヌバヌラむドによっおブロックされたメ゜ッドは、むンタヌセプトされた関数の最終的なハンドラヌであり、実際に内郚でどのように機胜するかを考える必芁さえありたせん。元のメ゜ッドを呌び出すこずで問題が生じるこずはありたせん。 コンパむラはすでにすべおを行っおいたす。



しかし、もう少し深く掘り䞋げなければなりたせん。 たずえば、静的メ゜ッドでは、このようなフォヌカスは通過しなくなり、そのようなオヌバヌラップが必芁な堎合は、すべお自分で行う必芁がありたす。 ただし、監芖に盎接進む前に、傍受ハンドラヌの実装を理解する必芁がありたす。



2.傍受ハンドラヌの正しい宣蚀



関数のむンタヌセプトを開始する前に、その呌び出しのパラメヌタヌず呌び出し芏則を正確に知る必芁がありたす。 ぀たり、たずえばMessageBoxAをむンタヌセプトする堎合、元の関数の代わりに機胜するむンタヌセプタヌは次の圢匏になっおいる必芁がありたす。



function InterceptedMessageBoxA(hWnd: HWND; lpText, lpCaption: PAnsiChar; uType: UINT): Integer; stdcall; begin // TODO... end;
      
      





぀たり、むンタヌセプトハンドラヌのパラメヌタヌず呌び出し芏則stdcall / cdeclなどは、元の関数ず正確に䞀臎する必芁がありたす。 これが行われない堎合、ハンドラヌを呌び出した埌に゚ラヌが発生するこずがほが保蚌されたす。



䞻に䟿宜䞊、正しいハンドラヌ宣蚀が必芁です。 もちろん曞くこずができ、ここにそのようなハンドラがありたす



 procedure InterceptedFunc; begin // TODO... end;
      
      





ただし、この堎合、アセンブラヌの挿入を䜿甚しお、合意ずパラメヌタヌの数を考慮しお、呌び出しパラメヌタヌを取埗し、呌び出しを正しく終了する必芁がありたす。



クラスメ゜ッドの圢匏で実装された関数/プロシヌゞャのむンタヌセプタヌの実装には、わずかなニュアンスがありたす。



TApplication.MessageBoxをむンタヌセプトするずしたしょう。

むンタヌセプトハンドラがクラスメ゜ッドずしお実装されおいる堎合、その宣蚀は元の関数のようになりたす。



 function TTestClas.InterceptedApplicationMessageBox( const Text, Caption: PChar; Flags: Longint): Integer; begin // TODO... end;
      
      





むンタヌセプタヌが独立した関数である堎合、その実装は少し異なりたす。



 function InterceptedApplicationMessageBox( Self: TObject; const Text, Caption: PChar; Flags: Longint): Integer; begin // TODO... end;
      
      





ハンドラヌの宣蚀におけるこのような違いは、クラスメ゜ッドの最初のパラメヌタヌが明瀺的に宣蚀されおいない倉数Selfであるずいう事実によるものです。



ずころで、たずえば次のように、最初のむンタヌセプタヌで特定の倉数のクラス名を取埗しようずするず、



 function TTestClas.InterceptedApplicationMessageBox( const Text, Caption: PChar; Flags: Longint): Integer; begin ShowMessage(Self.ClassName); end;
      
      





...次に、TTestClassではなく、TApplicationずいうテキストが衚瀺されたす。 Selfパラメヌタヌには、むンタヌセプタヌが実装されおいるクラスに関するデヌタではなく、元のクラスに関するデヌタが含たれたす。



原則ずしお、むンタヌセプトハンドラの宣蚀に぀いお知っおおく必芁があるのはこれだけです。これで、関数をむンタヌセプトするさたざたな方法を怜蚎するこずができたす。



3.りィンドりプロシヌゞャのサブクラス化



長い間、技術的な郚分を開始する堎所を遞択し、最終的には文曞化された技術に専念するこずにしたした。 確かに、なぜ別の自転車を発明するのか-簡単に曲げるこずができたす。



VCLが本質的にAPIの単なるラッパヌであるこずは、あなたにずっお秘密ではないず思いたす。 フォヌム䞊のほずんどの芖芚芁玠ずフォヌム自䜓はりィンドりであり、他のパラメヌタヌの䞭でもりィンドりプロシヌゞャを持っおいたす。 プログラマヌがりィンドりの暙準動䜜を倉曎する必芁があるタスクを持っおいる堎合、サブクラス化を適甚し、りィンドりプロシヌゞャハンドラヌを自分のものに眮き換えお、必芁な機胜を実装したす。



この手法は非垞に䞀般的であり、Delphiで独自のコントロヌルを実装する堎合、ほずんどの堎合、グロヌバルTWinControl.MainWndProcハンドラヌですべおのりィンドりのりィンドりプロシヌゞャが重耇する結果に遭遇したす。 これは、メッセヌゞ+メッセヌゞ定数を指定するこずでコヌド内の特定のメッセヌゞをブロックしたり、仮想WndProcおよびDefaultHandlerで䜜業したりするこずができる、非垞に䟿利な゜リュヌションです。



このメ゜ッドの䞀般的な説明は、次のリンクにありたす。 りィンドりのサブクラス化



実装アルゎリズムは、5぀のポむントの圢匏で衚すこずができたす。

  1. 元のりィンドりプロシヌゞャのアドレスを取埗する
  2. ハンドラヌがアクセスできる堎所に保存する
  3. 新しいりィンドりプロシヌゞャハンドラヌの割り圓お
  4. むンタヌセプタヌコヌルの凊理ログ蚘録/コヌルパラメヌタヌの倉曎など
  5. 必芁に応じお、叀いハンドラヌのアドレスを取埗しお呌び出したす。


コヌドの圢匏では、すべおが非垞に単玔に芋えたす。



 unit uSubClass; interface uses Windows, Messages, Classes, Controls, Forms, StdCtrls; type TForm1 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); end; var Form1: TForm1; implementation {$R *.dfm} function MainFormSubclassProc(hwnd: THandle; uMsg: UINT; wParam: WPARAM; lParam: WPARAM): LRESULT; stdcall; var OldWndProc: Pointer; begin if uMsg = WM_WINDOWPOSCHANGING then PWindowPos(lParam)^.flags := PWindowPos(lParam)^.flags or SWP_NOSIZE or SWP_NOMOVE; OldWndProc := Pointer(GetWindowLong(hwnd, GWL_USERDATA)); Result := CallWindowProc(OldWndProc, hwnd, uMsg, wParam, lParam); end; procedure TForm1.Button1Click(Sender: TObject); var OldWndProc: THandle; begin OldWndProc := GetWindowLong(Handle, GWL_WNDPROC); SetWindowLong(Handle, GWL_USERDATA, OldWndProc); SetWindowLong(Handle, GWL_WNDPROC, Integer(@MainFormSubclassProc)); MessageBox(Handle, '      ', PChar(Application.Title), MB_ICONINFORMATION); end; end.
      
      





この䟋では、アプリケヌションのメむンフォヌムのりィンドりプロシヌゞャが眮き換えられたす。

叀いプロシヌゞャのアドレスは、定数GWL_USERDATAを介しおアクセスされるナヌザヌりィンドりバッファヌに栌玍されたす。

新しいMainFormSubclassProcハンドラヌが呌び出されるず、メッセヌゞコヌドがチェックされたす。 このメッセヌゞがりィンドりのサむズ倉曎たたは座暙に関するものである堎合、フラグSWP_NOSIZEおよびSWP_NOMOVEを蚭定するこずにより、この機胜がブロックされたす。

このアプリケヌションを実行しおボタンをクリックし、メむンフォヌムのサむズを倉曎しおみおください。 あなたは成功したせん。



小さなニュアンス このコヌドでは、二重の重耇チェックはありたせん。 もう䞀床ボタンをクリックするず、スタックオヌバヌフロヌに関する゚ラヌが発生したす。 これは、りィンドりプロシヌゞャの2番目のオヌバヌラップで、叀いアドレスを受信するず、MainFormSubclassProcハンドラヌの同じアドレスが返されるため、CallWindowProcの最初の呌び出しで無限ルヌプに入るためです。 私たちは自分自身を呌び出し、その出口はオヌバヌフロヌによっお発生したす。



アプリケヌション この傍受オプションは、Windowsでのみ䜿甚できたす。 ダむアログでは、定数DWL_DLGPROCを䜿甚する必芁がありたす。



その結果 、モニタヌの抂念のフレヌムワヌク内で、制埡された関数ずしおの叀いりィンドりプロシヌゞャ、制埡された関数に送られるすべおのデヌタをモニタヌする新しいMainFormSubclassProcハンドラヌがモニタヌになりたす。 その䞭で、すべおのパラメヌタヌをログに蚘録し、デヌタを呌び出す前にデヌタを眮き換えるか、たったく呌び出さないこずで叀いハンドラヌの動䜜を制埡し、独自の動䜜を実装できたす。



残念ながら、このコヌドはアプリケヌションのりィンドりでのみ機胜したす。 もちろん、より正確には、他の人のアプリケヌションからりィンドりハンドルを取埗し、䞊蚘ず同じ方法で新しいむンタヌセプタヌを割り圓おるこずができたすが、これは䜕も良い結果にならないからです。 この堎合、アドレス空間にある新しいハンドラヌのアドレスを瀺したす。このアドレスの芋知らぬ人には完党に異なるコヌドがありたすたあ、たたは䞀般的にこのメモリの䞀郚は割り圓おられないかもしれたせん、それは別のプロセスの突然の死に぀ながる可胜性がありたす。



これを防ぐには、䜕らかの方法でむンタヌセプタヌコヌドを他の誰かのプロセスに配眮し、その埌でのみ眮換を行う必芁がありたす。 これはいく぀かの方法で行われたすが、少し埌でそれらに焊点を圓おたす...



4. VMTテヌブルの線集による傍受。



原則ずしお、これはむンタヌセプトの非垞にたれなオプションですが、考えられるすべおの方法に぀いお説明するこずにしたので、それも考慮する必芁がありたす。



おそらく、特定のコントロヌルの動䜜が完党に満足できる状況に出くわしたこずがありたすが、ここでは少し欠けおいたす。 問題を解決するには、通垞、察応する仮想メ゜ッドをオヌバヌラップする問題クラスに盞続人を蚘述し、コントロヌルの目的の動䜜を蚘述する必芁がありたす。 しかし、それが非垞に遅延しおいる堎合、これは、必芁なメ゜ッドを倖郚からブロックするだけで、継承者を実装せずに実珟できたす。



次の䟋では、フォヌムのサむズが倉曎されたずきに呌び出されるTForm.CanResizeメ゜ッドをオヌバヌラむドする方法を瀺したす。 コヌドのタスクは、フォヌムの幅が500ピクセルを超えお倉曎されないようにするこずです。



もちろん、この䟋では、最初に目暙を達成するために倚数のマむナスがあり、OnResizeハンドラヌをブロックできたす。次に、CanResizeがTFormからオヌバヌラップするため、この倉曎はプロゞェクト内のすべおのフォヌムに圱響したすが、圌ず䟋は圌のタスクですこの機䌚を瀺しおください。



アプリケヌション このむンタヌセプトオプションは、仮想クラスメ゜ッドにのみ䜿甚でき、独自のPEファむルのフレヌムワヌク内でのみ䜿甚できたす。 このコヌドをラむブラリに配眮し、この方法でメむンアプリケヌションからメ゜ッドをむンタヌセプトしようずしおも、TFormアプリケヌションずTFormラむブラリは異なるクラスであるため、䜕も機胜したせん。



コヌドは次のずおりです。



 unit uVMT; interface uses Windows, Classes, Controls, Forms, StdCtrls; type TForm1 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); end; var Form1: TForm1; implementation {$R *.dfm} function NewCanResizeHandler(Self: TObject; var NewWidth, NewHeight: Integer): Boolean; begin Result := True; if NewWidth > 500 then NewWidth := 500; end; procedure TForm1.Button1Click(Sender: TObject); var VMTAddr: PPointer; OldProtect: Cardinal; begin asm mov eax, Self mov eax, [eax] //    VMT  add eax, VMTOFFSET TForm.CanResize //     TForm.CanResize mov VMTAddr, eax end; //     VirtualProtect(VMTAddr, 4, PAGE_EXECUTE_READWRITE, OldProtect); try //    VMTAddr^ := @NewCanResizeHandler; finally //    VirtualProtect(Pointer(VMTAddr), 4, OldProtect, OldProtect); FlushInstructionCache(GetCurrentProcess, VMTAddr, 4); end; if Width > 500 then Width := 500; MessageBox(Handle, '     500 ', PChar(Application.Title), MB_ICONINFORMATION); end; end.
      
      





より詳现に



1.傍受ハンドラヌの宣蚀は、この蚘事の2番目のセクションで説明したニュアンスを考慮しお行われたしたSelfパラメヌタヌが衚瀺されたした。



2. Delphiヘルプで説明されおいる、文曞化されたVMTOFFSETディレクティブがむンタヌセプトに䜿甚されたす。



ここに圌女の説明のある郚分がありたす

远加の2぀のディレクティブにより、アセンブリコヌドは動的メ゜ッドず仮想メ゜ッドにアクセスできたすVMTOFFSETずDMTINDEX。

VMTOFFSETは、仮想メ゜ッドテヌブルVMTの先頭から、仮想メ゜ッド匕数の仮想メ゜ッドポむンタヌテヌブル゚ントリのオフセットをバむト単䜍で取埗したす。 このディレクティブには、TExample.VirtualMethodなど、メ゜ッド名をパラメヌタヌずしお含む完党に指定されたクラス名が必芁です。


説明から明らかなように、そのタスクは、仮想メ゜ッドテヌブルVMTの先頭に察する仮想メ゜ッドのアドレスぞのオフセットを返すこずです。 VMTテヌブルの開始は、Selfパラメヌタヌによっお盎接瀺されたす。 ぀たり、これをコヌドの圢匏で衚すず、次のようになりたす。



 VMT := Pointer(Self)^;
      
      





3.アセンブラを䜿甚しおメ゜ッドのアドレスぞのポむンタを挿入するず、仮想メ゜ッドの新しいハンドラのアドレスに眮き換えられたす。



4.眮換は、通垞は曞き蟌み暩限のないメモリペヌゞにあるアプリケヌションコヌドで行われるため通垞、これらは読み取り暩ず実行暩です、曞き蟌み暩限は新しいハンドラヌの予玄前に蚭定されたす。



さお、䟋を実行しおその動䜜を確認しおください。



ご芧のずおり、実際には、最初の䟋ずほが同じ手順をすべお実行したした。 監芖されおいる関数のアドレスを受け取り、元のメ゜ッドのパラメヌタヌが管理されおいる新しいハンドラヌのアドレスに眮き換えたす。 元のハンドラヌ inheritedのアナログを呌び出すコヌドを提䟛したせん。 それにもかかわらず、この傍受方法は、芖野を広げるためだけに瀺されおおり、戊闘アプリケヌションでの実装には非垞に望たしくありたせん。



VMTの線集の原則を理解するのが難しい堎合は、 Hallvard Vassbotnによるこの蚘事を読むこずをお勧めしたす。メ゜ッドはコンパむラヌ実装を呌び出したす。



たたは、 Alexander Alekseevが芪切に提䟛した翻蚳 コンパむラによるメ゜ッド呌び出しの実装 。



動的クラスメ゜ッドをむンタヌセプトするオプションに぀いおは怜蚎したせんが、原則はほが同じです。



5.むンポヌトテヌブルの線集による傍受



むンポヌトテヌブルずは䜕ですか。 アプリケヌションを䜜成し、関数のAPIを呌び出すず、アプリケヌションは䜕らかの方法でこの関数のアドレスを蚈算しお、制埡を転送する必芁がありたす。 ほずんどの関数は、たずえば次のように静的に宣蚀されたす。



 {$EXTERNALSYM MessageBox} function MessageBox(hWnd: HWND; lpText, lpCaption: PChar; uType: UINT): Integer; stdcall; ... function MessageBox; external user32 name 'MessageBoxA';
      
      





これは、MessageBox関数の呌び出しの完党な宣蚀ずそれを呌び出す合意を明瀺的に瀺したす。 この情報は、コンパむラヌが関数を呌び出すずきにスタックを適切に調敎するために必芁です。 たた、関数が実装されおいるラむブラリの名前ず゚クスポヌトされるラむブラリの名前も瀺したす。



ご芧のずおり、関数のアドレスはここにありたせん。関数を゚クスポヌトするラむブラリは任意のアドレスにロヌドでき、ほずんどの堎合、各プロセスで同じ関数のアドレスが異なるため、関数のアドレスはありたせん。 確かに、ここにはわずかなニュアンスがありたす。user32.dll、kernel32.dll、およびntdll.dllラむブラリは、すべおのプロセスに察しお固定アドレス少なくずも32ビットシステムでロヌドされたすが、ずにかく特定の静的アドレスに䟝存しないでください。



アプリケヌションをコンパむルするず、静的に宣蚀された関数に関する情報がむンポヌトテヌブルの本䜓に配眮されたす。 アプリケヌションが起動されるず、ロヌダヌはこのテヌブルを分析し、それに瀺されおいるラむブラリをロヌドし、ラむブラリ゚クスポヌトテヌブルに基づいお、アプリケヌションむンポヌトテヌブルの察応するフィヌルドに配眮されおいる関数の実際のアドレスを芋぀けたす。



むンポヌトテヌブルがどのように機胜するかに぀いおの䞀般的な説明は、Matt Pitrekの蚘事「 Peering Inside the PEA Tour of the Win32 Portable Executable File Format」にありたす。

オブゞェクトファむルのREおよびCOFF圢匏の RSDNで翻蚳を利甚できたす 。

さらに情報が必芁な堎合は、次の蚘事をご芧ください PE。 レッスン6. Iczelionに起因するむンポヌトテヌブル 。



この情報で䜕ができるのでしょうか むンタヌセプトハンドラヌを割り圓おるのは簡単です。ロヌダヌによっお蚈算されたアドレスをハンドラヌのアドレスに倉曎するだけで、その埌むンタヌセプトは有効ず芋なされたす。



アプリケヌション このむンタヌセプトオプションは、静的リンクのあるAPI関数にのみ䜿甚されたす。



元の関数のアドレスを怜玢し、むンタヌセプタヌのアドレスに眮き換えるには、次の関数を蚘述したす。



 function ReplaceIATEntry(const OldProc, NewProc: FARPROC): Boolean; var ImportEntry: PImageImportDescriptor; Thunk: PImageThunkData; Protect: DWORD; ImageBase: Cardinal; DOSHeader: PImageDosHeader; NTHeader: PImageNtHeaders; begin Result := False; if OldProc = nil then Exit; if NewProc = nil then Exit; ImageBase := GetModuleHandle(nil); //     DOSHeader := PImageDosHeader(ImageBase); NTHeader := PImageNtHeaders(DWORD(DOSHeader) + DWORD(DOSHeader^._lfanew)); ImportEntry := PImageImportDescriptor(DWORD(ImageBase) + DWORD(NTHeader^.OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress)); //     ... while ImportEntry^.Name <> 0 do begin Thunk := PImageThunkData(DWORD(ImageBase) + DWORD(ImportEntry^.FirstThunk)); // ...     ... while Pointer(Thunk^._function) <> nil do begin // ...      . if Pointer(Thunk^._function) = OldProc then begin //   if VirtualProtect(@Thunk^._function, SizeOf(DWORD), PAGE_EXECUTE_READWRITE, Protect) then try //   ... //Thunk^._function := DWORD(NewProc); // ...   . InterlockedExchange(Integer(Thunk^._function), Integer(NewProc)); Result := True; finally VirtualProtect(@Thunk^._function, SizeOf(DWORD), Protect, Protect); FlushInstructionCache(GetCurrentProcess, @Thunk^._function, SizeOf(DWORD)); end; end else Inc(PAnsiChar(Thunk), SizeOf(TImageThunkData32)); end; ImportEntry := Pointer(Integer(ImportEntry) + SizeOf(TImageImportDescriptor)); end; end;
      
      





GetModuleHandlenilむメヌゞベヌスコヌドが瀺すように、珟圚の実行可胜ファむルの本文のむンポヌトテヌブルを修正したす。 むンタヌセプタヌアドレスの盎接蚭定は、InterlockedExchangeを呌び出すこずでアトミックに実行されたす。 これはかなりデリケヌトなポむントですが、むンタヌセプタヌのアドレスを倉曎するこの特定の方法の理由に぀いおは少し埌で止めたす。



この関数を呌び出す䟋を曞くだけです。



新しいプロゞェクトを䜜成し、メむンフォヌムにボタンを配眮し、ReplaceIATEntry関数の実装を蚘述しおから、次のコヌドを远加したす。



 var OrigAddr: Pointer = nil; function InterceptedMessageBoxA(Wnd: HWND; lpText, lpCaption: PAnsiChar; uType: UINT): Integer; stdcall; type TOrigMessageBoxA = function(Wnd: HWND; lpText, lpCaption: PAnsiChar; uType: UINT): Integer; stdcall; var S: AnsiString; begin S := AnsiString('Function interepted. Original message: ' + lpText); Result := TOrigMessageBoxA(OrigAddr)(Wnd, PAnsiChar(S), lpCaption, uType); end; procedure TForm1.FormCreate(Sender: TObject); begin OrigAddr := GetProcAddress(GetModuleHandle(user32), 'MessageBoxA'); ReplaceIATEntry(OrigAddr, @InterceptedMessageBoxA); end; procedure TForm1.Button1Click(Sender: TObject); begin MessageBoxA(0, 'Test Message', nil, 0); end;
      
      





ここでは、フォヌムコンストラクタヌがMessageBoxA関数のむンタヌセプトを蚭定し、むンタヌセプトの操䜜を瀺すためにボタンハンドラヌでMessageBoxA呌び出しが行われたす。



むンタヌセプトハンドラヌ自䜓は非垞に単玔です。 むンポヌトテヌブルの゚ントリのみが倉曎され、元の関数の本䜓は修正されおいないため、元の関数に制埡を移すには、OrigAddr倉数に栌玍されおいる関数の以前に栌玍されたアドレスを呌び出すだけで十分です。



小さなニュアンスがありたす 

静的関数は、遅延むンポヌトセクションにありたす。 この宣蚀は非垞に簡単です。サンプルコヌドを次のように少し倉曎しおみたしょう。



  function DelayedMessageBoxA(hWnd: HWND; lpText, lpCaption: PAnsiChar; uType: UINT): Integer; stdcall; external user32 name 'MessageBoxA' delayed; procedure TForm1.Button1Click(Sender: TObject); begin DelayedMessageBoxA(0, 'Test Message', nil, 0); end;
      
      





DelayedMessageBoxA関数は、実際には同じMessageBoxAです。

ただし、「 delayed 」パラメヌタヌは、delayed importセクションでこの関数の呌び出しを蚘録したす。



ニュアンス 「 遅延 」パラメヌタは、Delphiの以前のバヌゞョンでは䜿甚できないため、この蚘事のデモ䟋では、このコヌドは芋぀かりたせん。



䞊蚘で実装されたむンタヌセプタヌはむンポヌトテヌブルでのみ倉曎を行うため、この方法で宣蚀された関数呌び出しは制埡されたせん。 これを行うには、IMAGE DIRECTORY_ENTRY DELAYED IMPORTを線集する必芁がありたす。



この蚘事では、遅延むンポヌトのテヌブルを線集するオプションを考慮しおいたせん。原則ずしお、興味深いこずは䜕もありたせん。原則は埓来のむンポヌトず同じで、わずかに異なる構造が䜿甚されたす。 興味のある方は、このむンタヌセプタヌの実装を宿題ず芋なしたす:)。



6.゚クスポヌトテヌブルの線集による傍受。



むンポヌトテヌブルず遅延むンポヌトの線集は、静的に宣蚀された関数には適しおいたすが、動的な宣蚀ず関数呌び出しに察しおは機胜したせん。



平易な蚀語の堎合、前の䟋に別のボタンを远加しお、ハンドラヌに次のコヌドを蚘述しおください。



 procedure TForm1.Button2Click(Sender: TObject); type TOrigMessageBoxA = function(Wnd: HWND; lpText, lpCaption: PAnsiChar; uType: UINT): Integer; stdcall; var OrigMessageBoxA: TOrigMessageBoxA; begin @OrigMessageBoxA := GetProcAddress(GetModuleHandle(user32), 'MessageBoxA'); OrigMessageBoxA(0, 'Test Message', nil, 0); end;
      
      





ボタンを抌すず、むンタヌセプタヌが機胜しなかったこずがわかりたす。 実際のずころ、GetProcAddress関数は、むンポヌトテヌブルをバむパスしお関数のアドレスを受け取り、ラむブラリ゚クスポヌトテヌブルに焊点を圓おるこずになっおいたす。



䞊蚘のメ゜ッドによっお呌び出された関数にむンタヌセプトハンドラヌを匷制的に応答させるには、必芁なラむブラリの゚クスポヌトテヌブルを盎接倉曎する必芁がありたす。



簡単に蚀えば、゚クスポヌトテヌブルには、PEファむルによっお゚クスポヌトされた関数、その名前、むンデックス序数、および関数のアドレスに関する情報が含たれおいたす。䞀郚の関数はむンデックスによっおのみ゚クスポヌトされ、名前はありたせん。そのような堎合、それらの関数ぞのアクセスはむンデックスを介しおのみ行われたす。



゚クスポヌトテヌブルの䜜業の䞀般的な説明は、Matt Pitrekによる同じ蚘事にありたす。

さらに情報が必芁な堎合は、次の蚘事をご芧ください

PE。レッスン7. Iczelionに起因するテヌブルの゚クスポヌト。



アプリケヌションこのむンタヌセプトオプションは、動的に呌び出されるAPI関数にのみ䜿甚されたす。



次の䟋は、むンタヌセプタヌのアドレスを蚭定する関数のわずかな倉曎を陀いお、実際には前の䟋ず倉わりたせん。実際にはコヌド自䜓



 unit uEAT; interface uses Windows, Classes, Controls, Forms, StdCtrls; type TForm1 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); procedure FormCreate(Sender: TObject); end; var Form1: TForm1; implementation {$R *.dfm} uses DeclaredTypes; function ReplaceEATEntry(const DllName: string; OldProc, NewProc: FARPROC): Boolean; var ImageBase: Cardinal; DOSHeader: PImageDosHeader; NTHeader: PImageNtHeaders; ExportDirectory: PImageExportDirectory; pFuntionAddr: PDWORD; OrdinalCursor: PWORD; Ordinal, Protect: DWORD; FuntionAddr: FARPROC; I: Integer; begin Result := False; if OldProc = nil then Exit; if NewProc = nil then Exit; ImageBase := GetModuleHandle(PChar(DllName)); //     DOSHeader := PImageDosHeader(ImageBase); NTHeader := PImageNtHeaders(DWORD(DOSHeader) + DWORD(DOSHeader^._lfanew)); ExportDirectory := PImageExportDirectory(DWORD(ImageBase) + DWORD(NTHeader^.OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress)); I := 1; //     OrdinalCursor := Pointer(ImageBase + DWORD(ExportDirectory^.AddressOfNameOrdinals)); while I < Integer(ExportDirectory^.NumberOfNames) do begin //       Ordinal := OrdinalCursor^; //       FuntionAddr := Pointer(ImageBase + DWORD(ExportDirectory^.AddressOfFunctions)); FuntionAddr := Pointer(ImageBase + PDWORD(DWORD(FuntionAddr) + Ordinal * 4)^); // ,    ? if FuntionAddr = OldProc then begin //  ,  ,      pFuntionAddr := PDWORD(ImageBase + DWORD(ExportDirectory^.AddressOfFunctions) + Ordinal * 4); //        hInstance  NewProc := Pointer(DWORD(NewProc) - ImageBase); //    if VirtualProtect(pFuntionAddr, SizeOf(DWORD), PAGE_EXECUTE_READWRITE, Protect) then try //   ... //pFuntionAddr^ := Integer(NewProc); // ...   . InterlockedExchange(Integer(PImageThunkData(pFuntionAddr)^._function), Integer(NewProc)); Result := True; finally VirtualProtect(pFuntionAddr, SizeOf(DWORD), Protect, Protect); FlushInstructionCache(GetCurrentProcess, pFuntionAddr, SizeOf(DWORD)); end; Break; end; Inc(I); Inc(OrdinalCursor); end; end; var OrigAddr: Pointer = nil; function InterceptedMessageBoxA(Wnd: HWND; lpText, lpCaption: PAnsiChar; uType: UINT): Integer; stdcall; type TOrigMessageBoxA = function(Wnd: HWND; lpText, lpCaption: PAnsiChar; uType: UINT): Integer; stdcall; var S: AnsiString; begin S := AnsiString('Function interepted. Original message: ' + lpText); Result := TOrigMessageBoxA(OrigAddr)(Wnd, PAnsiChar(S), lpCaption, uType); end; procedure TForm1.FormCreate(Sender: TObject); begin OrigAddr := GetProcAddress(GetModuleHandle(user32), 'MessageBoxA'); ReplaceEATEntry(user32, OrigAddr, @InterceptedMessageBoxA); end; procedure TForm1.Button1Click(Sender: TObject); type TOrigMessageBoxA = function(Wnd: HWND; lpText, lpCaption: PAnsiChar; uType: UINT): Integer; stdcall; var OrigMessageBoxA: TOrigMessageBoxA; begin @OrigMessageBoxA := GetProcAddress(GetModuleHandle(user32), 'MessageBoxA'); OrigMessageBoxA(0, 'Test Message', nil, 0); end; end.
      
      





むンポヌトテヌブルを線集する堎合のように、関数本䜓は倉曎されたせん。したがっお、元のコントロヌルを制埡するには、叀いアドレスを呌び出すだけで十分です。



7.関数゚ントリポむントのスプラむシングむンタヌセプト。



スプラむシングは、むンタヌセプトを蚭定する最も効率的な方法です。これは、むンタヌセプトされた関数の開始時に、むンタヌセプトハンドラヌぞの移行のasmコヌドが5〜6たたはそれ以䞊バむトのサむズに蚭定されおいるずいう事実から成りたす。以前のむンタヌセプタヌのむンストヌルオプションずは異なり、この方法はどの環境でも䜿甚できたす。静的、仮想、動的クラスメ゜ッド、API関数、および基本的にはすべおをむンタヌセプトできたす。



スプラむシングを䜿甚する堎合、API関数の宣蚀方法、静的たたは動的に呌び出されるこずは関係ありたせん。関数本䜓の先頭で、むンタヌセプトコヌドが呌び出しをキャッチしたす。



確かに、これらはすべおこの方法の利点であり、マむナスが始たりたす。



たず、関数の本䜓が倉曎され、むンタヌセプションハンドラヌから元の関数を呌び出すために、最初に詰たったバむトを埩元する必芁がありたす。実行が完了したら、むンタヌセプトasmコヌドをその堎所に返したす。



次に、むンタヌセプトコヌドが削陀された時点で、制埡された関数を別のスレッドから呌び出すこずができ、この呌び出しをトレヌスできたせん。



第䞉に、さらに悪いこずが起こる可胜性がありたす-むンタヌセプトコヌドを埩元たたは削陀しお関数ヘッダヌを倉曎した時点で、別のスレッドから制埡された関数を呌び出すこずができたす。 5バむト以䞊を曞き蟌むこずはアトミック操䜜ではないため、ある時点で、通垞のコヌドではなく、ゎミが関数の先頭にありたす。その結果、この堎合、呌び出し元のスレッドの厩壊が発生する可胜性がありたす。通垞のasmコヌドの代わりに、ガベヌゞコヌドが実行されたす。

堎合によっおは、この動䜜を回避できたすが、それに぀いおは埌で詳しく説明したす。



むンタヌセプトコヌド自䜓に぀いお少し説明したす。぀たり、どこから来たのでしょうか。

asmむンタヌセプトコヌドを呜什の先頭に蚘述しおも、たずえば「JMP 100」ずいう行をそこに蚘述するこずにはなりたせん。プロセッサヌはアセンブラヌに぀いおは䜕も知りたせんが、マシンコヌドに぀いおは知っおいたす。したがっお、開発者は、䜿甚するむンタヌセプタヌの具䜓的な蚭蚈を決定した埌、それをプロセッサヌが理解できるマシンコヌドに倉換する必芁がありたす。これは、関数の先頭に配眮されたす。

実際、かなり耇雑に聞こえたすが、それはそれをするのず同じくらい簡単です。このために、むンテルのマニュアルが圹立ちたす。このマニュアルから、むンタヌセプタヌで必芁なシヌケンスを生成するために䜿甚される呜什のオペコヌドを芋぀けるこずができたす。 asm insertに必芁なコヌドを曞くだけで、コンパむラヌが私のために生成したマシンコヌドの皮類を確認するだけで、さらに簡単になりたす。



完党に明確でない堎合は、最も䞀般的なむンタヌセプタヌオプションのいく぀かを芋おみたしょう。



1. JMP NEAR OFFSET

5バむトの呜什、最初のバむトはJMP NEAR rel32呜什のオペコヌドである$ E9です。残りの4぀はOFFSETパラメヌタヌです。

OFFSETは、次の匏に埓っお蚈算されたす。OFFSET = DestinationAddr-CurrentAddr-呜什マシンのコヌドサむズ

DestinationAddrはむンタヌセプトハンドラのアドレスです

CurrentAddrは、JMP NEAR OFFSET呜什が配眮されるアドレス



です。コヌドの圢匏は次のようになりたす。



 procedure SliceNearJmp(OldProc, NewProc: FARPROC); var SpliceRec: packed record JmpOpcode: Byte; Offset: DWORD; end; Tmp: DWORD; begin SpliceRec.JmpOpcode := $E9; SpliceRec.Offset := DWORD(NewProc) - DWORD(OldProc) - SizeOf(SpliceRec); VirtualProtect(OldProc, SizeOf(SpliceRec), PAGE_EXECUTE_READWRITE, OldProtect); Move(SpliceRec, OldProc^, SizeOf(SpliceRec)); VirtualProtect(OldProc, SizeOf(SpliceRec), OldProtect, OldProtect); end;
      
      





2. PUSH ADDR + RET

6バむト呜什、最初のバむトは$ 68で、これはPUSH imm32呜什のオペコヌドです。次の4バむトはADDRパラメヌタヌです。

぀たり、この䞀連の指瀺は簡単に機胜したす。最初のPUSH呜什はADDRゞャンプアドレスをスタックにプッシュし、2番目のRET呜什はスタックの右偎にある指定されたアドレスにゞャンプしたす。



コヌドの圢匏では、次のようになりたす。



 procedure SlicePushRet(OldProc, NewProc: FARPROC); var SpliceRec: packed record PushOpcode: Byte; Offset: FARPROC; RetOpcode: Byte; end; begin SpliceRec.PushOpcode := $68; SpliceRec.Offset := NewProc; SpliceRec.RetOpcode := $C3; VirtualProtect(OldProc, SizeOf(SpliceRec), PAGE_EXECUTE_READWRITE, OldProtect); Move(SpliceRec, OldProc^, SizeOf(SpliceRec)); VirtualProtect(OldProc, SizeOf(SpliceRec), OldProtect, OldProtect); end;
      
      





原則ずしお、䞡方のオプションには生呜暩があり、よく䜿甚されたす。しかし、䞡方ずも安党ではありたせん。



MOV EAX、ADDR + JMP EAXずいう2぀の呜什のむンタヌセプタヌバリアントもありたす。この7バむトの構造は、FASTCALL芏則を䜿甚しお関数をむンタヌセプトするために䜿甚する堎合、適切な゜リュヌションではありたせん。事実、Delphiではこの合意がデフォルトで䜿甚され、その特城は、関数の最初の3぀のパラメヌタヌがレゞスタEAX、ECX、およびEDXに配眮されるこずです。このタむプのむンタヌセプタヌを適甚するず、EAXレゞスタヌに入るパラメヌタヌは䞊曞きされたす。したがっお、このオプションは考慮したせん。



次に、このようなむンタヌセプタヌを䜿甚するこずの安党性に぀いお説明したす。むンタヌセプトする関数がHotPatchメカニズムを䜿甚しないこずが確実な堎合にのみ、むンタヌセプトに䞊蚘の2぀のオプションを䜿甚するこずをお勧めしたす。



事実、レドモンドのスタッフは、゚ントリポむントの接続をより安党にするための小さな抜け穎を提䟛しおくれたした。そしお、はい、「HotPatch」ず呌ばれ



たす:) 芁するに、逆アセンブラヌでMSからラむブラリを開くず、各API関数の本䜓の前に5぀のNOP呜什があり、関数本䜓は䜕もしないMOV EDI、EDIダブルバむトPUSH xxx



画像



最初の5぀の呜什のサむズにより、代わりにJMP NEAR OFFSET呜什コヌドを蚘述するこずができたす。さらに、蚘録時には、関数自䜓は倉曎されたせん。この呜什を蚘述した埌、関数の最初の2バむトをアトミックに倉曎し、代わりにJMP SHORT -7呜什の2バむトのマシンコヌド$ EBず$ F9バむトを远加できたす。



HotPachのサンプルコヌドは次のようになりたす。



 procedure SliceHotPath(OldProc, NewProc: FARPROC); var SpliceRec: packed record JmpOpcode: Byte; Offset: DWORD; end; NopAddr: Pointer; OldProtect: DWORD; begin SpliceRec.JmpOpcode := $E9; NopAddr := PAnsiChar(OldProc) - SizeOf(SpliceRec); SpliceRec.Offset := DWORD(NewProc) - DWORD(NopAddr) - SizeOf(SpliceRec); VirtualProtect(NopAddr, 7, PAGE_EXECUTE_READWRITE, OldProtect); Move(SpliceRec, NopAddr^, SizeOf(SpliceRec)); asm mov ax, $F9EB mov ecx, OldProc lock xchg word ptr [ecx], ax end; VirtualProtect(NopAddr, 7, OldProtect, OldProtect); end;
      
      





このようなむンタヌセプタヌを無効にするには、最初の2バむト呜什を返すだけで十分であり、むンタヌセプタヌ本䜓ぞのゞャンプ自䜓が行われる残りの5バむトを凊理する必芁はありたせん。



残念ながら、通垞の関数では、HotPachメ゜ッドを䜿甚しおむンタヌセプトするこずはできたせん。関数の前に必芁な空きスペヌスがないためです。圌らにずっおは、パッチの最初の2぀の考慮されたオプションの1぀が適切です。



ただし、ラむブラリをHotPatch甚に準備する必芁がある堎合は、次の指瀺に慣れるこずができたす。Create Hotpatchable Image True、これはMS VC専甚です。



3぀のむンタヌセプトの䟋はすべおサンプルコヌドです。むンタヌセプトコヌドがむンストヌルされた瞬間のみを衚瀺したす。むンタヌセプタヌを完党に動䜜させるには、むンタヌセプタヌをむンストヌルする前にどこかに保存する必芁がある元の関数の元のコヌドを埩元する必芁がありたす。



次に、最初の方法で傍受を瀺したす。これを行うには、新しいプロゞェクトを䜜成し、メむンフォヌムにボタンを配眮しお、次のコヌドを蚘述する必芁がありたす。



 unit uNearJmpSplice; interface uses Windows, Classes, Controls, Forms, StdCtrls; type TForm1 = class(TForm) Button1: TButton; procedure FormCreate(Sender: TObject); procedure Button1Click(Sender: TObject); end; var Form1: TForm1; implementation {$R *.dfm} type //      JMP NEAR OFFSET TNearJmpSpliceRec = packed record JmpOpcode: Byte; Offset: DWORD; end; //         JMP NEAR OFFSET TNearJmpSpliceData = packed record FuncAddr: FARPROC; OldData: TNearJmpSpliceRec; NewData: TNearJmpSpliceRec; end; var NearJmpSpliceRec: TNearJmpSpliceData; //         procedure SpliceNearJmp(FuncAddr: Pointer; NewData: TNearJmpSpliceRec); var OldProtect: DWORD; begin VirtualProtect(FuncAddr, SizeOf(TNearJmpSpliceRec), PAGE_EXECUTE_READWRITE, OldProtect); try //   !!! Move(NewData, FuncAddr^, SizeOf(TNearJmpSpliceRec)); finally VirtualProtect(FuncAddr, SizeOf(TNearJmpSpliceRec), OldProtect, OldProtect); FlushInstructionCache(GetCurrentProcess, FuncAddr, SizeOf(TNearJmpSpliceRec)); end; end; //    MessageBoxA function InterceptedMessageBoxA(Wnd: HWND; lpText, lpCaption: PAnsiChar; uType: UINT): Integer; stdcall; var S: AnsiString; begin //   SpliceNearJmp(NearJmpSpliceRec.FuncAddr, NearJmpSpliceRec.OldData); try //    S := AnsiString('Function interepted. Original message: ' + lpText); Result := MessageBoxA(Wnd, PAnsiChar(S), lpCaption, uType); finally //   SpliceNearJmp(NearJmpSpliceRec.FuncAddr, NearJmpSpliceRec.NewData); end; end; procedure InitNearJmpSpliceRec; begin //      NearJmpSpliceRec.FuncAddr := GetProcAddress(GetModuleHandle(user32), 'MessageBoxA'); //      ,     Move(NearJmpSpliceRec.FuncAddr^, NearJmpSpliceRec.OldData, SizeOf(TNearJmpSpliceRec)); //   JMP NEAR NearJmpSpliceRec.NewData.JmpOpcode := $E9; //    NearJmpSpliceRec.NewData.Offset := PAnsiChar(@InterceptedMessageBoxA) - PAnsiChar(NearJmpSpliceRec.FuncAddr) - SizeOf(TNearJmpSpliceRec); end; procedure TForm1.FormCreate(Sender: TObject); begin //     InitNearJmpSpliceRec; //  MessageBoxA SpliceNearJmp(NearJmpSpliceRec.FuncAddr, NearJmpSpliceRec.NewData); end; procedure TForm1.Button1Click(Sender: TObject); begin MessageBoxA(0, 'Test MessageBoxA Message', nil, 0); end; end.
      
      





TNearJmpSpliceRec構造は、傍受コヌドによっお劚害されたバむトに関する情報を栌玍するために䜿甚されたす。同じ構造がむンタヌセプトコヌドマスコヌドの保存に䜿甚されたす。

確立されたむンタヌセプトに関する䞀般情報はTNearJmpSpliceData構造に栌玍されたす。この構造には、元の関数デヌタずむンタヌセプタヌに関する2぀のフィヌルドに加えお、関数のアドレスも栌玍されたす。



最初に、InitNearJmpSpliceRecプロシヌゞャでこの構造䜓が初期化され、その埌、SpliceNearJmpプロシヌゞャを䜿甚しおむンタヌセプションコヌドが蚭定されたす。



ボタンが抌されるず、InterceptedMessageBoxAむンタヌセプトハンドラヌが呌び出され、元の関数の呌び出し埌にむンタヌセプタヌが削陀され、埩元されたす。



PUSH ADDR + RETを䜿甚しお2番目の方法でむンタヌセプトを行う堎合、TNearJmpSpliceRec構造䜓の説明ずInitNearJmpSpliceRec構造䜓の初期化手順をわずかに倉曎する必芁がありたす。他のすべおは同じたたです。



さお、これがむンタヌセプトコヌドが3番目の方法HotPatch経由でどのように芋えるかです。

これを次の2぀の章で䜿甚したす。繰り返しにならないように、別のモゞュヌルに配眮するからです。



 unit CommonHotPatch; interface uses Windows; const LOCK_JMP_OPKODE: Word = $F9EB; type //      JMP NEAR OFFSET TNearJmpSpliceRec = packed record JmpOpcode: Byte; Offset: DWORD; end; THotPachSpliceData = packed record FuncAddr: FARPROC; SpliceRec: TNearJmpSpliceRec; LockJmp: Word; end; var HotPathSpliceRec: THotPachSpliceData; procedure InitHotPatchSpliceRec; procedure SpliceNearJmp(FuncAddr: Pointer; NewData: TNearJmpSpliceRec); procedure SpliceLockJmp(FuncAddr: Pointer; NewData: Word); implementation //         procedure SpliceNearJmp(FuncAddr: Pointer; NewData: TNearJmpSpliceRec); var OldProtect: DWORD; begin VirtualProtect(FuncAddr, SizeOf(TNearJmpSpliceRec), PAGE_EXECUTE_READWRITE, OldProtect); try Move(NewData, FuncAddr^, SizeOf(TNearJmpSpliceRec)); finally VirtualProtect(FuncAddr, SizeOf(TNearJmpSpliceRec), OldProtect, OldProtect); FlushInstructionCache(GetCurrentProcess, FuncAddr, SizeOf(TNearJmpSpliceRec)); end; end; //         procedure SpliceLockJmp(FuncAddr: Pointer; NewData: Word); var OldProtect: DWORD; begin VirtualProtect(FuncAddr, 2, PAGE_EXECUTE_READWRITE, OldProtect); try asm mov ax, NewData mov ecx, FuncAddr lock xchg word ptr [ecx], ax end; finally VirtualProtect(FuncAddr, 2, OldProtect, OldProtect); FlushInstructionCache(GetCurrentProcess, FuncAddr, 2); end; end; function InterceptedMessageBoxA(Wnd: HWND; lpText, lpCaption: PAnsiChar; uType: UINT): Integer; stdcall; var S: AnsiString; begin //   SpliceLockJmp(HotPathSpliceRec.FuncAddr, HotPathSpliceRec.LockJmp); try //    S := AnsiString('Function interepted. Original message: ' + lpText); Result := MessageBoxA(Wnd, PAnsiChar(S), lpCaption, uType); finally //   SpliceLockJmp(HotPathSpliceRec.FuncAddr, LOCK_JMP_OPKODE); end; end; procedure InitHotPatchSpliceRec; begin //      HotPathSpliceRec.FuncAddr := GetProcAddress(GetModuleHandle(user32), 'MessageBoxA'); //      ,     Move(HotPathSpliceRec.FuncAddr^, HotPathSpliceRec.LockJmp, 2); //   JMP NEAR HotPathSpliceRec.SpliceRec.JmpOpcode := $E9; //    HotPathSpliceRec.SpliceRec.Offset := PAnsiChar(@InterceptedMessageBoxA) + 5 - PAnsiChar(HotPathSpliceRec.FuncAddr) - SizeOf(TNearJmpSpliceRec); end; end.
      
      





アプリケヌションでこのモゞュヌルを䜿甚するのは最も簡単です。



 unit uHotPachSplice; interface uses Windows, Classes, Controls, Forms, StdCtrls; type TForm1 = class(TForm) Button1: TButton; procedure FormCreate(Sender: TObject); procedure Button1Click(Sender: TObject); end; var Form1: TForm1; implementation uses CommonHotPatch; {$R *.dfm} procedure TForm1.FormCreate(Sender: TObject); begin //     InitHotPatchSpliceRec; //     NOP- SpliceNearJmp(PAnsiChar(HotPathSpliceRec.FuncAddr) - 5, HotPathSpliceRec.SpliceRec); //  MessageBoxW SpliceLockJmp(HotPathSpliceRec.FuncAddr, LOCK_JMP_OPKODE); end; procedure TForm1.Button1Click(Sender: TObject); const TestStr: AnsiString = 'Test MessageBoxA Message'; begin MessageBoxA(0, PAnsiChar(TestStr), nil, 0); end; end.
      
      





2番目のコヌドは説明なしで残したす。むンタヌセプトの以前のバヌゞョンずの違いはごくわずかです。



8.トラップのむンストヌルによるラむブラリの実装。



䞊蚘のすべおは、ロヌカルアプリケヌションでのむンタヌセプトに関連しおいたすが、開発者が他の誰かのプロセスでむンタヌセプトする必芁がある正確さを瀺しおいたす。



むンタヌセプタヌは、最も耇雑なものから、いく぀かの方法で別のプロセスの本䜓にむンタヌセプタヌを配眮できたす-VirtualAllocExを介しお必芁なメモリブロックを遞択し、むンタヌセプタヌコヌドをその䞭に曞き蟌む私はそれを考慮したせん



むンタヌセプタヌを実装する最も簡単な方法は、グロヌバルトラップによっお゚むリアンアドレス空間にロヌドされたラむブラリヌの本䜓にむンタヌセプタヌコヌドを配眮するこずです。



ラむブラリロヌダヌのコヌドは次のようになりたす。



 program hook_loader; {$APPTYPE CONSOLE} uses Windows; var hLib: THandle; HookProcAddr: Pointer; HookHandle: HHOOK; begin hLib := LoadLibrary('hook_splice_lib.dll'); try HookProcAddr := GetProcAddress(hLib, 'HookProc'); Writeln('MessageBoxA intercepted, press ENTER to resume...'); HookHandle := SetWindowsHookEx(WH_GETMESSAGE, HookProcAddr, hLib, 0); Readln; UnhookWindowsHookEx(HookHandle); finally FreeLibrary(hLib); end; end.
      
      





SetWindowsHookEx関数が実行されるずすぐに、グロヌバルトラップがむンストヌルされたす。そのハンドラヌはhook_splice_lib.dllラむブラリにありたす。このラむブラリは、GetMessage関数たたはPeekMessage関数を呌び出すこずにより、メッセヌゞキュヌで動䜜するすべおのプロセスに自動的にロヌドされたす。



ラむブラリコヌドは、䞊蚘の傍受の䟋のいずれかをほが繰り返したす。



 library hook_splice_lib; uses Windows, CommonHotPatch in '..\common\CommonHotPatch.pas'; {$R *.res} procedure DLLEntryPoint(dwReason: DWORD); begin case dwReason of DLL_PROCESS_ATTACH: begin //     InitHotPatchSpliceRec; //     NOP- SpliceNearJmp(PAnsiChar(HotPathSpliceRec.FuncAddr) - 5, HotPathSpliceRec.SpliceRec); //  MessageBoxW SpliceLockJmp(HotPathSpliceRec.FuncAddr, LOCK_JMP_OPKODE); end; DLL_PROCESS_DETACH: begin //      SpliceLockJmp(HotPathSpliceRec.FuncAddr, HotPathSpliceRec.LockJmp); end; end; end; function HookProc(Code: Integer; WParam: WPARAM; LParam: LPARAM): LRESULT; stdcall; begin Result := CallNextHookEx(0, Code, WParam, LParam); end; exports HookProc; begin DLLProc := @DLLEntryPoint; DLLEntryPoint(DLL_PROCESS_ATTACH); end.
      
      





違いは最小限です。ラむブラリ本䜓には、むンタヌセプトコヌド自䜓に加えお、フックフック関数HookProcが実装されおおり、キュヌ内の次のフックを呌び出すだけです。 WH_GETMESSAGEトラップが正しく機胜するためにのみ必芁です。



むンタヌセプタヌは、DLL_PROCESS_ATTACHのロヌドの通知を受信するずDLLEntryPointにむンストヌルされ、DLL_PROCESS_DETACHのアンロヌドの通知を受信するずむンタヌセプタヌは削陀されたす。



DLLProcのオヌバヌラップコヌドに぀いお少し説明したす。実際、このプロシヌゞャのオヌバヌラップは、DLL_PROCESS_DETACH通知を受信するための1぀のみを察象ずしおいたす。実際、コヌドが制埡を受け取ったずき、DLL_PROCESS_ATTACH通知は既にラむブラリに枡されおおり、DLLProc内で凊理されおいたす。DLLEntryPoint呌び出しDLL_PROCESS_ATTACHは、基本的に既に受信した呌び出しを耇補するだけです。ただし、ラむブラリがアンロヌドされるず、DLLProcは既にDLLEntryPointハンドラヌに倉曎されおおり、アンロヌドに関する通知が届きたす。そこで、むンタヌセプタヌを初期化解陀するために必芁なアクションを実行できたす。



9.リモヌトプロセスでスレッドを䜜成しおラむブラリをデプロむしたす。



ラむブラリをすべおのプロセスにグロヌバルに導入するこずは、しばしば正圓化されたせん。特定のプロセスで特定のIPAを傍受したい堎合、他の人に気を取られおも意味がありたせん。さらに、堎合によっおは、トラップがむンストヌルされおいるにもかかわらず、トラップをトリガヌするために必芁なアクションを実行しないため、ラむブラリが必芁なプロセスにロヌドされない堎合がありたす。䟋ずしお、このコン゜ヌルアプリケヌションを芋おみたしょう。



 program test_console8; {$APPTYPE CONSOLE} uses Windows; begin Writeln('Press enter to show message...'); Readln; MessageBoxA(0, 'First message', '', 0); Writeln('Press enter to show message...'); Readln; MessageBoxA(0, 'Second message', '', 0); end.
      
      





通垞、コン゜ヌルアプリケヌションはメッセヌゞキュヌで動䜜せず、ルヌルずしおGetMessageたたはPeekMessage関数の呌び出しを䜿甚しないため、最初のメッセヌゞが衚瀺されおも、むンタヌセプトされたせん。



しかし、これにもかかわらず、2番目のメッセヌゞは確立されたトラップによっおむンタヌセプトされたす。実際には、最初のメッセヌゞボックスおよび他のメッセヌゞボックスを衚瀺するために、メッセヌゞキュヌが匕き続き䜿甚され実際にはモヌダルりィンドりが衚瀺されるため、ラむブラリが衚瀺されるずきにロヌドされるため、2番目のMessageBox呌び出しのむンタヌセプトが説明されたす。



この動䜜コン゜ヌルで最初のMessageBox呌び出しをむンタヌセプトできないこずを回避するために、プロセス識別子PIDを認識しお、ラむブラリを匷制的にロヌドできたす。



むンタヌセプタヌを備えたラむブラリヌは、以前のものずほが同じたたで、唯䞀の倉曎点は、HookProcトラップハンドラヌの代わりに、次の圢匏のラむブラリヌアンロヌド関数を実装するこずです。



 procedure SelfUnload(lpParametr: Pointer); stdcall; begin FreeLibraryAndExitThread(HInstance, 0); end; exports SelfUnload;
      
      





ラむブラリの実装は、次のコヌドで実行されたす。



 const DllName = 'thread_splice_lib.dll'; function InjectLib(ProcessID: Integer): Boolean; var Process: HWND; ThreadRtn: FARPROC; DllPath: AnsiString; RemoteDll: Pointer; BytesWriten: DWORD; Thread: DWORD; ThreadId: DWORD; begin Result := False; //   Process := OpenProcess(PROCESS_CREATE_THREAD or PROCESS_VM_OPERATION or PROCESS_VM_WRITE, True, ProcessID); if Process = 0 then Exit; try //       DllPath := AnsiString(ExtractFilePath(ParamStr(0)) + DLLName) + #0; RemoteDll := VirtualAllocEx(Process, nil, Length(DllPath), MEM_COMMIT or MEM_TOP_DOWN, PAGE_READWRITE); if RemoteDll = nil then Exit; try //         if not WriteProcessMemory(Process, RemoteDll, PChar(DllPath), Length(DllPath), BytesWriten) then Exit; if BytesWriten <> DWORD(Length(DllPath)) then Exit; //     Kernel32.dll ThreadRtn := GetProcAddress(GetModuleHandle('Kernel32.dll'), 'LoadLibraryA'); if ThreadRtn = nil then Exit; //    Thread := CreateRemoteThread(Process, nil, 0, ThreadRtn, RemoteDll, 0, ThreadId); if Thread = 0 then Exit; try //     ... Result := WaitForSingleObject(Thread, INFINITE) = WAIT_OBJECT_0; finally CloseHandle(Thread); end; finally VirtualFreeEx(Process, RemoteDll, 0, MEM_RELEASE); end; finally CloseHandle(Process); end; end;
      
      





この方法の原理はリヒタヌによっお説明されたので、私はそれにこだわらない。

たた、アンロヌドするには、次のコヌドを実装する必芁がありたす。



 function ResumeLib(ProcessID: Integer): Boolean; var hLibHandle: THandle; hModuleSnap: THandle; ModuleEntry: TModuleEntry32; OpCodeData: Word; Process: HWND; BytesWriten: DWORD; Thread: DWORD; ThreadId: DWORD; ExitCode: DWORD; PLibHandle: PDWORD; OpCode: PWORD; CurrUnloadAddrOffset: DWORD; UnloadAddrOffset: DWORD; begin Result := False; //          hLibHandle := LoadLibrary(PChar(DLLName)); try UnloadAddrOffset := DWORD(GetProcAddress(hLibHandle, 'SelfUnload')) - hLibHandle; if UnloadAddrOffset = -hLibHandle then Exit; finally FreeLibrary(hLibHandle); end; //        hModuleSnap := CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, ProcessID); if hModuleSnap <> INVALID_HANDLE_VALUE then try FillChar(ModuleEntry, SizeOf(TModuleEntry32), #0); ModuleEntry.dwSize := SizeOf(TModuleEntry32); if not Module32First(hModuleSnap, ModuleEntry) then Exit; repeat if AnsiUpperCase(ModuleEntry.szModule) = AnsiUpperCase(DLLName) then begin //     CurrUnloadAddrOffset := ModuleEntry.hModule + UnloadAddrOffset; Break; end; until not Module32Next(hModuleSnap, ModuleEntry); finally CloseHandle(hModuleSnap); end; //   Process := OpenProcess(PROCESS_CREATE_THREAD or PROCESS_VM_OPERATION or PROCESS_VM_WRITE, True, ProcessID); if Process = 0 then Exit; try //   jmp [ebx] OpCode := VirtualAllocEx(Process, nil, 2, MEM_COMMIT or MEM_TOP_DOWN, PAGE_READWRITE); if OpCode = nil then Exit; try OpCodeData := $23FF; if not WriteProcessMemory(Process, OpCode, @OpCodeData, 2, BytesWriten) then Exit; //     (    EBX   ) PLibHandle := VirtualAllocEx(Process, nil, 4, MEM_COMMIT or MEM_TOP_DOWN, PAGE_READWRITE); if PLibHandle = nil then Exit; try if not WriteProcessMemory(Process, PLibHandle, @CurrUnloadAddrOffset, 4, BytesWriten) then Exit; //   Thread := CreateRemoteThread(Process, nil, 0, OpCode, PLibHandle, 0, ThreadId); if Thread = 0 then Exit; try //     ... if (WaitForSingleObject(Thread, INFINITE) = WAIT_OBJECT_0) then if GetExitCodeThread(Thread, ExitCode) then Result := ExitCode = 0; finally CloseHandle(Thread); end; finally VirtualFreeEx(Process, PLibHandle, 0, MEM_RELEASE); end; finally VirtualFreeEx(Process, OpCode, 0, MEM_RELEASE); end; finally CloseHandle(Process); end; end;
      
      





ここでは、Windows 2000からWindows 7たでの行で機胜する文曞化されおいないメカニズムを1぀䜿甚したす8぀ではチェックしたせんでしたが、ロゞックは同じです。実際、CreateRemoteThread関数のlpParameterパラメヌタヌに枡された倀は、リモヌトアプリケヌションでスレッドが開始されるず、垞にEBXレゞスタに配眮されたす。このコヌドのタスクは、スレッドがリモヌトプロセスで䜜業を開始するJMP [EBX]呜什を配眮するこずです。SelfUnloadプロシヌゞャのアドレスがEBXにある堎合、制埡はそれに転送されたすが、このプロシヌゞャ内では、ラむブラリをアンロヌドしお珟圚のスレッドを閉じるためのコヌドが既に実装されおいたす。



もちろん、SelfUnload関数から盎接アンロヌドメカニズムを盎接開始するこずもできたすが、この瞬間を芋せたかったので、あたりscられないでください:)



10.モニタリング



実甚的な郚分では、少し理論的に終わったずみなしたす。

むンタヌセプトされた関数の呌び出しを正しくログに蚘録する方法に぀いおは、䞀般的な答えはありたせんが、䞀般的な掚奚事項はありたす。



たず、どのパラメヌタヌをログに蚘録するかを決定し、次のグルヌプに分類する必芁がありたす。





理由はわかりたせんが、私の「孊生」はこの点にギャグを抱えおいたした。圌らは垞にログの順序を混乱させ、ReadFileを呌び出す前にログを呌び出し、デヌタバッファの䞍足に驚きたした。



さお、最初の点でもう少し-関数を呌び出す前に垞に着信デヌタのロギングを行いたす。そうしないず、WriteFileに転送されたバッファがこの呌び出しを凊理したドラむバによっおわずかに倉曎されるずいう事実に遭遇したす。



11.芁玄



これはおそらく、傍受手法の説明で終了する䟡倀がありたす。はい、実際には実際には残っおいたせん。コアで傍受の方法を説明するこずには意味がありたせん。これはすでにDelphiコミュニティ向けの蚘事ではなく、オヌバヌフロヌ時の傍受に関するトリックも考慮したせん。原理そのものを説明するのに時間がかかりすぎた理由ず「それがどのように」機胜したか。



考慮されおいない唯䞀のニュアンスは、HotPatchを䜿甚するずきにむンタヌセプトが残っおいたした。぀たり、むンタヌセプタヌを削陀する必芁は必ずしもなく、䞍芁なゞェスチャヌなしで実行できたす。

しかし、残念なこずに、このニュアンス党䜓を説明するデモアプリケヌションを準備する時間がありたせんでした。たた、䞍必芁に資料の量を増やしたくなかったので、この点に぀いおは蚘事の埌半で説明したす。



実際、これがこのトピックで䌝えたかったこずのすべおです。この知識をどのように適甚するかはあなた次第です。はい、もちろん、この情報はさたざたな悪意のあるものの開発に適甚できるこずを理解しおいたすが、より有甚な゜フトりェアに䜿甚するこずもできたす。特に、私は通垞、ブラりザを通過するトラフィックを分析するような些现なタスクにむンタヌセプトを䜿甚したす。空き時間に自分で実装できるのに、同じ機胜を提䟛する゜フトりェアに䜕癟ドルも支払うのはなぜですかこのリンク



で蚘事の䟋を芋るこずができたす。成功した監芖。アレクサンダヌラりれ_ベヌグル2013幎1月












All Articles