接触过缓冲区溢出的朋友对这个绝对不陌生,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;
}

输出结果就成了:

EIP&EBP&ESP 寄存器_朋友


调试结果指向:EIP&EBP&ESP 寄存器_寄存器_02

注意这里的值70 6F 6E 6D 分别是什么?对照码表就可以查出来是ponm,当然存储方式应该是mnop。

EIP&EBP&ESP 寄存器_return_03

堆栈的存储是低位向高位存储,整个操作完成,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("函数调用结束");
}

这是一个再简单不过的函数调用的例子了。
当程序进行函数调用的时候,我们经常说的是先将函数压栈,当函数调用结束后,再出栈。这一切的工作都是系统帮我们自动完成的。
但在完成的过程中,系统会用到下面三种寄存器:
1.EIP
2.ESP
3.EBP
当调用fun函数开始时,三者的作用。
1.EIP寄存器里存储的是CPU下次要执行的指令的地址。
也就是调用完fun函数后,让CPU知道应该执行main函数中的printf("函数调用结束")语句了。
2.EBP寄存器里存储的是是栈的栈底指针,通常叫栈基址,这个是一开始进行fun()函数调用之前,由ESP传递给EBP的。(在函数调用前你可以这么理解:ESP存储的是栈顶地址,也是栈底地址。)
3.ESP寄存器里存储的是在调用函数fun()之后,栈的栈顶。并且始终指向栈顶。

当调用fun函数结束后,三者的作用:
1.系统根据EIP寄存器里存储的地址,CPU就能够知道函数调用完,下一步应该做什么,也就是应该执行main函数中的printf(“函数调用结束”)。
2.EBP寄存器存储的是栈底地址,而这个地址是由ESP在函数调用前传递给EBP的。等到调用结束,EBP会把其地址再次传回给ESP。所以ESP又一次指向了函数调用结束后,栈顶的地址。