栈溢出原理及简单利用
实验目的
- 掌握栈溢出相关的基础知识
- 掌握栈溢出的原理及相关操作方法
实验环境
实验环境
- 操作机
Windows xp - OllyDbg
Visual Studio 6.0
OllyDbg
OLLYDBG是一个新的动态追踪工具,将IDA与SoftICE结合起来的思想,Ring 3级调试器,非常容易上手,己代替SoftICE成为当今最为流行的调试解密工具了。同时还支持插件扩展功能,是目前最强大的调试工具。
- 操作机
实验内容
PART 1 基本的栈溢出原理
步骤一:了解掌握基础知识
什么是栈?
栈(stack)又名堆栈,它是一种运算受限的线性表。其限制是仅允许在表的一端进行插入和删除运算。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。栈的示意图如下s

两个汇编指令
- push
将数据压入栈 - pop
将数据弹出栈
- push
栈在程序的运行中有着举足轻重的作用。最重要的是栈保存了一个函数调用时所需要的维护信息,这常常称之为堆栈帧或者活动记录。堆栈帧一般包含如下几方面的信息:
1.函数的返回地址和参数
2.临时变量:包括函数的非静态局部变量以及编译器自动生成的其他临时变量。
什么是栈溢出?
栈溢出就是缓冲区溢出的一种。 由于缓冲区溢出而使得有用的存储单元被改写,往往会引发不可预料的后果。程序在运行过程中,为了临时存取数据的需要,一般都要分配一些内存空间,通常称这些空间为缓冲区。如果向缓冲区中写入超过其本身长度的数据,以致于缓冲区无法容纳,就会造成缓冲区以外的存储单元被改写,这种现象就称为缓冲区溢出。栈溢出原理
栈溢出就是不顾栈中分配的局部数据块大小,向该数据块写入了过多的数据,导致数据越界,结果覆盖了老的栈数据。 或者解释为 在长字符串中嵌入一段代码,并将过程的返回地址覆盖为这段代码的地址,这样当过程返回时,程序就转而开始执行这段自编的代码了.汇编知识
mov
赋值语句call
在执行call命令时,程序会执行两个操作1.向堆栈中压入下一行程序的地址
2.JMP到call的子程序地址处
RET
与call命令想类似,在执行ret命令的同时,程序也会执行以下两个操作1.将当前的ESP中指向的地址出栈
2.JMP到这个地址
步骤二:编写测试程序以及逆向分析
新建一个文本文档,将其重命名为2.c,右键 -> 打开方式 -> Microsoft (R) Developer Studio,用Visual Studio 6.0打开并编辑,写入如下内容:
|
|
依次点击 Compile -> Build -> Execute三个键,来运行程序:
编译结果如下

找出刚刚编译出的程序文件,位于2.c同一目录的Debug目录下,2.exe。双击打开OllyDbg,在OllyDbg界面的左上角点击File -> Open,然后找到test.exe,选中后点击打开,界面会变成这样

这个地方我们叫做OEP,即是程序的入口。很简单的一个小程序,主要是看栈的变化
我们右键点击OllyDbg的反汇编窗口,点击转到->表达式 输入00401045,跳转到00401045 这个地址 对应的汇编指令为call 4.00401005,按下F2下断点。

接下来按下F9让OllyDbg运行程序,发现会断在这个断点。
进入之前。这里有个小细节需要注意,那就是call的下一行代码地址是0040104A,该地址对应的指令是xor eax,eax
接下来按两下F7来到如下图的位置

我们可以观察0012FF30储存的内容刚好就是call的下一行代码地址0040104A
再按四次F7,来到下图的位置。

00401070地址处对应的指令push ebp意思是保存ebp,00401071地址处对应的指令mov ebp,esp和00401073地址处对应的指令sub esp,0x48表示ebp=esp sub+0x48(抬升栈顶,形成栈帧),进入函数之后,观察发现ebp指向0012FF2C,而这个函数的返回地址则在ebp下,ebp指向0012FF2C 即是ebp+4的位置,储存着0040104A这个地址。

在程序中可以看到定义了两个参数,在这里它们进入了栈,观察发现遵循了后入在顶的原则。

接下来一直F8到如图的位置,发现了红框中连续三个pop指令与add指令,以及函数返回前的栈结构。
再按一下F8返回。

可以看到esp指向``0012FF34这个地址,刚好指向上个函数的返回地址0012FF30下一行。
####实验结果分析与总结
本节内容主要让大家了解栈溢出相关的基础知识。便于后面的实验
思考
1、函数是如何开辟栈空间的?
2、栈帧中的相关变量的内存是如何分部的?
####题目
1.数据入栈的顺序 ()
A 先进先出
B 先进后出
C 通过系统中的链表选择储存地址
D 由EIP决定
正确答案 B
解析:数据入栈的顺序,计算机定义的便是先进后出,后进先出
2.call 指令的意义()
A 调用函数
B JMP到某个地址
C 条件判断
D 普通的汇编指令。
正确答案 A
解析:call指令调用函数,其他答案例如jmp到某个地址,回答的并不全面,事实上还有压栈的操作。
3.三个pop指令和add指令对栈的影响?
正确答案:恢复之前的寄存器所保存的值,然后取消栈帧,恢复到之前的栈布局实现堆栈平衡
###PART2: 简单的栈溢出实验
本部分主要内容
明白栈溢出发生的时刻,以及对栈的改变
步骤一:栈溢出的时刻以及影响
很简单,我们通过上面一个小程序进行一些小改动。
新建一个文本文档,将其重命名为Two.c,右键 -> 打开方式 -> Microsoft (R) Developer Studio,用Visual Studio 6.0打开并编辑,写入如下内容:
|
|
依次点击 Compile -> Build -> Execute三个键,来运行程序:

果不其然的崩溃了,那么我们用OD来看看发生了什么
找出刚刚编译出的程序文件,位于Two .c同一目录的Debug目录下,Two.exe。双击打开OllyDbg,在OllyDbg界面的左上角点击File -> Open,然后找到Two.exe,选中后点击打开,界面会变成这样:

我们右键点击OllyDbg的反汇编窗口,点击转到->表达式 输入00401045,跳转到00401045 这个地址 对应的汇编指令为call 4.00401005,按下F2下断点。
接下来按下F9让OllyDbg运行程序,发现会断在这个断点。

连续按下两次F7 进入这个函数,并且运行到如图所示的位置。

由上面的例子已经知道0012FF30储存的是函数的返回地址。
接下来我们通过拖拽反汇编窗口,到0040109F,这个地址对应的汇编指令为call Two.strcpysgzeHeaderListle_pages
由C语言strcpy(buffer,stackbreak) 可以看出,进入strcpy函数之前要传入两个参数,在OllyDbg界面可以看到地址位于00401096的push offset Two.stackbreakdecommitable_指令,与位于 0040109B 的lea eax,[local.5]和0040109E的 push eax指令共同完成了传参的任务。
,以下是两个变量储存的位置的截图

这个是存储着我们定义的字符串的地址

这里是即将被覆盖的地址的起始位置以及EBP的指向。

由上文可知,程序在0040109F处执行strcpy,固在此处按下F2下断点,再按F9让程序运行,断在了0040109F这个地址

在执行strcpy前的栈内存分部情况,此时按下F8,观察栈的变化

在执行strcpy之后的栈内存分部情况,发现原本属于返回地址,即是0012FF30的地方已经被ff填充掉了,也就是说现在的返回地址是FFFFFFFF,这显然是不可达的。
于是我们继续F8运行直到RETN指令被执行,结果如下图

于是程序顺理成章的崩溃掉了。
####实验结果分析与总结
本节内容主要让大家编写,并且逆向调试真正会发生栈溢出的程序
####思考
1、栈溢出发生时,覆盖了哪些内存地址?
2、函数返回时,EIP的值是可以控制的吗?
####题目
1.栈溢出发生的条件()
A 栈空间开辟不足
B 数据填充不足
C 栈发生崩溃
D 向缓冲区中写入超过其本身长度的数据
正确答案: D
解析:由于写入的数据超出了申请的空间,导致了后续的数据被覆盖,从而发生栈溢出。
2.本次实验中我们覆盖的返回地址是()
A 0012FF80
B 0040104A
C 0065DD00
D 0012B880
正确答案:B
解析:根据函数调用规则可知,A选项为EBP,B选项为函数返回地址,C和D选项为函数的参数