| | <---ESP
|返回地址|
|参数1|
|参数2|
|参数3|
|参数4|
|.. |
我们再看MessageBoxA的汇编代码,
77D5050B 8B FF mov edi,edi
77D5050D 55 push ebp
77D5050E 8B EC mov ebp,esp
注意到堆栈的操作有PUSH ebp,这是保存当前的基址指针,以便一会儿恢复堆栈后返回调用线程时使用,然后再有mov ebp,esp就是把当前esp的值赋给ebp,这时候我们就可以使用 ebp+偏移 来表示堆栈中的数据,比如参数1就可以表示成[ebp+8],返回地址就可以表示成[ebp+4]..如果我们在拦截的时候要对这些参数和返回地址做任何处理,就可以使用这种方法。如果这个时候函数有局部变量的话,就通过减小ESP的值的方式来为之分配空间。接下来就是保存一些寄存器:EDI,ESI,EBX.要注意的是,函数堆栈是反方向生长的。这时候堆栈的样子:
|.|
|EDI| <---ESP
|ESI|
|EBX|
|局部变量|
|EBP |
|返回地址|
|参数1|
|参数2|
|参数3|
|参数4|
|.. |
在函数返回的时候,由函数自身来进行堆栈的清理,这时候清理的顺序和开始入栈的顺序恰恰相反,类似的汇编代码可能是这样的:
pop edi
pop esi
pop ebx
add esp, 4
pop ebp
ret 0010
先恢复那些寄存器的值,然后通过增加ESP的值的方式来释放局部变量。这里可以用mov esp, ebp来实现清空所有局部变量和其他一些空闲分配空间。接着函数会恢复EBP的值,利用指令POP EBP来恢复该寄存器的值。接着函数运行ret 0010这个指令。该指令的意思是,函数把控制权交给当前栈顶的地址的指令,同时清理堆栈的16字节的参数。如果函数有返回值的话,那在EAX寄存器中保存着当前函数的返回值。如果是__cdecl调用方式,则执行ret指令,对于堆栈参数的处理交给调用线程去做。如wsprintf函数。
这个时候堆栈又恢复了原来的样子。线程得以继续往下执行
在拦截api的过程之中一个重要的任务就是保证堆栈的正确性。你要理清每一步堆栈中发生了什么。
三:形成思路
呵呵,不知道你现在脑海是不是有什么想法。怎么去实现拦截一个api?
这里给出一个思路,事实上拦截的方法真的很多,理清了一个,其他的也就容易了。而且上面所说的2个关键知识,也可以以另外的形式来利用。
我以拦截CreateFile这个api为例子来简单说下这个思路吧:
首先,既然我们要拦截这个api就应该知道这个函数在内存中的位置吧,至少需要知道从哪儿入口。CreateFile这个函数是由kernel32.dll这个动态库导出的。我们可以使用下面的方法来获取他映射到内存中的地址:
HMODULE hkernel32 = LoadLibrary("Kernel32.dll");
PVOID dwCreateFile = GetProcAddress(hkernei32, "CreateFileA");
这就可以得到createfile的地址了,注意这里是获取的createfile的ansic版本。对于UNICODE版本的则获取CreateFileW。这时dwCreateFile的值就是他的地址了。对于其他进程中的createfile函数也是这个地址,前面说过windows指定了他提供的所