浅谈钩子技术(转)

来源:互联网 发布:淘宝网首页女装冬装 编辑:程序博客网 时间:2024/04/29 01:10
钩子:(Hook)是Windows中提供的一种用以替换DOS下“中断”的系统机制,中文译为“挂钩”或“钩子”。钩子在软件中的应用可以说是很广泛了,想当年学习钩子那会儿,什么IAT钩子,INLINE钩子......真是有点老牛啃南瓜......

看多了网上的其他一些关于此话题的文章,感觉对于刚接触的人不适用,所以今天把经验与大家分享(高手就不用看了......呵呵.)

WINDOWS HOOK有什么用呢?说的明白点就是给特定的某一个事件,说的具体点是给一个函数挂上一个钩子(我们自己的函数),让它在执行前先执行我们挂的钩子(我们挂接的函数),从而达到拦截事件和函数调用等的目的.

IAT钩子:IAT(Import Address Table)意思是导入地址表,有的朋友看不懂了,!什么是导入地址表.你可以去下载一个exescope,然后用它打开一个EXE文件看看导入栏,一个程序在执行时,它所(静态调用,即不用loadlibrary法)调用的函数,在它被编译成为一个可执行文件时,这些模块的函数的地址就会被写入导入表,IAT钩子即是替换IAT表中的这些地址为自己的函数地址,那么当程序调用到这个地址的时候就会跳到我们的函数里面来,在这里替换并不是说我们要去替换文件中的IAT表,而是要去替换内存中的IAT表,一个可执行文件运行之后,它的程序主体会做为一个模块被加载到内存里,在需要调用函数的时候它会去IAT表中查询所调用的函数地址,我们只需修改内存中IAT表地址即可,具体修改方法很简单,这里不作介绍,那么怎么样去修改呢,换句话说,我们怎么知道什么时候去修改?这个时候就需要用到一个windows系统函数SetWindowsHookEx(方法当然不只这一种,比如用远程注入也可以,有兴趣可以去网上查询),这个函数会在运行时负责把我们的DLL文件带到与我们所注册的钩子有关的所有程序中(注意进程的用户权限),这就是为什么用户态的全局钩要做成DLL(动态链接库的原因).

比如,我们用SetWindowsHookEx注册一个鼠标钩子,并用一个A.DLL的模块与之挂钩,那么在WINDOWS系统中响应鼠标事件的所有程序会先加载A.DLL,在加载DLL初始化时(执行DLLMAIN函数)我们就可以先在内存中找出IAT中的函数地址,再用WriteProcessMemory或memcpy修改为A.DLL模块内的某个函数(我们挂钩的函数),那么这个程序在响应鼠标事件时会先执行我们挂好的那个在A.DLL的某个函数,这个时候大权就在我们手里了,我们可以让它继续执行,也可以让它立即返回无效.同理我们可以挂钩所有响应键盘事件的程序加载我们的模块以达到监视键盘记录的作用,也可以挂钩所有响应消息的程序来挂钩NtopenProcess和NtTerminateProcess来防止我们的程序被其他程序结束.也可以挂钩诸多的API函数来监视系统API的调用,不过在这里有个误区,SetWindowsHookEx只会负责把你的DLL加载到其他的执行程序(不会执行),所以我们的替换动作必须在DLLMAIN函数中(或调用)完成.还有一点要注意IAT HOOK修改的只是IAT表而已,并不是函数本身,而下面这中方法就是直接修改函数本身实现HOOK,同理,都可以用SetWindowsHookEx注册一个钩子把我们的钩子模块带到其他进程。那么它是怎么实现HOOK的呢?我们需要准备一个5个字节的BYTE(或char)变量,然后把它的第一个字节设为0xE9(16进制),然后把(我们的函数地址-原函数地址-5)(long类型)写入从第2个字节开始后的4个字节,我们都知道LONG类型所占的空间刚好为4个字节(在这众多类型里面,不管它的类型怎么变,它都有以字节为单位的大小,在我看来,把一个变量叫做int 也好 long 也好,纯粹是为了编写程序方便和好记,因为我们完全可以把int x 定义为 BYTE x[2]),再把这5个字节复制到内存中以原函数(要挂钩的那个函数)地址为起始点的5个字节,这么做的意义是什么呢?可能有的人不知道0xE9 相当于汇编里的Jmp指令,(当然有点反汇编基础的人都知道如0xC3 = ret, 0xE9=Jmp,、、很多)在汇编里jmp是一个跳转指令,在这里就是让它跳转至后面的4个字节指示的位置(我们的函数地址),从而达到HOOK的目的,这就是为什么叫INLINE HOOK为5字节跳转法,同样的这种方法不仅仅适用于用户态,它还适用于核心态,这就是SDT INLINE HOOK。

在系统内核有一个内核文件的加载的镜像(内核模块),NTOSKRNL或ntkrnlpa及ntkrnlmp、ntkrnlup、ntkrpamp都是OS内核文件,它提供了一整套核心态函数,供用户态和核心态调用,我们在用户态调用的函数大多数在最后都进入了核心态如kernel32.dll的OpenProcess,在调用OpenProcess,以后会进入Ntdll.dll中的NtOpenProcess然后调用系统函数调用号进入核心态的NtOpenProcess,所以核心态的钩子才是王道,核心态的钩子有SSDT HOOK和SDT INLINE HOOK,上面说了,内核导出了一系列的函数供用户态调用,那么导出的这个函数表就是SSDT(系统服务描述表),它的地址可由内核导出的一个变量 KeServiceDescriptorTable 查询,每4个字节(long 大小)为一个地址,在SSDT HOOK时我们只需要修改SSDT表中的地址为我们的地址即可,相当简单.

例:修改NtOpenProcess:

*(ULONG*)(KeServiceDescriptorTable+(0x7A*4))=(ULONG)MyOpenProcess

0x7A为NtOpenProcess的系统调用号,每个调用号占4个字节,KeServiceDescriptorTable+(0x7A*4)就代表了NtOpenProcess地址的存放位置,MyOpenProcess为我们自定义的函数,这样系统在执行NtOpenProcess的时候就会跳到MyOpenProcess里。还有一种就是Inline Hook它和用户态的一样直接修改函数前5个字节即可,这里不再赘述。但是在核心态的inline hook比较危险,这涉及到多线程调用,有兴趣的朋友可以去查阅该方面资料。

已经讲了这几种HOOK 的实现原理,那么我们怎么来预防Hook呢。在用户态我们可以挂接(inlinehook)LoadLibraryExW来判断,这个资料在网上很多,朋友们可以自己去找,还有一点就是,网上的方法不能防止远程注入HOOK,在LoadLibrary的时候加上判断TED或PEB就可以防止了。上述原理有不懂的或具体实现方法可联系我。(2008)
原创粉丝点击