使用objdump工具对程序进行反汇编。
1 2 3 objdump -s -d bomb > bomb.out objdump -s -j .data bomb > bomb.data objdump -s -j .rodata bomb > bomb.rodata
Phase 1 找到Phase_1部分的汇编代码。
1 2 3 4 5 6 7 8 9 0000000000400ee0 phase_1: 400ee0: 48 83 ec 08 subq $8, %rsp 400ee4: be 00 24 40 00 movl $4203520, %esi 400ee9: e8 4a 04 00 00 callq 1098 <strings_not_equal> 400eee: 85 c0 testl %eax, %eax 400ef0: 74 05 je 5 <phase_1+0x17> 400ef2: e8 43 05 00 00 callq 1347 <explode_bomb> 400ef7: 48 83 c4 08 addq $8, %rsp 400efb: c3 retq
对它以及调用它的上下文简单分析可以将它转为下列C语言代码。(部分强大的反汇编工具允许生成C语言代码,这里建议自己手动尝试)
Test指令用于将两个操作数进行与操作,然后根据结果设置标志寄存器。JE指令表示ZF被置位时跳转。
这里的作用是检测eax是否为0,如果与的结果为0,则ZF置位。如果ZF被置位则跳转到<phase_1+0x17>的位置。
1 2 3 4 5 6 phase_1(char *input) { int r = strings_not_equal(input, 4203520 ); if (r != 0 ) { explode_bomb(); } }
strings_not_equal方法可以猜测是一个是一个字符串比较函数,因此可以猜测4203520(0x402400)是一个地址。
1 2 3 4 5 6 7 8 # objdump -s -j .rodata bomb 402400 426f7264 65722072 656c6174 696f6e73 Border relations 402410 20776974 68204361 6e616461 20686176 with Canada hav 402420 65206e65 76657220 6265656e 20626574 e never been bet 402430 7465722e 00000000 576f7721 20596f75 ter.....Wow! You 402440 27766520 64656675 73656420 74686520 've defused the 402450 73656372 65742073 74616765 2100666c secret stage!.fl 402460 79657273 00000000 00000000 00000000 yers............
从中地址4203520提取出字符串“Border relations with Canada have never been better.”(字符串以0x00结束)。
此处补充一下strings_not_equal的逻辑,可以根据汇编代码得出下列结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 int strings_not_equal (char *input, char *str) { if (strlen (input) != strlen (str)) { return 1 ; } while (*input != 0 ) { if (*input == *str) { input++; str++; } else { return 1 ; } } return 0 ;
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 pushq %rbp 400efd: 53 pushq %rbx 400efe: 48 83 ec 28 subq $40, %rsp 400f02: 48 89 e6 movq %rsp, %rsi 400f05: e8 52 05 00 00 callq 1362 <read_six_numbers> 400f0a: 83 3c 24 01 cmpl $1, (%rsp) 400f0e: 74 20 je 32 <phase_2+0x34> 400f10: e8 25 05 00 00 callq 1317 <explode_bomb> 400f15: eb 19 jmp 25 <phase_2+0x34> 400f17: 8b 43 fc movl -4(%rbx), %eax 400f1a: 01 c0 addl %eax, %eax 400f1c: 39 03 cmpl %eax, (%rbx) 400f1e: 74 05 je 5 <phase_2+0x29> 400f20: e8 15 05 00 00 callq 1301 <explode_bomb> 400f25: 48 83 c3 04 addq $4, %rbx 400f29: 48 39 eb cmpq %rbp, %rbx 400f2c: 75 e9 jne -23 <phase_2+0x1b> 400f2e: eb 0c jmp 12 <phase_2+0x40> 400f30: 48 8d 5c 24 04 leaq 4(%rsp), %rbx 400f35: 48 8d 6c 24 18 leaq 24(%rsp), %rbp 400f3a: eb db jmp -37 <phase_2+0x1b> 400f3c: 48 83 c4 28 addq $40, %rsp 400f40: 5b popq %rbx 400f41: 5d popq %rbp 400f42: c3 retq
同样对它进行翻译
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 phase_2(char *input) { %rsp -= 40 read_six_number(input, %rsp - 40 ); if (*(%rsp) != 1 ) { explode_bomb(); } %rbx = %rsp + 4 ; %rbp = %rsp + 24 ; loop: %eax = *(%rbx - 4 ) * 2 ; if (%eax != *(%rbx)) { explode_bomb(); } %rbx += 4 ; if (%rbp != %rbx) { goto loop; } }
很容易看出来,比较6个数。已经规定了第一个数是1,要求每个后面的数,都是前一个数的两倍。
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 0000000000400f43 phase_3: 400f43: 48 83 ec 18 subq $24, %rsp 400f47: 48 8d 4c 24 0c leaq 12(%rsp), %rcx 400f4c: 48 8d 54 24 08 leaq 8(%rsp), %rdx 400f51: be cf 25 40 00 movl $4203983, %esi 400f56: b8 00 00 00 00 movl $0, %eax 400f5b: e8 90 fc ff ff callq -880 <__isoc99_sscanf@plt> 400f60: 83 f8 01 cmpl $1, %eax 400f63: 7f 05 jg 5 <phase_3+0x27> 400f65: e8 d0 04 00 00 callq 1232 <explode_bomb> 400f6a: 83 7c 24 08 07 cmpl $7, 8(%rsp) 400f6f: 77 3c ja 60 <phase_3+0x6a> 400f71: 8b 44 24 08 movl 8(%rsp), %eax 400f75: ff 24 c5 70 24 40 00 jmpq *4203632(,%rax,8) # switch跳转表 400f7c: b8 cf 00 00 00 movl $207, %eax 400f81: eb 3b jmp 59 <phase_3+0x7b> 400f83: b8 c3 02 00 00 movl $707, %eax 400f88: eb 34 jmp 52 <phase_3+0x7b> 400f8a: b8 00 01 00 00 movl $256, %eax 400f8f: eb 2d jmp 45 <phase_3+0x7b> 400f91: b8 85 01 00 00 movl $389, %eax 400f96: eb 26 jmp 38 <phase_3+0x7b> 400f98: b8 ce 00 00 00 movl $206, %eax 400f9d: eb 1f jmp 31 <phase_3+0x7b> 400f9f: b8 aa 02 00 00 movl $682, %eax 400fa4: eb 18 jmp 24 <phase_3+0x7b> 400fa6: b8 47 01 00 00 movl $327, %eax 400fab: eb 11 jmp 17 <phase_3+0x7b> 400fad: e8 88 04 00 00 callq 1160 <explode_bomb> 400fb2: b8 00 00 00 00 movl $0, %eax 400fb7: eb 05 jmp 5 <phase_3+0x7b> 400fb9: b8 37 01 00 00 movl $311, %eax 400fbe: 3b 44 24 0c cmpl 12(%rsp), %eax 400fc2: 74 05 je 5 <phase_3+0x86> 400fc4: e8 71 04 00 00 callq 1137 <explode_bomb> 400fc9: 48 83 c4 18 addq $24, %rsp 400fcd: c3 retq
同样进行翻译,先说结论,其解释在后面。
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 phase_3 (char *input) { int r = __isoc99_sscanf(input, 4203983 , %rsp + 8 , %rsp + 12 ); if (r <= 1 ) { explode_bomb(); } if (*(%rsp + 8 ) > 7 ) { goto tag_1; } %eax = *(%rsp + 8 ) switch (%eax) { case 0 : %eax = 207 ; goto tag_2; case 1 : %eax = 311 ; goto tag_2; case 2 : %eax = 707 ; goto tag_2; case 3 : %eax = 256 ; goto tag_2; case 4 : %eax = 389 ; goto tag_2; case 5 : %eax = 206 ; goto tag_2; case 6 : %eax = 682 ; goto tag_2; case 7 : %eax = 327 ; goto tag_2; }; tag_1: explode_bomb(); %eax = 0 ; goto tag_2; tag_2: if (%eax == *(%rsp + 12 )) { goto ret; } explode_bomb(); ret: return ; }
首先来看下输入给__isoc99_sscanf的字符串,即“%d %d”。即从input中读取两个数,到%esp + 8和%esp + 12的位置。
根据指令jmpq *4203632(,%rax,8)
,以及下列一系列的操作可以判断是一个switch语句,然后根据这个地址找到switch的跳转表。找到跳转表之后就可以根据%rax的值来还原case的情况。
最后就很简单了,输入两个数,让第一个数对应的case获得的值等于第二个数。
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 000000000040100c phase_4: 40100c: 48 83 ec 18 subq $24, %rsp 401010: 48 8d 4c 24 0c leaq 12(%rsp), %rcx 401015: 48 8d 54 24 08 leaq 8(%rsp), %rdx 40101a: be cf 25 40 00 movl $4203983, %esi 40101f: b8 00 00 00 00 movl $0, %eax 401024: e8 c7 fb ff ff callq -1081 <__isoc99_sscanf@plt> 401029: 83 f8 02 cmpl $2, %eax 40102c: 75 07 jne 7 <phase_4+0x29> # 0x401035 40102e: 83 7c 24 08 0e cmpl $14, 8(%rsp) 401033: 76 05 jbe 5 <phase_4+0x2e> # 0x40103a 401035: e8 00 04 00 00 callq 1024 <explode_bomb> 40103a: ba 0e 00 00 00 movl $14, %edx 40103f: be 00 00 00 00 movl $0, %esi 401044: 8b 7c 24 08 movl 8(%rsp), %edi 401048: e8 81 ff ff ff callq -127 <func4> 40104d: 85 c0 testl %eax, %eax 40104f: 75 07 jne 7 <phase_4+0x4c> # 0x401058 401051: 83 7c 24 0c 00 cmpl $0, 12(%rsp) 401056: 74 05 je 5 <phase_4+0x51> # 0x40105d 401058: e8 dd 03 00 00 callq 989 <explode_bomb> 40105d: 48 83 c4 18 addq $24, %rsp 401061: c3 retq
解释称对应的C语言:
1 2 3 4 5 6 7 8 9 10 11 12 phase_4 (char *input) { int r = __isoc99_sscanf(input, 4203983 , %rsp + 8 , %rsp + 12 ); if (r != 2 || *(%rsp + 8 ) > 14 ) { explode_bomb(); } r = func4(*(%rsp + 8 ), 0 , 14 ); if (r != 0 || *(%rsp + 12 ) != 0 ) { explode_bomb(); } }
看来还需要进一步关注func4的内容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 0000000000400fce func4: 400fce: 48 83 ec 08 subq $8, %rsp 400fd2: 89 d0 movl %edx, %eax 400fd4: 29 f0 subl %esi, %eax 400fd6: 89 c1 movl %eax, %ecx 400fd8: c1 e9 1f shrl $31, %ecx 400fdb: 01 c8 addl %ecx, %eax 400fdd: d1 f8 sarl %eax 400fdf: 8d 0c 30 leal (%rax,%rsi), %ecx 400fe2: 39 f9 cmpl %edi, %ecx 400fe4: 7e 0c jle 12 <func4+0x24> # 0x400ff2 400fe6: 8d 51 ff leal -1(%rcx), %edx 400fe9: e8 e0 ff ff ff callq -32 <func4> 400fee: 01 c0 addl %eax, %eax 400ff0: eb 15 jmp 21 <func4+0x39> # 0x401007 400ff2: b8 00 00 00 00 movl $0, %eax 400ff7: 39 f9 cmpl %edi, %ecx 400ff9: 7d 0c jge 12 <func4+0x39> # 0x401007 400ffb: 8d 71 01 leal 1(%rcx), %esi 400ffe: e8 cb ff ff ff callq -53 <func4> 401003: 8d 44 00 01 leal 1(%rax,%rax), %eax 401007: 48 83 c4 08 addq $8, %rsp 40100b: c3 retq
同样对其进行解析,大致可以看出来func4做的是一个二分查找,在y和z中间查找x,并且返回搜索的次数。
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 func4(%edi, %esi, %edx) { %eax = %edx - %esi; %ecx = %eax >> 31 ; %eax += %ecx; %eax >>= 1 ; %ecx = %rax + %rsi; if (%ecx <= %edi) { %eax = 0 ; if (%ecx >= %edi) { return %eax; } %esi = %rcx + 1 ; %eax = func4(%edi, %esi, %edx); %eax = %rax * 2 + 1 ; } else { %edx = %rcx - 1 ; %eax = func4(%edi, %esi, %edx); %eax *= 2 ; } return %eax; }
这样就很明显了,要使炸弹不爆炸需要输入两个数,第一个数在(0, 14)之间进行二分查找的搜索次数为0,第二数要求为0。
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 0000000000401062 phase_5: 401062: 53 pushq %rbx 401063: 48 83 ec 20 subq $32, %rsp 401067: 48 89 fb movq %rdi, %rbx 40106a: 64 48 8b 04 25 28 00 00 00 movq %fs:40, %rax 401073: 48 89 44 24 18 movq %rax, 24(%rsp) 401078: 31 c0 xorl %eax, %eax 40107a: e8 9c 02 00 00 callq 668 <string_length> 40107f: 83 f8 06 cmpl $6, %eax 401082: 74 4e je 78 <phase_5+0x70> # 0x4010d2 401084: e8 b1 03 00 00 callq 945 <explode_bomb> 401089: eb 47 jmp 71 <phase_5+0x70> # 0x4010d2 40108b: 0f b6 0c 03 movzbl (%rbx,%rax), %ecx 40108f: 88 0c 24 movb %cl, (%rsp) 401092: 48 8b 14 24 movq (%rsp), %rdx 401096: 83 e2 0f andl $15, %edx 401099: 0f b6 92 b0 24 40 00 movzbl 4203696(%rdx), %edx 4010a0: 88 54 04 10 movb %dl, 16(%rsp,%rax) 4010a4: 48 83 c0 01 addq $1, %rax 4010a8: 48 83 f8 06 cmpq $6, %rax 4010ac: 75 dd jne -35 <phase_5+0x29> # 0x40108b 4010ae: c6 44 24 16 00 movb $0, 22(%rsp) 4010b3: be 5e 24 40 00 movl $4203614, %esi 4010b8: 48 8d 7c 24 10 leaq 16(%rsp), %rdi 4010bd: e8 76 02 00 00 callq 630 <strings_not_equal> 4010c2: 85 c0 testl %eax, %eax 4010c4: 74 13 je 19 <phase_5+0x77> # 0x4010d9 4010c6: e8 6f 03 00 00 callq 879 <explode_bomb> 4010cb: 0f 1f 44 00 00 nopl (%rax,%rax) 4010d0: eb 07 jmp 7 <phase_5+0x77> # 0x4010d9 4010d2: b8 00 00 00 00 movl $0, %eax 4010d7: eb b2 jmp -78 <phase_5+0x29> # 0x40108b 4010d9: 48 8b 44 24 18 movq 24(%rsp), %rax 4010de: 64 48 33 04 25 28 00 00 00 xorq %fs:40, %rax 4010e7: 74 05 je 5 <phase_5+0x8c> # 0x4010ee 4010e9: e8 42 fa ff ff callq -1470 <__stack_chk_fail@plt> 4010ee: 48 83 c4 20 addq $32, %rsp 4010f2: 5b popq %rbx 4010f3: c3 retq
还是C语言相较于汇编容易理解一些。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 phase_5(input) { %rbx = input; %eax = strlen (input); if (%eax != 6 ) { explode_bomb(); } %eax = 0 ; do { %ecx = *(%rbx + %rax); %edx = *((%cl & 0xf ) + 4203696 ); *(%rsp + %rax + 16 ) = %dl; %rax++; } while (%rax != 6 ); *(%rsp + 22 ) = 0 ; %eax = strings_not_equal(*(%rsp + 16 ), 4203614 ); if (%eax != 0 ) { explode_bomb(); } }
这里有一部分看着很奇怪的指令组合。在stack overflow上有类似的讨论what does this instruction do?:- mov %gs:0x14,%eax 。忽略它。
1 2 3 4 5 6 7 40106 a: 64 48 8b 04 25 28 00 00 00 movq %fs:40 , %rax401073 : 48 89 44 24 18 movq %rax, 24 (%rsp)401078 : 31 c0 xorl %eax, %eax4010 d9: 48 8b 44 24 18 movq 24 (%rsp), %rax4010 de: 64 48 33 04 25 28 00 00 00 xorq %fs:40 , %rax4010e7 : 74 05 je 5 <phase_5+0x8c > # 0x4010ee 4010e9 : e8 42 fa ff ff callq -1470 <__stack_chk_fail@plt>
先看一下地址4203614(0x40245e)的信息,可以得到一个字符串“flyers”。
1 2 402450 73656372 65742073 74616765 2100666c secret stage!.fl 402460 79657273 00000000 00000000 00000000 yers............
然后看一下地址4203696(0x4024b0)的信息,可以看到一个很长的字符串,但是由于我们仅需要15个字符(偏移量最大为15),因此从中提取6个字符“maduiersnfotvbyl”。
1 2 4024b0 6d616475 69657273 6e666f74 7662796c maduiersnfotvbyl 4024c0 536f2079 6f752074 68696e6b 20796f75 So you think you
也就是说我们要根据input的信息和字符串“maduiersnfotvbyl”进过函数内的处理得到字符串“flyers”。
首先找到所需字母分别对应的位置。{'f': 0x9, 'l': 0xf, 'y': 0xe, 'e': 0x5, 'r': 0x6, 's': 0x7}
。因此我们构造的输入需要低4位分别是上述数字的字符。
用python打印出所有小写字母对应的ascii码值,根据这个结果构造输入“ionefg”。
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 In [18 ]: for i in range (0 , 26 ): ...: print (f"{chr (i + ord ('a' ))} -> hex {hex (i + ord ('a' ))} " ) ...: a -> hex 0x61 b -> hex 0x62 c -> hex 0x63 d -> hex 0x64 e -> hex 0x65 f -> hex 0x66 g -> hex 0x67 h -> hex 0x68 i -> hex 0x69 j -> hex 0x6a k -> hex 0x6b l -> hex 0x6c m -> hex 0x6d n -> hex 0x6e o -> hex 0x6f p -> hex 0x70 q -> hex 0x71 r -> hex 0x72 s -> hex 0x73 t -> hex 0x74 u -> hex 0x75 v -> hex 0x76 w -> hex 0x77 x -> hex 0x78 y -> hex 0x79 z -> hex 0x7a
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 00000000004010f4 phase_6: 4010f4: 41 56 pushq %r14 4010f6: 41 55 pushq %r13 4010f8: 41 54 pushq %r12 4010fa: 55 pushq %rbp 4010fb: 53 pushq %rbx 4010fc: 48 83 ec 50 subq $80, %rsp 401100: 49 89 e5 movq %rsp, %r13 401103: 48 89 e6 movq %rsp, %rsi 401106: e8 51 03 00 00 callq 849 <read_six_numbers> 40110b: 49 89 e6 movq %rsp, %r14 40110e: 41 bc 00 00 00 00 movl $0, %r12d 401114: 4c 89 ed movq %r13, %rbp 401117: 41 8b 45 00 movl (%r13), %eax 40111b: 83 e8 01 subl $1, %eax 40111e: 83 f8 05 cmpl $5, %eax 401121: 76 05 jbe 5 <phase_6+0x34> # 0x401128 401123: e8 12 03 00 00 callq 786 <explode_bomb> 401128: 41 83 c4 01 addl $1, %r12d 40112c: 41 83 fc 06 cmpl $6, %r12d 401130: 74 21 je 33 <phase_6+0x5f> # 0x401153 401132: 44 89 e3 movl %r12d, %ebx 401135: 48 63 c3 movslq %ebx, %rax 401138: 8b 04 84 movl (%rsp,%rax,4), %eax 40113b: 39 45 00 cmpl %eax, (%rbp) 40113e: 75 05 jne 5 <phase_6+0x51> # 0x401145 401140: e8 f5 02 00 00 callq 757 <explode_bomb> 401145: 83 c3 01 addl $1, %ebx 401148: 83 fb 05 cmpl $5, %ebx 40114b: 7e e8 jle -24 <phase_6+0x41> # 0x401135 40114d: 49 83 c5 04 addq $4, %r13 401151: eb c1 jmp -63 <phase_6+0x20> # 0x401114 401153: 48 8d 74 24 18 leaq 24(%rsp), %rsi 401158: 4c 89 f0 movq %r14, %rax 40115b: b9 07 00 00 00 movl $7, %ecx 401160: 89 ca movl %ecx, %edx 401162: 2b 10 subl (%rax), %edx 401164: 89 10 movl %edx, (%rax) 401166: 48 83 c0 04 addq $4, %rax 40116a: 48 39 f0 cmpq %rsi, %rax 40116d: 75 f1 jne -15 <phase_6+0x6c> # 0x401160 40116f: be 00 00 00 00 movl $0, %esi 401174: eb 21 jmp 33 <phase_6+0xa3> # 0x401197 401176: 48 8b 52 08 movq 8(%rdx), %rdx 40117a: 83 c0 01 addl $1, %eax 40117d: 39 c8 cmpl %ecx, %eax 40117f: 75 f5 jne -11 <phase_6+0x82> # 0x401176 401181: eb 05 jmp 5 <phase_6+0x94> # 0x401188 401183: ba d0 32 60 00 movl $6304464, %edx 401188: 48 89 54 74 20 movq %rdx, 32(%rsp,%rsi,2) 40118d: 48 83 c6 04 addq $4, %rsi 401191: 48 83 fe 18 cmpq $24, %rsi 401195: 74 14 je 20 <phase_6+0xb7> # 0x4011ab 401197: 8b 0c 34 movl (%rsp,%rsi), %ecx 40119a: 83 f9 01 cmpl $1, %ecx 40119d: 7e e4 jle -28 <phase_6+0x8f> # 0x401183 40119f: b8 01 00 00 00 movl $1, %eax 4011a4: ba d0 32 60 00 movl $6304464, %edx 4011a9: eb cb jmp -53 <phase_6+0x82> # 0x401176 4011ab: 48 8b 5c 24 20 movq 32(%rsp), %rbx 4011b0: 48 8d 44 24 28 leaq 40(%rsp), %rax 4011b5: 48 8d 74 24 50 leaq 80(%rsp), %rsi 4011ba: 48 89 d9 movq %rbx, %rcx 4011bd: 48 8b 10 movq (%rax), %rdx 4011c0: 48 89 51 08 movq %rdx, 8(%rcx) 4011c4: 48 83 c0 08 addq $8, %rax 4011c8: 48 39 f0 cmpq %rsi, %rax 4011cb: 74 05 je 5 <phase_6+0xde> # 0x4011d2 4011cd: 48 89 d1 movq %rdx, %rcx 4011d0: eb eb jmp -21 <phase_6+0xc9> # 0x4011bd 4011d2: 48 c7 42 08 00 00 00 00 movq $0, 8(%rdx) 4011da: bd 05 00 00 00 movl $5, %ebp 4011df: 48 8b 43 08 movq 8(%rbx), %rax 4011e3: 8b 00 movl (%rax), %eax 4011e5: 39 03 cmpl %eax, (%rbx) 4011e7: 7d 05 jge 5 <phase_6+0xfa> # 0x4011ee 4011e9: e8 4c 02 00 00 callq 588 <explode_bomb> 4011ee: 48 8b 5b 08 movq 8(%rbx), %rbx 4011f2: 83 ed 01 subl $1, %ebp 4011f5: 75 e8 jne -24 <phase_6+0xeb> # 0x4011df 4011f7: 48 83 c4 50 addq $80, %rsp 4011fb: 5b popq %rbx 4011fc: 5d popq %rbp 4011fd: 41 5c popq %r12 4011ff: 41 5d popq %r13 401201: 41 5e popq %r14 401203: c3 retq
手工解析成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 phase_6(input) { %r13 = %rsp; read_six_numbers(input, %rsp); %r14 = %rsp; %r12d = 0 ; while (true ) { %rbp = %r13; %eax = *(%r13); %eax--; if (%eax > 5 ) { explode_bomb(); } %r12d++; if (%r12d == 6 ) { break ; } %ebx = %r12d; do { %rax = %ebx; %eax = *(%rsp + %rax * 4 ); if (*(%rbp) == %eax) { explode_bomb(); } %ebx++; } while (%ebx <= 5 ); %r13 += 4 ; } %rsi = *(%rsp + 24 ); %rax = %r14; %ecx = 7 ; do { %edx = %ecx - *(%rax); *(%rax) = %edx; %rax += 4 ; } while (%rax != %rsi); %esi = 0 ; do { %ecx = *(%rsp + %rsi); if (%ecx <= 1 ) { %edx = 6304464 ; } else { %eax = 1 ; %edx = 6304464 ; do { %rdx = *(%rdx + 8 ); %eax++; } while (%eax != %ecx); } *(%rsp + %rsi * 2 + 32 ) = %rdx; %rsi += 4 ; } while (%rsi != 24 ); %rbx = *(%rsp + 32 ); %rax = %rsp + 40 ; %rsi = %rsp + 80 ; %rcx = %rbx; while (true ) { %rdx = *(%rax); *(%rcx + 8 ) = %rdx; %rax += 8 ; if (%rax == %rsi) { break ; } %rcx = %rdx; } *(%rdx + 8 ) = 0 ; %ebp = 5 ; do { %rax = *(%rbx + 8 ); %eax = *(%rax); if (*(%rbx) < %eax) { explode_bomb(); } %rbx = *(%rbx + 8 ); %ebp--; } while (*(%rbx) != %eax); return ; }
之前的常量都在rodata区,但是6304464这个地址却是data区的,通过gdb将其打印出来。结合%rdx = *(%rdx + 8);
可以看出每一个该位置的值,都是下一个位置的地址。大概可以猜测此处是一个类似链表的结构。
1 2 3 4 5 6 7 (gdb) x/24xw 6304464 0x6032d0 <node1>: 0x0000014c 0x00000001 0x006032e0 0x00000000 0x6032e0 <node2>: 0x000000a8 0x00000002 0x006032f0 0x00000000 0x6032f0 <node3>: 0x0000039c 0x00000003 0x00603300 0x00000000 0x603300 <node4>: 0x000002b3 0x00000004 0x00603310 0x00000000 0x603310 <node5>: 0x000001dd 0x00000005 0x00603320 0x00000000 0x603320 <node6>: 0x000001bb 0x00000006 0x00000000 0x00000000
经过分析,其实代码流程已经很清楚了。从input读取六个数,保证它们均小于等于6,并且各不相同;然后将他们的值设为与7的差值;接着根据处理后的这六个数,对链表节点进行重排序,保证新的链表是一个非严格的递减序列。
从上述链表节点的值可以看出,构造的新链表的顺序为3,4,5,6,1,2。因此对应的原六个数分别是4,3,2,1,6,5。
至此拆弹完成,但是真的吗?根据bomb.c最后部分的内容以及汇编代码中看到fun7和secret_phase可以猜测,还有一个隐藏关。
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 0000000000401242 secret_phase: 401242: 53 pushq %rbx 401243: e8 56 02 00 00 callq 598 <read_line> 401248: ba 0a 00 00 00 movl $10, %edx 40124d: be 00 00 00 00 movl $0, %esi 401252: 48 89 c7 movq %rax, %rdi 401255: e8 76 f9 ff ff callq -1674 <strtol@plt> 40125a: 48 89 c3 movq %rax, %rbx 40125d: 8d 40 ff leal -1(%rax), %eax 401260: 3d e8 03 00 00 cmpl $1000, %eax 401265: 76 05 jbe 5 <secret_phase+0x2a> # 0x40126c 401267: e8 ce 01 00 00 callq 462 <explode_bomb> 40126c: 89 de movl %ebx, %esi 40126e: bf f0 30 60 00 movl $6303984, %edi 401273: e8 8c ff ff ff callq -116 <fun7> 401278: 83 f8 02 cmpl $2, %eax 40127b: 74 05 je 5 <secret_phase+0x40> # 0x401282 40127d: e8 b8 01 00 00 callq 440 <explode_bomb> 401282: bf 38 24 40 00 movl $4203576, %edi 401287: e8 84 f8 ff ff callq -1916 <puts@plt> 40128c: e8 33 03 00 00 callq 819 <phase_defused> 401291: 5b popq %rbx 401292: c3 retq
对其进行分析:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 secret_phase(input) { %rax = strtol(input, 0 , 10 ); %rbx = %rax; %eax = %rax - 1 ; if (%rax > 1000 ) { explode_bomb(); } %eax = fun7(6303984 , %ebx); if (%eax != 2 ) { explode_bomb(); } puts (4203576 ); phase_defused(); }
可以看得出,从字符串中读取一个数,保证其小于等于1001。然后放入fun7函数处理,要使得fun7的返回值为2。
进一步关注fun7的内容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 0000000000401204 fun7: 401204: 48 83 ec 08 subq $8, %rsp 401208: 48 85 ff testq %rdi, %rdi 40120b: 74 2b je 43 <fun7+0x34> # 0x401238 40120d: 8b 17 movl (%rdi), %edx 40120f: 39 f2 cmpl %esi, %edx 401211: 7e 0d jle 13 <fun7+0x1c> # 0x401220 401213: 48 8b 7f 08 movq 8(%rdi), %rdi 401217: e8 e8 ff ff ff callq -24 <fun7> 40121c: 01 c0 addl %eax, %eax 40121e: eb 1d jmp 29 <fun7+0x39> # 0x40123d 401220: b8 00 00 00 00 movl $0, %eax 401225: 39 f2 cmpl %esi, %edx 401227: 74 14 je 20 <fun7+0x39> # 0x40123d 401229: 48 8b 7f 10 movq 16(%rdi), %rdi 40122d: e8 d2 ff ff ff callq -46 <fun7> 401232: 8d 44 00 01 leal 1(%rax,%rax), %eax 401236: eb 05 jmp 5 <fun7+0x39> # 0x40123d 401238: b8 ff ff ff ff movl $4294967295, %eax 40123d: 48 83 c4 08 addq $8, %rsp 401241: c3 retq
对其进行解析:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 fun7(void *p, int x) { int r; if (p == NULL ) { return 0xffffffff ; } if (p->value <= x) { r = 0 ; if (p->value == x) { return r; } p = p->right; r = fun7(p, x); r = 2 * r + 1 ; } else { p = p->left; r = fun7(p, x); r *= 2 ; } return r; }
可以看得出fun7的主要功能是在链表中搜索x,并返回搜索的次数。
地址6303984处的部分值如下。从这个值分布可以看出,这应该是一个二叉树的节点,一个节点里面包含一个value,以及两个地址。
1 2 3 4 5 6 7 8 9 10 11 (gdb) x/40xw 0x6030f0 0x6030f0 <n1>: 0x00000024 0x00000000 0x00603110 0x00000000 0x603100 <n1+16>: 0x00603130 0x00000000 0x00000000 0x00000000 0x603110 <n21>: 0x00000008 0x00000000 0x00603190 0x00000000 0x603120 <n21+16>: 0x00603150 0x00000000 0x00000000 0x00000000 0x603130 <n22>: 0x00000032 0x00000000 0x00603170 0x00000000 0x603140 <n22+16>: 0x006031b0 0x00000000 0x00000000 0x00000000 0x603150 <n32>: 0x00000016 0x00000000 0x00603270 0x00000000 0x603160 <n32+16>: 0x00603230 0x00000000 0x00000000 0x00000000 0x603170 <n33>: 0x0000002d 0x00000000 0x006031d0 0x00000000 0x603180 <n33+16>: 0x00603290 0x00000000 0x00000000 0x00000000
需求都已经很明晰了,构造这么一个特殊的输入即可。
我们已经知道了隐藏关的通过值,但是进入隐藏关的方法还需要进一步分析。通过搜索,可以得知,在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 00000000004015c4 phase_defused: 4015c4: 48 83 ec 78 subq $120, %rsp 4015c8: 64 48 8b 04 25 28 00 00 00 movq %fs:40, %rax 4015d1: 48 89 44 24 68 movq %rax, 104(%rsp) 4015d6: 31 c0 xorl %eax, %eax 4015d8: 83 3d 81 21 20 00 06 cmpl $6, 2105729(%rip) 4015df: 75 5e jne 94 <phase_defused+0x7b> # 0x40163f 4015e1: 4c 8d 44 24 10 leaq 16(%rsp), %r8 4015e6: 48 8d 4c 24 0c leaq 12(%rsp), %rcx 4015eb: 48 8d 54 24 08 leaq 8(%rsp), %rdx 4015f0: be 19 26 40 00 movl $4204057, %esi # "%d %d %s" 4015f5: bf 70 38 60 00 movl $6305904, %edi 4015fa: e8 f1 f5 ff ff callq -2575 <__isoc99_sscanf@plt> 4015ff: 83 f8 03 cmpl $3, %eax 401602: 75 31 jne 49 <phase_defused+0x71> # 0x401635 401604: be 22 26 40 00 movl $4204066, %esi # "DrEvil" 401609: 48 8d 7c 24 10 leaq 16(%rsp), %rdi 40160e: e8 25 fd ff ff callq -731 <strings_not_equal> 401613: 85 c0 testl %eax, %eax 401615: 75 1e jne 30 <phase_defused+0x71> # 0x401635 401617: bf f8 24 40 00 movl $4203768, %edi 40161c: e8 ef f4 ff ff callq -2833 <puts@plt> 401621: bf 20 25 40 00 movl $4203808, %edi 401626: e8 e5 f4 ff ff callq -2843 <puts@plt> 40162b: b8 00 00 00 00 movl $0, %eax 401630: e8 0d fc ff ff callq -1011 <secret_phase> 401635: bf 58 25 40 00 movl $4203864, %edi 40163a: e8 d1 f4 ff ff callq -2863 <puts@plt> 40163f: 48 8b 44 24 68 movq 104(%rsp), %rax 401644: 64 48 33 04 25 28 00 00 00 xorq %fs:40, %rax 40164d: 74 05 je 5 <phase_defused+0x90> # 0x401654 40164f: e8 dc f4 ff ff callq -2852 <__stack_chk_fail@plt> 401654: 48 83 c4 78 addq $120, %rsp 401658: c3 retq
这个就不翻了,逻辑挺简单的,从6305904的位置中读取两个数和一个字符串,然后将字符串与4204066地址的串进行比较,如果通过了就进入secret_phase。
因此关键的是6305904的位置是哪里。通过gdb查看该部分的内存,可以猜测,这可能是input的某个部分。
1 2 3 (gdb) x/16xb 6305904 0x603870 <input_strings+240>: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x603878 <input_strings+248>: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
通过gdb的watch功能,来设置一个内存地址的监测点。通过gdb的输出,可以猜测是phase 4的地方操作了这个地址。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 (gdb) watch *6305904 Hardware watchpoint 2: *6305904 (gdb) r The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /home/bingo/Workdir/bomb/bomb input.file 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! Hardware watchpoint 2: *6305904 Old value = 0 New value = 170926135 __memcpy_sse2 () at ../sysdeps/x86_64/multiarch/../memcpy.S:98 98 ../sysdeps/x86_64/multiarch/../memcpy.S: No such file or directory.
进一步进行单步调试,在phase 4的阶段调用read_line函数,操作了地址6305904。
至此,已经可以知道进入隐藏关的方法,以及隐藏关的通关指令。
Tips GDB工具 如果自己的输入有问题,或者说查看各个函数输入的值,可以试试gdb工具进行反汇编调试。
相关命令参考文档GDB调试命令(二)—反汇编相关 。
ps:上面的题其实都可以用gdb来“作弊”,因为大多都涉及比较,可以直接通过设置断点来查看寄存器的值。
答案
Border relations with Canada have never been better.
1 2 4 8 16 32
0 207
7 0 DrEvil
ionefg
4 3 2 1 6 5
22