栈溢出相关

栈溢出原理及简单利用

实验目的

  • 掌握栈溢出相关的基础知识
  • 掌握栈溢出的原理及相关操作方法

实验环境

  • 实验环境

    • 操作机Windows xp
    • OllyDbg
    • Visual Studio 6.0

      OllyDbg

    OLLYDBG是一个新的动态追踪工具,将IDA与SoftICE结合起来的思想,Ring 3级调试器,非常容易上手,己代替SoftICE成为当今最为流行的调试解密工具了。同时还支持插件扩展功能,是目前最强大的调试工具。

实验内容

PART 1 基本的栈溢出原理

步骤一:了解掌握基础知识

  • 什么是栈?

    栈(stack)又名堆栈,它是一种运算受限的线性表。其限制是仅允许在表的一端进行插入和删除运算。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。

  • 栈的示意图如下s

1

  • 两个汇编指令

    • push 将数据压入栈
    • pop 将数据弹出栈
  • 栈在程序的运行中有着举足轻重的作用。最重要的是栈保存了一个函数调用时所需要的维护信息,这常常称之为堆栈帧或者活动记录。堆栈帧一般包含如下几方面的信息:

    • 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打开并编辑,写入如下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
int test();
int main()
{
printf("test\n");
test();
return 0;
}
int test()
{
int a=3;
int b=4;
printf("%d\n",a+b);
return 0 ;
}

依次点击 Compile -> Build -> Execute三个键,来运行程序:

编译结果如下

2

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

2

这个地方我们叫做OEP,即是程序的入口。很简单的一个小程序,主要是看栈的变化

我们右键点击OllyDbg的反汇编窗口,点击转到->表达式 输入00401045,跳转到00401045 这个地址 对应的汇编指令为call 4.00401005,按下F2下断点。

2

接下来按下F9让OllyDbg运行程序,发现会断在这个断点。

进入之前。这里有个小细节需要注意,那就是call的下一行代码地址是0040104A,该地址对应的指令是xor eax,eax

接下来按两下F7来到如下图的位置

2

我们可以观察0012FF30储存的内容刚好就是call的下一行代码地址0040104A

再按四次F7,来到下图的位置。

3

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

4

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

4

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

再按一下F8返回。

4

可以看到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打开并编辑,写入如下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <stdio.h>
#include <string.h>
int test();
char stackbreak[1024]=
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff";
int main()
{
printf("test\n");
test();
return 0;
}
int test()
{
int a=3;
int b=4;
char buffer[10];
strcpy(buffer,stackbreak); //在这里崩溃
return 0 ;
}

依次点击 Compile -> Build -> Execute三个键,来运行程序:

5

果不其然的崩溃了,那么我们用OD来看看发生了什么

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

6

我们右键点击OllyDbg的反汇编窗口,点击转到->表达式 输入00401045,跳转到00401045 这个地址 对应的汇编指令为call 4.00401005,按下F2下断点。

接下来按下F9让OllyDbg运行程序,发现会断在这个断点。

6

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

6

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

6

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

6

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

6

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

6

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

7

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

8

于是程序顺理成章的崩溃掉了。

####实验结果分析与总结

本节内容主要让大家编写,并且逆向调试真正会发生栈溢出的程序

####思考

1、栈溢出发生时,覆盖了哪些内存地址?

2、函数返回时,EIP的值是可以控制的吗?

####题目

1.栈溢出发生的条件()
A 栈空间开辟不足
B 数据填充不足
C 栈发生崩溃
D 向缓冲区中写入超过其本身长度的数据

正确答案: D

解析:由于写入的数据超出了申请的空间,导致了后续的数据被覆盖,从而发生栈溢出。

2.本次实验中我们覆盖的返回地址是()
A 0012FF80
B 0040104A
C 0065DD00
D 0012B880

正确答案:B

解析:根据函数调用规则可知,A选项为EBP,B选项为函数返回地址,C和D选项为函数的参数

文章目录
  1. 1. 栈溢出原理及简单利用
    1. 1.1. 实验目的
    2. 1.2. 实验环境
    3. 1.3. 实验内容
      1. 1.3.1. PART 1 基本的栈溢出原理
        1. 1.3.1.1. 步骤一:了解掌握基础知识
        2. 1.3.1.2. 步骤二:编写测试程序以及逆向分析
        3. 1.3.1.3. 思考
        4. 1.3.1.4. 步骤一:栈溢出的时刻以及影响