home ホーム search 検索 -  login ログイン  | reload edit datainfo version cmd icon diff delete  | help ヘルプ

日記/2010/06/30/SetWindowsHookEx()の実験その1

日記/2010/06/30/SetWindowsHookEx()の実験その1

日記 / 2010 / 06 / 30 / SetWindowsHookEx()の実験その1
id: 688 所有者: msakamoto-sf    作成日: 2010-06-30 16:03:23
カテゴリ: Windows プログラミング 

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"

サンプルコードの構成

今回のサンプルは3つのEXE/DLLより構成されます。

  1. foo.exe : HOOK「される」のを確認する為のダミーEXE
  2. hook.dll : HOOK本体が格納されるDLL
  3. 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:

#include <windows.h>
#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:

#include <windows.h>
#include <stdio.h>
 
// キーボード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:

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:

#ifndef IDC_STATIC
#define IDC_STATIC (-1)
#endif
 
#define IDD_DIALOG1                             100
#define ID_HOOK_ON                              1002
#define ID_HOOK_OFF                             1003

hookctrl.rc:

// Generated by ResEdit 1.4.13
// Copyright (C) 2006-2010
// http://www.resedit.net

#include <windows.h>
#include <commctrl.h>
#include <richedit.h>
#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:

#include <windows.h>
#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

サンプルで遊んでみる

  1. 最初にfoo.exeを起動します。この時点ではfoo.exeのウインドウ内でキーボードを押しても何の反応もありません。
  2. hookctrl.exeを起動します。hook.dllも自動的にロードされるので、attach時のメッセージボックスが表示されます。
  3. hookctrl.exeの"Hook ON" ボタンをクリックします。
  4. foo.exeのウインドウ内で適当なキーを押してみます。
    1. 最初に、hook.dllがfoo.exeにattachされるメッセージボックスが表示されます。
    2. 続いて、キーを押した時に一度/離した時に一度、計二度、hook.dllのHOOK本体によりメッセージボックスが表示されます。
  5. hookctrl.exeの"Hook OFF" ボタンをクリックします。
    1. foo.exeで、hook.dllがdetachされるメッセージボックスが表示されます。
    2. 以降、foo.exeのウインドウ内でキーを押しても反応が無くなります。

foo.exeをOllyDbg上などで動かしてみると、HOOK ON/OFFに応じてhook.dllがメモリ内に読み込まれ、解放される様子を眺めることも出来ます。

ところで、今回の実験では同じデスクトップ上の全てのスレッドに対してHOOKされてしまいます。
次回は、HOOK対象のプロセスIDを指定出来るよう改造してみましょう。


プレーンテキスト形式でダウンロード
現在のバージョン : 1
更新者: msakamoto-sf
更新日: 2010-06-30 17:45:57
md5:ddb29027f0275a7bae35635df9b13e90
sha1:2654ad3ec8044e1adf4c105d344b5c3740ab92f5
コメント
コメントを投稿するにはログインして下さい。