声明:这个例子,是冉林仓的《Windows API编程》中,HOOK API中的一个例子,我只是看明白,又做了一点改进。
原理:
1、在一个进程内部(当然可以是想要HOOK的API,或者任意知道的函数),随时都可以用一个jmp指令,把当前的执行路径跳到其它位置。
2、只要构造好跳到位置的执行代码,进程就可以正常执行下去了。
3、也要处理好跳走时,函数栈才行。要不然就要乱套了。
实现:
1、自己写一个MyMessageBoxA,注意调用规则和参数要和MessageBoxA完全一样,不然的话,栈是没有办法平衡的,程序就要乱了。
2、jmp指令,机器码就是0xe9,一个完整的指令是命令码+命令内容,jmp的命令内容是一个位置,用4个字节表示。所以总计要5字节
3、要跳到位置,就是相对于当前位置的偏移量,可以是正值,也可以是负值。
可以事先计算好自己写的MyMessageBoxA起始位置到MessageBoxA函数的距离,再减掉5。
因为是从MessageBoxA的第5个字节开始跳的,而这个位置比MessageBoxA起始地址高了5个字节,所以要减掉5。
4、把2计算出来的偏量,放到jmp指令后边。这样如果执行这个jmp指令,就会跳到想去的地方啦!
5、现在跳转指令已经做好了,可以在想HOOK的时候,找到MessageBoxA的地址,把它的前5字节改成咱们计算好的跳转指令
6、这样在此进程中,任意地方,执行MessageBoxA,就会马上跳到咱们写好的MyMessageBox中啦,帅呆了!
具体实现:
1、既然是HOOK,当然不只想HOOK自己进程的API了,HOOK其它进程才有用嘛。这就需要把代码注入到其它进程中了。
进程代码注入有很多种方法:
1、改注册表
2、利用远程线程,实现DLL注入
3、利用远程线程,将本地代码拷贝到远程进程中。
4、利用系统钩子,将DLL注入到远程进程中
我们用4来实现代码注入。
2、注入代码在什么时候初始化HOOK需要的内容呢?可以在自己进程中,来修改远程进程中的MessageBoxA的前5字节,
但是这样做真是太不划算了。既然代码能够,并且已经注入到别的进程中了,为什么不让这些代码,来完成HOOK的初始化呢?
所以当然在DLL刚注入的时候了,就是在DLL_PROCESS_ATTACH的时候。在DLL_PROCESS_DETACH时清除
3、怎么让系统钩子,把DLL注入到别的进程中呢?这是系统的一个特性。每种钩子,系统都维护一个大钩子链,链中每个节点,都是
挂上来的回调函数,这些回调函数可能是由不同的DLL提供的。每个进程中发生与此钩子相关的事件时,都要执行一下链中所有的
回调函数。当执行某一个回调函数时,因为这个回调函数可能不在进程空间中,所以要把回调函数所在的DLL载入到进程中,这样
回调函数才能执行。这样,顺便就把DLL给载入进来啦!顺便说一下,为什么回调函数要放在DLL中,却不放在EXE中呢?这是因为
DLL可以被别的进程载入。其实在EXE中输出的函数,也可以被别人使用。但是因为EXE的载入首选地址,已经写死了,
就是0x00400000,而每个进程的首选地址都一样,所以在EXE中载入别的EXE,LoadLibrary肯定要出错的啦。所以EXE输出的函数,
只能被这个EXE载入的DLL中的代码来使用了---只有自己载入的DLL,才会牵就一下EXE强硬的首选地址要求。
4、其实构造那些5个字节,用C代码也一样可以,用汇编更酷一点吧,呵呵
一点体会:
1、操作系统其实很简单。就像自己的进程管理自己的线程,或者管理多个进程一样,操作系统管理系统中所有进程,还有内容等等。
操作系统就是底层硬件和应用程序的一个中间层,它把所有的不统一的东西,都变成统一的,提供给应用程序。
2、那些钩子啊,接口啊,之类的,就是操作系统提供的中间层中,一些小小的部分。比如接口,就好像是操作系统已经给我们设计好
了一辆车,然后写了一份详细的文档,上面说:这个车啊,需要这么大,的一个轮子,你有没有?有的话,安上车就能跑啦。然后
我们就费了半天劲,好不容易弄好了一个合格的轮子,安上,哦,真的能开啦!当然我们可以弄一个不符合的轮子,安上,一看,
哦,怎么车不能开啊,或者怎么一开,车不往前跑,却往旁边跑啊?我们做的轮子,大不不合适,车当然要往旁边跑啦:)
3、其实自己可以设计操作系统的,并不像想像的那样,比登天还难。