CSAPP lab2: bomb lab
概述 bomb lab由6个(其实是7个, 有一个隐藏的)“炸弹”组成, 每个炸弹都需要特定的输入来解除, 任何错误输入都会导致炸弹爆炸。
lab给出了三个文件: README bomb bomb.c
其中bomb
即是我们要输入来拆弹的可执行文件, bomb.c
提供了部分bomb
的源代码
思路 bomb.c
的内容并不足以让我们拆除炸弹, 所以要利用第三章所学的逆向工程技术, 对bomb
进行反汇编, 根据汇编代码中的指令来拆弹
但是只看汇编代码也不足以拆除炸弹, 因为我们有时需要查看内存(或寄存器)的内容, 并且要时不时的暂停程序来检查程序运行在某个特定函数或指令时的内存(或寄存器)内容, 这就要求我们要使用gdb 来调试bomb
可执行文件, 这也是作者想让我们做的
题解 写在前面 约定 6个炸弹是按照顺序排好的, 每个都需要输入, 只有前一个炸弹被拆除才能进行下一个炸弹的输入; 否则炸弹爆炸, 程序结束
bomb.c
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 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 #include <stdio.h> #include <stdlib.h> #include "support.h" #include "phases.h" FILE *infile; int main (int argc, char *argv[]) { char *input; if (argc == 1 ) { infile = stdin ; } else if (argc == 2 ) { if (!(infile = fopen(argv[1 ], "r" ))) { printf ("%s: Error: Couldn't open %s\n" , argv[0 ], argv[1 ]); exit (8 ); } } else { printf ("Usage: %s [<input_file>]\n" , argv[0 ]); exit (8 ); } initialize_bomb(); printf ("Welcome to my fiendish little bomb. You have 6 phases with\n" ); printf ("which to blow yourself up. Have a nice day!\n" ); input = read_line(); phase_1(input); phase_defused(); printf ("Phase 1 defused. How about the next one?\n" ); input = read_line(); phase_2(input); phase_defused(); printf ("That's number 2. Keep going!\n" ); input = read_line(); phase_3(input); phase_defused(); printf ("Halfway there!\n" ); input = read_line(); phase_4(input); phase_defused(); printf ("So you got that one. Try this one.\n" ); input = read_line(); phase_5(input); phase_defused(); printf ("Good work! On to the next...\n" ); input = read_line(); phase_6(input); phase_defused(); return 0 ; }
通用函数 string_length()
1 2 3 4 5 6 7 8 9 10 11 12 000000000040131b <string_length>: 40131b: 80 3f 00 cmpb $0x0,(%rdi) 40131e: 74 12 je 401332 <string_length+0x17> 401320: 48 89 fa mov %rdi,%rdx 401323: 48 83 c2 01 add $0x1,%rdx 401327: 89 d0 mov %edx,%eax 401329: 29 f8 sub %edi,%eax 40132b: 80 3a 00 cmpb $0x0,(%rdx) 40132e: 75 f3 jne 401323 <string_length+0x8> 401330: f3 c3 repz ret 401332: b8 00 00 00 00 mov $0x0,%eax 401337: c3 ret
该函数接受一个参数为字符串地址, 返回这个字符串的长度
strings_not_equal()
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 0000000000401338 <strings_not_equal>: 401338: 41 54 push %r12 40133a: 55 push %rbp 40133b: 53 push %rbx 40133c: 48 89 fb mov %rdi,%rbx 40133f: 48 89 f5 mov %rsi,%rbp 401342: e8 d4 ff ff ff call 40131b <string_length> 401347: 41 89 c4 mov %eax,%r12d 40134a: 48 89 ef mov %rbp,%rdi 40134d: e8 c9 ff ff ff call 40131b <string_length> 401352: ba 01 00 00 00 mov $0x1,%edx 401357: 41 39 c4 cmp %eax,%r12d 40135a: 75 3f jne 40139b <strings_not_equal+0x63> 40135c: 0f b6 03 movzbl (%rbx),%eax 40135f: 84 c0 test %al,%al 401361: 74 25 je 401388 <strings_not_equal+0x50> 401363: 3a 45 00 cmp 0x0(%rbp),%al 401366: 74 0a je 401372 <strings_not_equal+0x3a> 401368: eb 25 jmp 40138f <strings_not_equal+0x57> 40136a: 3a 45 00 cmp 0x0(%rbp),%al 40136d: 0f 1f 00 nopl (%rax) 401370: 75 24 jne 401396 <strings_not_equal+0x5e> 401372: 48 83 c3 01 add $0x1,%rbx 401376: 48 83 c5 01 add $0x1,%rbp 40137a: 0f b6 03 movzbl (%rbx),%eax 40137d: 84 c0 test %al,%al 40137f: 75 e9 jne 40136a <strings_not_equal+0x32> 401381: ba 00 00 00 00 mov $0x0,%edx 401386: eb 13 jmp 40139b <strings_not_equal+0x63> 401388: ba 00 00 00 00 mov $0x0,%edx 40138d: eb 0c jmp 40139b <strings_not_equal+0x63> 40138f: ba 01 00 00 00 mov $0x1,%edx 401394: eb 05 jmp 40139b <strings_not_equal+0x63> 401396: ba 01 00 00 00 mov $0x1,%edx 40139b: 89 d0 mov %edx,%eax 40139d: 5b pop %rbx 40139e: 5d pop %rbp 40139f: 41 5c pop %r12 4013a1: c3 ret
该函数接受两个参数, 均为字符串的地址, 如果这两个字符串相同, 返回0, 否则返回1
read_six_numbers()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 000000000040145c <read_six_numbers>: 40145c: 48 83 ec 18 sub $0x18,%rsp 401460: 48 89 f2 mov %rsi,%rdx 401463: 48 8d 4e 04 lea 0x4(%rsi),%rcx 401467: 48 8d 46 14 lea 0x14(%rsi),%rax 40146b: 48 89 44 24 08 mov %rax,0x8(%rsp) 401470: 48 8d 46 10 lea 0x10(%rsi),%rax 401474: 48 89 04 24 mov %rax,(%rsp) 401478: 4c 8d 4e 0c lea 0xc(%rsi),%r9 40147c: 4c 8d 46 08 lea 0x8(%rsi),%r8 401480: be c3 25 40 00 mov $0x4025c3,%esi 401485: b8 00 00 00 00 mov $0x0,%eax 40148a: e8 61 f7 ff ff call 400bf0 <__isoc99_sscanf@plt> 40148f: 83 f8 05 cmp $0x5,%eax 401492: 7f 05 jg 401499 <read_six_numbers+0x3d> 401494: e8 a1 ff ff ff call 40143a <explode_bomb> 401499: 48 83 c4 18 add $0x18,%rsp 40149d: c3 ret
该函数接受两个参数, 第一个参数是一个字符串的地址, 作为sscanf()
的第一个参数(这个不用管, 就是我们的输入被存起来的地址); 第二个参数比较重要, 如果把我们的6个输入看成数组, 这个参数就是这个数组的地址(第一个元素的地址), 这里可以看出每个元素是4个字节的, 也就是int
然后讲一下sscanf()
的参数和返回值, 在这里由于要输入6个数, 所以sscanf()
一共有8个参数(第一个是输入字符串的地址, 第二个是”format”(输入格式), 然后就是6个数的地址), 我们看到0x401280
处的指令表示”format”在内存0x4025c3
处, 我们可以把它打出来看看:
不出所料, 是6个int
再来一张图表示一下6个输入保存的地址(站在read_six_numbers()
的栈帧角度, 也就是所有寄存器内容都是刚进这个函数时候的)
sscanf()
的参数序号
意义
参数内容
1
输入字符串的地址
不用管
2
format的地址
也不用管, 但是是0x4025c3
3
第一个数的地址
$rsi
4
第二个数的地址
$rsi + 0x4
5
第三个数的地址
$rsi + 0x8
6
第四个数的地址
$rsi + 0xc
7
第五个数的地址
$rsi + 0x10
8
第六个数的地址
$rsi + 0x14
站在sscanf()
的栈帧角度的话, 8个参数被保存的位置依次为
rdi rsi rdx rcx r8 r9 ($rsp + 8) ($rsp + 16)
sscanf()
的返回值是输入和”format”的对应个数, 如果输入了6个int
那么就会返回6, sscanf()
的返回值也是read_six_numbers
的返回值
拆弹过程 phase_1 1 2 3 4 5 6 7 8 9 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 call 401338 <strings_not_equal> 400eee: 85 c0 test %eax,%eax 400ef0: 74 05 je 400ef7 <phase_1+0x17> 400ef2: e8 43 05 00 00 call 40143a <explode_bomb> 400ef7: 48 83 c4 08 add $0x8,%rsp 400efb: c3 ret
可以看出phase_1将我们的输入和内存在0x402400位置处的字符串比较, 如果相同则拆弹成功, 那我们就在输入前打个断点, 看看这个内存存的字符串是什么再输入就好了
这个phase的答案是
Border relations with Canada have never been better.
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 0000000000400efc <phase_2>: 400efc: 55 push %rbp 400efd: 53 push %rbx 400efe: 48 83 ec 28 sub $0x28,%rsp 400f02: 48 89 e6 mov %rsp,%rsi 400f05: e8 52 05 00 00 call 40145c <read_six_numbers> 400f0a: 83 3c 24 01 cmpl $0x1,(%rsp) 400f0e: 74 20 je 400f30 <phase_2+0x34> # 第一个数要是1 400f10: e8 25 05 00 00 call 40143a <explode_bomb> 400f15: eb 19 jmp 400f30 <phase_2+0x34> 400f17: 8b 43 fc mov -0x4(%rbx),%eax # eax里存的a[i - 1] 400f1a: 01 c0 add %eax,%eax # 2 * a[i - 1] 400f1c: 39 03 cmp %eax,(%rbx) # (%rbx) 是 a[i] 400f1e: 74 05 je 400f25 <phase_2+0x29> 400f20: e8 15 05 00 00 call 40143a <explode_bomb> # 不同就炸 400f25: 48 83 c3 04 add $0x4,%rbx # i ++ 400f29: 48 39 eb cmp %rbp,%rbx # 检查 i < 6 400f2c: 75 e9 jne 400f17 <phase_2+0x1b> 400f2e: eb 0c jmp 400f3c <phase_2+0x40> 400f30: 48 8d 5c 24 04 lea 0x4(%rsp),%rbx 400f35: 48 8d 6c 24 18 lea 0x18(%rsp),%rbp 400f3a: eb db jmp 400f17 <phase_2+0x1b> 400f3c: 48 83 c4 28 add $0x28,%rsp 400f40: 5b pop %rbx 400f41: 5d pop %rbp 400f42: c3 ret
首先我们将输入的6个数存到了rsp的位置, 也就是第一个数存在$rsp位置, 第二个数存在$rsp + 4位置, …
继续看从0x400f0a开始的三条指令, 告诉我们第一个数是1
继续看后续的指令, 发现需要从第二个数开始, 每一个数都要是前一个数的2倍才能通过
写一个类似的C代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void explode_bomb () ;void phase_2 () { int a[6 ]; scanf ("%d %d %d %d %d %d" , &a[0 ], &a[1 ], &a[2 ], &a[3 ], &a[4 ], &a[5 ]); if (a[0 ] != 1 ) explode_bomb(); for (int i = 2 ; i < 6 ; i ++) if (a[i] != 2 * a[i - 1 ]) explode_bomb(); return ; }
所以这个phase的答案就是
1 2 4 8 16 32
phase_3 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 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 # format的地址 400f56: b8 00 00 00 00 mov $0x0,%eax 400f5b: e8 90 fc ff ff call 400bf0 <__isoc99_sscanf@plt> 400f60: 83 f8 01 cmp $0x1,%eax # 400f63: 7f 05 jg 400f6a <phase_3+0x27> # 必须读两个int, 要不爆炸 400f65: e8 d0 04 00 00 call 40143a <explode_bomb> 400f6a: 83 7c 24 08 07 cmpl $0x7,0x8(%rsp) # 400f6f: 77 3c ja 400fad <phase_3+0x6a> # 如果第一个数大于7或小于0, 爆炸 400f71: 8b 44 24 08 mov 0x8(%rsp),%eax # 第一个数 400f75: ff 24 c5 70 24 40 00 jmp *0x402470(,%rax,8) # 根据第一个数跳转, 跳转后将一个数放进eax 400f7c: b8 cf 00 00 00 mov $0xcf,%eax 400f81: eb 3b jmp 400fbe <phase_3+0x7b> 400f83: b8 c3 02 00 00 mov $0x2c3,%eax 400f88: eb 34 jmp 400fbe <phase_3+0x7b> 400f8a: b8 00 01 00 00 mov $0x100,%eax 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 call 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 call 40143a <explode_bomb> 400fc9: 48 83 c4 18 add $0x18,%rsp 400fcd: c3 ret
首先0x400f47开始的两个指令告诉我们要读入两个东西, 我们通过format看看读的是什么
2个int, 但是限定第一个数要在0-7之间, 然后会根据第一个数的大小跳到一个位置(0x400f75处的指令), 我们看看都会跳到哪去
可以看到就是0x400f7c到0x400fb9中的某些置eax的位置, 那我们根据这个来选一个第一个数, 就找到对应的第二个数了
这里选第一个数为0, 那么会跳到0x400f7c处, 这个时候第二个数要是0xcf, 也就是207
成功拆除, 所以这个phase有八个答案, 我用的是
0 207
phase_4 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 0000000000400fce <func4>: 400fce: 48 83 ec 08 sub $0x8,%rsp 400fd2: 89 d0 mov %edx,%eax 400fd4: 29 f0 sub %esi,%eax 400fd6: 89 c1 mov %eax,%ecx 400fd8: c1 e9 1f shr $0x1f,%ecx 400fdb: 01 c8 add %ecx,%eax 400fdd: d1 f8 sar %eax 400fdf: 8d 0c 30 lea (%rax,%rsi,1),%ecx 400fe2: 39 f9 cmp %edi,%ecx 400fe4: 7e 0c jle 400ff2 <func4+0x24> 400fe6: 8d 51 ff lea -0x1(%rcx),%edx 400fe9: e8 e0 ff ff ff call 400fce <func4> 400fee: 01 c0 add %eax,%eax 400ff0: eb 15 jmp 401007 <func4+0x39> 400ff2: b8 00 00 00 00 mov $0x0,%eax 400ff7: 39 f9 cmp %edi,%ecx 400ff9: 7d 0c jge 401007 <func4+0x39> 400ffb: 8d 71 01 lea 0x1(%rcx),%esi 400ffe: e8 cb ff ff ff call 400fce <func4> 401003: 8d 44 00 01 lea 0x1(%rax,%rax,1),%eax 401007: 48 83 c4 08 add $0x8,%rsp 40100b: c3 ret 000000000040100c <phase_4>: 40100c: 48 83 ec 18 sub $0x18,%rsp # 401010: 48 8d 4c 24 0c lea 0xc(%rsp),%rcx # 401015: 48 8d 54 24 08 lea 0x8(%rsp),%rdx # 40101a: be cf 25 40 00 mov $0x4025cf,%esi # 40101f: b8 00 00 00 00 mov $0x0,%eax # 401024: e8 c7 fb ff ff call 400bf0 <__isoc99_sscanf@plt> # 又是读进来两个int 401029: 83 f8 02 cmp $0x2,%eax 40102c: 75 07 jne 401035 <phase_4+0x29> 40102e: 83 7c 24 08 0e cmpl $0xe,0x8(%rsp) # 401033: 76 05 jbe 40103a <phase_4+0x2e> # 401035: e8 00 04 00 00 call 40143a <explode_bomb> #第一个数必须大于等于0并且小于14 40103a: ba 0e 00 00 00 mov $0xe,%edx 40103f: be 00 00 00 00 mov $0x0,%esi 401044: 8b 7c 24 08 mov 0x8(%rsp),%edi 401048: e8 81 ff ff ff call 400fce <func4> # 调用fun4(第一个数, 0, 14) 40104d: 85 c0 test %eax,%eax # 40104f: 75 07 jne 401058 <phase_4+0x4c> # 返回结果必须是0, 否则爆炸 401051: 83 7c 24 0c 00 cmpl $0x0,0xc(%rsp) # 第二个数必须是0 401056: 74 05 je 40105d <phase_4+0x51> 401058: e8 dd 03 00 00 call 40143a <explode_bomb> 40105d: 48 83 c4 18 add $0x18,%rsp 401061: c3 ret
这个phase也是输入两个int, 但是不同的是这里面还额外调用了一个函数, 并且这个函数返回值必须是0
我们用C语言写一下func4()
函数
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 int func4 (int x, int y, int z) { int res = z; res -= y; int tmp = res; tmp >>= 31 ; res += tmp; res >>= 1 ; tmp = res + y; if (tmp <= x) { res = 0 ; if (tmp >= x) return res; else { y = tmp + 1 ; return func4(x, y, z); } } else { z --; return func4(x, y, z); } }
虽然不太懂这个函数的意义是什么, 但是我们知道phase_4
调用了func4(第一个输入, 0, 14)
, 我们很容易可以看出如果第一个输入是7的话, func4()
会避免递归, 直接第一次就返回0, 所以
7 0
是一个答案, 另外
3 0, 1 0, 0 0
也是答案, 对于第一个数大于7的情况我还没有去想
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 0000000000401062 <phase_5>: 401062: 53 push %rbx 401063: 48 83 ec 20 sub $0x20,%rsp 401067: 48 89 fb mov %rdi,%rbx # rbx里存的是我们输入的字符串的地址 40106a: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax 401071: 00 00 401073: 48 89 44 24 18 mov %rax,0x18(%rsp) 401078: 31 c0 xor %eax,%eax 40107a: e8 9c 02 00 00 call 40131b <string_length> # 40107f: 83 f8 06 cmp $0x6,%eax # 401082: 74 4e je 4010d2 <phase_5+0x70> # 401084: e8 b1 03 00 00 call 40143a <explode_bomb> # 输入要是一个长度为6的字符串 401089: eb 47 jmp 4010d2 <phase_5+0x70> 40108b: 0f b6 0c 03 movzbl (%rbx,%rax,1),%ecx # 40108f: 88 0c 24 mov %cl,(%rsp) # 401092: 48 8b 14 24 mov (%rsp),%rdx # rdx里存的是我们输入的第i个字符的ascii码 401096: 83 e2 0f and $0xf,%edx # 取低4位, 也就是%16的结果 401099: 0f b6 92 b0 24 40 00 movzbl 0x4024b0(%rdx),%edx # 以上述低4位数为偏移(下标)取出一个在0x4024b0的字符串中的一个字符 4010a0: 88 54 04 10 mov %dl,0x10(%rsp,%rax,1) # 把取出的6个字符放到栈上, 等会比较用 4010a4: 48 83 c0 01 add $0x1,%rax # 4010a8: 48 83 f8 06 cmp $0x6,%rax # 循环6次 4010ac: 75 dd jne 40108b <phase_5+0x29> 4010ae: c6 44 24 16 00 movb $0x0,0x16(%rsp) # 字符串结束符号, '\0' 4010b3: be 5e 24 40 00 mov $0x40245e,%esi # 这就是我们最终要相比较的字符串的地址!!! 4010b8: 48 8d 7c 24 10 lea 0x10(%rsp),%rdi # 4010bd: e8 76 02 00 00 call 401338 <strings_not_equal> # 4010c2: 85 c0 test %eax,%eax # 4010c4: 74 13 je 4010d9 <phase_5+0x77> # 检查一不一样, 一样就拆除成功 4010c6: e8 6f 03 00 00 call 40143a <explode_bomb> 4010cb: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1) 4010d0: eb 07 jmp 4010d9 <phase_5+0x77> 4010d2: b8 00 00 00 00 mov $0x0,%eax # 置0 4010d7: eb b2 jmp 40108b <phase_5+0x29> 4010d9: 48 8b 44 24 18 mov 0x18(%rsp),%rax 4010de: 64 48 33 04 25 28 00 xor %fs:0x28,%rax 4010e5: 00 00 4010e7: 74 05 je 4010ee <phase_5+0x8c> 4010e9: e8 42 fa ff ff call 400b30 <__stack_chk_fail@plt> 4010ee: 48 83 c4 20 add $0x20,%rsp 4010f2: 5b pop %rbx 4010f3: c3 ret
这个phase要求我们输入6个字符, 以这6个字符的ASCII码值%16为下标从另一个字符串中取6个字符, 并与最终的字符串比较, 相同就能拆除炸弹
从0x4010b3处指令我们可以看出最终要比较的字符串地址为0x40245e, 看看是什么
是”flyer”
那我们再看看需要我们提供下标取6个字符的那个字符串是什么, 0x401099处指令给出这个字符串的地址为0x4024b0
所以我们需要的下标为9, 15, 14, 5, 6, 7。 放一张ASCII码表
可以看到前几个字符实际上是特殊字符(退格回车什么的), 不能输入, 反正我们的输入还需要%16, 而0的ASCII码刚好是48, 48 % 16 = 0, 那我们就以0为基准数就可以了, 最近的答案为
9?>567
其中每个字符也可以被别的字符替代(例如IONEFG)也是答案
phase_6 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 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 00000000004010f4 <phase_6>: 4010f4: 41 56 push %r14 4010f6: 41 55 push %r13 4010f8: 41 54 push %r12 4010fa: 55 push %rbp 4010fb: 53 push %rbx 4010fc: 48 83 ec 50 sub $0x50,%rsp 401100: 49 89 e5 mov %rsp,%r13 401103: 48 89 e6 mov %rsp,%rsi 401106: e8 51 03 00 00 call 40145c <read_six_numbers> 40110b: 49 89 e6 mov %rsp,%r14 40110e: 41 bc 00 00 00 00 mov $0x0,%r12d 401114: 4c 89 ed mov %r13,%rbp 401117: 41 8b 45 00 mov 0x0(%r13),%eax 40111b: 83 e8 01 sub $0x1,%eax 40111e: 83 f8 05 cmp $0x5,%eax 401121: 76 05 jbe 401128 <phase_6+0x34> 401123: e8 12 03 00 00 call 40143a <explode_bomb> 401128: 41 83 c4 01 add $0x1,%r12d 40112c: 41 83 fc 06 cmp $0x6,%r12d 401130: 74 21 je 401153 <phase_6+0x5f> 401132: 44 89 e3 mov %r12d,%ebx 401135: 48 63 c3 movslq %ebx,%rax 401138: 8b 04 84 mov (%rsp,%rax,4),%eax 40113b: 39 45 00 cmp %eax,0x0(%rbp) 40113e: 75 05 jne 401145 <phase_6+0x51> 401140: e8 f5 02 00 00 call 40143a <explode_bomb> 401145: 83 c3 01 add $0x1,%ebx 401148: 83 fb 05 cmp $0x5,%ebx 40114b: 7e e8 jle 401135 <phase_6+0x41> 40114d: 49 83 c5 04 add $0x4,%r13 401151: eb c1 jmp 401114 <phase_6+0x20> 401153: 48 8d 74 24 18 lea 0x18(%rsp),%rsi 401158: 4c 89 f0 mov %r14,%rax 40115b: b9 07 00 00 00 mov $0x7,%ecx 401160: 89 ca mov %ecx,%edx 401162: 2b 10 sub (%rax),%edx 401164: 89 10 mov %edx,(%rax) 401166: 48 83 c0 04 add $0x4,%rax 40116a: 48 39 f0 cmp %rsi,%rax 40116d: 75 f1 jne 401160 <phase_6+0x6c> 40116f: be 00 00 00 00 mov $0x0,%esi 401174: eb 21 jmp 401197 <phase_6+0xa3> 401176: 48 8b 52 08 mov 0x8(%rdx),%rdx 40117a: 83 c0 01 add $0x1,%eax 40117d: 39 c8 cmp %ecx,%eax 40117f: 75 f5 jne 401176 <phase_6+0x82> 401181: eb 05 jmp 401188 <phase_6+0x94> 401183: ba d0 32 60 00 mov $0x6032d0,%edx 401188: 48 89 54 74 20 mov %rdx,0x20(%rsp,%rsi,2) 40118d: 48 83 c6 04 add $0x4,%rsi 401191: 48 83 fe 18 cmp $0x18,%rsi 401195: 74 14 je 4011ab <phase_6+0xb7> 401197: 8b 0c 34 mov (%rsp,%rsi,1),%ecx 40119a: 83 f9 01 cmp $0x1,%ecx 40119d: 7e e4 jle 401183 <phase_6+0x8f> 40119f: b8 01 00 00 00 mov $0x1,%eax 4011a4: ba d0 32 60 00 mov $0x6032d0,%edx 4011a9: eb cb jmp 401176 <phase_6+0x82> 4011ab: 48 8b 5c 24 20 mov 0x20(%rsp),%rbx 4011b0: 48 8d 44 24 28 lea 0x28(%rsp),%rax 4011b5: 48 8d 74 24 50 lea 0x50(%rsp),%rsi 4011ba: 48 89 d9 mov %rbx,%rcx 4011bd: 48 8b 10 mov (%rax),%rdx 4011c0: 48 89 51 08 mov %rdx,0x8(%rcx) 4011c4: 48 83 c0 08 add $0x8,%rax 4011c8: 48 39 f0 cmp %rsi,%rax 4011cb: 74 05 je 4011d2 <phase_6+0xde> 4011cd: 48 89 d1 mov %rdx,%rcx 4011d0: eb eb jmp 4011bd <phase_6+0xc9> 4011d2: 48 c7 42 08 00 00 00 movq $0x0,0x8(%rdx) 4011d9: 00 4011da: bd 05 00 00 00 mov $0x5,%ebp 4011df: 48 8b 43 08 mov 0x8(%rbx),%rax 4011e3: 8b 00 mov (%rax),%eax 4011e5: 39 03 cmp %eax,(%rbx) 4011e7: 7d 05 jge 4011ee <phase_6+0xfa> 4011e9: e8 4c 02 00 00 call 40143a <explode_bomb> 4011ee: 48 8b 5b 08 mov 0x8(%rbx),%rbx 4011f2: 83 ed 01 sub $0x1,%ebp 4011f5: 75 e8 jne 4011df <phase_6+0xeb> 4011f7: 48 83 c4 50 add $0x50,%rsp 4011fb: 5b pop %rbx 4011fc: 5d pop %rbp 4011fd: 41 5c pop %r12 4011ff: 41 5d pop %r13 401201: 41 5e pop %r14 401203: c3 ret
这个phase其实和第五个差不多的, 但是由于汇编代码比较长, 我看了好久, 最后还是找规律才解决
输入要求是1-6的一个排列, 为什么要是排列呢, 因为我们要通过这个排列来映射重排一个链表(数组吧, 怎么说都行), 让这个链表变成递减的
先上个找规律的结果
可以看到它确实是先把我们的输入x都变成7-x, 然后根据这个重排链表(它甚至还给我标好了node编号), 我们只要通过输入把这个链表最终映射成递减的就可以了, 最后的答案是
4 3 2 1 6 5
6个炸弹全部拆除!
secret_phase 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 0000000000401204 <fun7>: 401204: 48 83 ec 08 sub $0x8,%rsp 401208: 48 85 ff test %rdi,%rdi 40120b: 74 2b je 401238 <fun7+0x34> 40120d: 8b 17 mov (%rdi),%edx 40120f: 39 f2 cmp %esi,%edx 401211: 7e 0d jle 401220 <fun7+0x1c> 401213: 48 8b 7f 08 mov 0x8(%rdi),%rdi 401217: e8 e8 ff ff ff call 401204 <fun7> 40121c: 01 c0 add %eax,%eax 40121e: eb 1d jmp 40123d <fun7+0x39> 401220: b8 00 00 00 00 mov $0x0,%eax 401225: 39 f2 cmp %esi,%edx 401227: 74 14 je 40123d <fun7+0x39> 401229: 48 8b 7f 10 mov 0x10(%rdi),%rdi 40122d: e8 d2 ff ff ff call 401204 <fun7> 401232: 8d 44 00 01 lea 0x1(%rax,%rax,1),%eax 401236: eb 05 jmp 40123d <fun7+0x39> 401238: b8 ff ff ff ff mov $0xffffffff,%eax 40123d: 48 83 c4 08 add $0x8,%rsp 401241: c3 ret 0000000000401242 <secret_phase>: 401242: 53 push %rbx 401243: e8 56 02 00 00 call 40149e <read_line> # 401248: ba 0a 00 00 00 mov $0xa,%edx # 40124d: be 00 00 00 00 mov $0x0,%esi # 401252: 48 89 c7 mov %rax,%rdi # 401255: e8 76 f9 ff ff call 400bd0 <strtol@plt> # 这里是读入了一个字符串, 把它变成十进制数, 我们叫这个数x 40125a: 48 89 c3 mov %rax,%rbx 40125d: 8d 40 ff lea -0x1(%rax),%eax # x - 1 401260: 3d e8 03 00 00 cmp $0x3e8,%eax # 0x3e8 == 1000 401265: 76 05 jbe 40126c <secret_phase+0x2a> # 0 <= x - 1 <= 1000, 1 <= x <= 1001 401267: e8 ce 01 00 00 call 40143a <explode_bomb> # 不在范围内就炸 40126c: 89 de mov %ebx,%esi # 40126e: bf f0 30 60 00 mov $0x6030f0,%edi # 401273: e8 8c ff ff ff call 401204 <fun7> # 调用fun7(0x6030f0, x) 401278: 83 f8 02 cmp $0x2,%eax # 要求fun7(0x6030f0, x) == 2 40127b: 74 05 je 401282 <secret_phase+0x40> 40127d: e8 b8 01 00 00 call 40143a <explode_bomb> 401282: bf 38 24 40 00 mov $0x402438,%edi 401287: e8 84 f8 ff ff call 400b10 <puts@plt> 40128c: e8 33 03 00 00 call 4015c4 <phase_defused> 401291: 5b pop %rbx 401292: c3 ret 401293: 90 nop 401294: 90 nop 401295: 90 nop 401296: 90 nop 401297: 90 nop 401298: 90 nop 401299: 90 nop 40129a: 90 nop 40129b: 90 nop 40129c: 90 nop 40129d: 90 nop 40129e: 90 nop 40129f: 90 nop
这是secret_phase和他调用的fun7的反汇编, 但是我们要知道谁调用了secret_phase
用vim的查找命令发现是phase_defused, 看一下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 00000000004015c4 <phase_defused>: 4015c4: 48 83 ec 78 sub $0x78,%rsp 4015c8: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax 4015cf: 00 00 4015d1: 48 89 44 24 68 mov %rax,0x68(%rsp) 4015d6: 31 c0 xor %eax,%eax 4015d8: 83 3d 81 21 20 00 06 cmpl $0x6,0x202181(%rip) # 603760 <num_input_strings> 4015df: 75 5e jne 40163f <phase_defused+0x7b> # 要6个phase全部拆除才能触发secret_phase 4015e1: 4c 8d 44 24 10 lea 0x10(%rsp),%r8 # 4015e6: 48 8d 4c 24 0c lea 0xc(%rsp),%rcx # 4015eb: 48 8d 54 24 08 lea 0x8(%rsp),%rdx # 4015f0: be 19 26 40 00 mov $0x402619,%esi # 4015f5: bf 70 38 60 00 mov $0x603870,%edi # 这是当作读入的字符串 4015fa: e8 f1 f5 ff ff call 400bf0 <__isoc99_sscanf@plt> # 用sscanf()读了三个东西进来 4015ff: 83 f8 03 cmp $0x3,%eax # 401602: 75 31 jne 401635 <phase_defused+0x71> # 三个东西要满足格式 401604: be 22 26 40 00 mov $0x402622,%esi # 401609: 48 8d 7c 24 10 lea 0x10(%rsp),%rdi # 40160e: e8 25 fd ff ff call 401338 <strings_not_equal> # 看来第三个是字符串, 要和0x40266处的字符串比较 401613: 85 c0 test %eax,%eax # 401615: 75 1e jne 401635 <phase_defused+0x71> # 相同就可以触发secret_phase! 401617: bf f8 24 40 00 mov $0x4024f8,%edi 40161c: e8 ef f4 ff ff call 400b10 <puts@plt> 401621: bf 20 25 40 00 mov $0x402520,%edi 401626: e8 e5 f4 ff ff call 400b10 <puts@plt> 40162b: b8 00 00 00 00 mov $0x0,%eax 401630: e8 0d fc ff ff call 401242 <secret_phase> 401635: bf 58 25 40 00 mov $0x402558,%edi 40163a: e8 d1 f4 ff ff call 400b10 <puts@plt> 40163f: 48 8b 44 24 68 mov 0x68(%rsp),%rax 401644: 64 48 33 04 25 28 00 xor %fs:0x28,%rax 40164b: 00 00 40164d: 74 05 je 401654 <phase_defused+0x90> 40164f: e8 dc f4 ff ff call 400b30 <__stack_chk_fail@plt> 401654: 48 83 c4 78 add $0x78,%rsp 401658: c3 ret 401659: 90 nop 40165a: 90 nop 40165b: 90 nop 40165c: 90 nop 40165d: 90 nop 40165e: 90 nop 40165f: 90 nop
那我们看看0x603870位置的那个字符串是什么东西
7 0 ??? 好像是我们的第四个输入, 不确定, 改一下再看看
好了现在确定了, 就是第四个phase的输入, 也就是说我们第四个phase其实可以在后面再输入一个字符串。 看看0x40266处的字符串, 这就是我们要输入的
“DrEvil”, 我们到phase_4的时候把它加到最后试试
成功触发了secret_phase! But finding it and solving it are quite different…
secret_phase要求我们输入一个数, 然后它调用fun7()函数, 要求返回值必须是2
我们给fun7()传的第一个参数是一个指针, 而且看看fun7()的反汇编, 感觉应该是个链表???但是又不太对, 因为它还有取值, 所以应该是一个结构体!!! 我们把0x6030f0附近的东西数出来看看(当然, 我写这篇博客的时候已经知道它是什么了)
这样看应该就是个结构体, 里面存了一个long, 两个指针, 后面是空白的填充。 这里一共有15个结构体元素, 在C代码里应该是这样的
1 2 3 4 5 struct Chain { int val; int nxt1, nxt2; } chn[15 ];
我们把这15个元素顺序排好, 然后根据fun7()的反汇编, 可以写出如下的C代码
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 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 #include <stdio.h> struct Chain { int val; int nxt1, nxt2; } chn[15 ]; void Init () { chn[0 ] = (struct Chain){36 , 1 , 2 }; chn[1 ] = (struct Chain){8 , 5 , 3 }; chn[2 ] = (struct Chain){50 , 4 , 6 }; chn[3 ] = (struct Chain){22 , 12 , 10 }; chn[4 ] = (struct Chain){45 , 7 , 13 }; chn[5 ] = (struct Chain){6 , 8 , 11 }; chn[6 ] = (struct Chain){107 , 9 , 14 }; chn[7 ] = (struct Chain){40 , -1 , -1 }; chn[8 ] = (struct Chain){1 , -1 , -1 }; chn[9 ] = (struct Chain){99 , -1 , -1 }; chn[10 ] = (struct Chain){35 , -1 , -1 }; chn[11 ] = (struct Chain){7 , -1 , -1 }; chn[12 ] = (struct Chain){20 , -1 , -1 }; chn[13 ] = (struct Chain){47 , -1 , -1 }; chn[14 ] = (struct Chain){1001 , -1 , -1 }; } int fun7 (int p, int x) { if (p == -1 ) return -1 ; int tmp = chn[p].val; if (tmp < x) return fun7(chn[p].nxt2, x) * 2 + 1 ; else if (tmp == x) return 0 ; else return fun7(chn[p].nxt1, x) * 2 ; } int main () { Init(); for (int i = 1 ; i <= 1001 ; i ++) if (fun7(0 , i) == 2 ) { printf ("%d\n" , i); return 0 ; } return 0 ; }
因为fun7()里有递归调用, 而且我也没有看出它的实际意义, 反正输入在1~1001之内, 那我暴力跑一跑看看谁能让它的的返回值是2不就行了
20可以, 输进去看看
成功拆除, 这个lab就算做完了