接触过缓冲区溢出的朋友对这个绝对不陌生,EIP,EBP,ESP寄存器。这里先不解释,先看一段代码吧。
char a[8] = "zpf06188";
for (int i=0;i<8;i++)
{
printf("%# x \n",&a[i]);
}
在VC6.0编译器里面,这样的代码是会报一个array bounds overflow的错,下标越界了。这是编译器直接对数组下标的检查,换一种方法,用拷贝函数呢?
#include<stdio.h>
#include<string.h>
char name[] = "abcdefghijklmnopqrstuvwxyz";
int main()
{
char output[8];
strcpy(output, name);
for(int i=0;i<8;i++)
printf("%# x ",output[i]);
return 0;
}
输出结果就成了:
调试结果指向:
注意这里的值70 6F 6E 6D 分别是什么?对照码表就可以查出来是ponm,当然存储方式应该是mnop。
堆栈的存储是低位向高位存储,整个操作完成,main函数执行完毕之后,堆栈中的EBP,EIP要回复回去,但是由于在strcpy的时候,拷贝的字符长度已经超过了数组限定的值,这就导致写入的值覆盖了EIP,EBP。最后弹出EIP的时候却发现,它原来的东西没了,被写成了6d6e6f70了。所以出错了。
EBP是"基址指针"(BASE POINTER), 它最经常被用作高级语言函数调用的"框架指针"(frame pointer). 在破解的时候,经常可以看见一个标准的函数起始代码:
ESP 专门用作堆栈指针,被形象地称为栈顶指针,堆栈的顶部是地址小的区域,压入堆栈的数据越多,ESP也就越来越小。在32位平台上,ESP每次减少4字节。
EIP:寄存器存放下一个CPU指令存放的内存地址,当CPU执行完当前的指令后,从EIP寄存器中读取下一条指令的内存地址,然后继续执行。
栈的基本模型:
参数N | ↓高地址 |
参数… | 函数参数入栈的顺序与具体的调用方式有关 |
参数 3 | |
参数 2 | |
参数 1 | |
EIP | 返回本次调用后,下一条指令的地址 |
EBP | 保存调用者的EBP,然后EBP指向此时的栈顶。 |
临时变量1 | |
临时变量2 | |
临时变量3 | |
临时变量… | |
临时变量5 |
void fun(void)
{
printf("hello world");
}
void main(void)
{
fun()
printf("函数调用结束");
}