堆栈溢出
  
堆栈溢出就是不顾堆栈中分配的局部数据块大小(在栈中分配的局部数据块大小和局部变量的声明的大小有关),向该数据块写入了过多的数据,导致数据越界,结果覆盖了老的堆栈数据(包括函数的返回地址)。 或者解释为在长字符串中嵌入一段代码,并将过程的返回地址覆盖为这段代码的地址,这样当过程返回时,程序就转而开始执行这段自编的代码了.这东西像病毒堆栈溢出_职场
基础知识
    首先简单讲两个基础知识,一是函数调用时堆栈的变化;二是函数调用约定对函数调用及返回时堆栈变化的影响
    我们用高级语言编写程序的时候,函数调用是很常见的事情,但是我们却很少去观察函数调用时汇编究竟是如何实现的。其实,函数调用实现的过程很简单。
    (1)父函数将函数的实参按照从右至左顺序压入堆栈;
    (2)系统将父函数中函数调用指令call xxxx的下一条指令地址EIP压入堆栈;
    (3)父函数通过push ebp指令将基址指针EBP压入堆栈,并通过mov ebp, esp将当前堆栈指针ESP赋予EBP;
    (4)最后就是通过sub esp,m(m 是字节数)为存放函数中局部变量开辟的内存。函数在执行的时候如果需要访问实参或者局部变量,都是通过EBP指针来完成的。

esp是堆栈指针(通过Push、Pop自动可改变)
ebp是基址指针

通常是一开始以 Push EBP 和MOV Ebp,Esp这两句开始的,
这两句的意思是以EBP代替ESP,作为访问堆栈的指针。
2.在结束的时候调用

mov esp,ebp
pop ebp
retn


    在Windows系统下,我们常见的函数调用约定有两种:

    _stdcall和_cdecl调用约定,前者是Windows API函数常用的调用约定,后者即是C调用约定.WINAPI、PASCAL等名称就是__stdcall的宏定义,是一个意思

_stcall:参数按从右向左的顺序入栈,由被调用函数清理堆栈.
_cdecl :参数按从右向左的顺序入栈,由调用函数清理堆栈.


    对于_cdecl调用方式的函数,其父函数在调用该函数的时候,会先将它的实参按照从右至左顺序压入堆栈;函数返回之后(注意这里是函数返回后,也就是有调用函数清理堆栈),父函数通过sub esp, n(n = 函数实参个数 * 4)(这里还是要考虑参数的类型的!)指令来负责恢复堆栈。
    对于__stdcall调用约定函数,函数调用时实参入栈顺序也是从右至左,但堆栈恢复是该函数返回时自己通过ret n(n = 函数实参个数 * 4)(这里也要考虑参数的类型吧)指令来完成的

#include <windows.h>
#include <stdio.h>
#include <string.h>
int fun(char *szIn, int nTest)
{
    char szBuf[8];
    printf("%d\n", nTest);//注意这里是8但是
szIn却不止八   //个,这也就是说,通过sub esp,m(m 是字节数)为存放函数中局部//变量开辟的内存,将会不够,导致数据覆盖,将函数的返回值覆写
    strcpy(szBuf, szIn);
    return 0;
}
int main()
{
    char sz_In[] = "1234567890abcdefghijklmn";
    fun(sz_In, 888);
    return 0;
}

编译、执行,弹出异常对话框:

显然是发生了堆栈溢出,这是从直观的角度进行了解。大家花1分钟时间看下程序代码,然后我们来仔细看看这个溢出的发生细节。
好了,我们继续。在debug模式下, 按F10,进入单步调试状态,再按下Alt+8,进入汇编状态

高级语言第15行代码char sz_In[] = "1234567890abcdefghijklmn"前面是main函数的启动代码,其实大家从这里就可以看到在调用主函数时堆栈的变化。一直按F10,直到第16行高级语言代码fun(sz_In, 888)停下来,这里是我们开始重点看的地方:

大家现在可以看到
    (1)main函数(前面讲的父函数)通过call @ILT+0(fun) (00401005)指令调用fun函数,在该指令之前还有两个push 指令操作。不难看出,这两条push 指令就是将fun函数的两个实参按照从右往左的顺序压入堆栈

    (2)继续按F10,直到call指令停下,用F11跟到fun函数里面去,跳到了00401005处,该处指令是一个跳转指令jmp fun(00401020),00401020就是函数fun开始的地方:
    (3)进入fun函数的第一条指令就是push ebp,即保存基址指针EBP。按F10执行完这条指令之前,我们观察当前堆栈顶部4字节是0x004010B6,也就是我们刚才调用fun函数的call指令下一条指令add esp, 8的地址,即保存了返回地址EIP(返回函数地址的压栈的工作由系统自动进行,不显示相应的汇编代码)。现在继续往单步执行,mov ebp, esp 使当前EBP指针指向父函数EBP指针保存的位置(便于进行恢复),并用于函数执行过程中实参和局部变量的访问。
    (4)sub esp, 48h指令是为fun函数的局部变量预分配堆栈空间,后面的szBuf就位于这片预分配空间里。
    (5)继续单步执行完00401051的call strcpy (004010e0)指令后停下,这里说一句,strcpy函数的调用约定是__cdecl, 因此调用完成后,fun函数作为其父函数要通过add esp, 8来恢复堆栈
   
(6)因为通过sub esp,m开辟的内存不够(这所以这样,是因为我们在变量声明的时候只是声明了8字节--char szBuf[8])而strcpy(szBuf, szIn);中的szIn的数据量有是比较大的(25字节),所以会导致数据覆盖,将函数的返回值覆写
    (7)这时候我们看看EBP+4指向的堆栈地址,即函数返回地址EIP的值为0x66656463(
0x004010B6==>0x66656463),就是我们覆盖的数据

    函数返回之后将跳转到该地址0x66656463来执行指令,而该地址上的指令无效,因此会发Run-Time Check Failure #2 - Stack around the variable 'szBuf' was corrupted.的读错误。到这一步,我们既验证了前面的基础知识,有了解了堆栈溢出的过程,虽然简单,但是却具有代表性。