栈溢出小例子

样例1

其c代码如下,在vc6++中编译

#include<stdio.h>
void Fun()
{
    int i;
    int arr[5] = {0,1,2,3,4};//数组的长度为5

    for( i=0 ; i<=5 ;i++)//循环次数为6,按道理应该只会输出6个Hello Word
    {
        arr[i]=0;
        printf("Hello Word\n");
    }
}


int main()
{
    Fun();
    return 0;    
}

运行结果会是一直输出Hello Word,实际上就是发生了栈溢出。

通过调试来分析怎么回事

先找到主函数,如下。

进入fun函数

注意这两句,ecx中的值实际上也是i,只是ebp-4的位置的值给了ecx寄存器。实际上就是,通过ecx的改变来将这个0给a[i]。

mov ecx,dword ptr ss:[ebp-4]
mov dword ptr ss:[ebp+ecx*4-18],0

开始调试,一步步f8,进行第1步循环。

进行第2步循环

第3,4,5循环都和前面一样,现在主要看第6次循环

注意灰色的这一句
mov dword ptr ss:[ebp+ecx4-18],0
现在ecx已经是5了,所以ss:[ebp+ecx
4-18]=ss:[ebp-4],然后它把0给了这个位置,意味着i将变为0。简单来说就是在第6次循环中,a[5]的地址和i的地址重复了,本来是改a[5],却把i的值改为了0(但根本还是因为原来定义a[5]={0,1,2,3,4}只定义了5个长度)。

执行完之后

这样就会造成一个无线循环的结果,因为没次ecx=5(也就是i=5时),都会因为a[5]的地址和i的地址重复了这个原因,导致i又变为0,永远i都不能满足i>5这个条件而跳出循环,所以会一直输出hello word。

如果感觉有兴趣的话还可以,尝试改一改原代码的一些内容

#include<stdio.h>
void Fun()
{
    int i;
    int arr[5] = {0,1,2,3,4};

    for( i=0 ; i<=5 ;i++)
    {
        arr[i]=0;//将这个0分别改为0,1,2,3,4,5
        printf("Hello Word\n");
    }
}


int main()
{
    Fun();
    return 0;    
}

会发现当a[i]=分别是0,1,2,3,4时都会发生栈溢出,无限输出hello world,只是调试时栈窗口中i地址(也就是ebp-4)中的值的变化范围会改变(但是第1轮都一样都是0~5),当然先前定义的数组的值也会变。改变如下

如果是0,将会是0-1-2-3-4-5-0-1-2-3-4-5-0-1-2-3-4-5-0-1-2....
如果是1,将会是0-1-2-3-4-5-1-2-3-4-5-1-2-3-4-5-1-2-3-4-5-1....
如果是2,将会是0-1-2-3-4-5-2-3-4-5-2-3-4-5-2-3-4-5....
如果是3,将会是0-1-2-3-4-5-3-4-5-3-4-5-3-4-5-3-4-5....
如果是4,将会是0-1-2-3-4-5-4-5-4-5-4-5-4-5-4-5-4-5....

而当让a[i]=5时,则将会是有限次循环(循环6次),放一张图片,就只看第6次循环

实际上还是发生了栈溢出的。

再改改for循环的条件

#include<stdio.h>
void Fun()
{
    int i;
    int arr[5] = {0,1,2,3,4};

    for( i=0 ; i<=7 ;i++)//将i<=5改为i<=7
    {
        arr[i]=5;//注意这里是5,避免一直循环,如果改为6或者7的话实际上会影响循环次数,具体为什么就自己亲自去调试看看吧。
        printf("Hello Word\n");
    }
}


int main()
{
    Fun();
    return 0;    
}

看看执行后的结果

欸,怎么少了一行字

Press any key to continue

调试看看。

实际上前5次都算正常,第6次也还行,第7次和第8次存在严重的问题。

第7次循环内部

执行完这一条指令后,ebp改变

第8次循环内部

执行完这一条指令后,返回原函数的地址改变

ebp变了,返回原函数的地址改变,所以这个子函数的那个ret指令,根本就没用了,所以不能回到原函数。

样例2

c代码如下,在vc6++中编译

#include<stdio.h>

//基于缓冲区溢出的HelloWord
void HelloWord()
{
    printf("Hello World");
}

void Fun()
{
    int arr[5] = {1,2,3,4,5};
    arr[6] = (int) HelloWord;//注意这里是a[6],在后面调试过程中会发现这个地址存储着主函数的返回地址。这也是为什么不是a[5]的原因,a[5]所在位置实际上存的是原ebp的值。
}

int main()
{
    Fun();
    return 0;
}

这一句

arr[6] = (int) HelloWord

如果arr[6]改为arr[5]编译会过但是执行时会出错。

如果是arr[6]的话最终执行结果如下

为什么会有hello world,但是却没有

Press any key to continue

通过调试来看看

找到mian函数

进入fun函数

执行这一句之后

mov dword ptr ss:[ebp+4],缓冲区溢出.40100F//也就是arr[6] = (int) HelloWord;


进入Hello world函数

输出Hello world后

看看ret指令执行会到哪里

会发现到了开始的EP处,而实际上如果没有发生缓冲区溢出,在fun函数内部的ret指令执行后,就会到达这个位置。

然后实际上还可以继续调试,会发现又会输出一个hello world,然后到某个位置停下,在这里就不继续调试。