您的位置:首页 > 其它

API hook原理和实例快速入门(inline hook),以dll线程注入方式使用(win7-64bit)

2012-07-20 10:56 399 查看
一个完整的hook,如果hook程序是以dll形式生成的,是分两步:1.完成dll本身的设计和生成,2.完成dll注入程序的设计和生成

本文完成第一步。

第二步在/article/1847956.html有详细讲解。



最近在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;
}




有什么不对的地方大家不要吝啬自己的意见,一定要留言给我指出来,谢谢!






http://download.csdn.net/detail/arvon2012/4440477

这个是核心代码,大家创建dll工程,或者空的工程,直接把这个代码拷贝到主文件中就OK,千万别忘了,如果你要注入的系统是64位的,或者目标是64位的程序,一定编程成x64的dll哦~~~

技术相关更多文章猛击:哇啦天堂论坛技术区
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐