抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

Hook各种API函数实现自己的功能

Inline Hook

实现原理

实现Inline Hook可以理解为修改程序的执行流,到另外一块内存中执行我们需要的操作,结束之后返回到原来的地方接着执行程序原本的操作。在这里需要考虑的问题有以下几点:

  • 如何找到跳转的地址;
  • 如何保护现场以及还原执行流;
  • 如果内存页不可写如何解决。

代码编写思路

使用JMP指令来进行跳转操作(无条件跳转就是好用)。但是JMP后的地址(操作数)如何寻找?

JMP的目标地址是相对于下一条指令的位置进行计算,计算公式为:JMP 后面的地址(操作数) = 目的地址 - 源地址 - 5。其中,5 是 JMP 指令的字节数。

接着考虑还原函数的地址,我们JMP到自定义函数后,执行完自定义函数的内容,接着就需要还原执行流恢复到原函数中。还原地址 = 获取下一条指令地址(原函数地址+5) - 分配到的内存的地址 - 指令长度(5)

代码实现:

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

// 用于跳转到MyMessageBoxA的指令,0xE9代表JMP指令
BYTE JmpOriginal[5] = { 0xE9, 0, 0, 0, 0 };
// 存储原始MessageBoxA的前5个字节
BYTE OldCode[5] = { 0 };
// MessageBoxA的函数地址
FARPROC MessageBoxAddress;
// 还原函数地址
void* Trampoline;

// 自定义的MessageBoxA函数
int WINAPI MyMessageBoxA(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType)
{
printf("MessageBoxA 已经被Hook\n"); // 打印被Hook的信息

// 返回原函数调用原始的MessageBoxA,这里需要类型转换
int ret = ((int (WINAPI*)(HWND, LPCTSTR, LPCTSTR, UINT))Trampoline)(hWnd, lpText, lpCaption, uType);
return ret;
}

void InlineHook()
{
// 加载user32.dll模块
HMODULE hModule_User32 = LoadLibraryA("user32.dll");
// 获取MessageBoxA的函数地址
MessageBoxAddress = GetProcAddress(hModule_User32, "MessageBoxA");

// 计算跳转到自定义函数的地址
DWORD JmpAddress = (DWORD)MyMessageBoxA - (DWORD)MessageBoxAddress - 5;
// 将跳转地址复制到JmpOriginal的第二个字节
memcpy(&JmpOriginal[1], &JmpAddress, 4);
// 读取并保存MessageBoxA的前5个字节
ReadProcessMemory(GetCurrentProcess(), MessageBoxAddress, OldCode, 5, NULL);

// 分配10个字节的内存空间用来放入跳转回原函数的指令
Trampoline = VirtualAlloc(NULL, 10, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
// 复制MessageBoxA的前5个字节
memcpy(Trampoline, OldCode, 5);

// 计算还原函数的地址(原函数地址+5获取下一条指令地址后-分配内存的地址-指令长度)
DWORD jmpBackAddr = (DWORD)MessageBoxAddress + 5 - (DWORD)Trampoline - 5;
// JMP
memcpy((void*)((DWORD)Trampoline + 5), &JmpOriginal[0], 5);
// MessageBoxA 函数中的下一条指令地址。
memcpy((void*)((DWORD)Trampoline + 6), &jmpBackAddr, 4);

DWORD dwOldProtect;
// 修改MessageBoxA的前5个字节的页属性,使其可读可写可执行
VirtualProtect(MessageBoxAddress, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect);

// 替换MessageBoxA的前5个字节为跳转到MyMessageBoxA的指令
WriteProcessMemory(GetCurrentProcess(), MessageBoxAddress, &JmpOriginal[0], 5, NULL);

// 恢复MessageBoxA的前5个字节的原始页属性
VirtualProtect(MessageBoxAddress, 5, dwOldProtect, &dwOldProtect);
}

void main()
{
InlineHook(); // 实施Inline Hook
MessageBoxA(NULL, "Hello World", "Title", MB_OK); // 调用MessageBoxA函数
}

大概流程就是将MessageBoxA的地址先通过WriteProcessMemory改为自定义函数的地址,同时开辟空间写入原函数的第一条指令和跳转回原函数的地址指令,接着等到自定义函数执行结束后作为函数指针调用原函数。

IAT Hook

实现原理

思路与Inline Hook大同小异。在Inline Hook中,我们关注的是JMP跳转后再跳转回去,而在IAT Hook中,我们只需要关注如何将自定义函数地址替换IAT函数地址即可,不需要自己来实现跳来跳去。配合一些DLL注入技术,就可以达到任意API函数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
// dllmain.cpp : 定义 DLL 应用程序的入口点。"
#include <windows.h>


//创建与Messagebox相同的函数指针,注意要设置相同的函数参数
typedef int (WINAPI *PfnMsgA)(
_In_opt_ HWND hWnd,
_In_opt_ LPCSTR lpText,
_In_opt_ LPCSTR lpCaption,
_In_ UINT uType);

PfnMsgA g_OldPfnMsgA = nullptr; //定义一个指向原先messagebox函数的空指针(nullptr)


//解析PE文件结构

//获取当前的ImagBase(基址)
HMODULE hModImageBase = GetModuleHandle(NULL);
//获取DOS头
PIMAGE_DOS_HEADER pDosHead = (PIMAGE_DOS_HEADER)(DWORD)hModImageBase;
//获取NT头
DWORD dwTemp = (DWORD)pDosHead + (DWORD)pDosHead->e_lfanew;
PIMAGE_NT_HEADERS pNtHead = (PIMAGE_NT_HEADERS)dwTemp;
//获取标准PE头
PIMAGE_FILE_HEADER pFileHead = (PIMAGE_FILE_HEADER)& pNtHead->FileHeader;
//获取扩展PE头
PIMAGE_OPTIONAL_HEADER pOptHead = (PIMAGE_OPTIONAL_HEADER)& pNtHead->OptionalHeader;
//找到导入表的偏移(RVA)
DWORD dwExportLocal=pOptHead->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
//获取导入表
PIMAGE_IMPORT_DESCRIPTOR pImport = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)GetModuleHandle(NULL) + dwExportLocal);


//定义自己设置的Messagebox函数
int WINAPI MyMessageBox(_In_opt_ HWND hWnd, _In_opt_ LPCSTR lpText, _In_opt_ LPCSTR lpCaption, _In_ UINT uType)
{

char szHookText[] = "Hook成功";
if (g_OldPfnMsgA != nullptr)
{
//调用原函数
return g_OldPfnMsgA(hWnd, szHookText, lpCaption, uType);
}
return 0;
}

//设置IATHook
void SetIatHook()
{
MessageBoxA(NULL, "开始进行HOOK", NULL, NULL);
//定义一个指向hook地址的空指针
PVOID pHookAddress = nullptr;
//将要hook的地址指向Messagebox函数
pHookAddress = GetProcAddress(GetModuleHandleA("user32.dll"), "MessageBoxA");

if (nullptr == pHookAddress)
{
OutputDebugString(TEXT("获取函数地址失败"));
MessageBoxA(NULL, "获取函数地址失败HOOK", NULL, NULL);

return;
}
//指向旧函数的指针
g_OldPfnMsgA = (PfnMsgA)pHookAddress;

//寻找IAT表的位置.
PIMAGE_IMPORT_DESCRIPTOR pCurrent = pImport;
//导入表的子表,也就是IAT存储函数地址的表
DWORD *pFirstThunk;

//遍历导入表
while (pCurrent->Characteristics && pCurrent->FirstThunk != NULL)
{
//找到IAT表的偏移地址
dwTemp = pCurrent->FirstThunk + (DWORD)GetModuleHandle(NULL);
//指向IAT表的指针
pFirstThunk = (DWORD *)dwTemp;
while (*pFirstThunk != NULL)
{
//遍历IAT表里的子表,若指针指向的是旧函数的地址,则将其修改成我们的函数地址
if (*pFirstThunk == (DWORD)g_OldPfnMsgA)
{
DWORD oldProtected;
//设置该内存区域属性为可写可读可执行
VirtualProtect(pFirstThunk, 0x1000, PAGE_EXECUTE_READWRITE, &oldProtected);
//将旧函数地址修改成自己的函数地址
dwTemp = (DWORD)MyMessageBox;
memcpy(pFirstThunk, (DWORD *)&dwTemp, 4);
//还原保护属性
VirtualProtect(pFirstThunk, 0x1000, oldProtected, &oldProtected);
}
//遍历IAT表
pFirstThunk++;
}
//遍历导入表
pCurrent++;
}

}


//恢复导入表
void UnIatHook()
{
MessageBoxA(NULL, "开始进行HOOK", NULL, NULL);
PVOID pHookAddress = nullptr;
pHookAddress = MyMessageBox;
if (nullptr == pHookAddress)
{
OutputDebugString(TEXT("获取函数地址失败"));
MessageBoxA(NULL, "恢复函数地址失败HOOK", NULL, NULL);
return;
}

PIMAGE_IMPORT_DESCRIPTOR pCurrent = pImport;
DWORD* pFirstThunk; //指向

//遍历导入表
while (pCurrent->Characteristics && pCurrent->FirstThunk != NULL)
{
dwTemp = pCurrent->FirstThunk + (DWORD)GetModuleHandle(NULL);
pFirstThunk = (DWORD*)dwTemp;
//遍历子表
while (*pFirstThunk != NULL)
{
//如果是我们的函数地址
if (*pFirstThunk == (DWORD)MyMessageBox)
{
//找到要修改的导入表了,修改内存保护属性.写入我们新的函数地址.
DWORD oldProtected;
VirtualProtect(pFirstThunk, 0x1000, PAGE_EXECUTE_READWRITE, &oldProtected);
dwTemp = (DWORD)GetProcAddress(GetModuleHandleA("user32.dll"), "MessageBoxA");
memcpy(pFirstThunk, (DWORD*)& dwTemp, 4); //将变量中保存的函数地址拷贝到导入表中.
VirtualProtect(pFirstThunk, 0x1000, oldProtected, &oldProtected);
}
pFirstThunk++; //继续遍历.
}
pCurrent++; //每次是加一个导入表结构.
}

}


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