学一学 hook 专题,今天是 inline hook。

课堂笔记

hook概念

hook 就是钩子,是一种劫持技术,把原本执行的函数替换,替换为我自己的一个函数。当然在学 pwn 的时候,也有接触过 hook,就是 malloc_hook 和 free_hook。这些 hook 本意是为了开发者方便调试自己编写的 malloc 或者 free 函数去使用的,比如我自己写了一个 free 函数,我想测试一下性能,那我直接 __free_hook=myfree 就可以测试了,但是没想到就一直被打,直到 glibc 2.34 版本,这些 hook 被彻底删除。

inline hook的用法

inline hook(内联钩子),是指在函数的代码当中的一个地方(一般是代码开头),写上跳转指令,跳转到我们执行的函数,跳转完成之后,为了保持函数正常运行我们可能会释放钩子(unhook),把程序放回原来的位置,或者是直接执行完自己的代码就结束。

无条件跳转指令编码

一般,无条件跳转指令编码为 E9 + 32位小端序整型(根据 %rip 寄存器寻址),比如 E9 05 00 00 00 表示跳转自己后五个字节,但是需要注意的是,跳转后五个字节是指执行完这个指令之后的那个字节开始算的。

hook实战

在 VS 当中新建一个动态链接库(dll)项目。比如我们要 hook 一个 MessageBoxA 函数,我们就先获取这个函数的地址,然后找到前五个字节进行替换,跳转到我们自己写的函数当中。

全局变量申请两个字节数组,一个是原字节,一个是被 hook 之后的字节,再申请一个函数指针来指向我们要 hook 的函数。

先把原字节取出来,保存在缓冲区中,然后再计算我们函数地址到 hook 位置的一个偏移,构造好 jmp 语句。因为我们这里hook 只更改参数,需要用到下面的逻辑,所以在自己的函数中,我们调用 MessageBoxA 之前先把钩子取消掉,等待执行完毕之后,再把钩子挂会去就可以了。

dllmain:

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

BYTE NewBytes[5];
BYTE OldBytes[5];
PROC HookedFunction;

VOID UNHOOK() {
memcpy(HookedFunction, OldBytes, 5);
}
VOID REHOOK() {
memcpy(HookedFunction, NewBytes, 5);
}
VOID HOOK(const char *Module,const char *function,PROC HookFunction) {
HookedFunction = (PROC)GetProcAddress(GetModuleHandleA(Module), function);
if (HookedFunction == NULL) {
return;
}
SIZE_T ret=0;
ReadProcessMemory(GetCurrentProcess(),HookedFunction,OldBytes,5,&ret);
NewBytes[0] = '\xE9';
*(DWORD*)(HookedFunction) = (DWORD)HookFunction-(DWORD)HookedFunction -5;
memcpy(HookedFunction, NewBytes, 5);

}
int
WINAPI
MyFunction(
_In_opt_ HWND hWnd,
_In_opt_ LPCSTR lpText,
_In_opt_ LPCSTR lpCaption,
_In_ UINT uType) {
UNHOOK();
int ret=MessageBoxA(hWnd, "xia0ji233", "hooked by xia0ji233", uType);
REHOOK();
return ret;
}



BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
HookedFunction = NULL;
memset(OldBytes, 0, 5);
memset(NewBytes, 0, 5);
HOOK("user32.dll","MessageBoxA",(PROC)MyFunction);
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}


先写一个待 hook 的程序。

demo:

1
2
3
4
5
6
7
#include<windows.h>
int main(){
while(1){
MessageBoxA(0,"test","test",0);
system("pause");
}
}

把两个都编译好之后,运行 test.exe,用注入器进行 dll 注入,再次弹窗可以发现已经成功被 hook。

课外研究

这次除了上课听的内容以外,还有很多收获。第一个就是说,我在 hook 的时候,看 WriteProcessMemory 函数参数太多了,想着写字节嘛, memcpy 也是一样的,结果出现了一个问题,就是注入并没有成功。

仔细去查的时候发现了,权限不足,那么为什么 WriteProcessMemory 函数就可以注入成功呢,它内部有一个修改内存权限的操作导致了我们写入可以成功。

按照老师的说法,因为WriteProcessMemory函数是一个调试用的API函数,权限可能比较大,能突破这个限制,关于我们队师傅对于这个 WriteProcessMemory 的解释在这里。大概就是说,微软文档写的是一定要内存权限,但是实际上通过调试可以发现它内部会调用一个 函数去修改它的内存权限然后写入。

总之这里也是长一个心眼吧,最最关键的是,会用IDA去动调了,而且貌似操作不难,也算迈出了第一步hhh。