前回( [[688]] )に引き続きWindowsでの"HOOK"の実験です。 前回は同じデスクトップ上の全てのスレッドでHOOKが発動してしまいました。今回は任意のプロセスにのみHOOKできるようにしてみます。といっても、SetWindowsHookEx()APIにはそこまでの柔軟性はありません。global-hookを使う以上は、どうしても全てのプロセスでHOOK-DLLがロードされてしまいますし、HOOK本体も実行されてしまいます。 そこで、HOOK対象となるプロセスIDをDLLの共有メモリ機能を使って予め登録しておき、HOOK本体の中で現在のプロセスIDがHOOK対象と同じ場合にのみ、HOOK処理を実行するような分岐を組み込みます。他のプロセス内で発動した場合は、分岐により何もせずSKIPするようになります。 #more|| #outline|| ---- * サンプルコードの構成 前回と同様、3つのEXE/DLLより構成されます。 + foo.exe : HOOK「される」のを確認する為のダミーEXE + hook.dll : HOOK本体が格納されるDLL + hookctrl.exe : HOOK ON/OFF を切り替えるEXE 各DLLの役割も前回と同じです。 では早速ソースとコンパイル方法の紹介に移ります。 ** foo.exe (前回と同じ) foo.exeを構成するファイルとコンパイル方法は前回( [[688]] )と同じ為、省略します。 ** hook.dll 前回と同様、hook.cとhook.defにより構成されています。変更点はファイルのコメントを参照して下さい。 - hook.c - hook.def hook.c: #code|c|> #include #include // プロセス間で共有出来る領域を用意します。 #pragma data_seg(".shared") // 実際にHOOK処理を実行したいプロセスID static DWORD dwTargetProcessId = 0; #pragma data_seg() // これが hookctrl.exe から呼ばれ、HOOK対象のプロセスIDを共有領域に保存します。 void SetMyKeyboardProcTarget(DWORD dwPid) { dwTargetProcessId = dwPid; } LRESULT CALLBACK MyKeyboardProc(int code, WPARAM wParam, LPARAM lParam) { char buf[200]; DWORD dwPid = GetCurrentProcessId(); if (dwTargetProcessId == dwPid) { // 現在のプロセスIDが対象と同じ時のみ、HOOK処理を実行します。 _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) { char buf[200]; DWORD dwPid = GetCurrentProcessId(); switch (dwReason) { case DLL_PROCESS_ATTACH: if (dwTargetProcessId == dwPid) { // attach/detachされるプロセスIDが対象と同じ場合のみ、 // メッセージボックスを表示します。 _snprintf(buf, 200, "ATTACH to 0x%08X.", dwPid); MessageBox(NULL, buf, buf, MB_OK); } break; case DLL_PROCESS_DETACH: if (dwTargetProcessId == dwPid) { // attach/detachされるプロセスIDが対象と同じ場合のみ、 // メッセージボックスを表示します。 _snprintf(buf, 200, "DETACH to 0x%08X.", dwPid); MessageBox(NULL, buf, buf, MB_OK); } break; } return TRUE; } ||< hook.def:SetMyKeyboardProcTargetを追加しています。 #pre||> LIBRARY hook.dll EXPORTS MyKeyboardProc SetMyKeyboardProcTarget ||< コンパイル方法: > cl /LD hook.c user32.lib /link /def:hook.def /SECTION:.shared,rws "/SECTION:.shared,rws" リンカオプションで data_seg(".shared") pragmaで指定したセクションを読み書き共有可能にしています。 ** hookctrl.exe 前回と同様、3ファイル構成となります。 - resource.h - hookctrl.rc - hookctrl.c resource.h, hookctrl.rc については前回と同じですので省略します。 hookctrl.cは次のようになります。変更点についてはコメントを参照して下さい。 #code|c|> #include #include #include #include "resource.h" // hook.dll からインポート LRESULT __declspec(dllimport) CALLBACK MyKeyboardProc(int, WPARAM, LPARAM); void __declspec(dllimport) SetMyKeyboardProcTarget(DWORD); HHOOK hHook; // 指定されたEXEファイルで実行中のプロセスIDを取得する DWORD find_proc_id(const char *exe_filename) { HANDLE hProcessSnap; HANDLE hProcess; HMODULE hModule; DWORD cbNeeded; PROCESSENTRY32 pe32; DWORD dwProcId = 0; char szProcName[MAX_PATH]; hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (INVALID_HANDLE_VALUE == hProcessSnap) { return 0; } pe32.dwSize = sizeof(PROCESSENTRY32); if (!Process32First(hProcessSnap, &pe32)) { return 0; } do { hProcess = OpenProcess( PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pe32.th32ProcessID); if (NULL != hProcess) { if (EnumProcessModules( hProcess, &hModule, sizeof(hModule), &cbNeeded)) { GetModuleBaseName(hProcess, hModule, szProcName, sizeof(szProcName) / sizeof(szProcName[0])); if (!strcmp(szProcName, exe_filename)) { dwProcId = pe32.th32ProcessID; } } CloseHandle(hProcess); } } while (Process32Next(hProcessSnap, &pe32)); CloseHandle(hProcessSnap); return dwProcId; } // ダイアログの処理は前回と同じです。 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; // "foo.exe"のプロセスIDを取得します。 DWORD dwTargetPid = find_proc_id("foo.exe"); if (0 == dwTargetPid) { MessageBox(NULL, "foo.exe does not exists.", "hook target missing", MB_OK); return 1; } // hook.dllの共有領域に、HOOK対象プロセスIDを保存します。 SetMyKeyboardProcTarget(dwTargetPid); // 以降は前回と同じ。 hCurrentInst = GetModuleHandle(NULL); hWndDesktop = GetDesktopWindow(); DialogBox( hCurrentInst, MAKEINTRESOURCE(IDD_DIALOG1), hWndDesktop, (DLGPROC)DialogProc); return 0; } ||< コンパイル方法:プロセスID取得の関数追加に伴い、psapi.libを追加。 > rc hookctrl.rc > cl hookctrl.c hookctrl.res user32.lib hook.lib psapi.lib * サンプルで遊んでみる 前回と同様の手順で遊ぶことができます。 ただし、今回のサンプルではfoo.exe以外のプロセスではHOOK処理が発動しないようになりました。実際にはhook.dllはロードされてMyKeyboardProc()も呼ばれていますが、foo.exe以外のプロセスでは分岐処理でSKIPするようになります。