存在着你修改的内容。windows进程运行所需要映射的一些系统dll就是以这种方式映射的,比如常用的ntdll.dll,kernel32.dll,gdi32.dll.几乎所有的进程都会加载这三个动态库。如果你在一个进程里修改这个映射文件的内容,并不会影响到其他的进程使用他们。你所修改的只是在本进程的地址空间之内的。事实上原始文件并没有被改变。
这样,在后面的修改系统api的时候,实际就是修改这些动态库地址内的内容。前面说到这不是修改全局api就是这个原因,因为他们都是以写入时拷贝的方式来映射的。不过这已经足够了,windows提供了2个强大的内存操作函数ReadProcessMemory和WriteProcessMemory.利用这两个函数我们就可以随便对任意进程的任意用户地址空间进行读写了。但是,现在有一个
问题,我们该写什么,说了半天,怎么实现跳转呢?现在来看一个简单的例子:
MessageBox(NULL, "World", "Hello", 0);
我们在执行这条语句的时候,调用了系统api MessageBox,实际上在
程序中我没有定义UNICODE宏,系统调用的是MessageBox的ANSI版本MessageBoxA,这个函数是由user32.dll导出的。下面是执行这条语句的汇编代码:
0040102A push 0
0040102C push offset string "Hello" (0041f024)
00401031 push offset string "World" (0041f01c)
00401036 push 0
00401038 call dword ptr [__imp__MessageBoxA@16 (0042428c)]
前面四条指令分别为参数压栈,因为MessageBoxA是__stdcall调用约定,所以参数是从右往左压栈的。最后再CALL 0x0042428c
看看0042428c这段内存的值:
0042428C 0B 05 D5 77 00 00 00
可以看到这个值0x77d5050b,正是user32.dll导出函数MessageBoxA的入口地址。
这是0x77D5050B处的内容,
77D5050B 8B FF mov edi,edi
77D5050D 55 push ebp
77D5050E 8B EC mov ebp,esp
理论上只要改变api入口和出口的任何机器码,都可以拦截该api。这里我选择最简单的修改方法,直接修改api入口的前十个字节来实现跳转。为什么是十字节呢?其实修改多少字节都没有关系,只要实现了函数的跳转之后,你能把他们恢复并让他继续运行才是最重要的。在CPU的指令里,有几条指令可以改变程序的流程:JMP,CALL,INT,RET,RETF,IRET等指令。这里我选择CALL指令,因为他是以函数调用的方式来实现跳转的,这样可以带一些你需要的参数。到这里,我该说说函数的堆栈了。
总结:windows进程所需要的动态库文件都是以写入时拷贝的方式映射到进程地址空间中的。这样,我们只能拦截指定的进程。修改目标进程地址空间中的指定api的入口和出口地址之间的任意数据,使之跳转到我们的拦截代码中去,然后再恢复这些字节,使之能顺利工作。
二:函数堆栈的一些知识
正如前面所看到MessageBoxA函数执行之前的汇编代码,首先将四个参数压