//@modify:由于自己理解能力的有限,所以我下面的理解是片面的,不全面的,所以在此更正。
这两天一直在用Detours(想要详细了解,请google搜索一下)这个api hook库,然后遇到不少的问题。Detours这个库的基本原理是:修改目标函数的首几个字节,跳转到我们的钩子函数,我们可以根据需要决定是否通过跳板执行原来的函数。
我遇到:有些函数能钩到,而另外一些函数却无法钩到,我一直在考虑这个问题,还有一个问题是:究竟什么样的函数能被钩到(这个时候我还分不清装载时加载和运行时加载)?于是我开始琢磨;
Detours介绍说:可以钩到win32的系统函数。Dll中的函数也能钩到么,我就在疑惑。因为dll是动态加载的,那Detours还怎么修改我们调用的函数呢?我想不通。在我认为动态链接的过程就是:用到某个函数系统通过:loadlibrary,然后调用GetProcAddress获取函数地址。在这种情况下,Detours是无法正常工作的哇。看来是我在某个地方理解错了。于是我开始查询什么是动态链接,以及动态链接是如何工作的;
再说动态链接之前,说一下静态链接:静态链接就是把程序用到的库都编译到程序中,这避免由于目标电脑没有dll而无法启动的问题。动态加载的工作原理是:程序中有一个import directory的表,该表的每一项包含一个库的名字。根据表中记录的名字,把需要的库加载到内存,我认为表中应该还有一项就是偏移地址,然后形成映射表;当程序执行到某个库的函数时,找到这个库的偏移地址,加上库内地址,就是一个真实地址。Detours的工作原理是:在加载完这所有的库之后,更新我们需要修改的函数;这样我们的程序就可以完全正常工作了。这也解释了为什么可以截取Win32API。
但是于此同时我还遇到的截取不到得函数,刚才提到了LoadLibrary,这个是真实的动态链接,直到我们调用这个函数的时候,这个库才被真正的加载进来,所以Detours是无法截取这些函数的。(@modify:在程序加载的时候,通过装载程序把需要的库全部链接进来,我们直接修改这些库函数就行。但是要想截取程序中动态加载的库,我们可以在其调用之前,我们通过LoadLibrary,来对这个库保持一个引用,然后修改函数内容,程序在LoadLibrary的时候,只是增加了一个引用计数,调用的函数也是我们修改过的.....Ok )
这个是我个人的认知,如果有错,请高手提醒一下。
//@更正内容:
唉,自从开始做api hook,这个问题一直困扰着我:就是一个dll,被夹在进来究竟会不会出现多个副本,答案明显是否定的。不过很多数据表明确实有多个副本(这样的误解产生的原因是我对__declspec(dllimport)的理解不够);
根据我的测试结果,我最终把问题归纳到是由于函数声明产生了问题:
下面是测试代码:
typedef int ( * MyFun)(int x,int y);
MyFun fun1,fun2;
fun1=Reduce;
HMODULE hFirst=LoadLibrary(_T("../Debug/firstDll.dll"));
fun2=(MyFun)GetProcAddress(hFirst,"Reduce");
CompareAddress((void *)fun1,(void*)fun2,"Reduce");
FreeLibrary(hFirst);
first.dll一个导出Reduce函数的dll,我用下列两种声明方式: int Reduce(int x,int y);__declspec(dllimport) int Reduce(int x,int y);
如果我用第一种声明方式,那么两个函数指针是不同的,但是用第二种就是相同的。原来我并没有意识到是由于函数声明出的问题。我就是认为在一个进程空间出现了多个dll的副本,知道今天我看到下面一篇文章我知道我错在哪里了:
clever101:http://blog.csdn.net/clever101/article/details/5421782
不使用 __declspec(dllimport) 也能正确编译代码,但使用 __declspec(dllimport) 使编译器可以生成更好的代码。编译器之所以能够生成更好的代码,是因为它可以确定函数是否存在于 DLL 中,这使得编译器可以生成跳过间接寻址级别的代码,而这些代码通常会出现在跨 DLL 边界的函数调用中。但是,必须使用 __declspec(dllimport) 才能导入 DLL 中使用的变量。
如果没有使用_declspec(dllimport)的话,程序会正常运行,但编译器看到我们的函数声明时,并不知道我们的这个函数是来自于dll,所以我们声明的函数被赋值了一个中间函数地址,这个地址并不是dll导出的函数地址,需要中间跳转两次才能到达真是的dll导出地址(具体编译器怎么实现的我们也不知道)。在反汇编中看到程序是用:
jmp ***;跳转到指定地址的;
现在我知道究竟是什么原因让我产生那样的误解了,心里顿时轻松许多,感谢clever101。
我看了windows提供的某些函数的头文件:有些用_declspec(dllimport)声明,有些没有。
现在开始回归到Hook,当我们想截取系统函数的时候不能直接引入一个头文件,直接使用地址。而应该通过LoadLibrary(),然后GetProcAddrss()获取真实的地址进行修改。这样无论是通过多次跳转到的,还是直接调用的,我们都可以截取到......soga