参考教材:计算机系统基础 第二版 袁春风 机械工业出版社
参考慕课:计算机系统基础(四):编程与调试实践 https://www.icourse163.org/learn/NJU-1449521162
计算机系统实验导航
实验一:环境安装 https://blog.csdn.net/weixin_46291251/article/details/122477054
实验二:数据的存储与运算 https://blog.csdn.net/weixin_46291251/article/details/122478255
实验三:程序的机器级表示 https://blog.csdn.net/weixin_46291251/article/details/122478979
实验四:二进制程序逆向工程 https://blog.csdn.net/weixin_46291251/article/details/122479554
实验五:缓冲区溢出攻击 https://blog.csdn.net/weixin_46291251/article/details/122479798
实验六:程序的链接 https://blog.csdn.net/weixin_46291251/article/details/122480049
实验源码: xxx
内容:
实验内容:
1 缓冲区溢出攻击实验的内容、原理、方法和基本步骤;
2 过程调用的机器级表示、栈帧组成结构、缓冲区溢出等知识的回顾与应用。
实验目标:
1 加深对函数调用规则、栈结构、缓冲区溢出攻击原理、方法与防范等方面知识的理解和掌
握;
2 从程序员角度认识计算机系统,将程序设计、汇编语言、系统结构、操作系统、编译链接
中的重要概念贯穿起来,对指令在硬件上的执行过程和指令的底层硬件执行机制有深入的理
解;能够以需求分析为基础,对计算机系统模块或单元进行操作。
3 掌握各种开源的编译调试工具。
实验任务:
1 学习 MOOC 内容
https://www.icourse163.org/learn/NJU-1449521162
第五周 缓冲区溢出攻击
第 1 讲 缓冲区溢出攻击实验:概述
第 2 讲 缓冲区溢出攻击实验:目标程序与辅助工具
第 3 讲 缓冲区溢出攻击实验:Level 0
第 4 讲 缓冲区溢出攻击实验:Level 1 及课后实验
2 完成作业
本实验的目的在于加深对IA-32过程调用规则和栈结构的具体理解。实验的主要内容是
对一个可执行程序“bufbomb” 实施- -系列缓冲区溢出攻击(buffer overflow attacks),也
就是设法通过造成缓冲区溢出来改变该程序的运行内存映像(例如将专门设计的字节序列插
入到栈中特定内存位置)和行为,以实现实验预定的目标。
实验中你需要针对目标可执行程序bufbomb,分别完成多个难度递增的缓冲区溢出攻
击(完成的顺序没有固定要求)。按从易到难的顺序,这些难度级分别命名为smoke (level
0)、fizz (level 1)、bang (level 2)、rumble (level 3)、boom (level 4)和kaboom (level 5)。
目标程序bufbomb说明
bufbomb程序接受下列命令行参数:
-u userid :以给定的用户ID“userid” (本实验 中应设为本慕课号“0809NJU064” )
运行程序。在每次运行程序时均应指定该参数,因为bufbomb程序将基于该ID决
定你应该使用的cookie值( 与makecookie 程序的输出相同),而bufbomb程序
运行时的一些关键栈地址取决于该cookie值。
-h:打印可用命令行参数列表
-n:以“Nitro” 模式运行,用于kaboom实验阶段
bufbomb 目标程序在运行时使用如下 getbuf 过程从标准输入读入一个字符串
1./* Buffer size for getbuf */
2.int getbuf()
3.{
4. other variables ...;
5. char buf[NORMAL_BUFFER_SIZE];
6. Gets(buf);
7. return 1;
8.}
其中,过程 Gets 类似于标准库过程 gets,它从标准输入读入一个字符串(以换行‘\n’或 文件结束 end-of-file 字符结尾),并将字符串(以 null 空字符结尾)存入指定的目标内存位 置。在 getbuf 过程代码中,目标内存位置是具有 NORMAL_BUFFER_SIZE 个字节存储空间的 数组 buf,而 NORMAL_BUFFER_SIZE 是大于等于 32 的一个常数。 注意,过程 Gets()并不判断 buf 数组是否足够大而只是简单地向目标地址复制全部输入 字符串,因此有可能超出预先分配的存储空间边界,即缓冲区溢出。如果用户输入给 getbuf() 的字符串不超过(NORMAL_BUFFER_SIZE-1)个字符长度的话,很明显 getbuf()将正常返回 1, 但是,如果输入一个更长的字符串,则可能会发生错误。
正如上面的错误信息所指,缓冲区溢出通常导致程序状态被破坏,产生存储器访问错误。(思 考:为什么会产生一个段错误?Linux x86 的栈结构组成是什么样的?) 本实验的任务就是精心设计输入给 bufbomb 的字符串,通过造成缓冲区溢出达成预定 的实验目标,这样的字符串称为“exploit string”(攻击字符串),实验的关键是确定栈中 哪些数据条目做为攻击的目标。
第一关 smoke
实验结果记录
说明:
getbuf函数结束后,即执行最后的ret指令时,将取出保存于test函数栈帧中的返回地址并跳转至它继续执行。
假设:
将返回地址的值改为本级别实验的目标smoke函数的首条指令的地址,则getbuf函数返回时,就会跳转到smoke函数执行,即达到了实验的目标。
用less打开反汇编代码,查看getbuf的代码:
可以看出,buf缓冲区开始于栈帧中地址EBP-0x67处, 0x67 = 103
返回地址的保存地址与缓冲区的起始地址之间相差:
0x67 +4= 107个字节
可知:
如果向缓冲区中写107个字节后,再写入的4个字节,将改写返回地址的值。
Level 0求解思路:
将攻击字符串中自第107个字节开始的4个字节设置为实验的目标跳转地址,即smoke函数首条指令的地址。
搜索bufbomb的反汇编代码,可以发现smoke函数的首条指令的地址为0x8048818.
因此构造如下的字符串:
其中,前103字节用于填充缓冲区,与实验目标无关,可以随意设置。
接下来的四个字节改写了栈中保存的ebp的旧值,也与实验无关.
最后四个字节用于保存栈帧中保存的返回地址,因此设置为smoke函数的首条指令的地址,按照IA-32平台小端顺序方式,四个字节上图。
这样当攻击字符串被Gets函数写入缓冲区后,栈帧中保存的返回地址将被修改为指向smoke函数。这样,当getbuf函数结束后,将跳转至函数smoke执行,从而实现了实验目标。
使用gdb观察攻击过程:
首先查看一下目标程序的汇编代码来确定断点设定的位置。
用objdump反汇编并用less工具打开代码:
找到getbuf函数中call函数位于的位置:
在调用Gets函数的call指令前后各设置一个断点,以查看getbuf函数和test函数的栈帧是否发生变化。
由上图可知,bufbomb正常执行时,test函数调用getbuf函数时,正常的返回地址应是0x8049584。
根据上述思路,建立字符串后运行测试:
可见,成功进行了缓冲区溢出攻击,将原本要执行的代码替换为了 smoke函数。
结果分析与讨论
进入gdb调试
首先将字符串文件转换后的结果保存起来方便后面调试使用。
然后根据上述得到的地址,在getbuf函数中,调用Gets函数读入攻击字符串之前的一条指令和之后的一条指令分别设置一个断点。
然后开始执行程序
程序成功进入第一个断点,即执行call指令之前。
此时输出ebp寄存器的值,得到函数的返回地址(ebp+4)。
可见此时程序的返回值是正常情况下执行test函数中用call getbuf函数的指令之后的下条指令的地址。
然后继续执行程序,程序来到第二个断点,即执行getbuf之后的下一条指令,再次查看返回地址可以发现其已经被修改,修改后的值即为smoke函数的第一条指令的地址。
然后继续执行程序
程序按照修改后的指令进行跳转,成功进入smoke函数,缓冲区溢出攻击成功。
第二关 fizz
实验结果记录
和前一个关卡作比较,不难看出:不同于Level 0 , fizz函数需要一个输入参数val ,并且其值应等于makecookie程序基于userid返回的cookie值。
打开反汇编文件,分析fizz函数,可以发现:
图中的cmp函数实现了eax和edx的比较,即0x804d1a0 和ebp + 8的比较。分析知,地址0x804d1a0处存储的是全局变量cookie的值。为了使两个值相等 即ebp + 8= 0x804d1a0,可得:ebp = 0x804d198.
注意到getbuf函数最后有一条leave指令,这条指令将从栈中弹出函数开始阶段保存的EBP旧值并存储到EBP寄存器中。另一方面栈帧中缓冲区起始地址以上的地址上的值都可以通过缓冲区的溢出来修改。
攻击字符串构造思路:
1.在攻击字符串中对应EBP旧值的保存位置处,放上前面分析得出的EBP修改的目标值,以使getbuf函数结束前的leave指令将其设置到EBP寄存器中。
2.在攻击字符串中对应返回地址的位置处,放.上fizz函数中合适指令的地址,以使getbuf函数结束后跳转到该地址处执行。
注意:
1.目标地址不应像Level 0那样设为fizz函数的首指令地址,因为fizz”函数的第二二条指令"mov %esp,%ebp"将覆盖掉在之前getbuf结束时通过leave指令设置的EBP寄存器中的目标值。
2.本实验并不要求真地调用fizz函数所以可以直接跳转至fizz函数中在cmp比较指令前读取EBP寄存器中值的相应指令。
在汇编代码中找到mov指令的地址为:0x08049408
然后利用上述信息,结合第一关中获得的缓冲区大小即可构造字符串如下:
其中,前103字节用于填充缓冲区,与实验目标无关,可以随意设置。
接下来的四个字节改写了栈中保存的ebp的旧值,设置为满足比较条件的值。
最后四个字节用于保存栈帧中保存的返回地址,这里设置为fizz函数的mov指令的地址,按照IA-32平台小端顺序方式,四个字节上图。
这样当攻击字符串被Gets函数写入缓冲区后,栈帧中保存的返回地址将被修改为指向fizz函数。这样,当getbuf函数结束后,将跳转至函数fizz执行,从而实现了实验目标。
利用上述字符串作为输入参数,运行程序:
程序按照修改后的指令进行跳转,成功进入fizz函数,缓冲区溢出攻击成功。
结果分析与讨论
进入gdb调试
首先将字符串文件转换后的结果保存起来方便后面调试使用。
然后根据上述得到的地址,在getbuf函数中,调用Gets函数读入攻击字符串之前的一条指令和之后的一条指令分别设置一个断点。
然后开始执行程序
程序成功进入第一个断点,即执行call指令之前。
此时输出ebp寄存器的值,得到函数的返回地址(ebp+4)。
Ebp当前值该地址处存放getbuf函数的调用函数test里的ebp寄存器的旧值。
查看该旧值:
再查看ebp+4地址上的值,即执行完call指令后会返回到的地方。
可见此时程序的返回值是正常情况下执行test函数中用call getbuf函数的指令之后的下条指令的地址。
然后继续执行程序,程序来到第二个断点,即执行getbuf之后的下一条指令,再次查看返回地址可以发现其已经被修改,修改后的值即为smoke函数的第一条指令的地址,并且存储了ebp旧值的存储单元的值已被改写
然后继续执行程序
程序按照修改后的指令进行跳转,成功进入fizz函数,缓冲区溢出攻击成功。