这是CSAPP的第二个实验,主要让我们理解代码的机器级表示,最重要的是理解每个寄存器的作用以及如何使用这些寄存器。本次的实验内容有点晦涩难懂,对于这些内容多看下习惯就好了。
本次实验中的bomb文件中共有7个炸弹问题(6个显式的和1个隐藏的),每条问题只有输入正确的答案才能进入下一题,否则则会触发爆炸。通过阅读bomb文件的汇编代码理解各个问题的运作方式,推出正确的输入答案。隐藏的问题需要通过gdb直接调用解决。
我的编译环境:Ubuntu 16.04,gcc 5.4.0。
从官网下载到实验,解压后一共三个文件,具体如下图所示。
readme中没写什么有用的内容,bomb文件是编译完成的文件,bomb.c是本次实验的源码,打开看下,大概浏览了一遍,一共有phase_1 ~ phase_6 6个炸弹,从命令行输入的内容必须要和phase函数中的一致,否则就会爆炸退出程序。phase函数并没有给出源码,所以无法得知其期望的字符串是什么。给了bomb可执行文件,我们就把这个文件反汇编下,从反汇编推算下其内容是什么。
首先使用objdump -d bomb > bomb.asm命令生成反汇编文件。
先运行bomb文件,提示没有权限,我的文件是从windwos拷贝到Linux虚拟机中的,所以会报这个错误。执行chmod +777 bomb 赋予权限。如下图所示。
然后随便输入一些内容看下会有什么后果,如下图所示,提示已经爆炸。
下面从main函数开始分析下反汇编。
0000000000400da0 <main>:
400da0: 53 push %rbx
400da1: 83 ff 01 cmp $0x1,%edi #if (argc == 1)
400da4: 75 10 jne 400db6 <main+0x16> # 不相等就跳转到400db6
400da6: 48 8b 05 9b 29 20 00 mov 0x20299b(%rip),%rax # 603748 <stdin@@GLIBC_2.2.5>
400dad: 48 89 05 b4 29 20 00 mov %rax,0x2029b4(%rip) # 603768 <infile> 相等就读取输入
400db4: eb 63 jmp 400e19 <main+0x79> #跳转到initialize_bomb
400db6: 48 89 f3 mov %rsi,%rbx
400db9: 83 ff 02 cmp $0x2,%edi #else if (argc == 2)
400dbc: 75 3a jne 400df8 <main+0x58> #不相等跳转到400df8
400dbe: 48 8b 7e 08 mov 0x8(%rsi),%rdi
400dc2: be b4 22 40 00 mov $0x4022b4,%esi
400dc7: e8 44 fe ff ff callq 400c10 <fopen@plt>
400dcc: 48 89 05 95 29 20 00 mov %rax,0x202995(%rip) # 603768 <infile>
400dd3: 48 85 c0 test %rax,%rax
400dd6: 75 41 jne 400e19 <main+0x79> #跳转到initialize_bomb
400dd8: 48 8b 4b 08 mov 0x8(%rbx),%rcx
400ddc: 48 8b 13 mov (%rbx),%rdx
400ddf: be b6 22 40 00 mov $0x4022b6,%esi
400de4: bf 01 00 00 00 mov $0x1,%edi #传参
400de9: e8 12 fe ff ff callq 400c00 <__printf_chk@plt> #printf("%s: Error: Couldn't open %s\n", argv[0], argv[1]);
400dee: bf 08 00 00 00 mov $0x8,%edi
400df3: e8 28 fe ff ff callq 400c20 <exit@plt> #exit(8);
400df8: 48 8b 16 mov (%rsi),%rdx
400dfb: be d3 22 40 00 mov $0x4022d3,%esi
400e00: bf 01 00 00 00 mov $0x1,%edi
400e05: b8 00 00 00 00 mov $0x0,%eax #传参
400e0a: e8 f1 fd ff ff callq 400c00 <__printf_chk@plt> #printf("Usage: %s [<input_file>]\n", argv[0]);
400e0f: bf 08 00 00 00 mov $0x8,%edi
400e14: e8 07 fe ff ff callq 400c20 <exit@plt> #exit(8);
400e19: e8 84 05 00 00 callq 4013a2 <initialize_bomb> #调用initialize_bomb();
400e1e: bf 38 23 40 00 mov $0x402338,%edi
400e23: e8 e8 fc ff ff callq 400b10 <puts@plt> #printf("Welcome to my fiendish little bomb. You have 6 phases with\n");
400e28: bf 78 23 40 00 mov $0x402378,%edi
400e2d: e8 de fc ff ff callq 400b10 <puts@plt> #printf("which to blow yourself up. Have a nice day!\n");
400e32: e8 67 06 00 00 callq 40149e <read_line> #调用read_line();
400e37: 48 89 c7 mov %rax,%rdi #传参
400e3a: e8 a1 00 00 00 callq 400ee0 <phase_1> #调用phase_1();
400e3f: e8 80 07 00 00 callq 4015c4 <phase_defused> #调用phase_defused();
400e44: bf a8 23 40 00 mov $0x4023a8,%edi
400e49: e8 c2 fc ff ff callq 400b10 <puts@plt> #printf("Phase 1 defused. How about the next one?\n");
400e4e: e8 4b 06 00 00 callq 40149e <read_line> #调用read_line();
400e53: 48 89 c7 mov %rax,%rdi #传参
400e56: e8 a1 00 00 00 callq 400efc <phase_2> #调用phase_2();
400e5b: e8 64 07 00 00 callq 4015c4 <phase_defused>
400e60: bf ed 22 40 00 mov $0x4022ed,%edi
400e65: e8 a6 fc ff ff callq 400b10 <puts@plt>
400e6a: e8 2f 06 00 00 callq 40149e <read_line>
400e6f: 48 89 c7 mov %rax,%rdi
400e72: e8 cc 00 00 00 callq 400f43 <phase_3> #调用phase_3();
400e77: e8 48 07 00 00 callq 4015c4 <phase_defused>
400e7c: bf 0b 23 40 00 mov $0x40230b,%edi
400e81: e8 8a fc ff ff callq 400b10 <puts@plt>
400e86: e8 13 06 00 00 callq 40149e <read_line>
400e8b: 48 89 c7 mov %rax,%rdi
400e8e: e8 79 01 00 00 callq 40100c <phase_4> #调用phase_4();
400e93: e8 2c 07 00 00 callq 4015c4 <phase_defused>
400e98: bf d8 23 40 00 mov $0x4023d8,%edi
400e9d: e8 6e fc ff ff callq 400b10 <puts@plt>
400ea2: e8 f7 05 00 00 callq 40149e <read_line>
400ea7: 48 89 c7 mov %rax,%rdi
400eaa: e8 b3 01 00 00 callq 401062 <phase_5> #调用phase_4();
400eaf: e8 10 07 00 00 callq 4015c4 <phase_defused>
400eb4: bf 1a 23 40 00 mov $0x40231a,%edi
400eb9: e8 52 fc ff ff callq 400b10 <puts@plt>
400ebe: e8 db 05 00 00 callq 40149e <read_line>
400ec3: 48 89 c7 mov %rax,%rdi
400ec6: e8 29 02 00 00 callq 4010f4 <phase_6> #调用phase_4();
400ecb: e8 f4 06 00 00 callq 4015c4 <phase_defused>
400ed0: b8 00 00 00 00 mov $0x0,%eax
400ed5: 5b pop %rbx
400ed6: c3 retq
大概分析了下主函数,主要还是传参和函数的调用,想要得出结果还是要看phase_1 ~ phase_6这些函数的反汇编。
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 callq 401338 <strings_not_equal> #strings_not_equal 比较两个字符串结果保存在 %eax
400eee: 85 c0 test %eax,%eax # 测试 %eax 为0还是1
400ef0: 74 05 je 400ef7 <phase_1+0x17> #相等就跳转到400ef7
400ef2: e8 43 05 00 00 callq 40143a <explode_bomb> #否则就explode_bomb,失败
400ef7: 48 83 c4 08 add $0x8,%rsp #正常结束
400efb: c3 retq
第二行表示为函数开辟内存空间。第三行给%esi传进了一个参数,然后就调用了strings_not_equal。由strings_not_equal的反汇编可以看出,这个函数是接受两个参数的。那么,除了%esi传进的一个,另一个就是我们输入的字符串了。strings_not_equal比较的结果放在%eax中。接下来测试%eax为0还是1 ,如果%eax为1就表明两个字符串相等,跳转到相等400ef7,正常结束程序。否则就跳转到explode_bomb,失败。
0000000000401338 <strings_not_equal>:
401338: 41 54 push %r12
40133a: 55 push %rbp
40133b: 53 push %rbx
40133c: 48 89 fb mov %rdi,%rbx # 这里传入了两个参数%rdi,%rsi,这两个参数一个是输入的字符串,另一个是期望的字符串
40133f: 48 89 f5 mov %rsi,%rbp
401342: e8 d4 ff ff ff callq 40131b <string_length>
通过以上分析,我们可以得出结论,在内存为0x402400的地方存储的就是程序期望我们输入的字符串,那么我们利用GDB工具调试下代码,打印0x402400处的值看下。
下面输入这个字符串测试下,结果显示完全正确。
下面继续看phase_2的反汇编。
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 读取6个数字
400f0a: 83 3c 24 01 cmpl $0x1,(%rsp) #比较 1 和(%rsp)的值
400f0e: 74 20 je 400f30 <phase_2+0x34> #相等就继续400f30
400f10: e8 25 05 00 00 callq 40143a <explode_bomb> #否则就调用explode_bomb
400f15: eb 19 jmp 400f30 <phase_2+0x34>
400f17: 8b 43 fc mov -0x4(%rbx),%eax # %rbx-0x4 的值放到%eax 也就是上上个元素 1
400f1a: 01 c0 add %eax,%eax # 2*%eax
400f1c: 39 03 cmp %eax,(%rbx) # 比较 2*%eax (%rbx)
400f1e: 74 05 je 400f25 <phase_2+0x29> #相等就跳转到400f25
400f20: e8 15 05 00 00 callq 40143a <explode_bomb> #否则就调用explode_bomb
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>
400f30: 48 8d 5c 24 04 lea 0x4(%rsp),%rbx #0x4+%rsp的值放到%rbx
400f35: 48 8d 6c 24 18 lea 0x18(%rsp),%rbp #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 retq
伪代码:
// %rsp %rsp+0x4 %rsp+0x18
if((%rsp) == 1)
{
goto 400f30;
}
400f17:
%eax = %rbx-4; //%eax其实就是 %rsp
%eax=2*%eax; //%rsp = 2*%rsp
if((%rbx)==%eax) //要满足两倍关系 %rbx代表的是%rsp+0x4地址处的数据,%eax代表的是 2*(%rsp),意思就是说下一个数要是上一个数的两倍
{
%rbx=%rbx+0x4; //下一地址 +0x4
if(%rbx==%rbp) //%rbp = %rsp+0x18 %rbp存储的是结束的条件
{
return;
}
else
{
goto 400f17
}
}
else
{
explode_bomb
}
else
{
explode_bomb
}
400f30:
%rbx=%rsp+0x4;
%rbp=%rsp+0x18;
goto 400f17;
直接看汇编代码有点复杂,所以捋了下思路,写一个伪代码出来会方便看一点。从反汇编的read_six_numbers可以看出,答案一定是6个数字。
程序一开始将%rsp的值和1比较,只有相等时,才会继续进行。说明第一个数一定要是1。接着跳转到400f30将%rsp+0x4,%rsp+0x18。跳转到400f17,到这里后,把%rbx的值减去了4,减完之后,%eax其实就是%rsp的值,回到了最初的状态。接着将%eax乘以2,%rbx代表的是%rsp+0x4地址处的数据,%eax代表的是 2*(%rsp),意思就是说下一个数要是上一个数的两倍才会跳进if语句中,否则就会爆炸。接着将%rbx+0x4,看起来像是将地址+4.指向下一个数字。这个时候再将%rbx和%rbp比较,从这里可以判断出%rbp很可能就是结束的条件,只有%rbx == %rbp,才会正常结束程序,而且只有这一条路可以结束正序,否则就会爆炸。
分析到这里可以得出三个重要的结论:1.第一个数是1。2. 6个数字的关系为:后一个数是前一个数的两倍。3.结束的条件存放在%rsp+0x18。所以根据这三个结论我们可以推断出6个数字为:1 2 4 8 16 32。运行程序测试结果完全正确。
0000000000400f43 <phase_3>:
400f43: 48 83 ec 18 sub $0x18,%rsp
400f47: 48 8d 4c 24 0c lea 0xc(%rsp),%rcx # %rcx = 0xc + %rsp 第二个参数
400f4c: 48 8d 54 24 08 lea 0x8(%rsp),%rdx # %rdx = 0x8 + %rsp 第一个参数
400f51: be cf 25 40 00 mov $0x4025cf,%esi # %esi = 0x4025cf %d %d
400f56: b8 00 00 00 00 mov $0x0,%eax # %eax = 0
400f5b: e8 90 fc ff ff callq 400bf0 <__isoc99_sscanf@plt>
400f60: 83 f8 01 cmp $0x1,%eax # %eax 和 1 比较 输入的数的个数要大于1
400f63: 7f 05 jg 400f6a <phase_3+0x27> # %eax > 0x1, 大于就跳转到400f6a,否则explode_bomb
400f65: e8 d0 04 00 00 callq 40143a <explode_bomb> # %eax <= 0x1 explode_bomb
400f6a: 83 7c 24 08 07 cmpl $0x7,0x8(%rsp) # 0x8(%rsp) 0x7
400f6f: 77 3c ja 400fad <phase_3+0x6a> # 0x8(%rsp)>=0x7 跳转 explode_bomb 第一个参数大于7 爆炸 <0
400f71: 8b 44 24 08 mov 0x8(%rsp),%eax # 0x8(%rsp) < 0x7 则 %eax = 0x8(%rsp) [0,6]
400f75: ff 24 c5 70 24 40 00 jmpq *0x402470(, %rax,8) # 跳转表 *0x402470 + 8 * %rax *0x402470 = 0x400f7c
400f7c: b8 cf 00 00 00 mov $0xcf,%eax # %eax = 0xcf = 207 case 0
400f81: eb 3b jmp 400fbe <phase_3+0x7b> # 400fbe
400f83: b8 c3 02 00 00 mov $0x2c3,%eax # %eax = 0x2c3 = 707 case 2
400f88: eb 34 jmp 400fbe <phase_3+0x7b>
400f8a: b8 00 01 00 00 mov $0x100,%eax # %eax = 0x100 = 256 case 3
400f8f: eb 2d jmp 400fbe <phase_3+0x7b>
400f91: b8 85 01 00 00 mov $0x185,%eax # %eax = 0x185 = 389 case 4
400f96: eb 26 jmp 400fbe <phase_3+0x7b>
400f98: b8 ce 00 00 00 mov $0xce,%eax # %eax = 0xce = 206 case 5
400f9d: eb 1f jmp 400fbe <phase_3+0x7b>
400f9f: b8 aa 02 00 00 mov $0x2aa,%eax # %eax = 0x2aa = 682 case 6
400fa4: eb 18 jmp 400fbe <phase_3+0x7b>
400fa6: b8 47 01 00 00 mov $0x147,%eax # %eax = 0x147 = 327 case 7
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 # %eax = 0x0 = 0
400fb7: eb 05 jmp 400fbe <phase_3+0x7b>
400fb9: b8 37 01 00 00 mov $0x137,%eax # %eax = 0x137 = 311 case 1
400fbe: 3b 44 24 0c cmp 0xc(%rsp),%eax # 0xc(%rsp) %eax
400fc2: 74 05 je 400fc9 <phase_3+0x86> # 0xc(%rsp)==%eax 输入的第二个参数 和 %eax相等则解除
400fc4: e8 71 04 00 00 callq 40143a <explode_bomb> # 0xc(%rsp)!=%eax explode_bomb
400fc9: 48 83 c4 18 add $0x18,%rsp
400fcd: c3 retq
从第5行的0x4025cf入手,查看后发现是%d %d ,说明输入的是两个整数。
第7行,第8行说明输入的参数个数要大于1。第11行将第一个参数0x8(%rsp) 和7比较,大于7则爆炸,说明输入的参数要小于等于7,同时ja为无符号跳转,则参数还有大于0,因此得出第一个参数的范围[0,7]。第14行为间接跳转,以 *0x402470 处的值为基地址,再加上8 * %rax 进行跳转,不同的 %rax 跳转到不同的位置。
我们可以看下0x402470的值为0x400f7c。当%rax 为0时,跳转到0x400f7c。此时%eax = 207,最后跳转到33行,将207于输入的值比较。说明0 和 207为一组正确数据。测试下
其他结果如汇编中的注释所示。
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 # %d %d
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>
40102e: 83 7c 24 08 0e cmpl $0xe,0x8(%rsp) # 第一个参数和14比较,要比14小
401033: 76 05 jbe 40103a <phase_4+0x2e>
401035: e8 00 04 00 00 callq 40143a <explode_bomb>
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> # 三个参数:0x8(%rsp),0,14
40104d: 85 c0 test %eax,%eax # 测试返回值是否为0
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> # 为0 不会爆炸,所以第二个数一定要输入0
401058: e8 dd 03 00 00 callq 40143a <explode_bomb>
40105d: 48 83 c4 18 add $0x18,%rsp
401061: c3 retq
从第5行看起,0x4025cf指向的地方存储的仍然是 两个int型整数。第8行和2比较,说明输入参数的个数为2。第10行和14比较,说明输入的第一个参数一定要小于14。第13,14,15行向func4()传递三个参数0x8(%rsp),0,14 。第17行测试函数返回值是否为0,要想不爆炸,函数返回值一定要为0。第19行说明输入的第二个参数一定要为0。
所以,我们要确定的是当输入的第一个参数为多少的时候,fun4()的返回值为0。下面看下fun4()的反汇编。
0000000000400fce <func4>:
400fce: 48 83 ec 08 sub $0x8,%rsp
400fd2: 89 d0 mov %edx,%eax # %edx第三个参数
400fd4: 29 f0 sub %esi,%eax # %esi = %esi-%eax %esi 第二个参数
400fd6: 89 c1 mov %eax,%ecx
400fd8: c1 e9 1f shr $0x1f,%ecx # 0x1f >> %ecx
400fdb: 01 c8 add %ecx,%eax # %ecx =%ecx,%eax
400fdd: d1 f8 sar %eax
400fdf: 8d 0c 30 lea (%rax,%rsi,1),%ecx # %ecx = %rax + %rsi
400fe2: 39 f9 cmp %edi,%ecx # %edi第一个参数
400fe4: 7e 0c jle 400ff2 <func4+0x24> # %edi<= %ecx 跳转400ff2
400fe6: 8d 51 ff lea -0x1(%rcx),%edx # %edx = %rcx-0x1
400fe9: e8 e0 ff ff ff callq 400fce <func4> # 递归
400fee: 01 c0 add %eax,%eax # %eax = 2 * %eax
400ff0: eb 15 jmp 401007 <func4+0x39> # return
400ff2: b8 00 00 00 00 mov $0x0,%eax
400ff7: 39 f9 cmp %edi,%ecx
400ff9: 7d 0c jge 401007 <func4+0x39> # %edi >= %ecx return
400ffb: 8d 71 01 lea 0x1(%rcx),%esi # %esi = %rcx + 0x1
400ffe: e8 cb ff ff ff callq 400fce <func4> # 递归调用
401003: 8d 44 00 01 lea 0x1(%rax,%rax,1),%eax # %eax = %rax + %rax
401007: 48 83 c4 08 add $0x8,%rsp
40100b: c3 retq
将汇编翻译为C如下所示
//x: %edi y:%esi z:%edx k: %ecx t:%eax
void func4(int x,int y,int z)
{//x in %rdi,y in %rsi,z in %rdx,t in %rax,k in %ecx
//y的初始值为0,z的初始值为14
int t=z-y;
int k=t>>31;
t=(t+k)>>1;
k=t+y;
if(k>x)
{
z=k-1;
func4(x,y,z);
t=2t;
return;
}
else
{
t=0;
if(k<x)
{
y=k+1;
func4(x,y,z);
t=2*t+1;
return;
}
else
{
return;
}
}
}
当x == k时,返回值为0。所以第一个参数为7。
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
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 # 字符串长度要为6
401082: 74 4e je 4010d2 <phase_5+0x70>
401084: e8 b1 03 00 00 callq 40143a <explode_bomb>
401089: eb 47 jmp 4010d2 <phase_5+0x70>
##############################################start################################################################################
40108b: 0f b6 0c 03 movzbl (%rbx,%rax,1),%ecx # %ecx = %rbx + %rax ASCII码值 给%ecx
40108f: 88 0c 24 mov %cl,(%rsp) # ASCII码值取低8位
401092: 48 8b 14 24 mov (%rsp),%rdx
401096: 83 e2 0f and $0xf,%edx # ASCII码值取低4位
401099: 0f b6 92 b0 24 40 00 movzbl 0x4024b0(%rdx),%edx # %edx = %rdx + 0x4024b0(maduiersnfotvbyl)%edx:%rdx低4位有效
4010a0: 88 54 04 10 mov %dl,0x10(%rsp,%rax,1) # %edx低八位存在 0x10 + %rsp + %rax 中
4010a4: 48 83 c0 01 add $0x1,%rax # %rax = %rax + 1
4010a8: 48 83 f8 06 cmp $0x6,%rax
4010ac: 75 dd jne 40108b <phase_5+0x29> # %rax 不等于6 则循环 #################################################end###################################################################################
4010ae: c6 44 24 16 00 movb $0x0,0x16(%rsp)
4010b3: be 5e 24 40 00 mov $0x40245e,%esi # %esi指向从0x40245e内存单元读入的字符串flyers
4010b8: 48 8d 7c 24 10 lea 0x10(%rsp),%rdi # %rdi指向前面循环中构造好的长度为6的字符串
4010bd: e8 76 02 00 00 callq 401338 <strings_not_equal> # 判断%esi和%rdi指向的字符串是否相等
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>
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
#%rbp %rbx %r12~%15 被调用者保存寄存器
# %r10 %r11 调用者保存寄存器
%rdi %rsi %rdx %rcx %r8 %r9 依次保存参数1~6
%dl表示%rdx寄存器最低8bit
这个题目有点扰啊,兜了很大一个圈子!
第7行 ~ 13行说明输入的字符串长度要为6。
第15行 ~ 23行为一个循环。输入的字符串存储在%rbx中,第15行表示把输入字符串的第%eax个字符的ASCII码值给%ecx,%cl为%ecx的低8位,所以第16行为取%ecx的低八位。
第18行表示再取低4位。
第19行的0x4024b0查看内容为maduiersnfotvbyl,这句话的意思是以0x4024b0为基地址,以%rdx为偏移,从maduiersnfotvbyl字符串中取字符的低32位,结果放在%edx中。
第20行,%dl中的值应为0x4024b0+%rdx表示的字符,将其赋值给0x10(%rsp,%rax,1),最后计数器%rax+1。
第22行,表示是否循环够了6次。
第25行,0x40245e字符串为flyers,比较两个字符串,如果%eax为0(两个字符串相同),则解除炸弹,否则爆炸。
所以,0x4024b0 + %rdx = {flyers的ASCII码}。
flyers对应的ascii值 0x66 0x6c 0x79 0x65 0x72 0x73。
与0x4024b0内存地址开始的查找表比较获得偏移量 0x9 0xF 0xE 0x5 0x6 0x72。
因此输入长度为6的字符串中每个字符的低4bit的值分别为0x9 0xF 0xE 0x5 0x6 0x72。
若输入为大写字母,将低4bit的值加上0x40,获得输入字符串IONEFG。
若输入为小写字母,将低4bit的值加上0x60,获得输入字符串ionefg。
(gdb) disas phase_6
Dump of assembler code for function phase_6:
0x00000000004010f4 <+0>: push %r14 将被调用者保存寄存器压入栈
0x00000000004010f6 <+2>: push %r13
0x00000000004010f8 <+4>: push %r12
0x00000000004010fa <+6>: push %rbp
0x00000000004010fb <+7>: push %rbx %rsp = 0x7fffffffe2c0
0x00000000004010fc <+8>: sub $0x50,%rsp 分配栈空间 %rsp = 0x7fffffffe270
0x0000000000401100 <+12>: mov %rsp,%r13
0x0000000000401103 <+15>: mov %rsp,%rsi
0x0000000000401106 <+18>: callq 0x40145c <read_six_numbers> 读入6个值,保存至从 %rsi 开始的地址
0x000000000040110b <+23>: mov %rsp,%r14
0x000000000040110e <+26>: mov $0x0,%r12d %r12 置0,并且%r13 %r14 %rbp 均和 %rsp 指向相同地址 0x7fffffffe270
0x0000000000401114 <+32>: mov %r13,%rbp
0x0000000000401117 <+35>: mov 0x0(%r13),%eax 将第 %r13 指向的输入数复制到 %eax
0x000000000040111b <+39>: sub $0x1,%eax 将输入数减1
0x000000000040111e <+42>: cmp $0x5,%eax 判断输入数是否小于等于6,因为上一步中减1操作
0x0000000000401121 <+45>: jbe 0x401128 <phase_6+52> 若大于6,则调用 explode_bomb
0x0000000000401123 <+47>: callq 0x40143a <explode_bomb>
=========================================================================================================================================================
0x0000000000401128 <+52>: add $0x1,%r12d 将 %r12 加1
0x000000000040112c <+56>: cmp $0x6,%r12d 判断 %r12 是否等于6
0x0000000000401130 <+60>: je 0x401153 <phase_6+95> 若等于6,跳转,否则继续执行
0x0000000000401132 <+62>: mov %r12d,%ebx 将 %r12 复制到 %ebx
0x0000000000401135 <+65>: movslq %ebx,%rax 将 %ebx 符号位扩展复制到 %rax
0x0000000000401138 <+68>: mov (%rsp,%rax,4),%eax 将第 %ebx 输入数复制到 %eax
0x000000000040113b <+71>: cmp %eax,0x0(%rbp) 比较 %r13 指向的输入数和 第 %ebx 输入数 是否相等
0x000000000040113e <+74>: jne 0x401145 <phase_6+81> 如果相等,则调用 explode_bomb
0x0000000000401140 <+76>: callq 0x40143a <explode_bomb>
0x0000000000401145 <+81>: add $0x1,%ebx 将 %ebx 加1
0x0000000000401148 <+84>: cmp $0x5,%ebx 判断 %ebx 是否小于等于5
0x000000000040114b <+87>: jle 0x401135 <phase_6+65> 若小于等于,跳转,否则继续执行;该循环判断 %r13 指向的数据和其后输入数不相等
0x000000000040114d <+89>: add $0x4,%r13 将 %r13 指向下一个输入数,该循环判断所有的输入数全部不相等
0x0000000000401151 <+93>: jmp 0x401114 <phase_6+32>
=========================================================================================================================================================
0x0000000000401153 <+95>: lea 0x18(%rsp),%rsi 将 %rsi 指向栈中跳过读入数据位置作为结束标记,并且 %r14 仍和 %rsp 指向同一个位置
0x0000000000401158 <+100>: mov %r14,%rax 将 %r14 复制到 %rax
0x000000000040115b <+103>: mov $0x7,%ecx
0x0000000000401160 <+108>: mov %ecx,%edx 将立即数0x7复制到 %edx
0x0000000000401162 <+110>: sub (%rax),%edx 立即数7减去 %r14 指向的数据
0x0000000000401164 <+112>: mov %edx,(%rax) 将7减的结果存回 %r14 执行的内存单元
0x0000000000401166 <+114>: add $0x4,%rax %rax 指向下一个输入数
0x000000000040116a <+118>: cmp %rsi,%rax 比较是否达到输入数组的末尾,
0x000000000040116d <+121>: jne 0x401160 <phase_6+108> 该循环使用立即数7减去每个输入数据
==========================================================================================================================================================
0x000000000040116f <+123>: mov $0x0,%esi 将 %rsi 置0
0x0000000000401174 <+128>: jmp 0x401197 <phase_6+163>
0x0000000000401176 <+130>: mov 0x8(%rdx),%rdx 将 0x8(%rdx) 指向内存单元的内容复制到 %rdx, 指向链表下一个元素
0x000000000040117a <+134>: add $0x1,%eax 将 %eax 加1
0x000000000040117d <+137>: cmp %ecx,%eax 比较 %ecx 和 %eax 是否相等
0x000000000040117f <+139>: jne 0x401176 <phase_6+130> 不相等,继续遍历链表,最终 %rdx 指向链表的第 %ecx 个节点
0x0000000000401181 <+141>: jmp 0x401188 <phase_6+148>
0x0000000000401183 <+143>: mov $0x6032d0,%edx 重置链表首地址
0x0000000000401188 <+148>: mov %rdx,0x20(%rsp,%rsi,2)
0x000000000040118d <+153>: add $0x4,%rsi
0x0000000000401191 <+157>: cmp $0x18,%rsi
0x0000000000401195 <+161>: je 0x4011ab <phase_6+183>
0x0000000000401197 <+163>: mov (%rsp,%rsi,1),%ecx 将 (%rsp + %rsi) 指向的数据复制到 %ecx
0x000000000040119a <+166>: cmp $0x1,%ecx 比较 %ecx 是否小于等于1
0x000000000040119d <+169>: jle 0x401183 <phase_6+143> 若小于等于,跳转,否则继续执行, 等于1, %edx 直接指向链表首地址
0x000000000040119f <+171>: mov $0x1,%eax 将 %eax 置1
0x00000000004011a4 <+176>: mov $0x6032d0,%edx 将 %rdx 指向内存单元 0x6032d0
0x00000000004011a9 <+181>: jmp 0x401176 <phase_6+130> 跳转; 该循环根据输入数将链表中对应的第输入数个节点的地址复制到 0x20(%rsp) 开始的栈中
==========================================================================================================================================================
0x00000000004011ab <+183>: mov 0x20(%rsp),%rbx 将0x20(%rsp)的链表节点地址复制到 %rbx
0x00000000004011b0 <+188>: lea 0x28(%rsp),%rax 将 %rax 指向栈中下一个链表节点的地址
0x00000000004011b5 <+193>: lea 0x50(%rsp),%rsi 将 %rsi 指向保存的链表节点地址的末尾
0x00000000004011ba <+198>: mov %rbx,%rcx
0x00000000004011bd <+201>: mov (%rax),%rdx
0x00000000004011c0 <+204>: mov %rdx,0x8(%rcx) 将栈中指向的后一个节点的地址复制到前一个节点的地址位置
0x00000000004011c4 <+208>: add $0x8,%rax 移动到下一个节点
0x00000000004011c8 <+212>: cmp %rsi,%rax 判断6个节点是否遍历完毕
0x00000000004011cb <+215>: je 0x4011d2 <phase_6+222>
0x00000000004011cd <+217>: mov %rdx,%rcx
0x00000000004011d0 <+220>: jmp 0x4011bd <phase_6+201>
0x00000000004011d2 <+222>: movq $0x0,0x8(%rdx) 该循环按照7减去输入数据的索引重新调整链表
==========================================================================================================================================================
0x00000000004011da <+230>: mov $0x5,%ebp
0x00000000004011df <+235>: mov 0x8(%rbx),%rax 将 %rax 指向 %rbx 下一个链表节点
0x00000000004011e3 <+239>: mov (%rax),%eax
0x00000000004011e5 <+241>: cmp %eax,(%rbx) 比较链表节点中第一个字段值的大小,如果前一个节点值大于后一个节点值,跳转
0x00000000004011e7 <+243>: jge 0x4011ee <phase_6+250>
0x00000000004011e9 <+245>: callq 0x40143a <explode_bomb>
0x00000000004011ee <+250>: mov 0x8(%rbx),%rbx 将 %rbx 向后移动,指向栈中下一个链表节点的地址
0x00000000004011f2 <+254>: sub $0x1,%ebp 判断循环是否结束,该循环判断栈中重新调整后的链表节点是否按照降序排列
0x00000000004011f5 <+257>: jne 0x4011df <phase_6+235>
0x00000000004011f7 <+259>: add $0x50,%rsp
0x00000000004011fb <+263>: pop %rbx
0x00000000004011fc <+264>: pop %rbp
0x00000000004011fd <+265>: pop %r12
0x00000000004011ff <+267>: pop %r13
0x0000000000401201 <+269>: pop %r14
0x0000000000401203 <+271>: retq
End of assembler dump.
%rsi存储调用者phase_2栈帧的局部变量开始地址
%rdx = %rsi + 0
%rcx = %rsi + 4
%r8 = %rsi + 8
%r9 = %rsi + 12
(%rsp) = %rsi + 16
8(%rsp) = %rsi + 20
Dump of assembler code for function read_six_numbers:
0x000000000040145c <+0>: sub $0x18,%rsp
0x0000000000401460 <+4>: mov %rsi,%rdx
0x0000000000401463 <+7>: lea 0x4(%rsi),%rcx
0x0000000000401467 <+11>: lea 0x14(%rsi),%rax
0x000000000040146b <+15>: mov %rax,0x8(%rsp)
0x0000000000401470 <+20>: lea 0x10(%rsi),%rax
0x0000000000401474 <+24>: mov %rax,(%rsp)
0x0000000000401478 <+28>: lea 0xc(%rsi),%r9
0x000000000040147c <+32>: lea 0x8(%rsi),%r8
0x0000000000401480 <+36>: mov $0x4025c3,%esi
0x0000000000401485 <+41>: mov $0x0,%eax
0x000000000040148a <+46>: callq 0x400bf0 <__isoc99_sscanf@plt>
0x000000000040148f <+51>: cmp $0x5,%eax
0x0000000000401492 <+54>: jg 0x401499 <read_six_numbers+61>
0x0000000000401494 <+56>: callq 0x40143a <explode_bomb>
0x0000000000401499 <+61>: add $0x18,%rsp
0x000000000040149d <+65>: retq
End of assembler dump.
%rbp %rbx %r12~%15 被调用者保存寄存器
%r10 %r11 调用者保存寄存器
%rdi %rsi %rdx %rcx %r8 %r9 依次保存输入数1~6
假设输入数据为4 3 2 1 6 5
猜测0x6032d8为链表首地址,链表中每个节点占用12个Byte,前8字节保存两个4字Byte的整型数,剩余的4Byte存放下个节点地址
GDB查看使用7减去对应的输入后的数据
(gdb) p /x $rsp
$1 = 0x7fffffffe270
(gdb) x/6dw 0x7fffffffe270
0x7fffffffe270: 3 4 5 6
0x7fffffffe280: 1 2
重新调整链表前的链表的结构
(gdb) x/24xw 0x006032d0
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
保存在栈中链表节点信息
(gdb) x/6xg 0x7fffffffe290
0x7fffffffe290: 0x00000000006032f0 0x0000000000603300
0x7fffffffe2a0: 0x0000000000603310 0x0000000000603320
0x7fffffffe2b0: 0x00000000006032d0 0x00000000006032e0
按照7减去对应的输入后重新调整链表后的链表结构,索引顺序为 3 4 5 6 1 2
(gdb) x/24xw 0x006032d0
0x6032d0 <node1>: 0x0000014c 0x00000001 0x006032e0 0x00000000
0x6032e0 <node2>: 0x000000a8 0x00000002 0x00000000 0x00000000
0x6032f0 <node3>: 0x0000039c 0x00000003 0x00603300 0x00000000
0x603300 <node4>: 0x000002b3 0x00000004 0x00603310 0x00000000
0x603310 <node5>: 0x000001dd 0x00000005 0x00603320 0x00000000
0x603320 <node6>: 0x000001bb 0x00000006 0x006032d0 0x00000000
破解思路:
将链表中每个节点按照前4字节降序排序:3 4 5 6 1 2
因为在前面使用7减去对应的值,所以破解密码:4 3 2 1 6 5
phase6太难了,心力憔悴。phase6的解析来自以下链接: 这是链接
实验太难了。分析汇编像看天书,不过通过本次实验也提高了自己阅读汇编代码的能力,学会了基本的GDB调试代码的步骤。收获颇丰!