缘由:本来想做个微信的机器人,发现wev方法已经歇菜了,微信貌似不让web版好好工作了,因此就顺带看了一下hook技术,调试了一个网上现成的代码。竟然调试成功,并且有了一点儿体会。
为了简单起见,介绍部分,主要来自各味前辈的总结。
文章主要参考
HOOK API入门之Hook自己程序的MessageBoxW(简单入门)
本文的目的是对其中的部分代码进行更小白的解释
Hook的本意是钩子,意思比较形象,就是对应用程序勾一下,干点儿别的。
下面进入我的个人理解的介绍:
hook的个人入门级理解##
1、hook的个人目的和用途
在现有程序中插一脚,瞄一眼,在不影响现有程序运行的情况下,干点儿事情,善意的或者恶意的。
2、实现思路
截获运行流程,令其转向,然后再返回。
3、怎么截获
原则上讲,水平高从程序的任何地方截获都可以,只要来的时候不漏痕迹就行了。然而,这是理论上而已,可行的方案是从调用入口截获,这样符合模块操作的风格,对原来的程序流程的影响容易做到最小。因此,入门级的截获就是截获已知函数。
4、截获原则
函数类型和参数等等等要和原来的函数一模一样。
具体实现思路
参考引文,实现对自己message box 的hook
很多问题参考原文即可。
下面整理一下具体过程的流程
1、定义自己的函数
2、获取自己的函数地址
3、获取Message box 在 User32.dll 中的地址
4、用自己的函数地址替换原函数地址
就上面的四个步骤,具体实现的时候,还有一些技巧,例如怎么替换,替换后的具体行为是什么等等。
代码和解释
原文是基于64位的win7环境,没有给出具体的32位做法,本文将给出两种环境下的代码。
64位:
1、准备工作
char szOldAPI[12] = {
};
// mov rax,13F371A3Ch
#ifdef _M_X64
char NewCode[12] = {
0x48,0xB8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x50,0xC3 };
#endif
定义存放函数指针附近附近的数据,实际上是存贮机器码(这个如果不懂,先不管吧)。这个机器码就是调用关系,或者理解成汇编语言就OK了。
szOldAPI: 是存贮原函数的地址附近的数据,这个用来恢复原函数的调用关系用。
NewCode:是我们新的函数地址附近的数据,用来替换原函数的数据。
第一个字节和第二个字节是 汇编代码 mov rax xxxxxxx,如图示,后面的数字就是函数地址。将函数地址里的值放入到rax寄存器中。
下面的50C3就是最后的两个字节,汇编代码就是 push rax, 将rax 寄存器压入堆栈,然后返回。

篮筐内的数据时替换后我们的函数地址。上面解释未必准确,但是方向应该是对的。因为我对汇编并不熟悉。
为了对比,下面列出没有替换时的原函数地址附近的数据情况

注意首地址是一样的,我们替换了最前面的12个字节,完成了向我们函数的跳转。
注:moe rax指令是完成一种系统调用,具体参考
汇编语言基础:寄存器和系统调用
2、替换操作
DWORD dwPid = ::GetCurrentProcessId();
hProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, dwPid);
//获取原API入口地址
HMODULE hmod = ::LoadLibrary(_T("User32.dll"));
OldMsgBoxW = (MsgBoxW)::GetProcAddress(hmod, "MessageBoxW");
pfOldMsgBoxW = (FARPROC)OldMsgBoxW;//注意这里有个转换,32位好像不用。
if (pfOldMsgBoxW == NULL)
{
MessageBox(NULL, _T("获取原API入口地址出错"), _T("error!"), 0);
return;
}
#ifdef _M_X64
DWORD64 dwJmpAddr = 0;
dwJmpAddr = (DWORD64)MyMessageBoxW; //存下我们自己的函数的地址
memcpy(NewCode + 2, &dwJmpAddr, 8); //把地址写到szNewAPI中间的8个0字节处
ReadProcessMemory((void*)-1, pfOldMsgBoxW, szOldAPI, FUN_ADD_LEN, NULL); //读出原来的前FUN_ADD_LEN个字节
//ReadProcessMemory(hProcess, pfOldMsgBoxW, OldCode, 12, NULL); //读出原来的前12个字节
#endif
上面是准备工作,还没有进行hook操作。
注意:12个字节长度的原因是因为利用上面的汇编语句,这个长度是最短的长度,并不是必须的,可以再长一些,只要记住被替换的部分,可以恢复即可。
3、hook 操作代码
ASSERT(hProcess != NULL);
//修改API函数入口前5个字节为jmp xxxxxx
VirtualProtectEx(hProcess, pfOldMsgBoxW, FUN_ADD_LEN, PAGE_READWRITE, &dwOldProtect);
WriteProcessMemory(hProcess, pfOldMsgBoxW, NewCode, FUN_ADD_LEN, 0);
//WriteProcessMemory((void*)-1, pfOldMsgBoxW, NewCode, FUN_ADD_LEN, NULL); //写入我们处理后的FUN_ADD_LEN个字节
VirtualProtectEx(hProcess, pfOldMsgBoxW, FUN_ADD_LEN, dwOldProtect, &dwTemp);
替换操作的代码是
WriteProcessMemory(hProcess, pfOldMsgBoxW, NewCode, FUN_ADD_LEN, 0);
FUN_ADD_LEN 的值是12, 32位同。
执行完上述代码之后,调用关系就已经被修改了。
修改之前,的内存内容

修改之后

请读者自行比较前12个字节的变化,一起获得感性认识。
至此,原理以及解释已经结束。
下面介绍一下32位的做法
原理一样,只不过汇编语言不同。这里用了jmp 汇编,该汇编代码是无条件跳转命令,后面数字是地址偏移量,而不是绝对地址,因此后面的数值要进行计算。
这个偏移量的计算如下(直接引用参考网友的注释)
#ifdef _M_IX86
DWORD32 dwJmpAddr = 0;
dwJmpAddr = <

本文作者因微信机器人开发需求,转而研究Hook技术。介绍了Hook的个人入门理解,包括目的用途、实现思路、截获方法和原则。给出具体实现流程,提供64位和32位环境下的代码及解释,还给出完整调试代码和参考界面,代码经测试成功。
最低0.47元/天 解锁文章
2070

被折叠的 条评论
为什么被折叠?



