实验二:拆解炸弹实验
要想拆解炸弹我们首先需要知道bomb程序是如何工作的,所以我们先通过 objdump -d bomb >bomb.s
指令来得到程序的汇编代码。
由于这关需要查看大量的汇编代码,同时需要通过查找在很多函数中来回跳转,通过原有的方式登录服务器的方式并不方便,所以通过vscode进行远程连接:

通过vscode连接到远端服务器后,可以方便的查看汇编代码,同时添加注释也更加方便快捷。左侧显示目录下的文件,下方时命令的输入窗口,中间则显示打开的文件内容。
第一关
我们先阅读bomb.c中的内容。找到第一关的函数名:phase_1
1 2 3 4 5 6 7
| input = read_line();
phase_1(input);
phase_defused();
|
在vscode中同时按 ctrl
+F
开启查找搜索框

找到第一关的汇编代码。
我们对第一关的汇编代码添加注释,方便我们理解这一关的步骤。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 0000000000400f2d <phase_1>:
400f2d: 48 83 ec 08 sub $0x8,%rsp //%rsp-=8
400f31: be 80 26 40 00 mov $0x402680,%esi //将地址$0x402680赋给%esi
400f36: e8 99 04 00 00 call 4013d4 <strings_not_equal> //调用函数strings_not_equal
400f3b: 85 c0 test %eax,%eax //检测%eax是否为0
400f3d: 74 05 je 400f44 <phase_1+0x17> //如果相等则跳转 400f44
400f3f: e8 64 07 00 00 call 4016a8 <explode_bomb> //引爆炸弹
400f44: 48 83 c4 08 add $0x8,%rsp //%rsp+=8
400f48: c3 ret
|
这里第一关中,又调用了 strings_not_equal
函数,那么我们在查找 strings_not_equal
函数看一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 00000000004013d4 <strings_not_equal>:
4013d4: 41 54 push %r12
4013d6: 55 push %rbp
4013d7: 53 push %rbx
4013d8: 48 89 fb mov %rdi,%rbx
4013db: 48 89 f5 mov %rsi,%rbp
4013de: e8 d3 ff ff ff call 4013b6 <string_length>
4013e3: 41 89 c4 mov %eax,%r12d
4013e6: 48 89 ef mov %rbp,%rdi
4013e9: e8 c8 ff ff ff call 4013b6 <string_length>
|
这里我们先看一下这个函数的前半部分,我们可以发现这个函数有两个参数,第一关参数存放在 %rdi
中,第二个参数存放在 %rsi
中,结合函数的名字可得,这个函数的功能大概是比较输入字符串和一个固定字符串是否相等。
我们可以在地址 4013d8
上设置一个断点来看一下:
通过 gdb bomb
来调试bomb
然后我们运行:

这里我们先随便输入一个字符串,不用在意。

分别打印两个寄存器中内存地址中的内容,可以看到一个字符串是我们自己输入的字符串,另一个字符串就是对比的字符串,也就是这一题的答案。

输入这串字符后,第一关就通关啦。
第二关
我们同样先找到 phase_2
函数的汇编代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 0000000000400f49 <phase_2>:
400f49: 55 push %rbp //将寄存器值入栈
400f4a: 53 push %rbx //将寄存器值入栈
400f4b: 48 83 ec 28 sub $0x28,%rsp //栈指针减去40
400f4f: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax //%fs中的基地址偏移40个字节的位置保存在%rax中
400f56: 00 00
400f58: 48 89 44 24 18 mov %rax,0x18(%rsp) //把%rax中的值保存在栈指针偏移24的位置
400f5d: 31 c0 xor %eax,%eax //%eax = 0
400f5f: 48 89 e6 mov %rsp,%rsi //%rsp的值移到%rsi
400f62: e8 77 07 00 00 call 4016de <read_six_numbers>
|
这里我们先看调用 read_six_numbers
之前的内容,这里在调用函数之前,先给栈开辟了一块40字节的空间,然后将栈顶指针当作函数参数传递给了 %rsi
。我们可以知道读取的6个数应该是存在了栈中
然后我们看看读取了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
| 400f67: 83 3c 24 00 cmpl $0x0,(%rsp) //将栈顶元素与0进行比较
400f6b: 79 05 jns 400f72 <phase_2+0x29> //如果(%rsp)-0 大于0,则跳转 400f72
400f6d: e8 36 07 00 00 call 4016a8 <explode_bomb> //若未跳转则爆炸
400f72: 48 89 e5 mov %rsp,%rbp //将栈顶指针赋给%rbp
400f75: bb 01 00 00 00 mov $0x1,%ebx //令%ebx = 1
400f7a: 89 d8 mov %ebx,%eax //令%ebx = %eax
400f7c: 03 45 00 add 0x0(%rbp),%eax //令%eax += (%rbp)
400f7f: 39 45 04 cmp %eax,0x4(%rbp) //令比较%eax 和 (%rbp)的下一个数
400f82: 74 05 je 400f89 <phase_2+0x40> //如果相等就跳转400f89
400f84: e8 1f 07 00 00 call 4016a8 <explode_bomb> //不等就会爆炸
400f89: 83 c3 01 add $0x1,%ebx //%ebx+=1
400f8c: 48 83 c5 04 add $0x4,%rbp //让%rbp指向下一个数字
400f90: 83 fb 06 cmp $0x6,%ebx //比较%ebx和6
400f93: 75 e5 jne 400f7a <phase_2+0x31> //不等就跳转(判断是否进行6次判断)
|
我们根据这段代码的逻辑可以发现这6个数字有以下要求
- 第一个数字要大于0
- a2-a1 = 1,a3-a2 = 2,a4-a3 = 3,a5-a4 = 4,a6-a5 = 5
既然已经知道这6个数字的要求,那么我们接下来看一下 read_six_numbers
的内容:
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
| 00000000004016de <read_six_numbers>:
4016de: 48 83 ec 08 sub $0x8,%rsp // 栈指针减8,分配栈空间
4016e2: 48 89 f2 mov %rsi,%rdx // %rdx = %rsi(指向第一个数的存储位置)
4016e5: 48 8d 4e 04 lea 0x4(%rsi),%rcx // %rcx = %rsi + 4(第二个数)
4016e9: 48 8d 46 14 lea 0x14(%rsi),%rax // %rax = %rsi + 20(第六个数)
4016ed: 50 push %rax // 将第六个数的地址压栈(第8个参数)
4016ee: 48 8d 46 10 lea 0x10(%rsi),%rax // %rax = %rsi + 16(第五个数)
4016f2: 50 push %rax // 将第五个数的地址压栈(第7个参数)
4016f3: 4c 8d 4e 0c lea 0xc(%rsi),%r9 // %r9 = %rsi + 12(第四个数)
4016f7: 4c 8d 46 08 lea 0x8(%rsi),%r8 // %r8 = %rsi + 8(第三个数)
4016fb: be 71 29 40 00 mov $0x402971,%esi // %esi = 格式化字符串地址
401700: b8 00 00 00 00 mov $0x0,%eax // 初始化%eax = 0
401705: e8 36 f5 ff ff call 400c40 <__isoc99_sscanf@plt>
40170a: 48 83 c4 10 add $0x10,%rsp // 恢复栈指针
40170e: 83 f8 05 cmp $0x5,%eax // 判断是否成功读取6个数
401711: 7f 05 jg 401718 <read_six_numbers+0x3a> // 如果大于5则继续
401713: e8 90 ff ff ff call 4016a8 <explode_bomb> // 否则引爆炸弹
401718: 48 83 c4 08 add $0x8,%rsp // 恢复栈指针
40171c: c3 ret // 返回
// 读取的6个数在栈中的顺序:
// 数字1 -> 地址 %rsi
// 数字2 -> 地址 %rsi + 4
// 数字3 -> 地址 %rsi + 8
// 数字4 -> 地址 %rsi + 12
// 数字5 -> 地址 %rsi + 16
// 数字6 -> 地址 %rsi + 20
|
这里我们根据每个寄存器作为第几个参数以及其中对应的地址,我们可以得到6个数字是按照输入顺序存储的在,所以这里无需额外注意。
这里我们需要查看以下地址 $0x402971
上的内容,这个字符串规定了6个数字的输入格式。
我们输入代码 p (char*) 0x402971

所以我们按照规定格式输入满足要求的数字: 1 2 4 7 11 16

这样我们就顺利通过第二关啦。
第三关

我们从而得知这关需要我们输入两个正整数。
然后我们就可以分析一下第三关的汇编代码:
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
| 0000000000400fb1 <phase_3>:
400fb1: 48 83 ec 18 sub $0x18,%rsp //栈指针减24
400fb5: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax //%fs中的基地址偏移40个字节的位置保存在%rax中
400fbc: 00 00
400fbe: 48 89 44 24 08 mov %rax,0x8(%rsp) //(%rsp+8) = %rax(栈中的第三个值)
400fc3: 31 c0 xor %eax,%eax //%eax^=%eax(%eax = 0)
400fc5: 48 8d 4c 24 04 lea 0x4(%rsp),%rcx //%rcx = %rsp+4 (第4个参数)(第2个读取的数的位置在栈的第2个位置)
400fca: 48 89 e2 mov %rsp,%rdx //%rdx = %rsp (作为第3个传入参数)(第1个读取的数的位置在栈的第1个位)
400fcd: be 7d 29 40 00 mov $0x40297d,%esi //%esi = 0x40297(输入格式字符串的地址)(第2个参数)
400fd2: e8 69 fc ff ff call 400c40 <__isoc99_sscanf@plt>//按照格式进行输入(%d %d)
400fd7: 83 f8 01 cmp $0x1,%eax //比较%eax和1的大小(检测是否成功读取两个数字)
400fda: 7f 05 jg 400fe1 <phase_3+0x30> //如果有符号大于则跳转到400fe1
400fdc: e8 c7 06 00 00 call 4016a8 <explode_bomb> //如果小于则爆照
400fe1: 83 3c 24 07 cmpl $0x7,(%rsp) //比较*(%rsp)的值和7的大小(0<=d1<=7)
400fe5: 77 63 ja 40104a <phase_3+0x99> //无符号大于则跳转40104a(会爆炸)
400fe7: 8b 04 24 mov (%rsp),%eax //%exa = *(%rsp) //%eax = d1
400fea: ff 24 c5 e0 26 40 00 jmp *0x4026e0(,%rax,8) //无条件跳转到*(0x4026e0+%rax*8)的位置(d1 = 0时不跳转)
400ff1: b8 0f 03 00 00 mov $0x30f,%eax //%eax = $0x30f
400ff6: eb 05 jmp 400ffd <phase_3+0x4c>//直接跳转到:400ffd
400ff8: b8 00 00 00 00 mov $0x0,%eax //%eax = 0(暂时看来无效)(d1 = 1时跳转到这里)
400ffd: 83 e8 77 sub $0x77,%eax //%eax-=119
401000: eb 05 jmp 401007 <phase_3+0x56>//直接跳转到:401007
401002: b8 00 00 00 00 mov $0x0,%eax //%eax = 0(暂时看来无效)(d1 = 2时跳转到这里)
401007: 05 95 02 00 00 add $0x295,%eax //%eax+=661
40100c: eb 05 jmp 401013 <phase_3+0x62>//直接跳转到:401013
40100e: b8 00 00 00 00 mov $0x0,%eax //%eax = 0(暂时看来无效)(d1 = 3时跳转到这里)
401013: 2d bc 01 00 00 sub $0x1bc,%eax //%eax-=444
401018: eb 05 jmp 40101f <phase_3+0x6e>//直接跳转到:40101f
40101a: b8 00 00 00 00 mov $0x0,%eax //%eax = 0(暂时看来无效)(d1 = 4时跳转到这里)
40101f: 05 bc 01 00 00 add $0x1bc,%eax //%eax+=444
401024: eb 05 jmp 40102b <phase_3+0x7a>//直接跳转到:40102b
401026: b8 00 00 00 00 mov $0x0,%eax //%eax = 0(暂时看来无效)(d1 = 5时跳转到这里)
40102b: 2d bc 01 00 00 sub $0x1bc,%eax //%eax-=444
401030: eb 05 jmp 401037 <phase_3+0x86>//直接跳转到:401037
401032: b8 00 00 00 00 mov $0x0,%eax //%eax = 0(暂时看来无效)(d1 = 6时跳转到这里)
401037: 05 bc 01 00 00 add $0x1bc,%eax //%eax+=444
40103c: eb 05 jmp 401043 <phase_3+0x92>//直接跳转到:401043
40103e: b8 00 00 00 00 mov $0x0,%eax //%eax = 0(暂时看来无效)(d1 = 7时跳转到这里)
401043: 2d bc 01 00 00 sub $0x1bc,%eax //%eax-=444
401048: eb 0a jmp 401054 <phase_3+0xa3>//直接跳转到:401054
40104a: e8 59 06 00 00 call 4016a8 <explode_bomb> //
40104f: b8 00 00 00 00 mov $0x0,%eax//%eax = 0(暂时看来无效)
401054: 83 3c 24 05 cmpl $0x5,(%rsp) //比较栈顶的值和5的大小(b1<=5)
401058: 7f 06 jg 401060 <phase_3+0xaf>//有符号数更大则跳转(爆炸)
40105a: 3b 44 24 04 cmp 0x4(%rsp),%eax //比d2和%eax的大小
40105e: 74 05 je 401065 <phase_3+0xb4> //相等则跳转(避免爆炸)
401060: e8 43 06 00 00 call 4016a8 <explode_bomb>
401065: 48 8b 44 24 08 mov 0x8(%rsp),%rax //将栈中第三个值赋给%rax
40106a: 64 48 33 04 25 28 00 xor %fs:0x28,%rax //%rax的值和%fs中的基地址偏移40个字节的位置的值异或
401071: 00 00
401073: 74 05 je 40107a <phase_3+0xc9> //如果相等就跳转
401075: e8 16 fb ff ff call 400b90 <__stack_chk_fail@plt>
40107a: 48 83 c4 18 add $0x18,%rsp //%rsp+=24,恢复栈的大小
40107e: c3 ret
|
第三关需要注意的是:当d1不同的时候,我们的程序会跳转到不同的地方,就需要不同的d2的值。注意在函数的最后,还限制了第一个数必须小于等于5,所以6和7虽然能通过前面的验证,但是过不了后面的检测。
d1可能的情况一共有5种,对应的正确的d2如下:
- 0 881
- 1 98
- 2 217
- 3 -444
- 4 0
- 5 -444
只要输入其中任一一组值,就可以通过第三关。每一个答案都经过验证(甚至在验证的过程种不小心爆了一次)。
第四关
我们同样首先来看第四关的汇编代码:
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
| 00000000004010b2 <phase_4>:
4010b2: 48 83 ec 18 sub $0x18,%rsp //%rsp-=24
4010b6: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax
4010bd: 00 00
4010bf: 48 89 44 24 08 mov %rax,0x8(%rsp) //%rax放在(%rsp+8)的地址上
4010c4: 31 c0 xor %eax,%eax //令%eax = 0
4010c6: 48 8d 4c 24 04 lea 0x4(%rsp),%rcx //%rcx = %rsp+4 (第四个参数)
4010cb: 48 89 e2 mov %rsp,%rdx //%rdx = %rsp (第三个参数)
4010ce: be 7d 29 40 00 mov $0x40297d,%esi //%esi = 0x40297d (第二个参数)(%d %d)
4010d3: e8 68 fb ff ff call 400c40 <__isoc99_sscanf@plt> //进行输入
4010d8: 83 f8 02 cmp $0x2,%eax //是否正确读入两个数据
4010db: 75 06 jne 4010e3 <phase_4+0x31> //如果不相等就跳转(爆炸)
4010dd: 83 3c 24 0e cmpl $0xe,(%rsp) //比较d1和和14的大小 (d1 <= 14)
4010e1: 76 05 jbe 4010e8 <phase_4+0x36> //如果无符号小于或等于就跳转(避免爆炸)
4010e3: e8 c0 05 00 00 call 4016a8 <explode_bomb>
4010e8: ba 0e 00 00 00 mov $0xe,%edx //%edx = 14 (第三个参数 = 14)
4010ed: be 00 00 00 00 mov $0x0,%esi //%esi = 0 (第二个参数 = 0)
4010f2: 8b 3c 24 mov (%rsp),%edi //%edi = d1 (第一个参数 = d1)
4010f5: e8 85 ff ff ff call 40107f <func4> //调用函数func4
4010fa: 83 f8 07 cmp $0x7,%eax //比较返回值和7的大小(返回值 == 7)
4010fd: 75 07 jne 401106 <phase_4+0x54> //如果不相等跳转401106,爆炸
4010ff: 83 7c 24 04 07 cmpl $0x7,0x4(%rsp) //比较b2和7的大小(b2 == 7)
401104: 74 05 je 40110b <phase_4+0x59> //相等跳转(避免爆炸)
401106: e8 9d 05 00 00 call 4016a8 <explode_bomb>
40110b: 48 8b 44 24 08 mov 0x8(%rsp),%rax //
401110: 64 48 33 04 25 28 00 xor %fs:0x28,%rax
401117: 00 00
401119: 74 05 je 401120 <phase_4+0x6e>
40111b: e8 70 fa ff ff call 400b90 <__stack_chk_fail@plt>
401120: 48 83 c4 18 add $0x18,%rsp
401124: c3 ret
|
这里输入的方式和第三关的地址是一样的,所以这一关照样是读取两个整数。
通过阅读汇编代码,我们发现第四关中还调用了一个func4函数,并且这个函数有三个参数,分别为
- %edi = d1(我们输入的第一个数)
- %esi = 0
- %edx = 14
同时,func4还有一个返回值,返回值的结果需要等于7
我们输入的第二个数字b2也需要等于7
就可以通过这一关了。
弄清了这一点,我们再来看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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| 000000000040107f <func4>:
40107f: 53 push %rbx
401080: 89 d0 mov %edx,%eax //%eax = %edx = 14
401082: 29 f0 sub %esi,%eax //%eax-= %esi = 14
401084: 89 c3 mov %eax,%ebx //%ebx = %eax = 14
401086: c1 eb 1f shr $0x1f,%ebx //%ebx >> 31(%ebx = 0)
401089: 01 d8 add %ebx,%eax //%eax += %ebx (=14)
40108b: d1 f8 sar $1,%eax //逻辑右移1位 %eax/=2 = 7
40108d: 8d 1c 30 lea (%rax,%rsi,1),%ebx //%ebx = %rax+ %rsi = 7
401090: 39 fb cmp %edi,%ebx //比较%edi(d1)和%ebx(7)
401092: 7e 0c jle 4010a0 <func4+0x21> //有符号小于等于就跳转4010a0 (d1 <= 14)
401094: 8d 53 ff lea -0x1(%rbx),%edx //%edx = %rbx-1
401097: e8 e3 ff ff ff call 40107f <func4> //再次调用func4
40109c: 01 d8 add %ebx,%eax //%eax+=%ebx
40109e: eb 10 jmp 4010b0 <func4+0x31> //跳转4010b0
4010a0: 89 d8 mov %ebx,%eax //%eax = %ebx(7)
4010a2: 39 fb cmp %edi,%ebx //比较%edi(d1)和%ebx(7)
4010a4: 7d 0a jge 4010b0 <func4+0x31> //有符号大于等于就跳转4010b0
4010a6: 8d 73 01 lea 0x1(%rbx),%esi //%esi = %rbx+1
4010a9: e8 d1 ff ff ff call 40107f <func4> //调用func4
4010ae: 01 d8 add %ebx,%eax //%eax+=%ebx
4010b0: 5b pop %rbx
4010b1: c3
|
在分析的中途,我发现func4中有两次调用了函数自己,这会让分析变的非常复杂,好在我们发现只要d1满足的一定条件,我们就可以跳过再次调用func4。要满足的条件如下:
只要满足这两个个条件,我们就可以让两次条件跳转都进行跳转,从而避免再次调用func4
我们可以化简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
| 000000000040107f <func4>:
40107f: 53 push %rbx
401080: 89 d0 mov %edx,%eax //%eax = %edx = 14
401082: 29 f0 sub %esi,%eax //%eax-= %esi = 14
401084: 89 c3 mov %eax,%ebx //%ebx = %eax = 14
401086: c1 eb 1f shr $0x1f,%ebx //%ebx >> 31(%ebx = 0)
401089: 01 d8 add %ebx,%eax //%eax += %ebx (=14)
40108b: d1 f8 sar $1,%eax //逻辑右移1位 %eax/=2 = 7
40108d: 8d 1c 30 lea (%rax,%rsi,1),%ebx //%ebx = %rax+ %rsi = 7
4010a0: 89 d8 mov %ebx,%eax //%eax = %ebx(7)
4010b0: 5b pop %rbx
4010b1: c3 ret
|
此时,返回值为%eax = %ebx = 7
满足第四关中对于函数返回值的要求,所以这一关的一个答案就是 7 7
。
第五关
我们直接看第五关的汇编代码:
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
| 0000000000401125 <phase_5>:
401125: 53 push %rbx
401126: 48 83 ec 10 sub $0x10,%rsp //栈指针减16
40112a: 48 89 fb mov %rdi,%rbx //%rbx = %rdi(传进来的第一个参数)
40112d: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax
401134: 00 00
401136: 48 89 44 24 08 mov %rax,0x8(%rsp)//(%rsp+8) = %rax
40113b: 31 c0 xor %eax,%eax//%eax = 0
40113d: e8 74 02 00 00 call 4013b6 <string_length>//调用string_length函数
401142: 83 f8 06 cmp $0x6,%eax//比较%eax和6的大小(字符串长度为6)
401145: 74 05 je 40114c <phase_5+0x27>//相等跳转,避免爆炸
401147: e8 5c 05 00 00 call 4016a8 <explode_bomb>
40114c: b8 00 00 00 00 mov $0x0,%eax//%eax = 0
401151: 0f b6 14 03 movzbl (%rbx,%rax,1),%edx//%edx = %rbx+%rax(%edx = Si)
401155: 83 e2 0f and $0xf,%edx //%edx &=15(保留前4位)(%edx = Si & 15)
401158: 0f b6 92 20 27 40 00 movzbl 0x402720(%rdx),%edx //%edx = 0x402720+%edx
40115f: 88 14 04 mov %dl,(%rsp,%rax,1) //(%rsp+%rax) = %edx
401162: 48 83 c0 01 add $0x1,%rax //%rax+=1
401166: 48 83 f8 06 cmp $0x6,%rax //比较%rax和6的大小
40116a: 75 e5 jne 401151 <phase_5+0x2c>不等就跳转401151
40116c: c6 44 24 06 00 movb $0x0,0x6(%rsp) //(%rsp+6) = 0
401171: be d6 26 40 00 mov $0x4026d6,%esi //%esi = 0x4026d6
401176: 48 89 e7 mov %rsp,%rdi //%rdi = %rsi
401179: e8 56 02 00 00 call 4013d4 <strings_not_equal>//比较字符串是否相等
40117e: 85 c0 test %eax,%eax //%eax是否等于0
401180: 74 05 je 401187 <phase_5+0x62> //等于则跳转,避免爆炸
401182: e8 21 05 00 00 call 4016a8 <explode_bomb>
401187: 48 8b 44 24 08 mov 0x8(%rsp),%rax
40118c: 64 48 33 04 25 28 00 xor %fs:0x28,%rax
401193: 00 00
401195: 74 05 je 40119c <phase_5+0x77>
401197: e8 f4 f9 ff ff call 400b90 <__stack_chk_fail@plt>
40119c: 48 83 c4 10 add $0x10,%rsp
4011a0: 5b pop %rbx
4011a1: c3 ret
|
这一关的代码要我们输入长度为6的字符串。其中,这个函数给出了两个固定的地址,我们都先打印出来看看:
- 0x402720的串为:
maduiersnfotvbylSo you think you can stop the bomb with ctrl-c, do you?
- 0x4026d6的串为:
bruin
我们阅读汇编代码可以得到如下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
|
intphase_5(char str[6])
{
charans[6];
char str2[100] ="maduiersnfotvbylSo you think you can stop the bomb with ctrl-c, do you?";
char str3[10] ="bruins";
for(int i =0;i<6;i++)
{
ans[i] =str2[(int)str[i] &15];
}
if(strings_not_equal(ans,str3))
{
printf("BOOM!!!\n");
return0;
}
return1;
}
|
所以我们需要从str2中找到前15个字符中的b r u i n s 每个字母的下标,并且给每个下标+=48,再从ascall码表中把每个数字翻译为字符,按顺序输入。
如字符b ,在str2中下标为13,13+48 = 61 为’=’。
按照这样的方法,我们得到字符串 =63487
。这就是这一题的其中一个答案。
第六关
第六关涉及多个循环,我们一个一个拆开来看,首先我们来看第一个大循环:
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
| 00000000004011a2 <phase_6>:
4011a2: 41 55 push %r13
4011a4: 41 54 push %r12
4011a6: 55 push %rbp
4011a7: 53 push %rbx
4011a8: 48 83 ec 68 sub $0x68,%rsp
4011ac: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax
4011b3: 00 00
4011b5: 48 89 44 24 58 mov %rax,0x58(%rsp) //
4011ba: 31 c0 xor %eax,%eax //%eax = 0
4011bc: 48 89 e6 mov %rsp,%rsi //%rsi = %rsp (第二个参数)
4011bf: e8 1a 05 00 00 call 4016de <read_six_numbers> //读取6个整数
4011c4: 49 89 e4 mov %rsp,%r12 //%r12 = %rsp
4011c7: 41 bd 00 00 00 00 mov $0x0,%r13d //%r13d = 0
4011cd: 4c 89 e5 mov %r12,%rbp //%rbp = %r12 (1.rbp = &d1,2.rbp = &d2)
4011d0: 41 8b 04 24 mov (%r12),%eax //%eax = *(%r12)
4011d4: 83 e8 01 sub $0x1,%eax //%eax -= 1
4011d7: 83 f8 05 cmp $0x5,%eax //%eax <= 5?(d1,d2,d3,d4,d5,d6 <=6)
4011da: 76 05 jbe 4011e1 <phase_6+0x3f> //满足则跳转4011e1
4011dc: e8 c7 04 00 00 call 4016a8 <explode_bomb>
4011e1: 41 83 c5 01 add $0x1,%r13d //%r13d +=1
4011e5: 41 83 fd 06 cmp $0x6,%r13d //%r13d == 6?
4011e9: 74 3d je 401228 <phase_6+0x86> //相等就跳转401228
4011eb: 44 89 eb mov %r13d,%ebx //%ebx = %r13d
4011ee: 48 63 c3 movslq %ebx,%rax //%rax = %ebx
4011f1: 8b 04 84 mov (%rsp,%rax,4),%eax //%eax = %rsp+ %rax*4
4011f44011f4: 39 45 00 cmp %eax,0x0(%rbp) //%eax == *(%rbp)?
4011f7: 75 05 jne 4011fe <phase_6+0x5c> //不相等就跳转4011fe
4011f9: e8 aa 04 00 00 call 4016a8 <explode_bomb> //相等就爆炸
4011fe: 83 c3 01 add $0x1,%ebx //%ebx +=1
401201: 83 fb 05 cmp $0x5,%ebx //%ebx <= 5?
401204: 7e e8 jle 4011ee <phase_6+0x4c> //小于等于则跳转(d1,d2,d3,d4,d5,d6 不能有重复数字)
401206: 49 83 c4 04 add $0x4,%r12 //%r12 += 4 (%r12 = &d2)
40120a: eb c1 jmp 4011cd <phase_6+0x2b>//直接跳转4011cd
|
第一个大循环中还套了一个小循环,大循环确保输入的6个数字范围都在1~6之间,小循环确保输入的6个数字都不重复。那么我们就大概知道了输入的6个数字应该是1到6的排列组合。
后续的要求可能是对于我们输入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
| //让%rdx = (dn * 8 + 0x6042f0)
40120c: 48 8b 52 08 mov 0x8(%rdx),%rdx //%rdx = *(%rdx+8)
401210: 83 c0 01 add $0x1,%eax //%eax+=1
401213: 39 c8 cmp %ecx,%eax //%ecx == %eax?
401215: 75 f5 jne 40120c <phase_6+0x6a> //不相等则跳转40120c
//将%rdx存到栈中
401217: 48 89 54 74 20 mov %rdx,0x20(%rsp,%rsi,2) //*(%rsp+%rsi*2+32) = %rdx
40121c: 48 83 c6 04 add $0x4,%rsi //%rsi+=4
401220: 48 83 fe 18 cmp $0x18,%rsi //%rsi == 24
401224: 75 07 jne 40122d <phase_6+0x8b>//不相等则跳转40122d
401226: eb 19 jmp 401241 <phase_6+0x9f>//相等则跳转401241
//%r13d = 6 %ebx = 6 %rsi = 6
401228: be 00 00 00 00 mov $0x0,%esi //%esi = 0
40122d: 8b 0c 34 mov (%rsp,%rsi,1),%ecx//%ecx = *(%rsp+%rsi)
401230: b8 01 00 00 00 mov $0x1,%eax //%eax = 1
401235: ba f0 42 60 00 mov $0x6042f0,%edx //%edx = 0x6042f0
40123a: 83 f9 01 cmp $0x1,%ecx //%ecx > 1?
40123d: 7f cd jg 40120c <phase_6+0x6a>//dn大于1跳转40120c
40123f: eb d6 jmp 401217 <phase_6+0x75>//否则跳转401217
|
第二个循环的作用是把地址是 0x6042f0
的一个数组中的第 dn 个数放在一个新数组的位置 n 上。
我们找到这个数组的前6个数分别是:
所以这段代码的主要作用是根据我们输入的6个数字对于这6个数进行重新排序,比如我们输入的数为:2 1 4 3 6 5
那么这6个数的排序为:
清楚这段代码的作用后,我们再来看下一段代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
401241: 48 8b 5c 24 20 mov 0x20(%rsp),%rbx //%rbx = *(%rsp+32)
401246: 48 8d 44 24 20 lea 0x20(%rsp),%rax //%rax = rsp+32(第1个数地址)
40124b: 48 8d 74 24 48 lea 0x48(%rsp),%rsi //%rsi = %rsp+72(第6个数地址)
401250: 48 89 d9 mov %rbx,%rcx //%rcx = %rbx
401253: 48 8b 50 08 mov 0x8(%rax),%rdx //%rdx = *(%rax+8)
401257: 48 89 51 08 mov %rdx,0x8(%rcx) //*(%rcx+8) = %rdx
40125b: 48 83 c0 08 add $0x8,%rax //%rax += 8
40125f: 48 89 d1 mov %rdx,%rcx //%rcx = %rdx
401262: 48 39 f0 cmp %rsi,%rax //%rax == %rsi
401265: 75 ec jne 401253 <phase_6+0xb1>//不相等则跳转401253
|
这段代码似乎作用不大,仅仅是将之前我们存在栈中的数据复制到了另一个地方。
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
|
//确保数组为升序
401267: 48 c7 42 08 00 00 00 movq $0x0,0x8(%rdx) //*(%rdx+8) = 0
40126e: 00
40126f: bd 05 00 00 00 mov $0x5,%ebp //%edp = 5
401274: 48 8b 43 08 mov 0x8(%rbx),%rax //%rax = *(%rbx+8)
401278: 8b 00 mov (%rax),%eax //%eax = *(%rax)
40127a: 39 03 cmp %eax,(%rbx) //*(%rbx)<=%eax
40127c: 7e 05 jle 401283 <phase_6+0xe1>//满足则跳转401283
40127e: e8 25 04 00 00 call 4016a8 <explode_bomb>
401283: 48 8b 5b 08 mov 0x8(%rbx),%rbx //%rbx = *(%rbx+8)
401287: 83 ed 01 sub $0x1,%ebp //%ebp -=1
40128a: 75 e8 jne 401274 <phase_6+0xd2>//%ebp != 0 则跳转
|
这段代码的作用是确保我们的6个数字必须逐渐增大,结合上面我们的理解,那么我们按照6个数字的大小顺序进行输入:
5 1 3 4 2 6
就可以通关这一题啦。
至此6个炸弹已经全部拆解完毕。
