IAT書き換えの絡みでHOOKを使いますが、その前にHOOKってどうやって使うのかサッパリだったのでMSDNで軽く調べ、サンプルを作って遊んでみました。 HOOKにはglobal hook(同じデスクトップで動作する全てのスレッドに対してHOOK)と、thread-specific hook(特定のスレッドに対してのみHOOK)がありますが、今回はglobal hookのみ取りあげます。 参考MSDNは、2010/6月末時点で以下の階層になります。 - "MSDN Library" > "Windows Development" > "Windows Application UI Development" > "Windows and Messages" > "Hooks" #more|| #outline|| ---- * サンプルコードの構成 今回のサンプルは3つのEXE/DLLより構成されます。 + foo.exe : HOOK「される」のを確認する為のダミーEXE + hook.dll : HOOK本体が格納されるDLL + hookctrl.exe : HOOK ON/OFF を切り替えるEXE 今回は、hook.dllでキーボードイベントをHOOKします。何かキーが押されたら/離されたら、そのキーコードをメッセージボックスで表示してみます。 foo.exeは後述するようにウインドウを表示するだけのEXEです。キーボードイベントは実装しませんので、HOOK前はキーボードを押しても無反応です。しかしHOOK後は、hook.dllのHOOK本体によりキーコードがメッセージボックスで表示されるようになるはずです。 最後のhookctrl.exeはHOOKのON/OFFを切り替えます。SetWindowsHookEx()とUnhookWindowsHookEx()を呼びます。簡単なCUIベースにしても良かったのですが、今回HOOKするのはキーボードイベントです。global-hook、つまりhookctrl.exe自身もHOOK対象になりますので、CUIベースですとON/OFFをキーボードで入力するたびにHOOKが動作してしまいます。それはさすがに煩わしいので、マウスクリックだけでON/OFFできるように簡単なダイアログボックスを使ったGUIアプリにしています。 では、各EXE/DLLの内容とコンパイル方法を紹介します。 ** foo.exe foo.exeを構成するファイルは1つだけです。 - foo.c foo.c: #code|c|> #include #define MYWNDCLSNAME "MyWindowClass" LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); int WINAPI WinMain( HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmdLine, int nCmdShow) { WNDCLASS wndcls; HWND hWnd; MSG msg; ZeroMemory(&wndcls, sizeof(wndcls)); wndcls.lpfnWndProc = WndProc; wndcls.hInstance = hInst; wndcls.hIcon = LoadIcon(0, IDI_APPLICATION); wndcls.hCursor = LoadCursor(0, IDC_ARROW); wndcls.hbrBackground = (HBRUSH)COLOR_BACKGROUND; wndcls.lpszClassName = MYWNDCLSNAME; if (0 == RegisterClass(&wndcls)) { return -1; } hWnd = CreateWindow( MYWNDCLSNAME, "My Window", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, hInst, NULL); if (0 == hWnd) { return -2; } ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); while (GetMessage(&msg, 0, 0, 0)) { DispatchMessage(&msg); } return msg.wParam; } LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_LBUTTONDOWN: MessageBox(hWnd, "Hello, World", "Message", MB_OK | MB_ICONINFORMATION); return 0; case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProc(hWnd, uMsg, wParam, lParam); } ||< 何もないウインドウを表示し、左クリックされたら"Hello, World"をメッセージボックスで表示するだけのWindowsプログラミング入門用サンプルです。 コンパイル方法: > cl foo.c user32.lib ** hook.dll hook.dllを構成するファイルは以下の2つです。HOOK本体の関数は、hookctrl.exeから見える必要があるので、hook.defで明示的にエキスポートしています。"__declspec(dllexport)"を使う場合はhook.defは不要です。 - hook.c - hook.def hook.c: #code|c|> #include #include // キーボードHOOK本体 LRESULT CALLBACK MyKeyboardProc(int code, WPARAM wParam, LPARAM lParam) { char buf[200]; _snprintf(buf, 200, "virtual-key code is 0x%08X\n", wParam); MessageBox(NULL, buf, buf, MB_OK); return CallNextHookEx(NULL, code, wParam, lParam); } BOOL WINAPI DllMain( HINSTANCE hInst, DWORD dwReason, LPVOID lpvReserved) { DWORD dwPid; char buf[200]; dwPid = GetCurrentProcessId(); switch (dwReason) { case DLL_PROCESS_ATTACH: _snprintf(buf, 200, "attached to process id [0x%08X].", dwPid); MessageBox(NULL, buf, buf, MB_OK); break; case DLL_PROCESS_DETACH: _snprintf(buf, 200, "de-attached to process id [0x%08X].", dwPid); MessageBox(NULL, buf, buf, MB_OK); break; } return TRUE; } ||< DllMain()で、プロセスにattachされたりdetachされる時に、プロセスIDをメッセージボックスで表示するようにしています。こうすると実際にHOOKが発動してhook.dllがプロセスにattachされた瞬間を知ることができます。 hook.def: #pre||> LIBRARY hook.dll EXPORTS MyKeyboardProc ||< コンパイル方法: > cl /LD hook.c user32.lib /link /def:hook.def ** hookctrl.exe ダイアログボックスを使う=リソースを使いますので、次の3ファイル構成となります。 - resource.h - hookctrl.rc - hookctrl.c resource.h: #code|c|> #ifndef IDC_STATIC #define IDC_STATIC (-1) #endif #define IDD_DIALOG1 100 #define ID_HOOK_ON 1002 #define ID_HOOK_OFF 1003 ||< hookctrl.rc: #pre||> // Generated by ResEdit 1.4.13 // Copyright (C) 2006-2010 // http://www.resedit.net #include #include #include #include "resource.h" // // Dialog resources // LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL IDD_DIALOG1 DIALOG 0, 0, 121, 47 STYLE DS_3DLOOK | DS_CENTER | DS_MODALFRAME | \ DS_SHELLFONT | WS_VISIBLE | WS_BORDER | WS_CAPTION | \ WS_DLGFRAME | WS_SYSMENU CAPTION "hook controller" FONT 8, "Ms Shell Dlg 2" { DEFPUSHBUTTON "Hook ON", ID_HOOK_ON, 12, 23, 33, 14 PUSHBUTTON "Hook OFF", ID_HOOK_OFF, 58, 23, 36, 14 LTEXT "WH_KEYBORD Hook Controller", IDC_STATIC, 11, 6, 98, 8, SS_LEFT } ||< hookctrl.c: #code|c|> #include #include "resource.h" LRESULT __declspec(dllimport) CALLBACK MyKeyboardProc(int, WPARAM, LPARAM); HHOOK hHook; BOOL CALLBACK DialogProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_COMMAND: switch (LOWORD(wParam)) { case ID_HOOK_ON: hHook = SetWindowsHookEx( WH_KEYBOARD, MyKeyboardProc, GetModuleHandle("hook.dll"), 0); if (NULL == hHook) { MessageBox(hWnd, "failed.", "hook on", MB_OK); } else { MessageBox(hWnd, "success.", "hook on", MB_OK); } return TRUE; case ID_HOOK_OFF: if (!UnhookWindowsHookEx(hHook)) { MessageBox(hWnd, "failed.", "hook off", MB_OK); } else { MessageBox(hWnd, "success.", "hook off", MB_OK); } return TRUE; case IDCANCEL: EndDialog(hWnd, wParam); return TRUE; } } return FALSE; } int main(int argc, char *argv[]) { HWND hWndDesktop = NULL; HINSTANCE hCurrentInst = NULL; UINT_PTR uDlgRet = 0; hCurrentInst = GetModuleHandle(NULL); hWndDesktop = GetDesktopWindow(); DialogBox(hCurrentInst, MAKEINTRESOURCE(IDD_DIALOG1), hWndDesktop, (DLGPROC)DialogProc); return 0; } ||< コンパイル方法: > rc hookctrl.rc > cl hookctrl.c hookctrl.res user32.lib hook.lib * サンプルで遊んでみる + 最初にfoo.exeを起動します。この時点ではfoo.exeのウインドウ内でキーボードを押しても何の反応もありません。 + hookctrl.exeを起動します。hook.dllも自動的にロードされるので、attach時のメッセージボックスが表示されます。 + hookctrl.exeの"Hook ON" ボタンをクリックします。 + foo.exeのウインドウ内で適当なキーを押してみます。 ++ 最初に、hook.dllがfoo.exeにattachされるメッセージボックスが表示されます。 ++ 続いて、キーを押した時に一度/離した時に一度、計二度、hook.dllのHOOK本体によりメッセージボックスが表示されます。 + hookctrl.exeの"Hook OFF" ボタンをクリックします。 ++ foo.exeで、hook.dllがdetachされるメッセージボックスが表示されます。 ++ 以降、foo.exeのウインドウ内でキーを押しても反応が無くなります。 foo.exeをOllyDbg上などで動かしてみると、HOOK ON/OFFに応じてhook.dllがメモリ内に読み込まれ、解放される様子を眺めることも出来ます。 ところで、今回の実験では同じデスクトップ上の全てのスレッドに対してHOOKされてしまいます。 次回は、HOOK対象のプロセスIDを指定出来るよう改造してみましょう。