[原创]揭示《shellcoder's handbook》中一个函数的运行机制
下面的这个函数是在《shellcoder's handbook》一书里遇到的,通过这个精心构造的函数可以执行我们的shellcode。看看这个C程序,它的功能就是执行我们的shellcode。也许你会问这一系列的赋值语句,怎么会执行我们的shellcode的呢?它又是如何做到的呢?《shellcoder's handbook》一书中并没有介绍这个函数的内在机制,只是用这个函数来运行我们写的shellcode.
如果你不明白这段代码是如何做到执行我们的shellcode的,我觉着这篇文章就有了它的价值意义。
/*shellcode_tester.c*/
#include <stdio.h>
char shellcode[]="\xbb\x02\x00\x00\x00"
"\xb8\x01\x00\x00\x00"
"\xcd\x80";
/*
上面的shellcode的意思就是:
movl $2,%ebx
movl $1,%eax
int $0x80
该功能就是调用exit(2)
*/
int main()
{
int *ret;
ret=(int *)&ret+2;
(*ret)=(int)shellcode;
}
首先,我们编译一下这段代码:gcc -o test shellcode_tester.c
接着,我们调试这段代码,并在main函数处下断点,如图所示

然后,运行至断点处,并反汇编EIP运行处周围的二进制代码,如图

此时,我们查看一下栈中的情况,如图

从这幅图中我们可以看出,此时寄存器ebp的值是0xbffffd28,在执行push %ebp之前,寄存器ebp的值是0xbffffda8,而地址0xbffffd2c处就存储着我们的返回地址,我们要做的就是更改这个地址。
紧接着,我们执行到leave指令(leave指令还没有被执行),如图

执行完上图中的两个赋值语句后,寄存器eax所存的值是0xbffffd2c,而地址0x8049628就是我们的shellcode的地址,该值被存放在寄存器edx中,最后使用mov指令将shellcode的地址重写到eax所指的地址处。现在我们查看一下栈中的情况如何,如图

看到了吧,我们已经成功地改写了地址为0xbffffd2c处的值,其值就是我们的shellcode的起始地址。此时,我们看看几个寄存器中的值如何,如下图

当我们执行完leave指令,而ret指令还没被执行时,如图

我们再来看看寄存器的情况,如下图

对比一下leave指令执行前后寄存器ebp,esp的变量,你发现什么了么。通过对比推导,我们可以总结出leave指令的功能,它是将当前子程序在堆栈中的局部变量进行释放,其功能相当于下面的这两条指令
mov %ebp,%esp
pop %ebp
经过leave指令后,我们的栈顶esp重新回到了0xbffffd2c地址处,而该栈顶的值是0x08049628。还记得这个值吧,这个值就是我们的shellcode的地址。
当我们执行完最后一条ret指令时,将执行一个出栈操作,将栈顶的元素赋值于EIP。此时,我想你已经知道了shellcode为什么会执行的原因了吧。
