PHASE_1
使用到的命令:
objdump -t bomb | less
我们得到的bomb文件是一个二进制文件,使用 objdump 可以得到反汇编的代码;
-t 表示生成符号表,不必关注 . 开头的内容,可以看到phase_1/phase_2/.../phase_6,显然应该是对应不同的关卡;
| less 表示使用一种方便浏览的分页方式,可以使用对应的快捷键辅助浏览。
objdump -d bomb > bomb.txt
-d将需要执行的内容生成反汇编代码。
chmod 777 bomb
赋予文件的读写及可执行权限,避免出现permission denied。
gdb bomb
用gab打开文件
break explode_bomb
可以在函数名、行号等地方设置断点
run
执行被gdb打开的文件
disas
显示当前函数的反汇编代码
info registers
查看当前寄存器使用情况
print $eax
输出一个寄存器的内容
x/s $eax(或是一个内存地址如0x12314)
x 命令表示访问相应地址下的内容,/s是可选项,表示这个地址的内容是string
stepi x
单步执行,可以跟一个可选参数表示单步执行x次,默认是一次。执行后再使用disas可以看到箭头指示的当前指令有所变化。
对于这关:
0000000000400ef0 <phase_1>: 400ef0: 48 83 ec 08 sub $0x8,%rsp 400ef4: be 18 1b 40 00 mov $0x401b18,%esi 400ef9: e8 10 04 00 00 callq 40130e <strings_not_equal> 400efe: 85 c0 test %eax,%eax 400f00: 74 05 je 400f07 <phase_1+0x17> 400f02: e8 dc 07 00 00 callq 4016e3 <explode_bomb> 400f07: 48 83 c4 08 add $0x8,%rsp 400f0b: c3 retq
使用strings_not_equal这个函数接受两个参数,判断输入命令和答案是否一致,而ESI/EDI 分别叫做"源/目标索引寄存器"(source/destination index),出现在很多字符串操作指令中,所以使用到它的语句应该是有猫腻的。
对mov $0x401b18,%esi 所在的地址打断点后直接使用x/s就可得到答案,使用shift+ctrl+c/v在终端下复制黏贴答案:Science isn't about why, it's about why not?
PHASE_2
> 0x0000000000400f0c <+0>: push %rbp 0x0000000000400f0d <+1>: push %rbx 0x0000000000400f0e <+2>: sub $0x28,%rsp 0x0000000000400f12 <+6>: mov %rsp,%rsi 0x0000000000400f15 <+9>: callq 0x401705 <read_six_numbers> 0x0000000000400f1a <+14>: cmpl $0x1,(%rsp) //rsp=1,否则爆炸。这里用栈指针%rsp,说明这个函数的传参大于六个 0x0000000000400f1e <+18>: je 0x400f45 <phase_2+57> 0x0000000000400f20 <+20>: callq 0x4016e3 <explode_bomb> 0x0000000000400f25 <+25>: jmp 0x400f45 <phase_2+57> 0x0000000000400f27 <+27>: add $0x1,%ebx //ebx=2 0x0000000000400f2a <+30>: mov %ebx,%eax //eax=2 0x0000000000400f2c <+32>: imul -0x4(%rbp),%eax //eax = 2*rsp = 2 0x0000000000400f30 <+36>: cmp %eax,0x0(%rbp) //rbp == 2? 0x0000000000400f33 <+39>: je 0x400f3a <phase_2+46> 0x0000000000400f35 <+41>: callq 0x4016e3 <explode_bomb> 0x0000000000400f3a <+46>: add $0x4,%rbp 0x0000000000400f3e <+50>: cmp $0x6,%ebx 0x0000000000400f41 <+53>: jne 0x400f27 <phase_2+27> 0x0000000000400f43 <+55>: jmp 0x400f51 <phase_2+69> 0x0000000000400f45 <+57>: lea 0x4(%rsp),%rbp //rbp = rsp+4 0x0000000000400f4a <+62>: mov $0x1,%ebx //ebx = 1 0x0000000000400f4f <+67>: jmp 0x400f27 <phase_2+27> 0x0000000000400f51 <+69>: add $0x28,%rsp 0x0000000000400f55 <+73>: pop %rbx 0x0000000000400f56 <+74>: pop %rbp 0x0000000000400f57 <+75>: retq
重点关注
0x0000000000400f27 <+27>: add $0x1,%ebx //ebx=2
0x0000000000400f2a <+30>: mov %ebx,%eax //eax=2
0x0000000000400f2c <+32>: imul -0x4(%rbp),%eax //eax = 2*rsp = 2
0x0000000000400f30 <+36>: cmp %eax,0x0(%rbp) //rbp == 2?
0x0000000000400f33 <+39>: je 0x400f3a <phase_2+46>
0x0000000000400f35 <+41>: callq 0x4016e3 <explode_bomb>
0x0000000000400f3a <+46>: add $0x4,%rbp
0x0000000000400f3e <+50>: cmp $0x6,%ebx
0x0000000000400f41 <+53>: jne 0x400f27 <phase_2+27>
这段代码是一个循环,cmp $0x6, %ebx以及跳转后$0x1, %ebx表明这段代码对应类似for(i = 0; i < 6; i++)的循环代码。
这段代码有两个会触发explode_bomb的地方,第二处在这个循环内,第一处是在一开始检测第一个输入的数,可以看出,这个数是1。
在循环中,每次flag加一并且将flag与上一次得到的数相乘。
所以得出来的数是1 2 6 24 120 720。
PHASE_3
这一关是一个switch的应该,第一个参数决定跳转到哪一条指令,这里以输入0为例。输入[1,7]对应的答案不同
=> 0x0000000000400f58 <+0>: sub $0x18,%rsp //参考中文教材P196-197,x86通过固定栈指针加偏移量的方式访问变量,这里一开始移动栈指针%rsp分配了18个字节 0x0000000000400f5c <+4>: lea 0x8(%rsp),%r8 0x0000000000400f61 <+9>: lea 0x7(%rsp),%rcx 0x0000000000400f66 <+14>: lea 0xc(%rsp),%rdx //x86通过六个寄存器及栈来传参(64位下为rdi/rsi/rdx/rcx/r8/r9),传参顺序从右到左,所以这是第一个参数 0x0000000000400f6b <+19>: mov $0x401b6e,%esi //有了PHASE_1的经验,这里esi应该保存了一个字符串,我们通过x/s 0x401b6e打印之,发现是下面sscanf的格式化输入 0x0000000000400f70 <+24>: mov $0x0,%eax 0x0000000000400f75 <+29>: callq 0x400c00 <__isoc99_sscanf@plt> 0x0000000000400f7a <+34>: cmp $0x2,%eax 0x0000000000400f7d <+37>: jg 0x400f84 <phase_3+44> //这里调用sscanf会返回输入参数数量,要求大于2,否则爆炸 0x0000000000400f7f <+39>: callq 0x4016e3 <explode_bomb> 0x0000000000400f84 <+44>: cmpl $0x7,0xc(%rsp) //第一个参数和7比较,说明第一个参数应该在属于[0,7],否则爆炸 0x0000000000400f89 <+49>: ja 0x40108b <phase_3+307> 0x0000000000400f8f <+55>: mov 0xc(%rsp),%eax //将第一个参数存入eax 0x0000000000400f93 <+59>: jmpq *0x401b80(,%rax,8) //参考中文书P146,jmp的操作数前缀为*,表明这是一个间接跳转,操作数指定一个存储器位置,偏移量由eax给出 0x0000000000400f9a <+66>: mov $0x6a,%eax //对之前的0x401b80间接跳转(或者说对地址0x401b80解引用)得到的就是这一条指令的地址-- 400f9a 0x0000000000400f9f <+71>: cmpl $0x240,0x8(%rsp) //第三个参数和0x240(即576)比较,不相等就爆炸 0x0000000000400fa7 <+79>: je 0x401095 <phase_3+317> 0x0000000000400fad <+85>: callq 0x4016e3 <explode_bomb> 0x0000000000400fb2 <+90>: mov $0x6a,%eax 0x0000000000400fb7 <+95>: jmpq 0x401095 <phase_3+317> 0x0000000000400fbc <+100>: mov $0x66,%eax 0x0000000000400fc1 <+105>: cmpl $0x3bc,0x8(%rsp) 0x0000000000400fc9 <+113>: je 0x401095 <phase_3+317> 0x0000000000400fcf <+119>: callq 0x4016e3 <explode_bomb> 0x0000000000400fd4 <+124>: mov $0x66,%eax 0x0000000000400fd9 <+129>: jmpq 0x401095 <phase_3+317> 0x0000000000400fde <+134>: mov $0x6a,%eax 0x0000000000400fe3 <+139>: cmpl $0x22a,0x8(%rsp) 0x0000000000400feb <+147>: je 0x401095 <phase_3+317> 0x0000000000400ff1 <+153>: callq 0x4016e3 <explode_bomb> 0x0000000000400ff6 <+158>: mov $0x6a,%eax 0x0000000000400ffb <+163>: jmpq 0x401095 <phase_3+317> 0x0000000000401000 <+168>: mov $0x76,%eax 0x0000000000401005 <+173>: cmpl $0xc9,0x8(%rsp) 0x000000000040100d <+181>: je 0x401095 <phase_3+317> 0x0000000000401013 <+187>: callq 0x4016e3 <explode_bomb> 0x0000000000401018 <+192>: mov $0x76,%eax 0x000000000040101d <+197>: jmp 0x401095 <phase_3+317> 0x000000000040101f <+199>: mov $0x62,%eax 0x0000000000401024 <+204>: cmpl $0x107,0x8(%rsp) 0x000000000040102c <+212>: je 0x401095 <phase_3+317> 0x000000000040102e <+214>: callq 0x4016e3 <explode_bomb> 0x0000000000401033 <+219>: mov $0x62,%eax 0x0000000000401038 <+224>: jmp 0x401095 <phase_3+317> 0x000000000040103a <+226>: mov $0x69,%eax 0x000000000040103f <+231>: cmpl $0x33b,0x8(%rsp) 0x0000000000401047 <+239>: je 0x401095 <phase_3+317> 0x0000000000401049 <+241>: callq 0x4016e3 <explode_bomb> 0x000000000040104e <+246>: mov $0x69,%eax 0x0000000000401053 <+251>: jmp 0x401095 <phase_3+317> 0x0000000000401055 <+253>: mov $0x71,%eax 0x000000000040105a <+258>: cmpl $0xc6,0x8(%rsp) 0x0000000000401062 <+266>: je 0x401095 <phase_3+317> 0x0000000000401064 <+268>: callq 0x4016e3 <explode_bomb> 0x0000000000401069 <+273>: mov $0x71,%eax 0x000000000040106e <+278>: jmp 0x401095 <phase_3+317> 0x0000000000401070 <+280>: mov $0x77,%eax 0x0000000000401075 <+285>: cmpl $0x174,0x8(%rsp) 0x000000000040107d <+293>: je 0x401095 <phase_3+317> 0x000000000040107f <+295>: callq 0x4016e3 <explode_bomb> 0x0000000000401084 <+300>: mov $0x77,%eax 0x0000000000401089 <+305>: jmp 0x401095 <phase_3+317> 0x000000000040108b <+307>: callq 0x4016e3 <explode_bomb> 0x0000000000401090 <+312>: mov $0x68,%eax 0x0000000000401095 <+317>: cmp 0x7(%rsp),%al //第二个参数和eax:0x0000000000400f9a <+66>: mov $0x6a,%eax,比较,这里没有使用立即数标志$,所以是ASCII字符的j 0x0000000000401099 <+321>: je 0x4010a0 <phase_3+328> 0x000000000040109b <+323>: callq 0x4016e3 <explode_bomb> 0x00000000004010a0 <+328>: add $0x18,%rsp 0x00000000004010a4 <+332>: retq
PHASE_4
00000000004010a5 <func4>: 4010a5: 53 push %rbx 4010a6: 89 fb mov %edi,%ebx 4010a8: b8 01 00 00 00 mov $0x1,%eax 4010ad: 83 ff 01 cmp $0x1,%edi 4010b0: 7e 0b jle 4010bd <func4+0x18> 4010b2: 8d 7f ff lea -0x1(%rdi),%edi 4010b5: e8 eb ff ff ff callq 4010a5 <func4> 4010ba: 0f af c3 imul %ebx,%eax 4010bd: 5b pop %rbx 4010be: c3 retq 00000000004010bf <phase_4>: 4010bf: 48 83 ec 18 sub $0x18,%rsp 4010c3: 48 8d 54 24 0c lea 0xc(%rsp),%rdx 4010c8: be 74 1b 40 00 mov $0x401b74,%esi 4010cd: b8 00 00 00 00 mov $0x0,%eax 4010d2: e8 29 fb ff ff callq 400c00 <__isoc99_sscanf@plt> 4010d7: 83 f8 01 cmp $0x1,%eax 4010da: 75 07 jne 4010e3 <phase_4+0x24> 4010dc: 83 7c 24 0c 00 cmpl $0x0,0xc(%rsp) 4010e1: 7f 05 jg 4010e8 <phase_4+0x29> 4010e3: e8 fb 05 00 00 callq 4016e3 <explode_bomb> 4010e8: 8b 7c 24 0c mov 0xc(%rsp),%edi 4010ec: e8 b4 ff ff ff callq 4010a5 <func4> 4010f1: 3d 00 5f 37 00 cmp $0x375f00,%eax 4010f6: 74 05 je 4010fd <phase_4+0x3e> 4010f8: e8 e6 05 00 00 callq 4016e3 <explode_bomb> 4010fd: 48 83 c4 18 add $0x18,%rsp 401101: c3 retq
在phase_4的代码中可以看到:
4010ec: e8 b4 ff ff ff callq 4010a5 <func4>
4010f1: 3d 00 5f 37 00 cmp $0x375f00,%eax
4010f6: 74 05 je 4010fd <phase_4+0x3e>
4010f8: e8 e6 05 00 00 callq 4016e3 <explode_bomb>
0x375f00等于3628800,接下来看func4的功能
参考书中p199的练习题3.52
递归阶乘函数 long int rfact(long int x) { if (x <= 0) return 1; else { long int xm1 = x - 1; return x * rfact(xm1); } } 生成汇编代码 rfact: pushq %rbx movq %rdi, %rbx movl $1, %eax testq %rdi, %rdi jle .L11 leaq -1(%rdi), %rdi call rfact imulq %rbx, %rax .L11: popq %rbx //restore %rbx ret
这段代码一开始pushq %rbx是为了保存寄存器rbx里的值,因为寄存器rbx是指定为被调用者保存的寄存器,包括%rbx,%rbp,%r12~%r15,在函数写这些寄存器时,必须先在栈上保存它们。
PHASE_6
0000000000401102 <phase_5>: 401102: 53 push %rbx 401103: 48 89 fb mov %rdi,%rbx 401106: e8 e6 01 00 00 callq 4012f1 <string_length> 40110b: 83 f8 06 cmp $0x6,%eax //输入字符串的长度必须是6,否则bomb 40110e: 74 05 je 401115 <phase_5+0x13> 401110: e8 ce 05 00 00 callq 4016e3 <explode_bomb> 401115: b8 00 00 00 00 mov $0x0,%eax // 把%eax %edx 寄存器赋值为0 ,%eax用作循环计数器, %edx用作累加器 40111a: ba 00 00 00 00 mov $0x0,%edx 40111f: 0f b6 0c 03 movzbl (%rbx,%rax,1),%ecx //%rbx + %rax*1 赋值给%ecx,%rbx保存的是%rdi的值,即第一个参数的值 401123: 83 e1 0f and $0xf,%ecx //取%ecx低4位赋值给%ecx 401126: 03 14 8d c0 1b 40 00 add 0x401bc0(,%rcx,4),%edx //以%rcx为index取出数组中的数并累加 40112d: 48 83 c0 01 add $0x1,%rax 401131: 48 83 f8 06 cmp $0x6,%rax //判断循环次数是否满6次 401135: 75 e8 jne 40111f <phase_5+0x1d> 401137: 83 fa 3e cmp $0x3e,%edx //判断累加是否已达到0x3e,即62 40113a: 74 05 je 401141 <phase_5+0x3f> 40113c: e8 a2 05 00 00 callq 4016e3 <explode_bomb> 401141: 5b pop %rbx 401142: c3 retq
一开始的输入检查要求我们输入六个数字a1,a2,a3,a4,a5,a6,随后通过一个循环把每个数字对应的A[ai]相加,使得到的总和为0x3e,即62
通过 x/6d 0x401bc0 可以得知数组中的六个数字是 2 10 6 1 12 16,下标从0开始。
这里可以选取1+1+12+16+16+16=62,对应下标3 3 4 5 5 5,每个下标都来自于一个寄存器的低4位,
所以可以取c c b e e e,ACSII值的低四位就是3 3 4 5 5 5。
PHASE_6
00000000004011b2 <phase_6>: 4011b2: 48 83 ec 08 sub $0x8,%rsp 4011b6: ba 0a 00 00 00 mov $0xa,%edx 4011bb: be 00 00 00 00 mov $0x0,%esi 4011c0: e8 1b fa ff ff callq 400be0 <strtol@plt> 4011c5: 89 05 55 21 20 00 mov %eax,0x202155(%rip) # 603320 <node0> 4011cb: bf 20 33 60 00 mov $0x603320,%edi 4011d0: e8 6e ff ff ff callq 401143 <fun6> 4011d5: 48 8b 40 08 mov 0x8(%rax),%rax 4011d9: 8b 0d 41 21 20 00 mov 0x202141(%rip),%ecx # 603320 <node0> 4011df: 39 08 cmp %ecx,(%rax) 4011e1: 74 05 je 4011e8 <phase_6+0x36> 4011e3: e8 fb 04 00 00 callq 4016e3 <explode_bomb> 4011e8: 48 83 c4 08 add $0x8,%rsp 4011ec: c3 retq
因为func6很长,所以可以用一个tricky的方法来解决问题,就是断点到最后判断爆炸与否的地方,直接x/d或者print *(int*)得到答案。