l 什么是API钩挂技术:
API挂钩技术的目的就是:用自己的函数替换别人程序里的函数。当程序试图调用它原来应该使用的函数A的时候,我们将函数A替换成函数B,让程序调用B去进行我们想要的处理。
一个例子就是我们经常使用的屏幕取词软件,例如金山词霸和博雅等等。当鼠标指向一个英文或者中文单词的时候,就会在旁边给出相应的翻译。实现该类软件的一个方法就是使用API钩挂技术。当鼠标在屏幕上移动时系统会产生重画消息,相应的程序就会调用TextOut,DrawText等API函数重画屏幕,翻译软件使用自己的函数代替系统提供的重画函数,在屏幕重画前进行自己的处理,显示翻译等等
l API钩挂和Windows事件消息钩挂有什么不同:
| 实现目标(相同点) | 技术本质(不同点) |
消息钩挂 | 在别人的程序试图做某事前,先进行我们的处理 | 消息挂钩截获消息,在调用程序的函数前先调用我们的钩子函数 |
API钩挂 |
| API钩挂替换程序原来的函数,使得程序对原函数的调用最终都变成对我们自己的函数的调用 |
我们知道,当有消息发送到程序的时候,程序肯定是通过调用某个函数进行处理的:但是程序调用某个函数的时候,却不一定是处理消息,我们可以调用函数进行任何处理,和Windows消息没有必然的联系。在非消息处理的情况下,消息钩子就起不了任何作用了。所以说,API钩挂技术比事件消息钩挂功能更加强大,使用更广泛,但是它的实现也更加多样化,更加复杂。
l 有哪些API钩挂的方法:
API钩挂是一个很灵活的技术,没有一个固定的方法,只要能达到我们的目的就行。但是API钩挂的方法有难有易,效果也有好有坏,下面我们来看看两种方法:
Ø 方法1:通过改写原函数的代码来进行API钩挂:
① 获得我们想要挂接的函数在内存中的地址。这一步比想象的要复杂,很多意外的情况都要考虑。
② 将该函数的头几个字节保存起来。
③ 用一个JUMP指令改写函数的头几个字节,跳转到我们的替换函数的地址,在我们的函数内进行处理。当然了,我们的函数必须和原函数的格式一样,有一样的输入输出参数。
④ 当你完成自己的处理后,则跳转回到原来保存的那几个头字节,执行原函数的代码。
这种方法在16位Windows编程中使用的非常普遍,而且效果很好,但在32位Windows程序里存在严重的问题:首先它对CPU的依赖性很大,X86,Alpha等各种CPU上JUMP等指令是不同的,必须使用手工地编写机器码;其次,在抢占式的多线程系统环境中,这种方法是很脆弱的,万一代码在替换的过程中另一个线程又调用了该函数,那样将会造成严重后果。
所以,在32位Windows操作系统中,我们不推荐这种方法。
Ø 方法2:通过修改模块的引入表来挂接API。
在前面的章节中我们已经介绍过PE文件的引入表的概念。这种方法的关键就是修改模块引入表里函数对应的地址。在内存里模块的引入表包含了一组该模块运行时需要的DLL,而且还包含了从每个DLL引入的符号的列表。当程序调用DLL里的某个函数时,它实际上是从引入表里读出该函数在内存里的地址,然后跳转到该地址执行函数的代码。
如果我们修改了引入表里函数A对应的地址,把我们自己的函数B的地址替换上去,那么当程序调用该函数A时,实际上从引入表里查出来的是函数B的地址,从而执行函数B的代码。
l 如何进行API的简单钩挂:
我们先来看一个简单的例子。该程序的功能很简单,它有一个函数ShowDlg,调用该函数的时候就弹出一个对话框;当我们使用API钩挂技术把原来的对话框函数用新函数NewFunction替换掉后,程序再来调用该函数时,就会执行NewFunction的代码,弹出另一个对话框。
修改引入表函数
void ReplaceIATEntry(PCSTR pszDLLName,PROCpfnOld,PROC pfnNew,HMODULE hmodCaller)
{
ULONGsize;
PIMAGE_IMPORT_DESCRIPTORpImportDesc=(PIMAGE_IMPORT_DESCRIPTOR)ImageDirectoryEntryToData(hmodCaller,TRUE,IMAGE_DIRECTORY_ENTRY_IMPORT,&size);
if(pImportDesc==NULL)
{
return ;
}
for(;pImportDesc->Name;pImportDesc++)
{
PSTRpszModName=(PSTR)((PBYTE)hmodCaller+pImportDesc->Name);
if(lstrcmpiA(pszModName,pszDllName)==0)
break;
}
if(pImportDesc->Name==0)
return ;
PIMAGE_THUNK_DATApThunk=(PIMAGE_THUNK_DATA)((PBYTE)hmodCaller+pImportDesc->FirstThunk);
for(;pThunk->u1;pThunk++)
{
PROC *ppfn=(PROC*)&pThunk->u1.Function;
BOOL fFound=(* ppfn==pfnOld);
if(fFound)
{
WriteProcessMemory(GetCurrentProcess(),ppfn,&pfnNew,sizeof(pfnNew),NULL);
return;
}
}
}