⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 如何实现api钩子.txt

📁 delphi知识收集 我个人的小小收集
💻 TXT
📖 第 1 页 / 共 2 页
字号:

另外,自定义的函数的参数形式可以和原先的API函数相同的,不过这也不是必须的,而且这样的话在有些时候也会出现一些问题,我在后面将会提到。因此要拦截API的调用,首先我们就要得到相应的IAT的地址。系统把一个进程模块加载到内存中,其实就是把PE文件几乎是原封不动的映射到进程的地址空间中去,而模块句柄HModule实际上就是模块映像在内存中的地址,PE文件中一些数据项的地址,都是相对于这个地址的偏移量,因此被称为相对虚拟地址(RVA,Relative Virtual Address)。

 

于是我们就可以从HModule开始,经过一系列的地址偏移而得到IAT的地址。不过我这里有一个简单的方法,它使用了一个现有的API函数 ImageDirectoryEntryToData,它帮助我们在定位IAT时能少走几步,省得把偏移地址弄错了,走上弯路。不过纯粹使用RVA从HModule开始来定位IAT的地址其实并不麻烦,而且这样还更有助于我们对PE文件的结构的了解。上面提到的API函数是在DbgHelp.dll中输出的(这是从Win 2K才开始有的,在这之前是由ImageHlp.dll提供的),有关这个函数的详细介绍可参见MSDN。 

 

在找到IAT之后,我们只需在其中遍历,找到我们需要的API地址,然后用我们自己的函数地址去覆盖它。下面给出一段对应的源码: 

procedure RedirectApiCall; var ImportDesc:PIMAGE_IMPORT_DESCRIPTOR; FirstThunk:PIMAGE_THUNK_DATA32; sz:DWORD; 

begin 

//得到一个输入描述结构列表的首地址,每个DLL都对应一个这样的结构 ImportDesc:=ImageDirectoryEntryToData(Pointer(HTargetModule), true, IMAGE_DIRECTORY_ENTRY_IMPORT, sz); 

 

while Pointer(ImportDesc.Name)<>nil do 

begin //判断是否是所需的DLL输入描述 

if StrIComp(PChar(DllName),PChar(HTargetModule+ImportDesc.Name))=0 then     begin 

//得到IAT的首地址 

FirstThunk:=PIMAGE_THUNK_DATA32(HTargetModule+ImportDesc.FirstThunk); 

 

while FirstThunk.Func<>nil do 

begin 

if FirstThunk.Func=OldAddressOfAPI then 

begin 

//找到了匹配的API地址 …… 

//改写API的地址 

break; 

end; 

Inc(FirstThunk); 

end; 

end; 

Inc(ImportDesc); 

end; 

end; 

 

最后有一点要指出,如果我们手工执行钩子DLL的退出目标进程,那么在退出前应该把函数调用地址改回原先的地址,也就是API的真正地址,因为一旦你的DLL退出了,改写的新的地址将指向一个毫无意义的内存区域,再使用它显然会出现一个非法操作。 

 

五、

 

替换函数的编写 前面关键的两步做完了,一个API钩子基本上也就完成了。不过还有一些相关的东西需要我们研究一番的,包括怎样做一个替换函数。 下面是一个做替换函数的步骤: 首先,不失一般性,我们先假设有这样的一个API函数,它的原型如下: 

function someAPI(param1: Pchar;param2: Integer): DWORD; 

 

接着再建立一个与之有相同参数和返回值的函数类型: 

type FuncType= function (param1: Pchar;param2: Integer): DWORD; 

然后我们把someAPI函数的地址存放在OldAddress指针中。接着我们就可以着手写替换函数的代码了: 

function DummyFunc(param1: Pchar;param2: Integer): DWORD; begin ……

//做一些调用前的操作 

result := FuncType(OldAddress) (param1 , param2); 

//调用原先的API函数 ……

//做一些调用后的操作 

end; 

 

我们再把这个函数的地址保存到NewAddress中,接着用这地址覆盖掉原先API的地址。这样当目标进程调用该API的时候,实际上是调用了我们自己的函数,在其中我们可以做一些操作,然后在调用原先的API函数,结果就像什么也没发生过一样。当然,我们也可以改变输入参数,甚至是屏蔽调这个API函数的调用。

 

尽管上述方法是可行的,但有一个明显的不足:这种替换函数的制作方法不具有通用性,只能针对少量的函数。如果只有几个API要拦截,那么只需照上述说的重复几次就行了。但如果有各种各样的API要处理,它们的参数个数和类型以及返回值的类型是各不相同的,还是采用这种方法就太没效率了。

 

的确是的,上面给出的只是一个最简单最容易想到的方法,只是一个替换函数的基本构架。正如我前面所提到的,替换函数的与原先的API函数的参数类型不必相同,一般的我们可以设计一个没有调用参数也没有返回值的函数,通过一定的技巧,使它能适应各种各样的API函数调用,不过这得要求你对汇编语言有一定的了解。

 

下面我就对此详细的说一下。 首先,我们来看一下执行到一个函数内部前的堆栈情况(这里函数的调用方式为stdcall)。 由上面的例图可知,函数的调用参数是按照从右到左的顺序压入堆栈的(堆栈是由高端向低端发展的),同时还压入了一个函数返回地址。在进入函数之前,ESP正指向返回地址。因此,我们只要从ESP+4开始就可以取得这个函数的调用参数了,每取一个参数递增4。另外,当从函数中返回时,一般在EAX中存放函数的返回值。

 

了解了上述知识,我们就可以设计如下的一个比较通用的替换函数,它利用了Delphi的内嵌式汇编语言的特性。 

Procedure DummyFunc;   

asm add esp,4 mov eax,esp//得到第一个参数 

mov eax,esp+4//得到第二个参数 …… 

//做一些处理,这里要保证esp在这之后恢复原样 

call OldAddress //调用原先的API函数 …… 

//做一些其它的事情 

end; 

 

当然,这个替换函数还是比较简单的,你可以在其中调用一些纯粹用OP语言写的函数或过程,去完成一些更复杂的操作(要是都用汇编来完成,那可得把你忙死了),不过应该这些函数的调用方式统一设置为stdcall方式,这使它们只利用堆栈来传递参数,因此你也只需时刻掌握好堆栈的变化情况就行了。如果你直接把上述汇编代码所对应的机器指令存放在一个字节数组中,然后把数组的地址当作函数地址来使用,效果是一样的。

 

以上代码在 Win 2K/xp & Delphi 6.0 中实现。 

 

 

六、后记

做一个API钩子的确是件不容易的事情,尤其对我这个使用Delphi的人来说,为了解决某个问题,经常在OP、C++和汇编语言的资料中东查西找,在程序调试中还不时的发生一些意想不到的事情,弄的自己是手忙脚乱。不过,好歹总算做出了一个API钩子的雏形,还是令自己十分的高兴,对计算机系统方面的知识也掌握了不少,受益非浅。当初在写这篇文章之前,我只是想翻译一篇从网上Down下来的英文资料(网址为www.codeproject.com ,文章名叫“API Hook Revealed”,示例源代码是用VC++写的,这里不得不佩服老外的水平,文章写得很有深度,而且每个细节都讲的十分详细)。

 

不过翻着翻着,就觉得自己真是水平有限,很多地方虽然自己明白了,就是不知怎样用中文来表达意思才明确,于是只得把已经翻译出来的觉得还可以一用的那部分加上一些自己在实际操作中的体会与心得,杂凑出了上面的这篇文章,尽管可能有些不伦不类,但也是化了我的很多时间,呕心沥血啊(惭愧,惭愧!),希望高手不要见笑,请多多指教。
 

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -