逆核系列No.6--Windows Hook

本节内容

Windows消息钩取并利用小实例进行理解该过程

源码分析

关键下钩子的API的原型

1
2
3
4
5
6
HHOOK SetWindowsHookExW(
[in] int idHook,
[in] HOOKPROC lpfn,
[in] HINSTANCE hmod,
[in] DWORD dwThreadId //该参数值为0时则该钩子为全局钩子会对所有实例以及后续新生实例产生作用
);

示例程序源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
//HookMain.exe源码

#include "stdio.h"
#include "conio.h"
#include "windows.h"

#define DEF_DLL_NAME "KeyHook.dll"
#define DEF_HOOKSTART "HookStart"
#define DEF_HOOKSTOP "HookStop"

typedef void (*PFN_HOOKSTART)();
typedef void (*PFN_HOOKSTOP)();

void main()
{
HMODULE hDll = NULL;
PFN_HOOKSTART HookStart = NULL;
PFN_HOOKSTOP HookStop = NULL;
char ch = 0;

// KeyHook.dll载入
hDll = LoadLibraryA(DEF_DLL_NAME);
if( hDll == NULL )
{
printf("LoadLibrary(%s) failed!!! [%d]", DEF_DLL_NAME, GetLastError());
return;
}

// export函数导入
HookStart = (PFN_HOOKSTART)GetProcAddress(hDll, DEF_HOOKSTART);
HookStop = (PFN_HOOKSTOP)GetProcAddress(hDll, DEF_HOOKSTOP);

// 启用Hook
HookStart();

// 直至接收到'q'否则不停止钩取
printf("press 'q' to quit!\n");
while( _getch() != 'q' ) ;

// 钩子断钩
HookStop();

// KeyHook.dll 卸载
FreeLibrary(hDll);
}

HookMain的源码比较简单,就是载入KeyHook.dll并且导入HookStart和HookStop用于控制钩子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
//KeyHook.dll源码

#include "stdio.h"
#include "windows.h"

#define DEF_PROCESS_NAME "notepad.exe"

HINSTANCE g_hInstance = NULL;
HHOOK g_hHook = NULL;
HWND g_hWnd = NULL;

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpvReserved)
{
switch( dwReason )
{
case DLL_PROCESS_ATTACH:
g_hInstance = hinstDLL;
break;

case DLL_PROCESS_DETACH:
break;
}

return TRUE;
}

LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
char szPath[MAX_PATH] = {0,};
char *p = NULL;

if( nCode >= 0 )
{
// bit 31 : 0 => press, 1 => release
if( !(lParam & 0x80000000) ) //释放键盘
{
GetModuleFileNameA(NULL, szPath, MAX_PATH); //获取模块的绝对路径,为null则获取正在执行的程序路径
p = strrchr(szPath, '\\');

//比对当前进程的名称,若为notepad.exe则消息不回传递给应用程序或下一个钩子
if( !_stricmp(p + 1, DEF_PROCESS_NAME) )
return 1;
}
}

// 若非notepad.exe则调用CallNextHookEx将消息传递给应用程序或下一个钩子
return CallNextHookEx(g_hHook, nCode, wParam, lParam);
}

#ifdef __cplusplus
extern "C" {
#endif
__declspec(dllexport) void HookStart()
{
g_hHook = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, g_hInstance, 0);
}

__declspec(dllexport) void HookStop()
{
if( g_hHook )
{
UnhookWindowsHookEx(g_hHook);
g_hHook = NULL;
}
}
#ifdef __cplusplus
}
#endif

KeyHook.dll则是控制钩子和钩子的内部逻辑,回调函数KeyboardProc是自写的逻辑完成的功能是在接受全局钩子钩取到的消息时判断该消息所对应的应用程序是否是notepad.exe,若为notepad.exe则return 1 否则CallNextHookEx。下钩HookStart 断钩HookStop。


梳理一下过程:
  • Hookmain.exe调用LoadLibrary尝试载入KeyHook.dll文件,成功则LoadLibrary返回KeyHook.dll的对应句柄

  • 成功载入KeyHook.dll由于Windows载入DLL的机制会使得KeyHook.DllMain因为dwReason = DLL_PROCESS_ATTACH触发,执行相应的行为(g_hInstance = hinstDLL:实际上hinstDLL是指向KeyHook.dll自身的实例句柄)

  • HookMain.exe接着获取了KeyHook.dll下的两个API :HookStart & HookStop,并启动了HookStart

  • 程序回到KeyHook.dll内部,执行SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, g_hInstance, 0); 含义是:钩子处理的消息类型是WH_KEYBOARD,一下是SetWindowsHookEx.idHook的可取值int类型(代号标识),比较多这里只列出源码中的WH_KEYBOARD,更多信息请自行查阅文档

    Value Meanning
    WH_KEYBOARD2 Installs a hook procedure that monitors keystroke messages. For more information, see the KeyboardProc hook procedure.

    表明钩子SetWindowsHookEx处理的是键盘事件,回调函数是KeyHook.KeyboardProc这是由于(g_hInstance指向KeyHook.dll的实例句柄),最后一个参数0表明钩子是个全局钩子。

  • 当钩子的触发事件(键盘事件)发生,会调用KeyHook.KeyboardProc,该函数的原型:

    1
    2
    3
    4
    5
    LRESULT CALLBACK KeyboardProc(
    _In_ int    code, //how to process the message
    _In_ WPARAM wParam, //contain information about a keystroke message
    _In_ LPARAM lParam //contain information about a keystroke message
    );

    第一个参数code:

    A code the hook procedure uses to determine how to process the message. If code is less than zero, the hook procedure must pass the message to the CallNextHookEx function without further processing and should return the value returned by CallNextHookEx. This parameter can be one of the following values.

    表明钩子相应处理过程如何处理钩取到的消息,当该值小于0,则不对钩取到的内容做更多处理,必须让KeyboardProc()函数返回CallNextHookEx()

    Value Meaning
    HC_ACTION 0 The wParam and lParam parameters contain information about a keystroke message.
    HC_NOREMOVE 3 The wParam and lParam parameters contain information about a keystroke message, and the keystroke message has not been removed from the message queue. (An application called the PeekMessage function, specifying the PM_NOREMOVE flag.)

    钩子回调函数KeyboardProc判断消息来源是否是关心的进(线)程,不是则放行,是则做截取(截取后可以进行相应的修改操作影响最终输入的内容,比如往记事本中写入内容,截取后将其修改成其他内容)由于不是本节的主题内容,这里截取后并未做更多的处理。


调试程序

程序运行在终端会提示关键字符串press 'q' to quit! 给了快速定位main函数的方式:

在0x00401006处调用LoadLibarayA() 载入KeyHook.dll 文件并且在0x0040103D处获取KeyHook.dll.HookStart()的地址在之后也载入了KeyHook.dll.HookStop()函数:

并且在不远处的代码0x0040104B的call ebx 对KeyHook.dll.HookStart() 进行调用,这里跟随:

由地址空间的变化可以确定此时所在的空间是KeyHook.dll的空间

前面的几个push操作是对SetWindowsHookExW参数的入栈,这是下钩的函数。

之后retn回程序用户地址空间,并且完成终端输出:press 'q' to quit!

并且后续出现了一个条件判断循环,不难判断出这是对键盘输入 ‘ q ’的循环判断,0x71对应的ascii码也是 ‘ q ’


接下来保持==HookMain.exe(管理员权限)==的运行(这是为了保持钩子处于开启的状态),然后OD附加notepad.exe进行调试观察。

运行notepad.exe,随后使用OD进行进程附加,附加成功后打开debugging Options打开载入‘终端与新模块DLL’选项,这样当有新模块载入时会中断程序运行

预期的效果是在notepad.exe中使用键盘,然后触发钩子,注入KeyHook.dll然后OD会中断notepad.exe的运行。

双击KeyHook.dll条目,跳转到KeyHook.dll的入口:

(ps:成功载入KeyHook.dll后需要取消OD中 DebuggingOptions 中的新模块加入中断的选项,已经载入了就不需要了,以保证不出错)

Author: Victory+
Link: https://cvjark.github.io/2022/05/02/逆核系列No-6-Windows-Hook/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.