CSAPP 之 Bomb Lab 解题记录,真的是太有趣辣
寄存器速查
寄存器名称 |
备注 |
%rax |
返回值 |
%rdi, %rsi, %rdx, %rcx, %r8, %r9 |
参数,按照顺序排列 |
%rsp |
栈指针 |
%rbx, %rbp, %r12-%r15 |
callee saved |
%10, %r11 |
caller saved |
Bomblab
GDB 的使用
gdb 常用指令
指令 |
全称 |
描述 |
r |
run |
开始执行程序,直到下一个断点或程序结束 |
q |
quit |
退出 GDB 调试器 |
ni |
nexti |
执行下一条指令,但不进入函数内部 |
si |
stepi |
执行当前指令,如果是函数调用则进入函数 |
b |
break |
在指定位置设置断点 |
c |
cont |
从当前位置继续执行程序,直到下一个断点或程序结束 |
p |
print |
打印变量的值 |
x |
|
打印内存中的值 |
j |
jump |
跳转到程序指定位置 |
disas |
|
反汇编当前函数或指定的代码区域 |
layout asm |
|
显示汇编代码视图 |
layout regs |
|
显示当前的寄存器状态和它们的值 |
备注:退出视图模式使用 Ctrl+X,然后按 A
关于 p 和 x,更详细的解释
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| p $rax p $rsp p/x $rsp p/d $rsp
x/2x $rsp x/2d $rsp x/2c $rsp x/s $rsp
x/b $rsp x/h $rsp x/w $rsp x/g $rsp
info registers info breakpoints
delete breakpoints 1
|
.gdbinit 文件妙用
这是一个可以在每次 gdb 启动时默认加载指令的文件,我们用它来安全化炸弹
使用如下命令创建.gdbinit 文件
1 2 3 4 5 6
| touch .gdbinit
mkdir -p ~/.config/gdb
echo "set auto-load safe-path /" > ~/.config/gdb/gdbinit
|
在工作目录下的.gdbinit 里便可以输入我们的初始化指令
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
| set args sol.txt
b explode_bomb
b phase_1 b phase_2 b phase_3 b phase_4 b phase_5 b phase_6
b phase_defused
command
jump *(phase_defused + 0x3F) end
layout asm
r
|
本来还可以直接 jump 掉 send_msg 速通,但是这个 bug 今年貌似被修复了,所以不再使用
汇编代码解读 and 解题
获得汇编代码
在汇编代码中找到 main 所在,即可找到各个 phase 代码
Phase 1
找到对应的汇编代码:
1 2 3 4 5 6 7 8 9 10 11
| 00000000004016b4 <phase_1>: 4016b4: f3 0f 1e fa endbr64 4016b8: 48 83 ec 08 sub $0x8,%rsp 4016bc: 48 8d 35 8d 1a 00 00 lea 0x1a8d(%rip),%rsi # 403150 <_IO_stdin_used+0x150> 4016c3: e8 4e 05 00 00 call 401c16 <strings_not_equal> 4016c8: 85 c0 test %eax,%eax 4016ca: 75 05 jne 4016d1 <phase_1+0x1d> 4016cc: 48 83 c4 08 add $0x8,%rsp 4016d0: c3 ret 4016d1: e8 2d 0e 00 00 call 402503 <explode_bomb> 4016d6: eb f4 jmp 4016cc <phase_1+0x18>
|
第一行代码是防止 ROP 攻击的,不用管
注意到第四行调用了字符串是否相等的函数,考虑校验方式为对比输入的字符串是否与预存的字符串相等
打开 gdb,注意这时候已经初始化完毕,可以先在文件中随便输入一些东西,然后前进到调用函数的前一句,查看%rsi 内存的东西即可(注意%rsi 是第二个参数,第一个参数在%rdi 里即为我们输入的东西)
得到的即为答案,放到文件里即可
1
| I turned the moon into something I like to call a Death Star.
|
Phase 2
找到对应的汇编代码:
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
| 00000000004016d8 <phase_2>: 4016d8: f3 0f 1e fa endbr64 4016dc: 41 55 push %r13 4016de: 41 54 push %r12 4016e0: 55 push %rbp 4016e1: 53 push %rbx 4016e2: 48 83 ec 28 sub $0x28,%rsp 4016e6: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax 4016ed: 00 00 4016ef: 48 89 44 24 18 mov %rax,0x18(%rsp) 4016f4: 31 c0 xor %eax,%eax 4016f6: 48 89 e3 mov %rsp,%rbx 4016f9: 48 89 de mov %rbx,%rsi 4016fc: e8 44 0e 00 00 call 402545 <read_six_numbers> 401701: 4c 8d 6c 24 0c lea 0xc(%rsp),%r13 401706: bd 00 00 00 00 mov $0x0,%ebp 40170b: eb 0d jmp 40171a <phase_2+0x42> 40170d: 41 03 2c 24 add (%r12),%ebp 401711: 48 83 c3 04 add $0x4,%rbx 401715: 4c 39 eb cmp %r13,%rbx 401718: 74 11 je 40172b <phase_2+0x53> 40171a: 49 89 dc mov %rbx,%r12 40171d: 8b 43 0c mov 0xc(%rbx),%eax 401720: 39 03 cmp %eax,(%rbx) 401722: 74 e9 je 40170d <phase_2+0x35> 401724: e8 da 0d 00 00 call 402503 <explode_bomb> 401729: eb e2 jmp 40170d <phase_2+0x35> 40172b: 85 ed test %ebp,%ebp 40172d: 74 1b je 40174a <phase_2+0x72> 40172f: 48 8b 44 24 18 mov 0x18(%rsp),%rax 401734: 64 48 2b 04 25 28 00 sub %fs:0x28,%rax 40173b: 00 00 40173d: 75 12 jne 401751 <phase_2+0x79> 40173f: 48 83 c4 28 add $0x28,%rsp 401743: 5b pop %rbx 401744: 5d pop %rbp 401745: 41 5c pop %r12 401747: 41 5d pop %r13 401749: c3 ret 40174a: e8 b4 0d 00 00 call 402503 <explode_bomb> 40174f: eb de jmp 40172f <phase_2+0x57> 401751: e8 6a fb ff ff call 4012c0 <__stack_chk_fail@plt>
|
略去栈保护,直接看核心部分:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 4016f6: 48 89 e3 mov %rsp,%rbx 4016f9: 48 89 de mov %rbx,%rsi 4016fc: e8 44 0e 00 00 call 402545 <read_six_numbers> 401701: 4c 8d 6c 24 0c lea 0xc(%rsp),%r13 401706: bd 00 00 00 00 mov $0x0,%ebp 40170b: eb 0d jmp 40171a <phase_2+0x42> 40170d: 41 03 2c 24 add (%r12),%ebp 401711: 48 83 c3 04 add $0x4,%rbx 401715: 4c 39 eb cmp %r13,%rbx 401718: 74 11 je 40172b <phase_2+0x53> 40171a: 49 89 dc mov %rbx,%r12 40171d: 8b 43 0c mov 0xc(%rbx),%eax 401720: 39 03 cmp %eax,(%rbx) 401722: 74 e9 je 40170d <phase_2+0x35>
|
注意到每次循环后加的是 0x4,但循环体内部比较的数字是它自己和地址加上 0xc 后的数字。所以这里的比较方式实际上是每个数字和它往后的第三个数字比,即第一个和第四个,第二个和第五个,第三个和第六个,具体内容如代码所示:
直接输入一套
即可
还有一种 phase 2,内容是递归数列,每一项根据前一项递归而来,且第一项一般为 1,注意一下这种情况
Phase 3
这一部分的内容是跳表,我们找到对应代码,并找到跳表前的核心段落
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 40176e: 48 8d 4c 24 0f lea 0xf(%rsp),%rcx 401773: 48 8d 54 24 10 lea 0x10(%rsp),%rdx 401778: 4c 8d 44 24 14 lea 0x14(%rsp),%r8 40177d: 48 8d 35 32 1a 00 00 lea 0x1a32(%rip),%rsi # 4031b6 <_IO_stdin_used+0x1b6> 401784: e8 17 fc ff ff call 4013a0 <__isoc99_sscanf@plt> 401789: 83 f8 02 cmp $0x2,%eax 40178c: 7e 20 jle 4017ae <phase_3+0x58> 40178e: 83 7c 24 10 07 cmpl $0x7,0x10(%rsp) 401793: 0f 87 0d 01 00 00 ja 4018a6 <phase_3+0x150> 401799: 8b 44 24 10 mov 0x10(%rsp),%eax 40179d: 48 8d 15 1c 1a 00 00 lea 0x1a1c(%rip),%rdx # 4031c0 <_IO_stdin_used+0x1c0> 4017a4: 48 63 04 82 movslq (%rdx,%rax,4),%rax 4017a8: 48 01 d0 add %rdx,%rax 4017ab: 3e ff e0 notrack jmp *%rax
|
注意到这里调用了 sscanf,观察上方传参可以发现参数顺序为%rdx, %rcx, %r8
再用 gdb 读取%rsi 的内容获取格式 %d %c %d
,其实也可以从传参时内存大小来判断(%rdx %r8 都占用 4 个字节,但%rcx 占用 1 个)
然后便是构造条表并跳转,这里要求第一个数字必须小于 7,然后根据第一个数字的大小来跳转到不同的分支
如果单看代码分析不出来跳表的话,可以直接启动 gdb,检查跳转前寄存器的值即可
这里仅记录第一个数字为 0 的情况
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 4017b5: b8 61 00 00 00 mov $0x61,%eax 4017ba: 81 7c 24 14 48 02 00 cmpl $0x248,0x14(%rsp) 4017c1: 00 4017c2: 0f 84 e8 00 00 00 je 4018b0 <phase_3+0x15a>
......
4018b0: 38 44 24 0f cmp %al,0xf(%rsp) 4018b4: 75 15 jne 4018cb <phase_3+0x175> 4018b6: 48 8b 44 24 18 mov 0x18(%rsp),%rax 4018bb: 64 48 2b 04 25 28 00 sub %fs:0x28,%rax 4018c2: 00 00 4018c4: 75 0c jne 4018d2 <phase_3+0x17c> 4018c6: 48 83 c4 28 add $0x28,%rsp 4018ca: c3 ret
|
于是第三个数字为 0x248,即 584,第二个字符为 0x61,即 a
故一组可行解为
Phase 4
这一段是函数的递归调用,贴出核心代码
1 2 3 4 5 6 7 8 9 10 11 12 13
| 401919: 48 8d 54 24 04 lea 0x4(%rsp),%rdx 40191e: 48 8d 35 97 18 00 00 lea 0x1897(%rip),%rsi # 4031bc <_IO_stdin_used+0x1bc> 401925: e8 76 fa ff ff call 4013a0 <__isoc99_sscanf@plt> 40192a: 83 f8 01 cmp $0x1,%eax 40192d: 75 07 jne 401936 <phase_4+0x35> 40192f: 83 7c 24 04 00 cmpl $0x0,0x4(%rsp) 401934: 7f 05 jg 40193b <phase_4+0x3a> 401936: e8 c8 0b 00 00 call 402503 <explode_bomb> 40193b: 8b 7c 24 04 mov 0x4(%rsp),%edi 40193f: e8 93 ff ff ff call 4018d7 <func4> 401944: 3d f7 90 0c 00 cmp $0xc90f7,%eax 401949: 75 15 jne 401960 <phase_4+0x5f>
|
其实就是先读数,然后用这个参数调用 func4,比较返回值和 0xc90f7 的大小
那观察 func4 即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 00000000004018d7 <func4>: 4018d7: f3 0f 1e fa endbr64 4018db: b8 01 00 00 00 mov $0x1,%eax 4018e0: 85 ff test %edi,%edi 4018e2: 7e 1c jle 401900 <func4+0x29> 4018e4: 48 83 ec 08 sub $0x8,%rsp 4018e8: 83 ef 01 sub $0x1,%edi 4018eb: e8 e7 ff ff ff call 4018d7 <func4> 4018f0: 89 c2 mov %eax,%edx 4018f2: 8d 04 c5 00 00 00 00 lea 0x0(,%rax,8),%eax 4018f9: 29 d0 sub %edx,%eax 4018fb: 48 83 c4 08 add $0x8,%rsp 4018ff: c3 ret 401900: c3 ret
|
原来是通过递归计算
$$
7^n
$$
注意到
$$
7^7=0xc90f7
$$
即可,答案为
Phase 5
最后两关逐渐变难,先贴上代码
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
| 000000000040196c <phase_5>: 40196c: f3 0f 1e fa endbr64 401970: 48 83 ec 18 sub $0x18,%rsp 401974: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax 40197b: 00 00 40197d: 48 89 44 24 08 mov %rax,0x8(%rsp) 401982: 31 c0 xor %eax,%eax 401984: 48 8d 4c 24 04 lea 0x4(%rsp),%rcx 401989: 48 89 e2 mov %rsp,%rdx 40198c: 48 8d 35 bd 1b 00 00 lea 0x1bbd(%rip),%rsi # 403550 <array.0+0x370> 401993: e8 08 fa ff ff call 4013a0 <__isoc99_sscanf@plt> 401998: 83 f8 01 cmp $0x1,%eax 40199b: 7e 5a jle 4019f7 <phase_5+0x8b> 40199d: 8b 04 24 mov (%rsp),%eax 4019a0: 83 e0 0f and $0xf,%eax 4019a3: 89 04 24 mov %eax,(%rsp) 4019a6: 83 f8 0f cmp $0xf,%eax 4019a9: 74 32 je 4019dd <phase_5+0x71> 4019ab: b9 00 00 00 00 mov $0x0,%ecx 4019b0: ba 00 00 00 00 mov $0x0,%edx 4019b5: 48 8d 35 24 18 00 00 lea 0x1824(%rip),%rsi # 4031e0 <array.0> 4019bc: 83 c2 01 add $0x1,%edx 4019bf: 48 98 cltq 4019c1: 8b 04 86 mov (%rsi,%rax,4),%eax 4019c4: 01 c1 add %eax,%ecx 4019c6: 83 f8 0f cmp $0xf,%eax 4019c9: 75 f1 jne 4019bc <phase_5+0x50> 4019cb: c7 04 24 0f 00 00 00 movl $0xf,(%rsp) 4019d2: 83 fa 0a cmp $0xa,%edx 4019d5: 75 06 jne 4019dd <phase_5+0x71> 4019d7: 39 4c 24 04 cmp %ecx,0x4(%rsp) 4019db: 74 05 je 4019e2 <phase_5+0x76> 4019dd: e8 21 0b 00 00 call 402503 <explode_bomb> 4019e2: 48 8b 44 24 08 mov 0x8(%rsp),%rax 4019e7: 64 48 2b 04 25 28 00 sub %fs:0x28,%rax 4019ee: 00 00 4019f0: 75 0c jne 4019fe <phase_5+0x92> 4019f2: 48 83 c4 18 add $0x18,%rsp 4019f6: c3 ret 4019f7: e8 07 0b 00 00 call 402503 <explode_bomb> 4019fc: eb 9f jmp 40199d <phase_5+0x31> 4019fe: e8 bd f8 ff ff call 4012c0 <__stack_chk_fail@plt>
|
大体上是一个 while 循环,同时维护了%ecx, %edx 两个循环变量,要求是用输入值作为下标访问数组,同时每次循环用访问得到的值作为新的下标继续访问,直到得到 15,且这时候一共跳转了 10 次,输入的第二个数字即为访问过程中的累计求和
关键是如何获得数组的内容,观察到代码里给出了数组首地址,那我们直接在 gdb 中访问即可
1 2 3 4 5
| (gdb) x/16w 0x4031e0 0x4031e0 <array.0>: 10 2 14 7 0x4031f0 <array.0+16>: 8 12 15 11 0x403200 <array.0+32>: 0 4 1 13 0x403210 <array.0+48>: 3 9 6 5
|
过程略,答案为
Phase 6
这一部分是链表重排,也是最难的地方,关键是理解链表重排的函数以及解题的要求
贴上 phase6 的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| 0000000000401a6e <phase_6>: 401a6e: f3 0f 1e fa endbr64 401a72: 48 83 ec 08 sub $0x8,%rsp 401a76: ba 0a 00 00 00 mov $0xa,%edx 401a7b: be 00 00 00 00 mov $0x0,%esi 401a80: e8 eb f8 ff ff call 401370 <strtol@plt> 401a85: 89 05 e5 3c 00 00 mov %eax,0x3ce5(%rip) # 405770 <node0> 401a8b: 48 8d 3d de 3c 00 00 lea 0x3cde(%rip),%rdi # 405770 <node0> 401a92: e8 6c ff ff ff call 401a03 <fun6> 401a97: 48 8b 40 08 mov 0x8(%rax),%rax 401a9b: 48 8b 40 08 mov 0x8(%rax),%rax 401a9f: 48 8b 40 08 mov 0x8(%rax),%rax 401aa3: 48 8b 40 08 mov 0x8(%rax),%rax 401aa7: 48 8b 40 08 mov 0x8(%rax),%rax 401aab: 48 8b 40 08 mov 0x8(%rax),%rax 401aaf: 48 8b 40 08 mov 0x8(%rax),%rax 401ab3: 8b 0d b7 3c 00 00 mov 0x3cb7(%rip),%ecx # 405770 <node0> 401ab9: 39 08 cmp %ecx,(%rax) 401abb: 75 05 jne 401ac2 <phase_6+0x54> 401abd: 48 83 c4 08 add $0x8,%rsp 401ac1: c3 ret 401ac2: e8 3c 0a 00 00 call 402503 <explode_bomb> 401ac7: eb f4 jmp 401abd <phase_6+0x4f>
|
大概就是将输入值送进 node0,然后进行链表重排,重排后返回的值为链表最大值的地址,要求从这里开始往后访问七次能回到 node0
明白到这里就发现其实是不难的,关键是知道链表内容,然后把 node0 设置成链表里第 8 小的数字就行了
1 2 3 4 5 6 7 8 9 10 11 12
| (gdb) x/36w 0x405770 0x405770 <node0>: 0 0 4216704 0 0x405780 <node1>: 104 1 4216720 0 0x405790 <node2>: 624 2 4216736 0 0x4057a0 <node3>: 812 3 4216752 0 0x4057b0 <node4>: 81 4 4216768 0 0x4057c0 <node5>: 201 5 4216784 0 0x4057d0 <node6>: 859 6 4216800 0 0x4057e0 <node7>: 108 7 4216816 0 0x4057f0 <node8>: 751 8 4215392 0 (gdb) x/4w 0x405260 0x405260 <node9>: 975 9 0 0
|
那么答案即为 104-108 里的随便一个数,为了吉利我选择 108
这里有一个小建议,如果被链表函数绕晕了,那我们可以先随便输入一个数,用 gdb 运行到 func6 刚返回那里,然后去找到这时候链表的内容即可(前提是你还是能大致看出来是在排序)
Secret Phase
由于 Lab 2 已经过了 ddl,我一开始跳过了和服务器通讯的那部分,所以这里的 secret phase 我现在已经无法访问,但是我可以写出来具体方法
进入 secret phase
通常是在 phase_defused 里暗藏玄机
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
| 000000000040267f <phase_defused>: 40267f: f3 0f 1e fa endbr64 402683: 48 83 ec 78 sub $0x78,%rsp 402687: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax 40268e: 00 00 402690: 48 89 44 24 68 mov %rax,0x68(%rsp) 402695: 31 c0 xor %eax,%eax 402697: bf 01 00 00 00 mov $0x1,%edi 40269c: e8 06 f8 ff ff call 401ea7 <send_msg> 4026a1: 83 3d e8 31 00 00 06 cmpl $0x6,0x31e8(%rip) # 405890 <num_input_strings> 4026a8: 74 15 je 4026bf <phase_defused+0x40> 4026aa: 48 8b 44 24 68 mov 0x68(%rsp),%rax 4026af: 64 48 2b 04 25 28 00 sub %fs:0x28,%rax 4026b6: 00 00 4026b8: 75 7f jne 402739 <phase_defused+0xba> 4026ba: 48 83 c4 78 add $0x78,%rsp 4026be: c3 ret 4026bf: 48 8d 4c 24 10 lea 0x10(%rsp),%rcx 4026c4: 48 8d 54 24 0c lea 0xc(%rsp),%rdx 4026c9: 48 8d 35 ca 0e 00 00 lea 0xeca(%rip),%rsi # 40359a <array.0+0x3ba> 4026d0: 48 8d 3d b9 32 00 00 lea 0x32b9(%rip),%rdi # 405990 <input_strings+0xf0> 4026d7: b8 00 00 00 00 mov $0x0,%eax 4026dc: e8 bf ec ff ff call 4013a0 <__isoc99_sscanf@plt> 4026e1: 83 f8 02 cmp $0x2,%eax 4026e4: 74 1a je 402700 <phase_defused+0x81> 4026e6: 48 8d 3d f3 0b 00 00 lea 0xbf3(%rip),%rdi # 4032e0 <array.0+0x100> 4026ed: e8 8e eb ff ff call 401280 <puts@plt> 4026f2: 48 8d 3d 17 0c 00 00 lea 0xc17(%rip),%rdi # 403310 <array.0+0x130> 4026f9: e8 82 eb ff ff call 401280 <puts@plt> 4026fe: eb aa jmp 4026aa <phase_defused+0x2b> 402700: 48 8d 7c 24 10 lea 0x10(%rsp),%rdi 402705: 48 8d 35 94 0e 00 00 lea 0xe94(%rip),%rsi # 4035a0 <array.0+0x3c0> 40270c: e8 05 f5 ff ff call 401c16 <strings_not_equal> 402711: 85 c0 test %eax,%eax 402713: 75 d1 jne 4026e6 <phase_defused+0x67> 402715: 48 8d 3d 64 0b 00 00 lea 0xb64(%rip),%rdi # 403280 <array.0+0xa0> 40271c: e8 5f eb ff ff call 401280 <puts@plt> 402721: 48 8d 3d 80 0b 00 00 lea 0xb80(%rip),%rdi # 4032a8 <array.0+0xc8> 402728: e8 53 eb ff ff call 401280 <puts@plt> 40272d: b8 00 00 00 00 mov $0x0,%eax 402732: e8 d3 f3 ff ff call 401b0a <secret_phase> 402737: eb ad jmp 4026e6 <phase_defused+0x67> 402739: e8 82 eb ff ff call 4012c0 <__stack_chk_fail@plt>
|
答案就藏在 0x4035a0!
1 2
| (gdb) x/s 0x4035a0 0x4035a0: "austinpowers"
|
在哪里输入呢,注意到
1
| lea 0x32b9(%rip),%rdi # 405990 <input_strings+0xf0>
|
而这是 phase_4 的输入地址,于是我们只需要在 phase4 后面添加上述字符串即可
对于 secret_phase 本身,是一个二叉树访问的递归函数问题,关键和 phase6 一样,一方面要看懂函数,另一方面要能在内存中找到二叉树的存储位置并访问之
这样我们的 sol.txt 即为:
1 2 3 4 5 6 7
| I turned the moon into something I like to call a Death Star. 1 2 3 1 2 3 0 a 584 7 austinpowers 13 69 108 (secret phase的答案我忘了)
|
完结!