bomb.c
/***************************************************************************
* Dr. Evil's Insidious Bomb, Version 1.1
* Copyright 2011, Dr. Evil Incorporated. All rights reserved.
*
* LICENSE:
*
* Dr. Evil Incorporated (the PERPETRATOR) hereby grants you (the
* VICTIM) explicit permission to use this bomb (the BOMB). This is a
* time limited license, which expires on the death of the VICTIM.
* The PERPETRATOR takes no responsibility for damage, frustration,
* insanity, bug-eyes, carpal-tunnel syndrome, loss of sleep, or other
* harm to the VICTIM. Unless the PERPETRATOR wants to take credit,
* that is. The VICTIM may not distribute this bomb source code to
* any enemies of the PERPETRATOR. No VICTIM may debug,
* reverse-engineer, run "strings" on, decompile, decrypt, or use any
* other technique to gain knowledge of and defuse the BOMB. BOMB
* proof clothing may not be worn when handling this program. The
* PERPETRATOR will not apologize for the PERPETRATOR's poor sense of
* humor. This license is null and void where the BOMB is prohibited
* by law.
***************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include "support.h"
#include "phases.h"
/*
* Note to self: Remember to erase this file so my victims will have no
* idea what is going on, and so they will all blow up in a
* spectaculary fiendish explosion. -- Dr. Evil
*/
FILE *infile;
int main(int argc, char *argv[])
{
char *input;
/* Note to self: remember to port this bomb to Windows and put a
* fantastic GUI on it. */
/* When run with no arguments, the bomb reads its input lines
* from standard input. */
if (argc == 1) {
infile = stdin;
}
/* When run with one argument <file>, the bomb reads from <file>
* until EOF, and then switches to standard input. Thus, as you
* defuse each phase, you can add its defusing string to <file> and
* avoid having to retype it. */
else if (argc == 2) {
if (!(infile = fopen(argv[1], "r"))) {
printf("%s: Error: Couldn't open %s\n", argv[0], argv[1]);
exit(8);
}
}
/* You can't call the bomb with more than 1 command line argument. */
else {
printf("Usage: %s [<input_file>]\n", argv[0]);
exit(8);
}
/* Do all sorts of secret stuff that makes the bomb harder to defuse. */
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");
/* Hmm... Six phases must be more secure than one phase! */
input = read_line(); /* Get input */
phase_1(input); /* Run the phase */
phase_defused(); /* Drat! They figured it out!
* Let me know how they did it. */
printf("Phase 1 defused. How about the next one?\n");
/* The second phase is harder. No one will ever figure out
* how to defuse this... */
input = read_line();
phase_2(input);
phase_defused();
printf("That's number 2. Keep going!\n");
/* I guess this is too easy so far. Some more complex code will
* confuse people. */
input = read_line();
phase_3(input);
phase_defused();
printf("Halfway there!\n");
/* Oh yeah? Well, how good is your math? Try on this saucy problem! */
input = read_line();
phase_4(input);
phase_defused();
printf("So you got that one. Try this one.\n");
/* Round and 'round in memory we go, where we stop, the bomb blows! */
input = read_line();
phase_5(input);
phase_defused();
printf("Good work! On to the next...\n");
/* This phase will never be used, since no one will get past the
* earlier ones. But just in case, make this one extra hard. */
input = read_line();
phase_6(input);
phase_defused();
/* Wow, they got it! But isn't something... missing? Perhaps
* something they overlooked? Mua ha ha ha ha! */
return 0;
}
phase_1: Border relations with Canada have never been better.
0x400e32 <main+146> callq 0x40149e <read_line>
#在此随意输入“asdfghjkl".
#换用stepi命令进入phase_1函数:(后面的phase相同)
0x400e3a <main+154> callq 0x400ee0 <phase_1>
#-----------------------------------------------------------------------------------
#phase_1函数内部:
0000000000400ee0 <phase_1>:
400ee0: 48 83 ec 08 sub $0x8,%rsp
400ee4: be 00 24 40 00 mov $0x402400,%esi
#这里出现了一个内存地址,进行查看:
#(gdb) x/20bc $esi
#0x402400: 66 'B' 111 'o' 114 'r' 100 'd' 101 'e' 114 'r' 32 ' ' 114 'r'
#0x402408: 101 'e' 108 'l' 97 'a' 116 't' 105 'i' 111 'o' 110 'n' 115 's'
#0x402410: 32 ' ' 119 'w' 105 'i' 116 't'
#貌似是一串字符,继续查看完整得到:Border relations with Canada have never been better.
#猜想该字符串可能是用来比较的
400ee9: e8 4a 04 00 00 callq 401338 <strings_not_equal>
400eee: 85 c0 test %eax,%eax
400ef0: 74 05 je 400ef7 <phase_1+0x17>
#如果strings_not_equal的返回后eax是0,则不进入explode_bomb,所以我们要进入strings_not_equal看看
400ef2: e8 43 05 00 00 callq 40143a <explode_bomb>
#注意这里的explode_bomb函数,要避免进入
400ef7: 48 83 c4 08 add $0x8,%rsp
400efb: c3 retq
#-----------------------------------------------------------------------------------
#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 callq 40131b <string_length>
##该处换用nexti,不进入string_length,完成后发现rax变为9,即我们输入字符串的长度
#401347: 41 89 c4 mov %eax,%r12d
#40134a: 48 89 ef mov %rbp,%rdi
#40134d: e8 c9 ff ff ff callq 40131b <string_length>
##该处rax变为52,即Border relations with Canada have never been better.的长度,随后比较两处长度
#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>
##.........
#到此我们已经可以大致猜想该处答案可能就是Border relations with Canada have never been better.经尝试,果然如此。
phase_2: 1 2 4 8 16 32
#输入”asdfghjkl“
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 callq 40145c <read_six_numbers>
#进入read_six_numbers,看这个名字好像就是读入六个数字
#-------------------------------------------------------------------
#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
##注意这里的一个内存地址,经查看为字符串:
##0x4025c3: 37 '%' 100 'd' 32 ' ' 37 '%' 100 'd' 32 ' ' 37 '%' 100 'd'
##0x4025cb: 32 ' ' 37 '%' 100 'd' 32 ' ' 37 '%' 100 'd' 32 ' ' 37 '%'
##0x4025d3: 100 'd' 0 '\000'
##明显为sscanf读入六个数字的format部分
#401485: b8 00 00 00 00 mov $0x0,%eax
#40148a: e8 61 f7 ff ff callq 400bf0 <__isoc99_sscanf@plt>
##这里是一个sscanf,nexti跳过即可
#40148f: 83 f8 05 cmp $0x5,%eax
##注意这里在比较了eax是否大于5(即是否读入了6个数字),当我们第一次的输入没有数字时,eax为0,从而引发explode_bomb
#401492: 7f 05 jg 401499 <read_six_numbers+0x3d>
#401494: e8 a1 ff ff ff callq 40143a <explode_bomb>
#401499: 48 83 c4 18 add $0x18,%rsp
#40149d: c3 retq
##由此,我们输入字符串”1 2 3 4 5 6 7 8 9 0",在该函数内未引爆炸弹,且eax为6.
400f0a: 83 3c 24 01 cmpl $0x1,(%rsp)
#查看rsp:
#(gdb) x/28bc $rsp
#0x7fffffffdba0: 1 '\001' 0 '\000' 0 '\000' 0 '\000' 2 '\002' 0 '\000' 0 '\000' 0 '\000'
#0x7fffffffdba8: 3 '\003' 0 '\000' 0 '\000' 0 '\000' 4 '\004' 0 '\000' 0 '\000' 0 '\000'
#0x7fffffffdbb0: 5 '\005' 0 '\000' 0 '\000' 0 '\000' 6 '\006' 0 '\000' 0 '\000' 0 '\000'
#0x7fffffffdbb8: 49 '1' 20 '\024' 64 '@' 0 '\000'
#可以发现rsp指向的就是刚刚读入的6个数字(多余数字并未读入)
400f0e: 74 20 je 400f30 <phase_2+0x34>
400f10: e8 25 05 00 00 callq 40143a <explode_bomb>
400f15: eb 19 jmp 400f30 <phase_2+0x34>
#首先是比较了第一个读入的数字是不是1,否则爆炸
400f17: 8b 43 fc mov -0x4(%rbx),%eax
400f1a: 01 c0 add %eax,%eax
400f1c: 39 03 cmp %eax,(%rbx)
400f1e: 74 05 je 400f25 <phase_2+0x29>
400f20: e8 15 05 00 00 callq 40143a <explode_bomb>
#第三步跳转到这里,首先将rbx指向的前一个数赋值给eax,并将eax*2,比较rbx指向的数和前一个数的两倍,如果不相等,就爆炸。
400f25: 48 83 c3 04 add $0x4,%rbx
400f29: 48 39 eb cmp %rbp,%rbx
400f2c: 75 e9 jne 400f17 <phase_2+0x1b>
400f2e: eb 0c jmp 400f3c <phase_2+0x40>
#第四部跳转到这里,将rbx指向后一个数,并比较rbp rbx的值(是否所有的数都被指向比较了一遍),如果不相等就从刚刚的第三部进行新的比较,否则退出函数。
400f30: 48 8d 5c 24 04 lea 0x4(%rsp),%rbx
400f35: 48 8d 6c 24 18 lea 0x18(%rsp),%rbp
#第二步跳转到这里,将第二个数的地址给rbx,第六个数后一位的地址给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 retq
#由以上分析可知,该函数就是检查读入的前6个数是否是按照*2依次递增的,并且第一个数还必须是1,所以输入1 2 4 8 16 32拆除炸弹。
phase_3: 1 311
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
400f56: b8 00 00 00 00 mov $0x0,%eax
400f5b: e8 90 fc ff ff callq 400bf0 <__isoc99_sscanf@plt>
400f60: 83 f8 01 cmp $0x1,%eax
400f63: 7f 05 jg 400f6a <phase_3+0x27>
400f65: e8 d0 04 00 00 callq 40143a <explode_bomb>
#这个地方和phase_3很相似,都是读入两个数字,否则爆炸,于是输入“1 2”
400f6a: 83 7c 24 08 07 cmpl $0x7,0x8(%rsp)
#查看rsp知,0x8(%rsp)表示读取的第一个数,0xc(%rsp)表示读取的第二个数
400f6f: 77 3c ja 400fad <phase_3+0x6a>
#比较第一个是否大于7,如果大于,则发生爆炸。
400f71: 8b 44 24 08 mov 0x8(%rsp),%eax
400f75: ff 24 c5 70 24 40 00 jmpq *0x402470(,%rax,8)
#用第一个数作为switch的条件,判断该跳转到jump table的哪个位置,这里rax为1.
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 callq 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
400fc2: 74 05 je 400fc9 <phase_3+0x86>
#第一步跳转到这里,发现其在比较第二个数是不是0x137,如果是的话就退出函数,于是我们输入“1 311”即可。
400fc4: e8 71 04 00 00 callq 40143a <explode_bomb>
400fc9: 48 83 c4 18 add $0x18,%rsp
400fcd: c3 retq
phase_4: [0, 1, 3, 7] 0
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 callq 400bf0 <__isoc99_sscanf@plt>
401029: 83 f8 02 cmp $0x2,%eax
40102c: 75 07 jne 401035 <phase_4+0x29>
#跟phase_3一样,读入两个数字,否则爆炸,于是输入“1 2"
40102e: 83 7c 24 08 0e cmpl $0xe,0x8(%rsp)
401033: 76 05 jbe 40103a <phase_4+0x2e>
401035: e8 00 04 00 00 callq 40143a <explode_bomb>
#如果第一个数字大于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 callq 400fce <func4>
#edx=14 esi=0 edi=第一个读入的数字
#--------------------------------------------------------------------------------
#0000000000400fce <func4>:
#400fce: 48 83 ec 08 sub $0x8,%rsp
#400fd2: 89 d0 mov %edx,%eax
#400fd4: 29 f0 sub %esi,%eax
##eax=14
#400fd6: 89 c1 mov %eax,%ecx
#400fd8: c1 e9 1f shr $0x1f,%ecx
##ecx中是14逻辑右移31位,ecx=0
#400fdb: 01 c8 add %ecx,%eax
##eax=14
#400fdd: d1 f8 sar %eax
##这个地方我犯了一个错误,书上说位移操作需要imm8或者%cl来指示位移的个数,这两个都是要显示写出来的,如果单单写一个位移操作符,默认是位移一位。
##eax=eax>>1=14/2=7
#400fdf: 8d 0c 30 lea (%rax,%rsi,1),%ecx
##ecx=7+0*1=7
#400fe2: 39 f9 cmp %edi,%ecx
#400fe4: 7e 0c jle 400ff2 <func4+0x24>
##比较7和第一个读入的数字,如果第一个数字大于等于7,跳至400ff2
#400fe6: 8d 51 ff lea -0x1(%rcx),%edx
##edx=7-1=6
#400fe9: e8 e0 ff ff ff callq 400fce <func4>
##递归调用
#400fee: 01 c0 add %eax,%eax
#400ff0: eb 15 jmp 401007 <func4+0x39>
##eax=eax*2 退出函数
#400ff2: b8 00 00 00 00 mov $0x0,%eax
#400ff7: 39 f9 cmp %edi,%ecx
#400ff9: 7d 0c jge 401007 <func4+0x39>
##如果7>=第一个读入的数,就退出函数,且eax=0
#400ffb: 8d 71 01 lea 0x1(%rcx),%esi
##esi=7+1=8
#400ffe: e8 cb ff ff ff callq 400fce <func4>
##递归调用
#401003: 8d 44 00 01 lea 0x1(%rax,%rax,1),%eax
#401007: 48 83 c4 08 add $0x8,%rsp
#40100b: c3 retq
##首先来看第一个数字小于7的情况,不断递归调用直到第一个数字大于等于ecx(((7-1)/2)-1.....)(7->3->1- >0),跳至400ff2,此时如果ecx大于等于第一个数,则退出函数。所以要想拆除炸弹,可以在小于7的数中间找一 个(并且要能够等于之后的ecx),所以0 1 3都可以,此时返回的eax为0。
##当数字大于等于7时,直接跳转到400ff2,可以使第一个数就是7,从而退出函数。
40104d: 85 c0 test %eax,%eax
40104f: 75 07 jne 401058 <phase_4+0x4c>
#如果返回的eax不为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 callq 40143a <explode_bomb>
40105d: 48 83 c4 18 add $0x18,%rsp
401061: c3 retq
phase_5: [ionefg]
0000000000401062 <phase_5>:
401062: 53 push %rbx
401063: 48 83 ec 20 sub $0x20,%rsp
401067: 48 89 fb mov %rdi,%rbx
40106a: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax
#这个地方是在设置“Stack Canaries”,后面会查看这只“金丝雀”是否被改变了,防止栈溢出攻击。
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 callq 40131b <string_length>
40107f: 83 f8 06 cmp $0x6,%eax
401082: 74 4e je 4010d2 <phase_5+0x70>
401084: e8 b1 03 00 00 callq 40143a <explode_bomb>
#判断读入字符串长度是否为6,否则爆炸,于是输入字符串”abcdef“测试。
401089: eb 47 jmp 4010d2 <phase_5+0x70>
40108b: 0f b6 0c 03 movzbl (%rbx,%rax,1),%ecx
#测试rbx是刚刚输入的字符串的地址:
#(gdb) x/20bc $rbx
#0x6038c0 <input_strings+320>: 97 'a' 98 'b' 99 'c' 100 'd' 101 'e' 102 'f' 0 '\000' 0 '\000'
#所以此时ecx内部为第一个字符
40108f: 88 0c 24 mov %cl,(%rsp)
401092: 48 8b 14 24 mov (%rsp),%rdx
401096: 83 e2 0f and $0xf,%edx
#此时edx内为字符ascii码十六进制的低位。
401099: 0f b6 92 b0 24 40 00 movzbl 0x4024b0(%rdx),%edx
#测试0x4024b0这个地址:
#(gdb) x/s 0x4024b0
#0x4024b0 <array.3449>: "maduiersnfotvbylSo you think you can stop the bomb with ctrl- #c, do you?"
#注意到”maduiersnfotvbyl“刚好是16个,代表字母十六进制低位的16种可能。所以说,这里就是将字母的十六进制低位加上0x4024b0这个基地址选出的字母放到edx中。
4010a0: 88 54 04 10 mov %dl,0x10(%rsp,%rax,1)
#验证rsp对应内容:为杂乱序列,这里就是将刚刚生成的分别6个字母分别放到以rsp+0x10为基地址的内存中。
4010a4: 48 83 c0 01 add $0x1,%rax
4010a8: 48 83 f8 06 cmp $0x6,%rax
4010ac: 75 dd jne 40108b <phase_5+0x29>
#此处为一个6次的循环
4010ae: c6 44 24 16 00 movb $0x0,0x16(%rsp)
#0x16=0x10+6将刚刚生成的6个字母形成一个字符串。
4010b3: be 5e 24 40 00 mov $0x40245e,%esi
#(gdb) x/s 0x40245e
#0x40245e: "flyers" 这可能就是等会要比较的字符串了
4010b8: 48 8d 7c 24 10 lea 0x10(%rsp),%rdi
#rdi指向刚刚生成的新字符串
4010bd: e8 76 02 00 00 callq 401338 <strings_not_equal>
4010c2: 85 c0 test %eax,%eax
#测试两个字符串是否相等
4010c4: 74 13 je 4010d9 <phase_5+0x77>
4010c6: e8 6f 03 00 00 callq 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
4010d7: eb b2 jmp 40108b <phase_5+0x29>
#第一步跳到这里,就是把eax置了个0 。。。
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 callq 400b30 <__stack_chk_fail@plt>
4010ee: 48 83 c4 20 add $0x20,%rsp
4010f2: 5b pop %rbx
4010f3: c3 retq
#由以上分析,maduiersnfotvbyl分别对应低位为0123456789ABCDEF的字符,所以为了生成”flyers“字符串,低位必须为”9FE567“,例如”ionefg“就可以。(查ascii码表)
phase_6: 4 3 2 1 6 5
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 callq 40145c <read_six_numbers>
#以rsp为基地址(栈)读取六个数字。
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 callq 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 callq 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>
#这个上面是一个嵌套循环,外侧循环6次,分别测试读入的数字是否小于等于6(实际是无符号数减1后是否小于等 #于5,所以0是不行的),内部的循环比较当前的数字和剩下的数字是否相同。总结起来就是要求这六个数字各不相 #同,且大于0小于7,所以这六个数字可以是123456的组合。
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>
#上面这个循环是依次将刚刚读入的六个数字将7作为被减数得到的结果。如输入654321将得到123456.
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
#上面这一段是本实验最难理解的部分,循环的大致意思是设置0x6032d0为基地址的内存模块和以rsp为基地址的内存模块保存刚刚输入的数字和0x6032d0/e0/f0 0x603300/10/20,其实并不用完全弄懂该部分,结合下面的”爆炸导火索“更好理解一些。
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 callq 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>
#当输入123456
#(gdb) x/20wx $rsp
#0x7fffffffdb60: 0x00000006 0x00000005 0x00000004 0x00000003
#0x7fffffffdb70: 0x00000002 0x00000001 0x00000000 0x00000000
#0x7fffffffdb80: 0x00603320 0x00000000 0x00603310 0x00000000
#0x7fffffffdb90: 0x00603300 0x00000000 0x006032f0 0x00000000
#0x7fffffffdba0: 0x006032e0 0x00000000 0x006032d0 0x00000000
# (gdb) x/20wx $rdx
#0x6032d0 <node1>: 0x0000014c 0x00000001 0x00000000 0x00000000
#0x6032e0 <node2>: 0x000000a8 0x00000002 0x006032d0 0x00000000
#0x6032f0 <node3>: 0x0000039c 0x00000003 0x006032e0 0x00000000
#0x603300 <node4>: 0x000002b3 0x00000004 0x006032f0 0x00000000
#0x603310 <node5>: 0x000001dd 0x00000005 0x00603300 0x00000000
#0x603320 <node6>: 0x000001bb 0x00000006 0x00603310 0x00000000
#该测试循环是这样工作的,从栈中后部的第一个地址开始(这里地址1是0x00603320)判读(地址1)>=((地址1+0x8)),注意有两层括号,是否成立,不成立即爆炸,若成立,则令地址1=(地址1+0x8),进行下一次循环。在这个例子中,由于1bb<1dd,发生爆炸。于是乎我们的任务就变成了寻找地址1是哪一个的规律以及地址+0x8保存的是哪些地址的规律。
#输入654321后,得到:
#(gdb) x/24wx $rsp
#0x7fffffffdb60: 0x00000001 0x00000002 0x00000003 0x00000004
#0x7fffffffdb70: 0x00000005 0x00000006 0x00000000 0x00000000
#0x7fffffffdb80: 0x006032d0 0x00000000 0x006032e0 0x00000000
#0x7fffffffdb90: 0x006032f0 0x00000000 0x00603300 0x00000000
#0x7fffffffdba0: 0x00603310 0x00000000 0x00603320 0x00000000
#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
#结合动态运行可知,地址1为rsp后面保存的第一个地址,并且将依次将随后的5个地址存入0x60...+0x8的地方。结合之前123456的测试,这里可以观察到,7-x操作后数字与地址对应关系:
# 1 - > 0x6032d0
# 2 - > 0x6032e0
# 3 - > 0x6032f0
# 4 - > 0x603300
# 5 - > 0x603310
# 6 - > 0x603320
#现在结合上面循环测试的规则,我们对(0x603...)的六个常数进行排列:0x0000039c > 0x000002b3 > 0x000001dd > 0x000001bb > 0x0000014c > 0x000000a8,用地址表示就是(0x6032f0) > (0x603300) > (0x603310) > (0x603320) > (0x6032d0) > (0x6032e0)
#所以我们应该得到这样一个内存:
#0x6032d0 <node1>: 0x0000014c 0x00000001 0x006032e0 0x00000000
#0x6032e0 <node2>: 0x000000a8 0x00000002 0x00000000 0x00000000
#0x6032e0+0x8应该是0,因为0x0a8它是最小的了,且只比较5次。
#0x6032f0 <node3>: 0x0000039c 0x00000003 0x00603300 0x00000000
#0x603300 <node4>: 0x000002b3 0x00000004 0x00603310 0x00000000
#0x603310 <node5>: 0x000001dd 0x00000005 0x00603320 0x00000000
#0x603320 <node6>: 0x000001bb 0x00000006 0x006032d0 0x00000000
#即得到这样一个栈空间:
#0x7fffffffdb60: xxxxxxxxxx xxxxxxxxxx xxxxxxxxxx xxxxxxxxxx
#0x7fffffffdb70: xxxxxxxxxx xxxxxxxxxx xxxxxxxxxx xxxxxxxxxx
#0x7fffffffdb80: 0x006032f0 0x00000000 0x00603300 0x00000000
#0x7fffffffdb90: 0x00603310 0x00000000 0x00603320 0x00000000
#0x7fffffffdba0: 0x006032d0 0x00000000 0x006032e0 0x00000000
#由于:
# 1 - > 0x6032d0
# 2 - > 0x6032e0
# 3 - > 0x6032f0
# 4 - > 0x603300
# 5 - > 0x603310
# 6 - > 0x603320
#所以7-x操作后应该是345612,即应该输入432165。
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 retq
综上,输入文件应该如下:
psol.txt
Border relations with Canada have never been better.
1 2 4 8 16 32
1 311
0 0
ionefg
4 3 2 1 6 5
运行输出:(也可以手动输入)
frank@under:~/Desktop/cs:app/lab/bomblab$ ./bomb psol.txt
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!
So you got that one. Try this one.
Good work! On to the next...
Congratulations! You've defused the bomb!
\[2017.10.27]更新:学校重新安排了一次相似的实验,只是把一些常量改变了,然后添加了一个“secret_phase”。这次把最后一个阶段将输入逐个7减后的算法详细分析下,上次是利用结果推测过程,没有具体研究算法。
注:下面分析的第六阶段我们学校改过的,密码每个人都不一样(这个的bomb_id为0x28),但算法是和cmu的那个炸弹一样的。secret阶段原CMU炸弹并没有
..........(参见前文)
#后部分的代码主要分两个算法
#“混淆算法开始”
#混淆阶段1
40116e: be 00 00 00 00 mov $0x0,%esi
401173: eb 1a jmp 40118f <phase_6+0xad>
401175: 48 8b 52 08 mov 0x8(%rdx),%rdx
401179: 83 c0 01 add $0x1,%eax
40117c: 39 c8 cmp %ecx,%eax
40117e: 75 f5 jne 401175 <phase_6+0x93>
401180: 48 89 54 74 20 mov %rdx,0x20(%rsp,%rsi,2)
401185: 48 83 c6 04 add $0x4,%rsi
401189: 48 83 fe 18 cmp $0x18,%rsi
40118d: 74 14 je 4011a3 <phase_6+0xc1>
40118f: 8b 0c 34 mov (%rsp,%rsi,1),%ecx
401192: b8 01 00 00 00 mov $0x1,%eax
401197: ba f0 32 60 00 mov $0x6032f0,%edx
40119c: 83 f9 01 cmp $0x1,%ecx
40119f: 7f d4 jg 401175 <phase_6+0x93>
4011a1: eb dd jmp 401180 <phase_6+0x9e>
#混淆阶段2
4011a3: 48 8b 5c 24 20 mov 0x20(%rsp),%rbx
4011a8: 48 8d 44 24 20 lea 0x20(%rsp),%rax
4011ad: 48 8d 74 24 48 lea 0x48(%rsp),%rsi
4011b2: 48 89 d9 mov %rbx,%rcx
4011b5: 48 8b 50 08 mov 0x8(%rax),%rdx
4011b9: 48 89 51 08 mov %rdx,0x8(%rcx)
4011bd: 48 83 c0 08 add $0x8,%rax
4011c1: 48 89 d1 mov %rdx,%rcx
4011c4: 48 39 c6 cmp %rax,%rsi
4011c7: 75 ec jne 4011b5 <phase_6+0xd3>
4011c9: 48 c7 42 08 00 00 00 movq $0x0,0x8(%rdx)
#验证算法开始
4011d0: 00
4011d1: bd 05 00 00 00 mov $0x5,%ebp
4011d6: 48 8b 43 08 mov 0x8(%rbx),%rax
4011da: 8b 00 mov (%rax),%eax
4011dc: 39 03 cmp %eax,(%rbx)
4011de: 7d 05 jge 4011e5 <phase_6+0x103>
4011e0: e8 52 02 00 00 callq 401437 <explode_bomb>
4011e5: 48 8b 5b 08 mov 0x8(%rbx),%rbx
4011e9: 83 ed 01 sub $0x1,%ebp
4011ec: 75 e8 jne 4011d6 <phase_6+0xf4>
.......(见前文)
经过前面的7减算法后,我们如果输入1 6 2 5 3 4, 7减后为6 1 5 2 4 3,查看rsp为:
另外注意到这一句话有一个常量地址
401197: ba f0 32 60 00 mov $0x6032f0,%edx
查看该地址内容:
可以观察到每一行(16个字节)是由 4字节xxx常数 + 4字节1~6 + 下一个16字节的地址(除了最后一个16字节除外)
分析混淆算法的第一阶段
40116e: be 00 00 00 00 mov $0x0,%esi
401173: eb 1a jmp 40118f <phase_6+0xad>
#小循环(根据取到的数在0x6032f0起始的内存块中寻找第几个地址,例如为5的话就是0x6032f0 + (5-1)*16 + 8代表的0x603330
401175: 48 8b 52 08 mov 0x8(%rdx),%rdx
401179: 83 c0 01 add $0x1,%eax
40117c: 39 c8 cmp %ecx,%eax
40117e: 75 f5 jne 401175 <phase_6+0x93>
#大循环,取出六个数,如果是1的话直接在栈中append 0x6032f0这个地址(最小的那个),否则进入上面的小循环取得一个地址然后append到栈中。
401180: 48 89 54 74 20 mov %rdx,0x20(%rsp,%rsi,2)
401185: 48 83 c6 04 add $0x4,%rsi
401189: 48 83 fe 18 cmp $0x18,%rsi
40118d: 74 14 je 4011a3 <phase_6+0xc1>
40118f: 8b 0c 34 mov (%rsp,%rsi,1),%ecx
401192: b8 01 00 00 00 mov $0x1,%eax
401197: ba f0 32 60 00 mov $0x6032f0,%edx
40119c: 83 f9 01 cmp $0x1,%ecx
40119f: 7f d4 jg 401175 <phase_6+0x93>
4011a1: eb dd jmp 401180 <phase_6+0x9e>
所以,我们如果输入的是1 6 2 5 3 4,7减操作后为 6 1 5 2 4 3,栈中就应该被依次append
3340
32f0
3330
3300
3320
3310
如图:
接着分析混淆算法的第二阶段
4011a3: 48 8b 5c 24 20 mov 0x20(%rsp),%rbx #栈中第一个append进的地址
4011a8: 48 8d 44 24 20 lea 0x20(%rsp),%rax #栈中第一个append进的地址的地址
4011ad: 48 8d 74 24 48 lea 0x48(%rsp),%rsi #栈中最后一个append进的地址的地址
4011b2: 48 89 d9 mov %rbx,%rcx #第一个append进的地址
#开始循环直到最后一个append进的地址也存入0x6032f0起始的内存块中
4011b5: 48 8b 50 08 mov 0x8(%rax),%rdx
4011b9: 48 89 51 08 mov %rdx,0x8(%rcx) #将rax指向的下一个地址存入0x6032f0起始的内存块中
4011bd: 48 83 c0 08 add $0x8,%rax #更新使rax指向栈中下一个之前存入的地址
4011c1: 48 89 d1 mov %rdx,%rcx #这次存入的地址变为下次要存入的基地址
4011c4: 48 39 c6 cmp %rax,%rsi
4011c7: 75 ec jne 4011b5 <phase_6+0xd3>
4011c9: 48 c7 42 08 00 00 00 movq $0x0,0x8(%rdx)
由上面的栈图,我们的操作就是(用<-代表写入):603340+8<-6032f0 6032f0+8<-603330 603330+8<-603300 603300+8<-603320 603320+8<-603310 而603310+8原本存储的603320没改变为0,验证:
开始分析导火索(验证算法)
4011d0: 00
4011d1: bd 05 00 00 00 mov $0x5,%ebp #设计循环量为5次
#开始循环
4011d6: 48 8b 43 08 mov 0x8(%rbx),%rax #第一次rbx保存的是第一个存入栈中的地址,rax将变为rbx+8中保存的地址
4011da: 8b 00 mov (%rax),%eax
4011dc: 39 03 cmp %eax,(%rbx)
4011de: 7d 05 jge 4011e5 <phase_6+0x103> #rbx指向的常数必须大于等于rbx+8指向的常数,否则爆炸
4011e0: e8 52 02 00 00 callq 401437 <explode_bomb>
4011e5: 48 8b 5b 08 mov 0x8(%rbx),%rbx #将rbx替换为rbx+8保存的地址
4011e9: 83 ed 01 sub $0x1,%ebp
4011ec: 75 e8 jne 4011d6 <phase_6+0xf4>
逆推构建输入
由导火索算法的分析,又因为常量如下:
2e4(0x603310) > 257(0x6032f0) > 235(0x603330) > 169(0x603300) > 098(0x603340) > 05b(0x603320)
所以我们应该将0x6032f0起始的内存块构建为这个样子:
0x6032f0 <node1>: 0x0000000100000257 0x0000000000603330
0x603300 <node2>: 0x0000000200000169 0x0000000000603340
0x603310 <node3>: 0x00000003000002e4 0x00000000006032f0
0x603320 <node4>: 0x000000040000005b 0x0000000000000000
0x603330 <node5>: 0x0000000500000235 0x0000000000603300
0x603340 <node6>: 0x0000000600000098 0x0000000000603320
即我们的存入操作为:603310+8<-6032f0 6032f0+8<-603330 603330+8<-603300 603300+8<-603340 603340+8<-603320
由混淆算法的第二阶段分析,我们的存入我们的栈append的地址应该是这个顺序:
603310 6032f0 603330 603300 603340 603320
由混淆算法的第一阶段分析,我们我们输入的数字7减操作后应该为:
3 1 5 2 6 4
所以减7前的密码为:4 6 2 5 1 3
secret_phase
默认情况下并不能激发隐藏关卡:
在bomb的反汇编代码内找到调用secret_phase
的函数,居然是phase_defused
...
phase_defused
内关键的代码:
4015fe: be c2 25 40 00 mov $0x4025c2,%esi
401603: 48 8d 7c 24 10 lea 0x10(%rsp),%rdi
401608: e8 2b fd ff ff callq 401338 <strings_not_equal>
40160d: 85 c0 test %eax,%eax
40160f: 75 1e jne 40162f <phase_defused+0x71>
401611: bf 98 24 40 00 mov $0x402498,%edi
401616: e8 c5 f4 ff ff callq 400ae0 <puts@plt>
40161b: bf c0 24 40 00 mov $0x4024c0,%edi
401620: e8 bb f4 ff ff callq 400ae0 <puts@plt>
401625: b8 00 00 00 00 mov $0x0,%eax
40162a: e8 1f fc ff ff callq 40124e <secret_phase>
注意到常数地址0x4025c2。
所以phase_defused应该是读入了字符,判断是否等于DrEvil,相等的话就进入隐藏关卡。
于是我们在phase_defused
中寻找是否有读入的命令,发现就在上面相邻处有:
4015ea: be b9 25 40 00 mov $0x4025b9,%esi
4015ef: bf 90 38 60 00 mov $0x603890,%edi
4015f4: e8 b7 f5 ff ff callq 400bb0 <__isoc99_sscanf@plt>
4015f9: 83 f8 03 cmp $0x3,%eax
4015fc: 75 31 jne 40162f <phase_defused+0x71>
查看0x4025b9
可见是读入两个整数和一个字符串
查看0x603890
就是我在第四阶段输入的密码——两个整数,所以我们在第四阶段输入10 5 DrEvil即可进入隐藏关卡:
分析隐藏关卡的算法
000000000040124e <secret_phase>:
40124e: 53 push %rbx
40124f: e8 44 02 00 00 callq 401498 <read_line> #读入一行数据
401254: ba 0a 00 00 00 mov $0xa,%edx
401259: be 00 00 00 00 mov $0x0,%esi
40125e: 48 89 c7 mov %rax,%rdi
401261: e8 2a f9 ff ff callq 400b90 <strtol@plt> #将读入的数据转化为整数
401266: 48 89 c3 mov %rax,%rbx
401269: 8d 40 ff lea -0x1(%rax),%eax
40126c: 3d e8 03 00 00 cmp $0x3e8,%eax #不能读入0x3e8+1 = 1001
401271: 76 05 jbe 401278 <secret_phase+0x2a>
401273: e8 bf 01 00 00 callq 401437 <explode_bomb>
401278: 89 de mov %ebx,%esi #参数二为刚刚读入的数
40127a: bf 10 31 60 00 mov $0x603110,%edi #参数一为
40127f: e8 8c ff ff ff callq 401210 <fun7>
401284: 83 f8 03 cmp $0x3,%eax #必须返回3
401287: 74 05 je 40128e <secret_phase+0x40>
401289: e8 a9 01 00 00 callq 401437 <explode_bomb>
.......
检查调用fun7
传入的第一个参数0x603110
进入fun7
(必须返回3)
0000000000401210 <fun7>:
401210: 48 83 ec 08 sub $0x8,%rsp
401214: 48 85 ff test %rdi,%rdi
401217: 74 2b je 401244 <fun7+0x34> #第一次由secret_phase调用的话,不可能为0
401219: 8b 17 mov (%rdi),%edx #0x24
40121b: 39 f2 cmp %esi,%edx #比较读入的数和0x24
40121d: 7e 0d jle 40122c <fun7+0x1c>
40121f: 48 8b 7f 08 mov 0x8(%rdi),%rdi
401223: e8 e8 ff ff ff callq 401210 <fun7> #又是TMD的递归。。史老师您就这么热爱递归嘛。。。(我们学校把第4阶段也改成了递归)
401228: 01 c0 add %eax,%eax
40122a: eb 1d jmp 401249 <fun7+0x39>
40122c: b8 00 00 00 00 mov $0x0,%eax
401231: 39 f2 cmp %esi,%edx
401233: 74 14 je 401249 <fun7+0x39>
401235: 48 8b 7f 10 mov 0x10(%rdi),%rdi
401239: e8 d2 ff ff ff callq 401210 <fun7>
40123e: 8d 44 00 01 lea 0x1(%rax,%rax,1),%eax
401242: eb 05 jmp 401249 <fun7+0x39>
401244: b8 ff ff ff ff mov $0xffffffff,%eax
401249: 48 83 c4 08 add $0x8,%rsp
40124d: c3 retq
为了方便分析算法,抽象成伪代码:
需要返回3,初始rdi=0x603110,esi = input_number, eax=input_number-1
若rdi=0,返回eax = -1
edx = (rdi)
若rdi指向的常数小于等于input_number,跳L1
rdi += 8;
递归
eax = eax*2
return
L1: eax = 0
如果esi=edx,return
rdi = (%rdi+0x10)
递归
eax = 2*eax+1
return
eax = -1
return
可以观察到,返回值只有eax = eax*2(偶数)eax = 2*eax+1(奇数)和-1与0这四种情况,所以为了获得3的返回值,我们的递归顺序应该如下:
返回3: 2*1 + 1 -> 返回1: 2*0 + 1 -> 返回0 : 0*2 -> eax=0, esi=edx,return
观察0x603110起始的内存块:
0x603110 <n1>: 0x0000000000000024 0x0000000000603130
0x603120 <n1+16>: 0x0000000000603150 0x0000000000000000
0x603130 <n21>: 0x0000000000000008 0x00000000006031b0
0x603140 <n21+16>: 0x0000000000603170 0x0000000000000000
0x603150 <n22>: 0x0000000000000032 0x0000000000603190
0x603160 <n22+16>: 0x00000000006031d0 0x0000000000000000
0x603170 <n32>: 0x0000000000000016 0x0000000000603290
0x603180 <n32+16>: 0x0000000000603250 0x0000000000000000
0x603190 <n33>: 0x000000000000002d 0x00000000006031f0
0x6031a0 <n33+16>: 0x00000000006032b0 0x0000000000000000
0x6031b0 <n31>: 0x0000000000000006 0x0000000000603210
0x6031c0 <n31+16>: 0x0000000000603270 0x0000000000000000
0x6031d0 <n34>: 0x000000000000006b 0x0000000000603230
0x6031e0 <n34+16>: 0x00000000006032d0 0x0000000000000000
0x6031f0 <n45>: 0x0000000000000028 0x0000000000000000
0x603200 <n45+16>: 0x0000000000000000 0x0000000000000000
0x603210 <n41>: 0x0000000000000001 0x0000000000000000
0x603220 <n41+16>: 0x0000000000000000 0x0000000000000000
0x603230 <n47>: 0x0000000000000063 0x0000000000000000
0x603240 <n47+16>: 0x0000000000000000 0x0000000000000000
0x603250 <n44>: 0x0000000000000023 0x0000000000000000
.......
按照递归分析逐步写出rdi的变化和对input_number的限制条件的缩小:
%rdi : 0x603110 -> 0x603150 -> 0x6031d0 -> 603230
input_number的范围:input_number >= 0x24 (要跳L1)input_number != 0x24-> input_number >= 0x32 (要跳L1)input_number != 0x32 -> input_number < 0x6b(不能跳L1)-> input_number >= 0x63(要跳L1)input_number = edx = 0x63
所以,密码应该为0x63即99。