栈溢出原理及简单利用
实验目的
- 掌握栈溢出相关的基础知识
- 掌握栈溢出的原理及相关操作方法
实验环境
实验环境
- 操作机
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选项为函数的参数