一个完整的hook,如果hook程序是以dll形式生成的,是分两步:1.完成dll本身的设计和生成,2.完成dll注入程序的设计和生成
本文完成第一步。
第二步在http://blog.csdn.net/arvon2012/article/details/7767437有详细讲解。
最近在64位win7上hook文件复制,拖拽和剪切的hook(这个要通过hook IFileOperating接口实现)。所以学习了API hook。这里是对自己的学习做个简单的总结,希望和hook新手们共同探讨和进步~~~
最后有全部源码下载地址(无毒无害)
inline hook API的原理:
最简单的说,hook api就是找到api所在的位置,然后在这个位置里做些文章。这样,当系统调用这个API的时候,它不知不脚的就运行了我们篡改过的代码,达到我们不可告人的目的~~~网上介绍用detour库进行APIhook,detour库是微软开发的专用的api hook库,内部hook原理和inline hook是一样的,但是因为添加了一些处理调用冲突的代码,所以会比直接手工inline hook稳定些。
而上面说的“做文章”,具体是干什么呢?还是根据例子说的清楚。
下面是一个helloworld级别的hook api,目标:(弹出对话框函数:MessageBoxW)
第一步:找到要hook的api!
我们想hook住这个MessageBoxW,就要知道系统调用它的时候,它在哪里。有些童鞋懂应该上MSDN查,然后大声吼出:在windows.h!!
擦!少年~你弱爆了(其实我在描述当时自学的自己,不是说你的,亲~)。
要知道windows系统在调用这些函数的时候,他们调用的当然是编译好的可执行函数(动态链接库--dll文件),当然不是调用源代码。MSDN这个函数的头文件下面就写着:
DLL
User32.dll
下面简单的调用几个API就能锁定函数在这个dll中的位置:
-
//LoadLibrary:先用自己的程序load目标库
-
HMODULE hModule = LoadLibrary(_T("user32.dll"));
-
//GetProcAddress:在目标中找到自己想hook的函数地址
-
pOldAPI = (pDefaultAPI)GetProcAddress(hModule, "MessageBoxW");
(关键先理解框架级别的东西,现在不追究里面详细的类型转换。)
第二步:在原api这里做文章
所谓的hook了api其实就是改变了这个api原先的功能,让调用者调用这个api的时候执行的是我们希望的功能。既然上面我们获得了目标api的地址,我们是不是希望可以修改这个地址里执行的代码。下面是所谓的inline hook的修改方式:函数被调用之后要一步一步往下执行是吧?OK,我们就把这个函数将要执行的第一个指令替换成【跳转指令】,跳转到我们设计的代码的位置。Look:
-
char szOldAPI[12] = {};
-
//存放原来API函数在内存中的前12个字节
-
char szNewAPI[12] = {0x48,0xB8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x50,0xC3};
-
//存放跳转指令,中间的8个字节是0,因为现在还没有存放目标地址
再提醒一下:这里我们是做12字节的替换,因为我用的是64位的系统,32位的系统替换前5字节。
上面这两行代码以全局变量定义,他们其实是汇编指令,意思是:
mov rax,XX XX XX XX XX XX XX XX ;把要跳转的地址存到RAX寄存器,XXX就是上面szNewAPI中间的几字节0
PUSH RAX ;把RAX寄存器压到栈顶
RET ;把栈顶的数据拿出来,存到指令寄存器(负责告诉系统下一步执行的代码在哪里)中
所以,我们在用来做hook工作的函数中,只要先把原函数的前12位保存到szOldAPI中(千万别搞丢了,用来恢复),然后把存放了目标跳转地址的跳转代码(szNewAPI)塞到这12字节。如下:
-
ReadProcessMemory ((void*)-1, pOldAPI, szOldAPI, 12, NULL); //读出原来的前5个字节
-
WriteProcessMemory((void*)-1, pOldAPI, szNewAPI, 12, NULL); //写入我们处理后的5个字节
这样,当系统调用被我们hook住的api时,他刚执行这个函数前面的代码,就不知不脚的执行了一个跳转指令,嘿嘿嘿。。。。其实hook的入门原理到这里就结束了。
下面解决一个遗留问题:定义我们的函数,并且把我们的函数所在地址放到跳转指令中。请看第三步
第三步:达到不可告人的目的,捏~~哈哈哈~~~~
为了解释的方便,这里先上代码再解释:
首先定义我们的函数:
-
typedef int (WINAPI* pDefaultAPI)(
-
HWND hWnd,
-
LPCTSTR lpText,
-
LPCTSTR lpCaption,
-
UINT uType
-
);
对于C语言不太熟悉的亲们,看到上面的定义一定会说。。。。。。靠!
C语言的类型定义就是繁杂,这个是历史遗留问题~~~恩恩。。。
下面和C新手一起探讨上面的东西是神马。
1.上面我们定义了一个类型,从typedef关键字可以看出,这个语句的原型是:typedef int XXXX。意思其实就是XXX就是int。
2.大家可以看出来XXX是个函数的摸样:有函数名(先这么表达吧),有参数表。想想最初我们学计算机语言的时候,函数是什么??【这个概念最初是数学里的函数来的,函数就是一种计算,最后得到的是一个量(返回值)】,所以这个typedef int XXXX形式的定义就是说XXX这个函数的计算结果其实就是int~~~哦~~~~用计算机专用术语说就是:XXX返回值是int。
3.我们要定义的是pDefaultAPI,所以进一步解刨,看看这个复杂的“函数名”是什么,WINAPI* pDefaultAPI,我们单单看这个,弱爆了~~~这一个片段是:定义了一个指向WINAPI类型的值的指针,指针名字(我们最后要使用的名字)叫做pDefaultAPI。
4.第3个分析的结果和前面的东西串在一起念就是:我们定义了一个指向WINAPI类型的指针(WINAPI类型是一种函数类型,所以有返回值),这个指针指向的WINAPI类型函数的返回值是int型的~~~我去!说人话!嗯。。。压缩一下就是:定义了一个WINAPI类型、返回值是int类型的“函数指针”,名字叫pDefaultAPI。
好,C语言课上完了,我们继续hook。
上面的定义,大家仔细看发现:1.返回值和原MessageBoxW返回值一致,都是int 2.参数表也和原函数一致。
原因是:我们是要用这种格式定义出来的指针去保存原函数的地址,所以当然返回值和参数要一直喽~~~~
这样,我们就能用这个pDefaultAPI去定义指针变量,并且用这个变量去存放原函数地址。这样存:
先定义一个全局的pDefaultAPI pOldAPI;然后。。。请看上面“第一步”中对pOldAPI的使用。
终于要定义我们自己的hook函数了:
-
int WINAPI NewAPI(
-
HWND hWnd,
-
LPCTSTR lpText,
-
LPCTSTR lpCaption,
-
UINT uType
-
)
-
{
-
//为所欲为
-
WriteProcessMemory((void*)-1, pOldAPI, szOldAPI, 12, NULL);//还原原函数
-
A = MessageBoxW(hWnd,lpText,lpCaption,uType); //调用还原后的原函数
-
WriteProcessMemory((void*)-1, pOldAPI, szNewAPI, 12, NULL);//原函数执行完,重新hook之!
-
//为所欲为
-
return A;
-
}
有上面的概念,大家应该注意到了这里参数表,返回值都。。。
函数里面干了什么?你可以再可以为所欲为的地方添加你为所欲为的代码,但是别忘了执行原函数(中间的三行)。
有两个问题大家按自己的理解思考下:
1.hook api有没有必要都保留原函数的功能,可不可以彻底不执行原函数
2.为什么不在两个WriteProcessMemory中间为所欲为?对这个地方原函数的执行没有影响。
第一个问题我没有可以给大家的有价值的答案。先说说第二个吧。
这里刚好涉及到inline hook这种hook形式的弊端:不稳定!
我们的系统想必都是多进程,多线程的吧,如果进程A执行到第一个WriteProcessMemory,把原函数开始的地址还原了,然后就在这个瞬间,B进程调用了MessageBoxW,那么。。。B进程成功逃出了我们的魔掌,不是吗?所以为了尽可能的减小inlinehook的这个缺点,我们最好不要在恢复和重新hook这两个操作之间添加需要执行时间的代码。(是代码,就都要执行时间。。。)
重新回到hook的主线上来,我们定义了自己的hook函数,下面要做的就是存下这个函数的地址,然后在替换的时候使用:
-
DWORD64 dwJmpAddr = 0;
-
dwJmpAddr = (DWORD64)NewAPI; //存下我们自己的函数的地址
-
memcpy(szNewAPI + 2, &dwJmpAddr, 8); //把地址写到szNewAPI中间的8个0字节处
-
ReadProcessMemory ((void*)-1, pOldAPI, szOldAPI, 12, NULL); //读出原来的前12个字节
-
WriteProcessMemory((void*)-1, pOldAPI, szNewAPI, 12, NULL); //写入我们处理后的12个字节
上面是存放新函数的地址,顺便做初次hook
到这里inline api hook的本身的原理和工作就结束了。
生成:
下面再稍微说下怎么生成我们想要的hook文件,这里我是生成dll,因为dll可以通过dll注入技术,注入其他进程,然后改变其他进程对某某函数的调用。
dll简单原理:大家要记住dll这个库给进程提供了可以调用的函数。如果一个进程要使用这个函数,它会先装载对应的dll,然后dll就会在这个进程中产生一个拷贝。拷贝是什么意思?就是说这个进程调用的是它进程空间中的dll,它自己的啊,亲~~~~。所以如果我们想控制一个进程,肯定要把我们的hook dll注入到这个进程,然后通过hook函数修改这个进程私有的dll中的内容,在这里我们要修改的就是目标进程的私有user32.dll库中的MessageBoxW所在位置的数据。
所以说,把我们的hook做成dll,又有了dll注入技术的支持,我们的hook工作就能指哪打哪。用起来很方便。
亲~您还等什么,打开你用的编程工具,新建一个dll工程。然后在dll的主函数DllMain中的case DLL_PROCESS_ATTACH:下添加上你负责初次hook的函数。我的程序中是这样的:
-
BOOL APIENTRY DllMain(HANDLE handle, DWORD dwReason, LPVOID reserved)
-
{
-
g_hThisModule = (HMODULE)handle;
-
switch(dwReason)
-
{
-
case DLL_PROCESS_ATTACH:
-
{
-
HookAPI();//HOOK!
-
break;
-
}
-
case DLL_PROCESS_DETACH:
-
{
-
UnHookAPI();
-
break;
-
}
-
}
-
return TRUE;
-
}
有什么不对的地方大家不要吝啬自己的意见,一定要留言给我指出来,谢谢!
这个是核心代码,大家创建dll工程,或者空的工程,直接把这个代码拷贝到主文件中就OK,千万别忘了,如果你要注入的系统是64位的,或者目标是64位的程序,一定编程成x64的dll哦~~~
http://download.csdn.net/detail/arvon2012/4440477