CSAPP - Bomb Lab

从可执行文件出发,利用gdbobjdump等工具检查出正确的炸弹拆解密码串。

objdump命令是用查看目标文件(app.o)或者可执行的目标文件(app)的构成的gcc工具。

首先使用 $objdump -d bomb >> bomb.a 命令disassemble可执行文件,重定向输出到bomb.a文件。接着就可以用gdb来调试bomb,同时关注bomb.a的汇编代码。

找到程序入口main。对比汇编代码,进行gdb调试。在bomb.c文件中可以看大体的程序结构。main函数中,都是先读取一行输入,输入的字符串地址保存$rdi中,可以在gdb中使用 (gdb) print (char *)($rdi) 查看。之后调用phase_x函数,phase_x中会对输入进行判断,如果不满足条件,则触发炸弹。

phase_1

根据汇编代码,可以看到,strings_not_equal还需要一个参数:esi,可以用gdb查看内存0x402400所在位置的字符串是什么。

0000000000400ee0 <phase_1>: 400ee0: 48 83 ec 08 sub $0x8,%rsp 400ee4: be 00 24 40 00 mov $0x402400,%esi 400ee9: e8 4a 04 00 00 callq 401338 <strings_not_equal> 400eee: 85 c0 test %eax,%eax 400ef0: 74 05 je 400ef7 <phase_1+0x17> 400ef2: e8 43 05 00 00 callq 40143a <explode_bomb> 400ef7: 48 83 c4 08 add $0x8,%rsp 400efb: c3 retq

gdb的一般使用就是先设置断点,再启动程序,进行调试。

sugar@ubuntuServer:~/csappLab/bomb$ gdb bomb GNU gdb (Ubuntu 8.1.1-0ubuntu1) 8.1.1 ...... (gdb) break phase_1 Breakpoint 1 at 0x400ee0 (gdb) run ans.txt Starting program: /home/sugar/csappLab/bomb/bomb ans.txt Welcome to my fiendish little bomb. You have 6 phases with which to blow yourself up. Have a nice day! Breakpoint 1, 0x0000000000400ee0 in phase_1 () (gdb) print (char *)0x402400 $1 = 0x402400 "Border relations with Canada have never been better."

所以可以把字符串"Border...."写入ans.txt文件,作为运行bomb的输入,检测是否是可以拆解炸弹的短语。

phase_2

这个短语的猜测需要了解 sscanf(src, pattern, args...) 函数调用时候的参数设置,在CSAPP第三章3.7.4节有关栈上的局部存储有详细的解释,也同样需要gdb调试,可以来查看pattern的形式,以及调用了sscanf后栈上的参数存储顺序。

(gdb) run ans.txt Starting program: /home/sugar/csappLab/bomb/bomb ans.txt Welcome to my fiendish little bomb. You have 6 phases with which to blow yourself up. Have a nice day! Phase 1 defused. How about the next one? 1 2 3 4 5 6 7 Breakpoint 1, 0x0000000000400efc in phase_2 () (gdb) print $rdi $1 = 6305744 (gdb) print (int *)($rdi) $2 = (int *) 0x6037d0 (gdb) print (char *)($rdi) $3 = 0x6037d0 "1 2 3 4 5 6 7" (gdb) break read_six_numbers Breakpoint 2 at 0x40145c (gdb) continue Continuing. Breakpoint 2, 0x000000000040145c in read_six_numbers () (gdb) print $rsp $4 = (void *) 0x7fffffffe318 (gdb) break *0x400f0a Breakpoint 3 at 0x400f0a (gdb) continue Continuing. Breakpoint 3, 0x0000000000400f0a in phase_2 () (gdb) print $eax $5 = 6 (gdb) print (int *)($rsp) $6 = (int *) 0x7fffffffe320 (gdb) print *(int *)($rsp) $7 = 1 (gdb) print *(int *)($rsp+4) $8 = 2 (gdb) print *(int *)($rsp+5) $9 = 50331648 (gdb) print *(int *)($rsp+8) $10 = 3 (gdb) print *(int *)($rsp+12) $11 = 4 (gdb) print *(int *)($rsp+16) $12 = 5 (gdb) print *(int *)($rsp+20) $13 = 6 (gdb) print *(int *)($rsp+24) $14 = 4199473 (gdb) print *(int *)(0x4025c3) $15 = 622879781 (gdb) print *(char *)(0x4025c3) $16 = 37 '%' (gdb) print (char *)(0x4025c3) $17 = 0x4025c3 "%d %d %d %d %d %d"

最后通过汇编代码确定输入整数的大小关系。即需要输入六个整数,第一个数为1,之后依次扩大两倍。

phase_3

一个类似switch结构的汇编代码,调试方法和前两个类似。

0000000000400f43 <phase_3>: 400f43: 48 83 ec 18 sub $0x18,%rsp 400f47: 48 8d 4c 24 0c lea 0xc(%rsp),%rcx 400f4c: 48 8d 54 24 08 lea 0x8(%rsp),%rdx 400f51: be cf 25 40 00 mov $0x4025cf,%esi 400f56: b8 00 00 00 00 mov $0x0,%eax 400f5b: e8 90 fc ff ff callq 400bf0 <__isoc99_sscanf@plt> 400f60: 83 f8 01 cmp $0x1,%eax # 至少输入两个数字 400f63: 7f 05 jg 400f6a <phase_3+0x27> 400f65: e8 d0 04 00 00 callq 40143a <explode_bomb> 400f6a: 83 7c 24 08 07 cmpl $0x7,0x8(%rsp) # 第一个数字和7比 400f6f: 77 3c ja 400fad <phase_3+0x6a> # 大于就爆炸 400f71: 8b 44 24 08 mov 0x8(%rsp),%eax 400f75: ff 24 c5 70 24 40 00 jmpq *0x402470(,%rax,8) # 偏移量在地址0x402470的位置 400f7c: b8 cf 00 00 00 mov $0xcf,%eax # eax = 207 400f81: eb 3b jmp 400fbe <phase_3+0x7b> 400f83: b8 c3 02 00 00 mov $0x2c3,%eax # eax = 707 400f88: eb 34 jmp 400fbe <phase_3+0x7b> 400f8a: b8 00 01 00 00 mov $0x100,%eax # eax = 256 400f8f: eb 2d jmp 400fbe <phase_3+0x7b> 400f91: b8 85 01 00 00 mov $0x185,%eax 400f96: eb 26 jmp 400fbe <phase_3+0x7b> 400f98: b8 ce 00 00 00 mov $0xce,%eax 400f9d: eb 1f jmp 400fbe <phase_3+0x7b> 400f9f: b8 aa 02 00 00 mov $0x2aa,%eax 400fa4: eb 18 jmp 400fbe <phase_3+0x7b> 400fa6: b8 47 01 00 00 mov $0x147,%eax 400fab: eb 11 jmp 400fbe <phase_3+0x7b> 400fad: e8 88 04 00 00 callq 40143a <explode_bomb> 400fb2: b8 00 00 00 00 mov $0x0,%eax 400fb7: eb 05 jmp 400fbe <phase_3+0x7b> 400fb9: b8 37 01 00 00 mov $0x137,%eax 400fbe: 3b 44 24 0c cmp 0xc(%rsp),%eax # 第二个数字和eax比较,不相等就爆炸 400fc2: 74 05 je 400fc9 <phase_3+0x86> 400fc4: e8 71 04 00 00 callq 40143a <explode_bomb> 400fc9: 48 83 c4 18 add $0x18,%rsp 400fcd: c3 retq

最终可以得到多个可能的解如:

0 ->207
1 ->311
2 ->707
3 ->256
...
有七组数据可以对应

phase_4

这个可以写出汇编对应的c代码,来确定输入的串(两个整数)的要求,我用了瞪眼法来确定了一个解:7 0.

主要是func4太糙蛋,前面几条汇编都是来迷惑人的。。。

phase_5

比较有意思,通过阅读汇编和使用gdb确定有关的字符串,以及一些简单的运算,得到答案:要求输入一个长度为6的字符串,对每个字符取低4位bit,作为偏移量的值。在串"maduiersnfotvbylSo you think you can stop the bomb with ctrl-c, do you?"中找到能组成串"flyers"的字符下标,总共有6个下标(偏移量),保证所以输入的字符的低四位是需要的偏移量。解决方法可见下面我的gdb调试过程。

(gdb) break phase_5 Breakpoint 1 at 0x401062 (gdb) run ans.txt Starting program: /home/sugar/csappLab/bomb/bomb ans.txt Welcome to my fiendish little bomb. You have 6 phases with which to blow yourself up. Have a nice day! Phase 1 defused. How about the next one? That's number 2. Keep going! Halfway there! So you got that one. Try this one. abcdef Breakpoint 1, 0x0000000000401062 in phase_5 () (gdb) print (char *)0x4024b0 $1 = 0x4024b0 "maduiersnfotvbylSo you think you can stop the bomb with ctrl-c, do you?" (gdb) print *(char *)(0x4024b0 + 3) $2 = 117 'u' (gdb) print (char *)0x40245e $3 = 0x40245e "flyers" (gdb) print *(char *)(0x4024b0 + 10) $4 = 111 'o' (gdb) print *(char *)(0x4024b0 + 9) $5 = 102 'f' (gdb) print *(char *)(0x4024b0 + 15) $6 = 108 'l' (gdb) print *(char *)(0x4024b0 + 14) $7 = 121 'y' (gdb) print *(char *)(0x4024b0 + 5) $8 = 101 'e' (gdb) print *(char *)(0x4024b0 + 6) $9 = 114 'r' (gdb) print *(char *)(0x4024b0 + 7) $10 = 115 's' (gdb) print /x 'c' $11 = 0x63 (gdb) print /x 'a' $12 = 0x61 (gdb) print /x 'a'-1 $13 = 0x60 (gdb) print /x 'a'+6 $14 = 0x67 (gdb) print /c 'a'+6 $15 = 103 'g' (gdb) print /c 0x69 $16 = 105 'i' (gdb) print /c 0x6f $17 = 111 'o' (gdb) print /c 0x6e $18 = 110 'n' (gdb) print /c 0x65 $19 = 101 'e' (gdb) print /c 0x66 $20 = 102 'f' (gdb) print /c 0x67 $21 = 103 'g' (gdb)

最终确定输入字符串是“ionefg”,也同样有多个解。

phase_6

这题很难,我自己顺序阅读汇编,只能弄懂输入的要求是6个整数,且都要小于7,并且保存在栈上的值是x = 7 - x,之后读不懂了。。参考了这篇博文,转变思路,逆向推导,在最后一个bomb_exploded的地方(0x4011e7)设置断点,同时程序输入1 2 3 4 5 6作为测试输入,看看最终的比较是在比较什么。发现phase_6最后的代码一直在进行一个6次的循环,每次要从rbx中取一些内容,同时变换rbx。0x4011e7的jge表示了这段内容的值要满足降序排列,否则炸弹爆炸。

应用gdb的x/3x,查看一段内存,发现这边有一个结构体node,根据node的值的特点,可以判断结构体的成员,如果按C定义的话是这样的:

struct node{ int val; int index; struct node *next; }
(gdb) x/3x $rbx 0x603320 <node6>: 0x000001bb 0x00000006 0x00603310 (gdb) x/2x $rbx 0x603320 <node6>: 0x000001bb 0x00000006 (gdb) x/2x *($rbx+8) 0x603310 <node5>: 0x000001dd 0x00000005 (gdb) x/4x *($rbx+8) 0x603310 <node5>: 0x000001dd 0x00000005 0x00603300 0x00000000 (gdb) x/3x *($rbx+8) 0x603310 <node5>: 0x000001dd 0x00000005 0x00603300 (gdb) x/3x *(*($rbx+8)+8) 0x603300 <node4>: 0x000002b3 0x00000004 0x006032f0 (gdb) x/3x *(*(*($rbx+8)+8)+8) 0x6032f0 <node3>: 0x0000039c 0x00000003 0x006032e0 (gdb) x/3x *(*(*(*($rbx+8)+8)+8)+8) 0x6032e0 <node2>: 0x000000a8 0x00000002 0x006032d0 (gdb) x/3x *(*(*(*(*($rbx+8)+8)+8)+8)+8) 0x6032d0 <node1>: 0x0000014c 0x00000001 0x00000000 (gdb) x/3x *(*(*(*(*(*($rbx+8)+8)+8)+8)+8)+8) 0x0: Cannot access memory at address 0x0

再看看每个节点的值(也就是node的第一个成员)分别是(从node6 -> node1):

(gdb) print /d 0x1bb $63 = 443 (gdb) print /d 0x1dd $62 = 477 (gdb) print /d 0x2b3 $64 = 691 (gdb) print /d 0x39c $65 = 924 (gdb) print /d 0xa8 $66 = 168 (gdb) print /d 0x14c $67 = 332 (gdb) print *0x6032d0 $68 = 332

所以并不满足降序的排列,会导致炸弹的爆炸。于是我更换下输入的六个数字,看看是如何对应节点变化的。

当不同的输入六个数字后,node节点出现的位置不同。

(gdb) run ans.txt Starting program: /home/sugar/csappLab/bomb/bomb ans.txt Welcome to my fiendish little bomb. You have 6 phases with which to blow yourself up. Have a nice day! Phase 1 defused. How about the next one? That's number 2. Keep going! Halfway there! So you got that one. Try this one. Good work! On to the next... 3 2 4 5 6 1 Breakpoint 4, 0x00000000004011e7 in phase_6 () (gdb) x/3x $rbx 0x603300 <node4>: 0x000002b3 0x00000004 0x00603310 (gdb) x/3x *($rbx+8) 0x603310 <node5>: 0x000001dd 0x00000005 0x006032f0 (gdb) x/3x *(*($rbx+8)+8) 0x6032f0 <node3>: 0x0000039c 0x00000003 0x006032e0 (gdb) x/3x *(*(*($rbx+8)+8)+8) 0x6032e0 <node2>: 0x000000a8 0x00000002 0x006032d0 (gdb) x/3x *(*(*(*($rbx+8)+8)+8)+8) 0x6032d0 <node1>: 0x0000014c 0x00000001 0x00603320 (gdb) x/3x *(*(*(*(*($rbx+8)+8)+8)+8)+8) 0x603320 <node6>: 0x000001bb 0x00000006 0x00000000 (gdb)

但要保证前一个节点的value大于第二个节点
node6: 442
node5: 477
node4: 691
node3: 924
node2: 168
node1: 332
正确顺序是 3 4 5 6 1 2
但汇编代码中有 x = 7 - x的输入变化
所以答案是 4 3 2 1 6 5

最终拆解了炸弹:

result