Windowsフック:ほぼ複雑

画像 フックとは何ですか?

関数フックとは何ですか? 英語から翻訳すると、「フック」はtrapです。 したがって、Windowsの関数フックの目的を推測できます-これは関数のトラップです。 つまり、関数をキャッチし、制御します。 この定義の後、魅力的な見通しが開かれます:関数の呼び出しをインターセプトし、コードを独自のものに置き換えることにより、プログラムの動作を必要なものに変更できます(もちろん、特定の制限内で)。



この記事の目的は、フックのインストールとその直接実装を示すことです。



「不可能は信じられない!」

「あなたは経験がほとんどない」と女王は言った。 -あなたの年齢で、私は毎日半時間を過ごしました! 他の日には、朝食前に何十もの不可能を信じることができました!


この知識が本当に必要な場所



この知識は非常に高度に専門化されており、日常の開発の実践では有用であるとは考えられませんが、この知識は純粋に理論的であっても、私の意見では非常に望ましいです。 私の実践では、この知識は次の問題を解決するのに役立ちました。



•着信HTTPトラフィックを制御し、「アダルト」コンテンツをより無害に置き換えます。

•制御されたネットワークフォルダーからファイルをコピーする場合の情報のログ。

•ソースコードが失われたプロジェクトのマイナーコード変更(はい、これも発生します)



フックの取り付け方法



一般的なフレーズからフックの詳細な説明に移りましょう。 私はいくつかの種類のフック実装を知っています:



●SetWindowsHookEx関数を使用します。 これは非常に単純なため、制限された方法です。 主にウィンドウに関連する特定の機能のみをインターセプトできます(たとえば、ウィンドウが受け取るイベントのインターセプト、マウスクリック、キーボード入力など)。 この方法の利点は、グローバルフックを設定できることです(たとえば、すべてのアプリケーションでキーボード入力を一度にインターセプトする)。

●DLLインポートセクションでのアドレススプーフィングの使用。 このメソッドの本質は、すべてのモジュールにインポートセクションがあり、そのモジュールで使用される他のすべてのモジュールと、このモジュールによってエクスポートされる関数のメモリ内のアドレスがリストされることです。 このモジュールのアドレスを自分のものに置き換える必要があり、制御は指定されたアドレスに移されます。

●レジストリキーHKEY_LOCAL_MACHINE \ Software \ Microsoft \ WindowsNT \ CurrentVersion \ Windows \ AppInit_Dllsを使用します。 DLLへのパスをそこに登録する必要がありますが、管理者権限を持つユーザーのみがこれを行うことができます。 このメソッドは、アプリケーションがkernel32.dllを使用しない場合に適しています(LoadLibrary関数を呼び出すことはできません)。

●プロセスへのDLLインジェクションの使用。 私の意見では、これは最も柔軟で最も明快な方法です。 詳細に検討します。



注入方法



CreateThread関数に渡されるThreadStart関数はLoadLibrary関数と同様のシグネチャを持っているため、注入が可能です(実際、dllと実行可能ファイルの構造は非常に似ています)。 これにより、ストリームの作成時にLoadLibraryメソッドを引数として指定できます。



DLLインジェクションアルゴリズムは次のようになります。



1. DLLを挿入するストリームのKernel32.dllからLoadLibrary関数のアドレスを見つけます。

2.この関数に引数を書き込むためのメモリを割り当てます。

3.スレッドを作成し、LoadLibraryとその引数をThreadStart関数として指定します。

4.スレッドは実行に移り、ライブラリをロードして終了します。

5.ライブラリは、外部ストリームのアドレス空間に挿入されます。 この場合、DLLのロード時に、PROCESS_ATTACHフラグを指定したDllMainメソッドが呼び出されます。 これはまさに、目的の機能にフックを設定できる場所です。 次に、フック自体のインストールを検討します。



フック設定



フックをインストールするときに使用されるアプローチは、次のコンポーネントに分けることができます。



1.呼び出しをインターセプトする関数のアドレスを見つけます(たとえば、user32.dllのMessageBox)。

2.この関数の最初の数バイトを別のメモリに保存します。

3.代わりに、JUMPマシンコマンドを挿入して、ダミー関数のアドレスに移動します。 当然、関数のシグネチャは元のものと同じである必要があります。つまり、すべてのパラメーター、戻り値、および呼び出し規則が一致する必要があります。

4.スレッドがフックされた関数を呼び出すので、JUMPコマンドはそれを関数にリダイレクトします。 この段階で、必要なコードを実行できます。



その後、手順2の最初のバイトをそれぞれの場所に戻すことで、トラップを削除できます。



したがって、ストリームのアドレス空間に必要なDLLを埋め込む方法と、関数にフックを設定する方法を理解しました。 次に、これらのアプローチを実際に組み合わせてみましょう。



テストアプリケーション



テストアプリケーションは非常にシンプルで、C#で記述されます。 MessageBoxを表示するボタンが含まれます。 たとえば、フックをこの関数に設定してみましょう。 テストアプリケーションコード:



public partial class MainForm : Form { [DllImport("user32.dll", CharSet = CharSet.Unicode)] public static extern int MessageBox(IntPtr hWnd, String text, String caption, uint type); public MainForm() { InitializeComponent(); this.Text = "ProcessID: " + Process.GetCurrentProcess().Id; } private void btnShowMessage_Click(Object sender, EventArgs e) { MessageBox(new IntPtr(0), "Hello World!", "Hello Dialog", 0); } }
      
      





インジェクター



インジェクターとして、2つのオプションを検討します。 C ++およびC#で記述されたインジェクター。 なぜバイリンガルですか? 事実は、多くの人がC#はシステムのものを使用できない言語だと考えているということです-これは神話です、できます:)。 したがって、C ++のインジェクターコードは次のとおりです。



 #include "stdafx.h" #include <iostream> #include <Windows.h> #include <cstdio> int Wait(); int main() { //   ,   . DWORD processId = 55; char* dllName = "C:\\_projects\\CustomHook\\Hooking\\Debug\\HookDll.dll"; //  PID    . printf("Enter PID to inject dll: "); std::cin >> processId; //    . HANDLE openedProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processId); if (openedProcess == NULL) { printf("OpenProcess error code: %d\r\n", GetLastError()); return Wait(); } //  kernel32.dll HMODULE kernelModule = GetModuleHandleW(L"kernel32.dll"); if (kernelModule == NULL) { printf("GetModuleHandleW error code: %d\r\n", GetLastError()); return Wait(); } //  LoadLibrary ( A     ANSI,    ) LPVOID loadLibraryAddr = GetProcAddress(kernelModule, "LoadLibraryA"); if (loadLibraryAddr == NULL) { printf("GetProcAddress error code: %d\r\n", GetLastError()); return Wait(); } //     LoadLibrary,   -     DLL LPVOID argLoadLibrary = (LPVOID)VirtualAllocEx(openedProcess, NULL, strlen(dllName), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); if (argLoadLibrary == NULL) { printf("VirtualAllocEx error code: %d\r\n", GetLastError()); return Wait(); } //     . int countWrited = WriteProcessMemory(openedProcess, argLoadLibrary, dllName, strlen(dllName), NULL); if (countWrited == NULL) { printf("WriteProcessMemory error code: %d\r\n", GetLastError()); return Wait(); } //  ,   LoadLibrary     HANDLE threadID = CreateRemoteThread(openedProcess, NULL, 0, (LPTHREAD_START_ROUTINE)loadLibraryAddr, argLoadLibrary, NULL, NULL); if (threadID == NULL) { printf("CreateRemoteThread error code: %d\r\n", GetLastError()); return Wait(); } else { printf("Dll injected!"); } //  . CloseHandle(openedProcess); return 0; } int Wait() { char a; printf("Press any key to exit"); std::cin >> a; return 0; }
      
      





同じことですが、C#のみです。 コードのコンパクトさを評価してください。タイプ(ハンドル、LPVOID、HMODULE、DWORD、本質的には同じことを意味する)の暴動はありません。



 public class Exporter { [DllImport("kernel32.dll", SetLastError = true)] public static extern IntPtr OpenProcess(ProcessAccessFlags processAccess, bool bInheritHandle, int processId); [DllImport("kernel32.dll", SetLastError = true)] public static extern IntPtr GetModuleHandle(string lpModuleName); [DllImport("kernel32.dll", CharSet = CharSet.Ansi, SetLastError = true)] public static extern IntPtr GetProcAddress(IntPtr hModule, string procName); [DllImport("kernel32.dll", SetLastError = true)] public static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, IntPtr dwSize, AllocationType flAllocationType, MemoryProtection flProtect); [DllImport("kernel32.dll", SetLastError = true)] public static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, UIntPtr nSize, out IntPtr lpNumberOfBytesWritten); [DllImport("kernel32.dll", SetLastError = true)] public static extern IntPtr CreateRemoteThread(IntPtr hProcess, IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, out IntPtr lpThreadId); [DllImport("kernel32.dll", SetLastError = true)] public static extern Int32 CloseHandle(IntPtr hObject); } public class Injector { public static void Inject(Int32 pid, String dllPath) { IntPtr openedProcess = Exporter.OpenProcess(ProcessAccessFlags.All, false, pid); IntPtr kernelModule = Exporter.GetModuleHandle("kernel32.dll"); IntPtr loadLibratyAddr = Exporter.GetProcAddress(kernelModule, "LoadLibraryA"); Int32 len = dllPath.Length; IntPtr lenPtr = new IntPtr(len); UIntPtr uLenPtr = new UIntPtr((uint)len); IntPtr argLoadLibrary = Exporter.VirtualAllocEx(openedProcess, IntPtr.Zero, lenPtr, AllocationType.Reserve | AllocationType.Commit, MemoryProtection.ReadWrite); IntPtr writedBytesCount; Boolean writed = Exporter.WriteProcessMemory(openedProcess, argLoadLibrary, System.Text.Encoding.ASCII.GetBytes(dllPath), uLenPtr, out writedBytesCount); IntPtr threadIdOut; IntPtr threadId = Exporter.CreateRemoteThread(openedProcess, IntPtr.Zero, 0, loadLibratyAddr, argLoadLibrary, 0, out threadIdOut); Exporter.CloseHandle(threadId); } }
      
      





注入可能なライブラリ



楽しい部分は、フックを設定するライブラリのコードです。 このライブラリはC ++で記述されていますが、これまではC#の類似物はありません。



 // dllmain.cpp : Defines the entry point for the DLL application. #include "stdafx.h" #include <Windows.h> #define SIZE 6 //      typedef int (WINAPI *pMessageBoxW)(HWND, LPCWSTR, LPCWSTR, UINT); int WINAPI MyMessageBoxW(HWND, LPCWSTR, LPCWSTR, UINT); void BeginRedirect(LPVOID); pMessageBoxW pOrigMBAddress = NULL; BYTE oldBytes[SIZE] = { 0 }; BYTE JMP[SIZE] = { 0 }; DWORD oldProtect, myProtect = PAGE_EXECUTE_READWRITE; BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: //       . MessageBoxW(NULL, L"I hook MessageBox!", L"Hello", MB_OK); //   MessageBox pOrigMBAddress = (pMessageBoxW)GetProcAddress(GetModuleHandleW(L"user32.dll"), "MessageBoxW"); if (pOrigMBAddress != NULL) { BeginRedirect(MyMessageBoxW); } break; case DLL_THREAD_ATTACH: break; case DLL_THREAD_DETACH: break; case DLL_PROCESS_DETACH: break; } return TRUE; } void BeginRedirect(LPVOID newFunction) { // -     BYTE tempJMP[SIZE] = { 0xE9, 0x90, 0x90, 0x90, 0x90, 0xC3 }; memcpy(JMP, tempJMP, SIZE); //      DWORD JMPSize = ((DWORD)newFunction - (DWORD)pOrigMBAddress - 5); //     VirtualProtect((LPVOID)pOrigMBAddress, SIZE, PAGE_EXECUTE_READWRITE, &oldProtect); //    memcpy(oldBytes, pOrigMBAddress, SIZE); //  4 . ,     x86 memcpy(&JMP[1], &JMPSize, 4); //    memcpy(pOrigMBAddress, JMP, SIZE); //     VirtualProtect((LPVOID)pOrigMBAddress, SIZE, oldProtect, NULL); } int WINAPI MyMessageBoxW(HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uiType) { //     VirtualProtect((LPVOID)pOrigMBAddress, SIZE, myProtect, NULL); //    (   ) memcpy(pOrigMBAddress, oldBytes, SIZE); //   ,    int retValue = MessageBoxW(hWnd, lpText, L"Hooked", uiType); //    memcpy(pOrigMBAddress, JMP, SIZE); //     VirtualProtect((LPVOID)pOrigMBAddress, SIZE, oldProtect, NULL); return retValue; }
      
      





まあ、最後にいくつかの写真。 フックを取り付ける前に:



画像



そしてインストール後:



画像



次の記事では、マネージコードを挿入するメカニズムは別の記事に値するため、C#でフックを設定するライブラリのコードを記述しようとします。



記事の著者: nikitamThoughtsAboutProgramming



All Articles