栈溢出小例子
样例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+ecx4-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,然后到某个位置停下,在这里就不继续调试。