Windows hook介绍

windows下hook机制主要是替换程序要使用的函数,以替换掉原函数的功能或者添加功能。

比如杀软主动防御功能,监控敏感API,对这些函数进行hook;木马病毒可能hook键盘消息来窃取用户输入,从而获得某些密码

windows下的hook主要分为应用层和内核层,内核层一般在x86平台使用,因为从Windows Vista x64开始引入Patch Guard技术极大地限制了Windows内核挂钩的使用。

内核层hook:

  • SSDT hook

应用层hook

  • 消息hook

    • SetWindowsHook
  • 注入hook

    • IAT hook

    • Inline Hook

    • HotFix Hook

  • 调试Hook

    • DebugActiveProcess

消息hook

Windows消息流:

  1. 发生键盘输入事件时,WM_KEYDOWN消息被添加到[OS message queue]

  2. OS判断哪个应用程序发生了事件,然后从[OS message queue]取出消息,添加到相应应用程序的[application message queue]中

  3. 应用程序监视自身的[application message queue],发现WM_KEYDOWN消息调用相应的事件处理程序

钩子位于[OS message queue]和[application message queue]

代码实现:调用SetWindowsHookEx API

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
#include <string>
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <Windows.h>
#include <iostream>
#include <fstream>

using namespace std;

#define DEF_PROCESS_NAME "notepad.exe"

HHOOK g_hHook;
HINSTANCE g_HInstance;

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

if (keyNum->flags == 128 || keyNum->flags == 129) {
printf("0x%x\n", keyNum->vkCode);
}
return CallNextHookEx(NULL, nCode, wParam, lParam);
}

_declspec(dllexport) void HookStart() {
g_HInstance = GetModuleHandleA(NULL);
g_hHook = SetWindowsHookExA(WH_KEYBOARD_LL, KeyboardProc, g_HInstance, NULL);
if (g_hHook) {
printf("Hook Success\n");
}
else {
printf("Hook Failed\n");
}

}
_declspec(dllexport) void HookStop() {
if (g_hHook) {
UnhookWindowsHookEx(g_hHook);
g_hHook = NULL;
printf("Unhook Success\n");
}
}
int main() {
char buf[256] = { 0 };
HookStart();
MSG msg;
while (1) {
// 这里的代码参考了 https://blog.csdn.net/Simon798/article/details/98848050
if (PeekMessageA(
&msg, // MSG 接收这个消息
NULL, // 检测消息的窗口句柄,NULL:检索当前线程所有窗口消息
NULL, // 检查消息范围中第一个消息的值,NULL:检查所有消息(必须和下面的同时为NULL)
NULL, // 检查消息范围中最后一个消息的值,NULL:检查所有消息(必须和上面的同时为NULL)
PM_REMOVE // 处理消息的方式,PM_REMOVE:处理后将消息从队列中删除
)) {
// 把按键消息传递给字符消息
TranslateMessage(&msg);

// 将消息分派给窗口程序
DispatchMessageW(&msg);
}
else
Sleep(0); //避免CPU全负载运行
}
HookStop();
return 0;
}

这里处理了所有的按键消息,如果有需要可以查阅相关资料。

如果有图形化界面,可以不用处理消息。因为图形化界面允许回调函数的。

弊端是只能监视较少消息,如击键消息,鼠标消息,窗口消息

调试Hook

和调试器类似,让进程发生异常,然后自己捕获异常,对于被调试状态下的级进行恶意操作。

核心思路是将API的第一个字节修改为0xCC(INT 3),当API被调用时,由于触发了异常,控制权交给调试器。

相关函数

  • CreateProcess()

    启动调试一个进程

  • DebugActiveProcess(ProcessID)

    附加到一个进程

  • 获取进程PID

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    bool traverseProcesses() {
    PROCESSENTRY32 pe32;
    pe32.dwSize = sizeof(pe32);
    HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
    if (hProcessSnap == INVALID_HANDLE_VALUE) {
    printf("Create snap Error!\n");
    return false;
    }
    BOOL bResult = Process32First(hProcessSnap, &pe32);
    int num(0);
    while (bResult) {
    _bstr_t b(pe32.szExeFile); // 将宽字节转化为窄字节
    cout << '[' << num++ << ']' << ' ' << b << ' ' << pe32.th32ProcessID << endl;
    bResult = Process32Next(hProcessSnap, &pe32);
    }
    return true;
    }

代码实现(64位,需要先阅读文档查看传参规则)

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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
#include <string>
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <Windows.h>
#include <iostream>
#include <fstream>
#include <TlHelp32.h>
#include <comdef.h>

using namespace std;

#define DEF_PROCESS_NAME "notepad.exe"

LPVOID WriteFileAddress = NULL;
CREATE_PROCESS_DEBUG_INFO CreateProcessDebugInfomation;
BYTE INT3 = 0xCC, OldByte = 0;

BOOL OnCreateProcessDebugEvent(LPDEBUG_EVENT pde)
{
// WriteFile()函数地址
WriteFileAddress = GetProcAddress(GetModuleHandleA("kernel32.dll"), "WriteFile");

// API Hook - WriteFile()
//将WriteFile函数的首个字节改为0xcc
memcpy(&CreateProcessDebugInfomation, &pde->u.CreateProcessInfo, sizeof(CREATE_PROCESS_DEBUG_INFO));
ReadProcessMemory(CreateProcessDebugInfomation.hProcess, WriteFileAddress,
&OldByte, sizeof(BYTE), NULL);
WriteProcessMemory(CreateProcessDebugInfomation.hProcess, WriteFileAddress,
&INT3, sizeof(BYTE), NULL);

return TRUE;
}

BOOL OnExceptionDebugEvent(LPDEBUG_EVENT pDebugEvent)
{
CONTEXT Context;
PBYTE lpBuffer = NULL;
DWORD64 dwNumOfBytesToWrite, dwAddrOfBuffer, i;
PEXCEPTION_RECORD pExceptionRecord = &pDebugEvent->u.Exception.ExceptionRecord;
cout <<hex<< EXCEPTION_BREAKPOINT;
cout << ' ';
cout << hex << pExceptionRecord->ExceptionCode;
cout << endl;
// BreakPoint exception
if (EXCEPTION_BREAKPOINT == pExceptionRecord->ExceptionCode)
{
// 发生异常的地方是否为我们要钩取的函数
if (WriteFileAddress == pExceptionRecord->ExceptionAddress)
{
// #1. Unhook
// 先恢复,以免进入死循环
WriteProcessMemory(CreateProcessDebugInfomation.hProcess, WriteFileAddress,
&OldByte, sizeof(BYTE), NULL);

// #2. 获得线程上下背景文 为了修改EIp的值,来使进程恢复正常运行
Context.ContextFlags = CONTEXT_FULL;
GetThreadContext(CreateProcessDebugInfomation.hThread, &Context);

// #3. WriteFile() 根据ESP来获得WriteFile 函数的参数,以达到修改数据的目的

//ReadProcessMemory(CreateProcessDebugInfomation.hProcess, (LPVOID)(Context.Rsp + 0x8),
// &dwAddrOfBuffer, sizeof(DWORD), NULL);

//ReadProcessMemory(CreateProcessDebugInfomation.hProcess, (LPVOID)(Context.Rsp + 0xC),
// &dwNumOfBytesToWrite, sizeof(DWORD), NULL);
// 调试发现,64位情况下传参使用rcx,rdx,r8d,r9
dwAddrOfBuffer = Context.Rdx;
dwNumOfBytesToWrite = Context.R8;
cout <<hex<< Context.Rip << endl;
// #4.
lpBuffer = (PBYTE)malloc(dwNumOfBytesToWrite + 1);
memset(lpBuffer, 0, dwNumOfBytesToWrite + 1);

// #5. WriteFile()
ReadProcessMemory(CreateProcessDebugInfomation.hProcess, (LPVOID)dwAddrOfBuffer,
lpBuffer, dwNumOfBytesToWrite, NULL);
printf("\n### original string ###\n%s\n", lpBuffer);

// #6. 修改数据
for (i = 0; i < dwNumOfBytesToWrite; i++)
{
if (0x61 <= lpBuffer[i] && lpBuffer[i] <= 0x7A)
lpBuffer[i] -= 0x20;
}

printf("\n### converted string ###\n%s\n", lpBuffer);

// #7. 调用原函数
WriteProcessMemory(CreateProcessDebugInfomation.hProcess, (LPVOID)dwAddrOfBuffer,
lpBuffer, dwNumOfBytesToWrite, NULL);
free(lpBuffer);
// 设置EIP的值来实现正常运行,注意EIP的值为0xcc的下一条指令的地址。
Context.Rip = (DWORD64)WriteFileAddress;
SetThreadContext(CreateProcessDebugInfomation.hThread, &Context);

// 运行
ContinueDebugEvent(pDebugEvent->dwProcessId, pDebugEvent->dwThreadId, DBG_CONTINUE);
Sleep(0);

// 再次钩取
WriteProcessMemory(CreateProcessDebugInfomation.hProcess, WriteFileAddress,
&INT3, sizeof(BYTE), NULL);

return TRUE;
}
}

return FALSE;
}

void DebugLoop()
{
DEBUG_EVENT DebugEvent;
DWORD dwContinueStatus;

// 等待调试事件
while (WaitForDebugEvent(&DebugEvent, INFINITE))
{
dwContinueStatus = DBG_CONTINUE;

// 调试事件为创建进程
if (CREATE_PROCESS_DEBUG_EVENT == DebugEvent.dwDebugEventCode)
{
OnCreateProcessDebugEvent(&DebugEvent);
}
// 调试事件
else if (EXCEPTION_DEBUG_EVENT == DebugEvent.dwDebugEventCode)
{
if (OnExceptionDebugEvent(&DebugEvent))
continue;
}
// 调试进程退出
else if (EXIT_PROCESS_DEBUG_EVENT == DebugEvent.dwDebugEventCode)
{

break;
}


ContinueDebugEvent(DebugEvent.dwProcessId, DebugEvent.dwThreadId, dwContinueStatus);
}
}
DWORD traverseProcesses() {
PROCESSENTRY32 pe32;
pe32.dwSize = sizeof(pe32);
HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
if (hProcessSnap == INVALID_HANDLE_VALUE) {
printf("Create snap Error!\n");
return -1;
}
BOOL bResult = Process32First(hProcessSnap, &pe32);
int num(0);
while (bResult) {
_bstr_t b(pe32.szExeFile);
if (!strcmp(b, "notepad.exe")) {
cout <<"find: "<<b<<" "<< pe32.th32ProcessID << endl;
return pe32.th32ProcessID;
}
bResult = Process32Next(hProcessSnap, &pe32);
}
return -1;
}
void debugHook(DWORD pid) {
printf("attach pid %d\n", pid);
if (!DebugActiveProcess(pid)) {
printf("attach Failed %d\n",GetLastError());
exit(-1);
}
DebugLoop();
}
int main() {
DWORD pid = traverseProcesses();
debugHook(pid);
return 0;
}

注入Hook

核心思想是修改API代码,使用DLL注入技术,将Hook的代码注入到另一进程,此时DLL在B进程的内存中,就有权限直接修改B内存中的代码了。

以下内容需要了解dll注入技术。

IAT Hook

修改IAT里的函数地址对API进行Hook

把IAT表中API的地址修改为恶意代码地址,就可以实现Hook效果

要hook的dll: 在main函数中计算IAT的位置并且修改对应的代码

  1. 穷举所有模块,找到要hook的模块

  2. 修改内存属性

  3. 替换iat表中表项

  4. 改回内存属性

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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
#include "pch.h"
#include <tchar.h>
#include <iostream>
#include <fstream>
#include <stdio.h>

using namespace std;

// 加载静态链接库
#pragma comment(lib,"urlmon.lib")
// dllMain函数是程序的入口点
HMODULE hMod = NULL;
PBYTE pAddr = NULL;
DWORD64 dwRVA;
PIMAGE_IMPORT_DESCRIPTOR pImportDesc;
LPCSTR szLibName;
char szDllName[] = "kernel32.dll";
PIMAGE_THUNK_DATA pThunk;
DWORD dwOldProtect;

typedef BOOL (*FnWriteFile)(
HANDLE hFile,
LPCVOID lpBuffer,
DWORD nNumberOfBytesToWrite,
LPDWORD lpNumberOfBytesWritten,
LPOVERLAPPED lpOverlapped
);

FnWriteFile pfnOld = nullptr;

BOOL pfnNew(HANDLE hFile,
LPCVOID lpBuffer,
DWORD nNumberOfBytesToWrite,
LPDWORD lpNumberOfBytesWritten,
LPOVERLAPPED lpOverlapped){

char szHookText[] = "IBinary -> Iat Hook";
MessageBoxA(NULL, "Successed", (LPCSTR)lpBuffer, MB_OK);
if (pfnOld != nullptr)
{
return pfnOld(hFile,
lpBuffer,
nNumberOfBytesToWrite,
lpNumberOfBytesWritten,
lpOverlapped);//调用以前的
}
return FALSE;
}
void iatHook()
{
MessageBoxA(NULL, "开始进行HOOK", NULL, NULL);
FARPROC pHookAddress = GetProcAddress(GetModuleHandleA("kernel32.dll"), "WriteFile"); //你要HOOK的函数.
if (nullptr == pHookAddress)
{
OutputDebugString(TEXT("获取函数地址失败"));
MessageBoxA(NULL, "获取函数地址失败HOOK", NULL, NULL);

return;
}
pfnOld = (FnWriteFile)pHookAddress; //保存旧的函数指针.
//解析PE头.寻找IAT.

HMODULE hModImageBase = GetModuleHandle(NULL);//获取当前的ImagBase
PIMAGE_DOS_HEADER pDosHead = (PIMAGE_DOS_HEADER)(DWORD64)hModImageBase; //获取DOS头
DWORD64 dwTemp = (DWORD64)pDosHead + (DWORD64)pDosHead->e_lfanew;
PIMAGE_NT_HEADERS pNtHead = (PIMAGE_NT_HEADERS)dwTemp;
PIMAGE_FILE_HEADER pFileHead = (PIMAGE_FILE_HEADER)&pNtHead->FileHeader;
PIMAGE_OPTIONAL_HEADER pOptHead = (PIMAGE_OPTIONAL_HEADER)&pNtHead->OptionalHeader;

//寻找导出表的位置.
DWORD dwExportLocal = pOptHead->DataDirectory[1].VirtualAddress; //找到导出表偏移.
//定位到导出表
dwTemp = (DWORD64)GetModuleHandle(NULL) + dwExportLocal;
PIMAGE_IMPORT_DESCRIPTOR pImport = (PIMAGE_IMPORT_DESCRIPTOR)dwTemp;
PIMAGE_IMPORT_DESCRIPTOR pCurrent = pImport;
DWORD64* pFirstThunk; //导入表子表,也就是IAT存储函数地址的表.
//遍历导入表

while (pCurrent->Characteristics && pCurrent->FirstThunk != NULL)
{
dwTemp = pCurrent->FirstThunk + (DWORD64)GetModuleHandle(NULL);//找到导入表
pFirstThunk = (DWORD64*)dwTemp; //加上偏移才是真正的导入表.
while (*(DWORD64*)pFirstThunk != NULL)
{
//遍历子表
if (*(DWORD64*)pFirstThunk == (DWORD64)pfnOld)
{
//找到要修改的导入表了//修改内存保护属性.写入我们新的函数地址.
DWORD oldProtected;
VirtualProtect(pFirstThunk, 0x1000, PAGE_EXECUTE_READWRITE, &oldProtected);
dwTemp = (DWORD64)pfnNew;
memcpy(pFirstThunk, (DWORD64*)&dwTemp, 8); //将变量中保存的函数地址拷贝到导入表中.
VirtualProtect(pFirstThunk, 0x1000, oldProtected, &oldProtected);
}
pFirstThunk++; //继续遍历.
}
pCurrent++; //每次是加一个导入表结构.
}

}

BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
){
HANDLE hThread = NULL;
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
iatHook();
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

Inline Hook

直接修改内存中任意函数的代码,将其劫持至Hook API。劫持后先进行“脱钩”,即能正常调用原函数,执行完毕后再次Hook。

步骤:

  1. 修改被hook函数的前几个字节,使其跳转到恶意函数

  2. 恶意函数的操作

    • 修复函数

    • 执行原函数

    • 执行恶意操作

    • 最后要把函数修改以便下次hook

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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
#include "pch.h"
#include <tchar.h>
#include <iostream>
#include <fstream>
#include <stdio.h>

using namespace std;
// 加载静态链接库
#pragma comment(lib,"urlmon.lib")
DWORD dwOldProtect;

typedef void (*FnPrintHello)(void);

FnPrintHello pfnOld = nullptr;


BYTE pOldBytes[5];

BOOL pfnNew(HWND hWnd,
LPCTSTR lpText,
LPCTSTR lpCaption,
UINT uType);
BOOL inlineHook() {
MessageBoxA(NULL, "start", "StartHook", MB_OK);
DWORD dwOldProtect;
DWORD64 dwAddress;
BYTE pBuf[5] = { 0xE9,0, };
PBYTE pByte;
pfnOld = (FnPrintHello)((0x140011C20LL - 0x140001000+ 0x1000LL) + (DWORD64)GetModuleHandleA("Project1.exe"));
pByte = (PBYTE)pfnOld;
// 已经被hook
if (pByte[0] == 0xE9)return FALSE;
VirtualProtect((LPVOID)pfnOld, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect);
memcpy(pOldBytes, pfnOld, 5);
// 计算相对偏移
dwAddress = (DWORD64)pfnNew - (DWORD64)pfnOld - 5;
cout <<hex<< dwAddress << endl;
memcpy(&pBuf[1], &dwAddress, 4);
// hook
memcpy(pfnOld, pBuf, 5);
VirtualProtect((LPVOID)pfnOld, 5, dwOldProtect, &dwOldProtect);
return TRUE;
}
BOOL inlineUnHook() {
DWORD dwOldProtect;
BYTE pBuf[5] = { 0xE9,0, };
PBYTE pByte;
// 这里是使用IDA查看的printHello函数地址+基址+text段基址
pfnOld = (FnPrintHello)((0x140011C20LL - 0x140001000LL+0x1000)+(DWORD64)GetModuleHandleA("Project1.exe"));
pByte = (PBYTE)pfnOld;
VirtualProtect((LPVOID)pfnOld, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect);
memcpy(pBuf, pOldBytes,5);
// hook
memcpy(pfnOld, pBuf, 5);
VirtualProtect((LPVOID)pfnOld, 5, dwOldProtect, &dwOldProtect);
return TRUE;
}
BOOL pfnNew(HWND hWnd,
LPCTSTR lpText,
LPCTSTR lpCaption,
UINT uType){

char szHookText[] = "IBinary -> Iat Hook";
MessageBoxA(NULL, "Successed", (LPCSTR)lpText, MB_OK);
// unhook
inlineUnHook();

BOOL result=FALSE;
if (pfnOld != nullptr){
pfnOld();//调用以前的
}
// hook again
inlineHook();
return result;
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
){
HANDLE hThread = NULL;
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
inlineHook();
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

被Hook的程序代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
#include <stdio.h>
#include <Windows.h>

void printHello() {
printf("HelloWorld\n");
}
int main() {
// 这里是应用程序自己加载,当然也可以dll注入
LoadLibraryA("myhackdll.dll");
printHello();

return 0;
}

HotFix Hook

上面的Hook方法存在效率问题(每次都需要修改程序的前5字节),并且不支持多线程。HotFix Hook可以解决这些问题。

API函数以 MOV EDI,EDI开始,并且上方有5个NOP指令。可以利用这7个字节来进行Hook操作。

当API被调用时,先jmp short至上面5个字节,然后再jmp至恶意代码。

代码实现(还未实验)

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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#include "pch.h"
#include <tchar.h>
#include <iostream>
#include <fstream>
#include <stdio.h>

using namespace std;

// 加载静态链接库
#pragma comment(lib,"urlmon.lib")
DWORD dwOldProtect;
ofstream ofs("hack.txt");

typedef BOOL(*FnMessageBoxA)(
HWND hWnd,
LPCTSTR lpText,
LPCTSTR lpCaption,
UINT uType
);

FnMessageBoxA pfnOld = nullptr;
FnMessageBoxA realFn = nullptr;

BOOL pfnNew(HWND hWnd,
LPCTSTR lpText,
LPCTSTR lpCaption,
UINT uType);
BOOL hotfixHook() {
DWORD dwOldProtect;
DWORD64 dwAddress;
PBYTE pByte;
MessageBoxA(NULL, "Start", "Hook start!!", MB_OK);
pfnOld = (FnMessageBoxA)GetProcAddress(GetModuleHandleA("user32.dll"),"MessageBoxA");
realFn = (FnMessageBoxA)((DWORD64)pfnOld + 2);
BYTE pBuf[5] = { 0xE9,0, };
BYTE pBuf2[2] = { 0xEB,0xF9 };

pByte = (PBYTE)pfnOld;
if (pByte[0] == 0xEB)
return FALSE;
VirtualProtect((LPVOID)((DWORD64)pfnOld - 5), 7, PAGE_EXECUTE_READWRITE, &dwOldProtect);
// 计算目标地址
dwAddress = (DWORD)pfnNew - (DWORD)pfnOld;
memcpy(&pBuf[1], &dwAddress, 4);
// hook
memcpy((LPVOID)((DWORD)pfnOld - 5), pBuf, 5);
memcpy(pfnOld, pBuf2, 2);
// 改回内存属性
VirtualProtect((LPVOID)((DWORD64)pfnOld - 5), 7, dwOldProtect, &dwOldProtect);
return TRUE;
}

BOOL pfnNew(HWND hWnd,
LPCTSTR lpText,
LPCTSTR lpCaption,
UINT uType){

ofs << "Your infomation is mine!!" << endl;
BOOL result=FALSE;
if (pfnOld != nullptr){
realFn(hWnd,lpText,lpCaption,uType);//调用以前的
}
return result;
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
){
HANDLE hThread = NULL;
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
hotfixHook();
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
ofs.close();
break;
}
return TRUE;
}

并不是所有都适用,如果没有7字节,只能用5字节修改技术。

SSDT Hook

用户层API最后的实质也是内核层API(Kernel32->Ntdll->Ntoskrnl)

该Hook方法最为强大

内核通过SSDT(System Device Descriptor Table)调用各种函数,SSDT是一个函数表,得到一个索引值,就能根据这个索引值在该表中得到想要的地址。

先找到SSDT的首地址,然后就可以根据索引,找到相应的函数

找索引号

1
u nt!函数名

根据反汇编结果找到索引号

内核Hook步骤

  1. 修改内存属性为RWX

  2. 拼接汇编代码jmp [Hook Func]

  3. 保存源代码头5个字节

  4. 将头5个字节替换为2的汇编码

  5. 恢复前5字节

  6. 恢复内存属性

由于内核层hook需要有权限,所以使用驱动方法

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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
#include <ntifs.h>

//内核之SSDT-HOOK
//系统服务表
typedef struct _KSYSTEM_SERVICE_TABLE
{
PULONG ServiceTableBase; //函数地址表的首地址
PULONG ServiceCounterTableBase;//函数表中每个函数被调用的次数
ULONG NumberOfService; //服务函数的个数
ULONG ParamTableBase; //参数个数表首地址
}KSYSTEM_SERVICE_TABLE;

//服务描述符
typedef struct _KSERVICE_TABLE_DESCRIPTOR
{
KSYSTEM_SERVICE_TABLE ntoskrnl;//ntoskrnl.exe的服务函数,SSDT
KSYSTEM_SERVICE_TABLE win32k; //win32k.sys的服务函数,ShadowSSDT
KSYSTEM_SERVICE_TABLE notUsed1;//暂时没用1
KSYSTEM_SERVICE_TABLE notUsed2;//暂时没用2
}KSERVICE_TABLE_DESCRIPTOR;

//定义HOOK的函数的类型
typedef NTSTATUS (NTAPI*FuZwOpenProcess)(
_Out_ PHANDLE ProcessHandle,
_In_ ACCESS_MASK DesiredAccess,
_In_ POBJECT_ATTRIBUTES ObjectAttributes,
_In_opt_ PCLIENT_ID ClientId
);

//自写的函数声明
NTSTATUS NTAPI MyZwOpenProcess(
_Out_ PHANDLE ProcessHandle,
_In_ ACCESS_MASK DesiredAccess,
_In_ POBJECT_ATTRIBUTES ObjectAttributes,
_In_opt_ PCLIENT_ID ClientId
);

//记录系统的该函数
FuZwOpenProcess g_OldZwOpenProcess;
//服务描述符表指针
KSERVICE_TABLE_DESCRIPTOR* g_pServiceTable = NULL;
//要保护进程的ID
ULONG g_Pid = 9527;

//安装钩子
void InstallHook();
//卸载钩子
void UninstallHook();
//关闭页写入保护
void ShutPageProtect();
//开启页写入保护
void OpenPageProtect();

//卸载驱动
void OutLoad(DRIVER_OBJECT* obj);



////***驱动入口主函数***/
NTSTATUS DriverEntry(DRIVER_OBJECT* driver, UNICODE_STRING* path)
{
path;
KdPrint(("驱动启动成功!\n"));
//DbgBreakPoint();

//安装钩子
InstallHook();

driver->DriverUnload = OutLoad;
return STATUS_SUCCESS;
}

//卸载驱动
void OutLoad(DRIVER_OBJECT* obj)
{
obj;
//卸载钩子
UninstallHook();
}

//安装钩子
void InstallHook()
{
//1.获取KTHREAD
PETHREAD pNowThread = PsGetCurrentThread();
//2.获取ServiceTable表
g_pServiceTable = (KSERVICE_TABLE_DESCRIPTOR*)
(*(ULONG*)((ULONG)pNowThread + 0xbc));
//3.保存旧的函数
g_OldZwOpenProcess = (FuZwOpenProcess)
g_pServiceTable->ntoskrnl.ServiceTableBase[0xbe];
//4.关闭页只读保护
ShutPageProtect();
//5.写入自己的函数到SSDT表内
g_pServiceTable->ntoskrnl.ServiceTableBase[0xbe]
= (ULONG)MyZwOpenProcess;
//6.开启页只读保护
OpenPageProtect();
}

//卸载钩子
void UninstallHook()
{
//1.关闭页只读保护
ShutPageProtect();
//2.写入原来的函数到SSDT表内
g_pServiceTable->ntoskrnl.ServiceTableBase[0xbe]
= (ULONG)g_OldZwOpenProcess;
//3.开启页只读保护
OpenPageProtect();
}

//关闭页只读保护
void _declspec(naked) ShutPageProtect()
{
__asm
{
push eax;
mov eax, cr0;
and eax, ~0x10000;
mov cr0, eax;
pop eax;
ret;
}
}

//开启页只读保护
void _declspec(naked) OpenPageProtect()
{
__asm
{
push eax;
mov eax, cr0;
or eax, 0x10000;
mov cr0, eax;
pop eax;
ret;
}
}

//自写的函数
NTSTATUS NTAPI MyZwOpenProcess(
_Out_ PHANDLE ProcessHandle,
_In_ ACCESS_MASK DesiredAccess,
_In_ POBJECT_ATTRIBUTES ObjectAttributes,
_In_opt_ PCLIENT_ID ClientId
)
{
//当此进程为要保护的进程时
if (ClientId->UniqueProcess == (HANDLE)g_Pid &&
DesiredAccess == PROCESS_TERMINATE)
{
//设为拒绝访问
DesiredAccess = 0;
}
//调用原函数
return g_OldZwOpenProcess(
ProcessHandle,
DesiredAccess,
ObjectAttributes,
ClientId);
}

参考资料

[1] HOOK实例之一:实现键盘钩子截获密码等键盘输入(vs2013详细流程)_Childe_Mu的博客-CSDN博客

[2] Hooks Overview - Win32 apps | Microsoft Docs

[3] C++ Hook 键盘记录器_LYSM-CSDN博客

[4] c++ 遍历获取所有进程名及PID_o_ohello的博客-CSDN博客

[5] IAT Hook - iBinary - 博客园

[6] [原创]SSDT-HOOK–看雪

[7] Windows Hook原理与实现_安全杂货铺-CSDN博客