CSAPP LAB Binary bombs实验报告
[toc]
姓名:
学号: 专业:计算机科学与技术
科目:计算机系统原理 题目:binary bombs 二进制炸弹
实验时间: 2021年12月8日 实验教师:
一、实验目的:
逆向工程拆除“二进制炸弹”程序
增强对程序机器级表示、汇编语言、调试器和逆向工程等 理解。
一个“Binary Bombs”(二进制炸弹,简称炸弹)是一个 Linux可执行C程序,包含phase1~phase6共6个阶段。
炸弹运行各阶段要求输入一个字符串,若输入符合程序预 期,该阶段炸弹被“拆除”,否则“爆炸”。
你需要拆除尽可能多的炸弹
二、实验要求
拆弹装备:
熟练使用gdb调试器和objdump;
单步跟踪调试每一阶段的机器代码;
理解汇编语言代码的行为或作用;
“推断”拆除炸弹所需的目标字符串。
在各阶段的开始代码前和引爆炸弹函数前设置断点,便于调试。
每个炸弹阶段考察机器级语言程序不同方面,难度递增
阶段1:字符串比较
阶段2:循环
阶段3:条件/分支:含switch语句
阶段4:递归调用和栈
阶段5:指针
阶段6:链表/指针/结构
隐藏阶段,第4阶段之后附加特定字符串后出现
三、实验内容(所修改函数代码,功能以及重要代码的解释):
phase_1
前期准备
objdump –d bomb > asm.txt
进入asm.txt文件,找到main函数和phase_1函数,代码和我的注释如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| 080489db <main>: .... 8048a6d: c7 04 24 48 a0 04 08 movl $0x804a048,(%esp) 8048a74: e8 47 fd ff ff call 80487c0 <puts@plt> 8048a79: e8 2e 07 00 00 call 80491ac <read_line> //读入字符串 8048a7e: 89 04 24 mov %eax,(%esp) 8048a81: e8 aa 00 00 00 call 8048b30 <phase_1> //调用phase_1 8048a86: e8 24 08 00 00 call 80492af <phase_defused> ....
08048b30 <phase_1>: 8048b30: 55 push %ebp 8048b31: 89 e5 mov %esp,%ebp 8048b33: 83 ec 10 sub $0x10,%esp 8048b36: 68 c4 a0 04 08 push $0x804a0c4 //将某个字符串的地址压栈 8048b3b: ff 75 08 pushl 0x8(%ebp) //将我们输入的字符串地址压栈 8048b3e: e8 04 05 00 00 call 8049047 <strings_not_equal> //比较两个字符串 8048b43: 83 c4 10 add $0x10,%esp 8048b46: 85 c0 test %eax,%eax //eax必须为0,说明两个字符串应该相等 8048b48: 74 05 je 8048b4f <phase_1+0x1f> 8048b4a: e8 fb 05 00 00 call 804914a <explode_bomb> 8048b4f: c9 leave 8048b50: c3 ret
|
所以我们直接用gdb调试查看0x804a0c4处的字符串

通过

phase_2
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 27 28 29 30 31 32
| 08048b51 <phase_2>: 8048b51: 55 push %ebp 8048b52: 89 e5 mov %esp,%ebp 8048b54: 53 push %ebx 8048b55: 83 ec 2c sub $0x2c,%esp 8048b58: 65 a1 14 00 00 00 mov %gs:0x14,%eax 8048b5e: 89 45 f4 mov %eax,-0xc(%ebp) 8048b61: 31 c0 xor %eax,%eax //清零 8048b63: 8d 45 dc lea -0x24(%ebp),%eax 8048b66: 50 push %eax 8048b67: ff 75 08 pushl 0x8(%ebp) 8048b6a: e8 03 06 00 00 call 8049172 <read_six_numbers> //字面意思,说明是读入六个数 8048b6f: 83 c4 10 add $0x10,%esp 8048b72: 83 7d dc 00 cmpl $0x0,-0x24(%ebp) 8048b76: 79 05 jns 8048b7d <phase_2+0x2c> //非负跳转,不能跳,所以第一个数不能是负数 8048b78: e8 cd 05 00 00 call 804914a <explode_bomb> 8048b7d: bb 01 00 00 00 mov $0x1,%ebx 8048b82: 89 d8 mov %ebx,%eax //循环从这里开始,eax的值为现在是第几次循环 8048b84: 03 44 9d d8 add -0x28(%ebp,%ebx,4),%eax //输入第i-1个的值加到eax 8048b88: 39 44 9d dc cmp %eax,-0x24(%ebp,%ebx,4) //第i个值与eax比较 8048b8c: 74 05 je 8048b93 <phase_2+0x42> //相等跳 8048b8e: e8 b7 05 00 00 call 804914a <explode_bomb> 8048b93: 83 c3 01 add $0x1,%ebx //ebx从1加到6 8048b96: 83 fb 06 cmp $0x6,%ebx 8048b99: 75 e7 jne 8048b82 <phase_2+0x31> 8048b9b: 8b 45 f4 mov -0xc(%ebp),%eax 8048b9e: 65 33 05 14 00 00 00 xor %gs:0x14,%eax//这六个数需要满足,第一个数非负,第i+1个数减去第i个数的值为i 8048ba5: 74 05 je 8048bac <phase_2+0x5b> //2 3 5 8 12 17为一组答案 8048ba7: e8 e4 fb ff ff call 8048790 <__stack_chk_fail@plt> 8048bac: 8b 5d fc mov -0x4(%ebp),%ebx 8048baf: c9 leave 8048bb0: c3 ret
|
不难看出,输入的六个数字需满足:1. 第一个数非负 2. 第i+1个数减去第i个数的值为i
2 3 5 8 12 17为一组答案

phase_3
phase_3主要考察switch语句,具体代码和注释如下
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
| 08048bb1 <phase_3>: 8048bb1: 55 push %ebp 8048bb2: 89 e5 mov %esp,%ebp 8048bb4: 83 ec 24 sub $0x24,%esp 8048bb7: 65 a1 14 00 00 00 mov %gs:0x14,%eax 8048bbd: 89 45 f4 mov %eax,-0xc(%ebp) 8048bc0: 31 c0 xor %eax,%eax 8048bc2: 8d 45 f0 lea -0x10(%ebp),%eax 8048bc5: 50 push %eax 8048bc6: 8d 45 eb lea -0x15(%ebp),%eax 8048bc9: 50 push %eax 8048bca: 8d 45 ec lea -0x14(%ebp),%eax 8048bcd: 50 push %eax 8048bce: 68 1e a1 04 08 push $0x804a11e 8048bd3: ff 75 08 pushl 0x8(%ebp) 8048bd6: e8 35 fc ff ff call 8048810 <__isoc99_sscanf@plt> //gdb调试发现是读入两个数,一个字符 8048bdb: 83 c4 20 add $0x20,%esp //读入的第一个数在-0x14(%ebp)里面 8048bde: 83 f8 02 cmp $0x2,%eax 8048be1: 7f 05 jg 8048be8 <phase_3+0x37> 8048be3: e8 62 05 00 00 call 804914a <explode_bomb> 8048be8: 83 7d ec 07 cmpl $0x7,-0x14(%ebp) 8048bec: 0f 87 e9 00 00 00 ja 8048cdb <phase_3+0x12a> //将第一个数和7比较,大于跳转到炸弹,说明的值小于7 8048bf2: 8b 45 ec mov -0x14(%ebp),%eax 8048bf5: ff 24 85 40 a1 04 08 jmp *0x804a140(,%eax,4) //相当于Switch语句,跳转到0x804a140+%eax*4 8048bfc: b8 7a 00 00 00 mov $0x7a,%eax //eax的值是根据第一个值跳转到不同的 case 得到的 8048c01: 81 7d f0 71 02 00 00 cmpl $0x271,-0x10(%ebp) //第三个输入的值,应该与0x271相等 625 8048c08: 0f 84 d7 00 00 00 je 8048ce5 <phase_3+0x134> 8048c0e: e8 37 05 00 00 call 804914a <explode_bomb> 8048c13: b8 7a 00 00 00 mov $0x7a,%eax 8048c18: e9 c8 00 00 00 jmp 8048ce5 <phase_3+0x134> 8048c1d: b8 62 00 00 00 mov $0x62,%eax 8048c22: 81 7d f0 a7 02 00 00 cmpl $0x2a7,-0x10(%ebp) 8048c29: 0f 84 b6 00 00 00 je 8048ce5 <phase_3+0x134> 8048c2f: e8 16 05 00 00 call 804914a <explode_bomb> 8048c34: b8 62 00 00 00 mov $0x62,%eax 8048c39: e9 a7 00 00 00 jmp 8048ce5 <phase_3+0x134> 8048c3e: b8 70 00 00 00 mov $0x70,%eax 8048c43: 83 7d f0 4a cmpl $0x4a,-0x10(%ebp) 8048c47: 0f 84 98 00 00 00 je 8048ce5 <phase_3+0x134> 8048c4d: e8 f8 04 00 00 call 804914a <explode_bomb> 8048c52: b8 70 00 00 00 mov $0x70,%eax 8048c57: e9 89 00 00 00 jmp 8048ce5 <phase_3+0x134> 8048c5c: b8 67 00 00 00 mov $0x67,%eax 8048c61: 81 7d f0 a8 00 00 00 cmpl $0xa8,-0x10(%ebp) 8048c68: 74 7b je 8048ce5 <phase_3+0x134> 8048c6a: e8 db 04 00 00 call 804914a <explode_bomb> 8048c6f: b8 67 00 00 00 mov $0x67,%eax 8048c74: eb 6f jmp 8048ce5 <phase_3+0x134> 8048c76: b8 73 00 00 00 mov $0x73,%eax 8048c7b: 81 7d f0 e6 02 00 00 cmpl $0x2e6,-0x10(%ebp) 8048c82: 74 61 je 8048ce5 <phase_3+0x134> 8048c84: e8 c1 04 00 00 call 804914a <explode_bomb> 8048c89: b8 73 00 00 00 mov $0x73,%eax 8048c8e: eb 55 jmp 8048ce5 <phase_3+0x134> 8048c90: b8 76 00 00 00 mov $0x76,%eax 8048c95: 81 7d f0 0e 03 00 00 cmpl $0x30e,-0x10(%ebp) 8048c9c: 74 47 je 8048ce5 <phase_3+0x134> 8048c9e: e8 a7 04 00 00 call 804914a <explode_bomb> 8048ca3: b8 76 00 00 00 mov $0x76,%eax 8048ca8: eb 3b jmp 8048ce5 <phase_3+0x134> 8048caa: b8 6f 00 00 00 mov $0x6f,%eax 8048caf: 81 7d f0 e7 03 00 00 cmpl $0x3e7,-0x10(%ebp) 8048cb6: 74 2d je 8048ce5 <phase_3+0x134> 8048cb8: e8 8d 04 00 00 call 804914a <explode_bomb> 8048cbd: b8 6f 00 00 00 mov $0x6f,%eax 8048cc2: eb 21 jmp 8048ce5 <phase_3+0x134> 8048cc4: b8 71 00 00 00 mov $0x71,%eax 8048cc9: 83 7d f0 4a cmpl $0x4a,-0x10(%ebp) 8048ccd: 74 16 je 8048ce5 <phase_3+0x134> 8048ccf: e8 76 04 00 00 call 804914a <explode_bomb> 8048cd4: b8 71 00 00 00 mov $0x71,%eax 8048cd9: eb 0a jmp 8048ce5 <phase_3+0x134> 8048cdb: e8 6a 04 00 00 call 804914a <explode_bomb> 8048ce0: b8 7a 00 00 00 mov $0x7a,%eax 8048ce5: 3a 45 eb cmp -0x15(%ebp),%al //switch语句,最后把ebp-0x15的值(就是读到的字符)与al比较,需要相等 8048ce8: 74 05 je 8048cef <phase_3+0x13e> //这道题我跳转到了第一个选项中,所以al为1111010,ascii为z 8048cea: e8 5b 04 00 00 call 804914a <explode_bomb> 8048cef: 8b 45 f4 mov -0xc(%ebp),%eax 8048cf2: 65 33 05 14 00 00 00 xor %gs:0x14,%eax 8048cf9: 74 05 je 8048d00 <phase_3+0x14f> 8048cfb: e8 90 fa ff ff call 8048790 <__stack_chk_fail@plt> 8048d00: c9 leave 8048d01: c3 ret
|
注意到:
1 2 3
| 8048bce: 68 1e a1 04 08 push $0x804a11e 8048bd3: ff 75 08 pushl 0x8(%ebp) 8048bd6: e8 35 fc ff ff call 8048810 <__isoc99_sscanf@plt>
|
gdb 调试,找到0x804a11e指向的字符串,发现是读入两个数,一个字符

又因为:
1 2
| 8048be8: 83 7d ec 07 cmpl $0x7,-0x14(%ebp) 8048bec: 0f 87 e9 00 00 00 ja 8048cdb <phase_3+0x12a>
|
将第一个数和7比较,大于跳转到炸弹,说明第一个值小于7
1 2 3
| 8048bf2: 8b 45 ec mov -0x14(%ebp),%eax 8048bf5: ff 24 85 40 a1 04 08 jmp *0x804a140(,%eax,4) //相当于Switch语句,跳转到0x804a140+%eax*4 8048bfc: b8 7a 00 00 00 mov $0x7a,%eax
|
相当于Switch语句,跳转到0x804a140+%eax4,所以查看0x804a140+%eax4的内容

不妨让第一个数为0,跳到0x8048bfc
1 2 3 4
| 8048bfc: b8 7a 00 00 00 mov $0x7a,%eax 8048c01: 81 7d f0 71 02 00 00 cmpl $0x271,-0x10(%ebp) 8048c08: 0f 84 d7 00 00 00 je 8048ce5 <phase_3+0x134> 8048c0e: e8 37 05 00 00 call 804914a <explode_bomb>
|
现在eax为0x7a。第三个输入的值,应该与0x271相等,为625
最后:
1 2 3
| 8048ce5: 3a 45 eb cmp -0x15(%ebp),%al 8048ce8: 74 05 je 8048cef <phase_3+0x13e> 8048cea: e8 5b 04 00 00 call 804914a <explode_bomb>
|
最后把读到的字符与al比较,需要相等(之前我们的eax等于0x7a),对应ascii为z
答案:0 z 625

phase_4
phase_4主要考察递归调用和栈,还是挺费劲的TwT
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
| 08048d02 <func4>: 8048d02: 55 push %ebp 8048d03: 89 e5 mov %esp,%ebp //建立新的栈帧 8048d05: 56 push %esi 8048d06: 53 push %ebx 8048d07: 8b 55 08 mov 0x8(%ebp),%edx //edx=第一个数记为a 8048d0a: 8b 4d 0c mov 0xc(%ebp),%ecx //ecx=0(首次调用),其余记为b 8048d0d: 8b 75 10 mov 0x10(%ebp),%esi //esi=14(首次调用),其余记为c 8048d10: 89 f0 mov %esi,%eax //eax=c 8048d12: 29 c8 sub %ecx,%eax //eax=b+c 8048d14: 89 c3 mov %eax,%ebx //ebx=b+c 8048d16: c1 eb 1f shr $0x1f,%ebx //逻辑右移31位,相当于去b+c符号,记为s 8048d19: 01 d8 add %ebx,%eax //eax=b+c+s 8048d1b: d1 f8 sar %eax //算数右移一位,eax=(b+c+s)/2 8048d1d: 8d 1c 08 lea (%eax,%ecx,1),%ebx //ebx=(b+c+s)/2+b,记为f 8048d20: 39 d3 cmp %edx,%ebx //比较edx和ebx 8048d22: 7e 15 jle 8048d39 <func4+0x37> //如果ebx<=edx,即f<=a,跳到第三块,否则继续第二块
8048d24: 83 ec 04 sub $0x4,%esp 8048d27: 8d 43 ff lea -0x1(%ebx),%eax //eax=f-1 8048d2a: 50 push %eax 8048d2b: 51 push %ecx 8048d2c: 52 push %edx 8048d2d: e8 d0 ff ff ff call 8048d02 <func4> //递归调用,参数:a,b,f-1 8048d32: 83 c4 10 add $0x10,%esp 8048d35: 01 d8 add %ebx,%eax //eax=f+返回值 8048d37: eb 19 jmp 8048d52 <func4+0x50>//跳到第四块
8048d39: 89 d8 mov %ebx,%eax //eax=f 8048d3b: 39 d3 cmp %edx,%ebx 8048d3d: 7d 13 jge 8048d52 <func4+0x50>//如果f>=a,跳转第四块 8048d3f: 83 ec 04 sub $0x4,%esp 8048d42: 56 push %esi 8048d43: 8d 43 01 lea 0x1(%ebx),%eax 8048d46: 50 push %eax 8048d47: 52 push %edx 8048d48: e8 b5 ff ff ff call 8048d02 <func4>//递归调用,参数:a,f,c 8048d4d: 83 c4 10 add $0x10,%esp 8048d50: 01 d8 add %ebx,%eax //eax=f+返回值
8048d52: 8d 65 f8 lea -0x8(%ebp),%esp // 8048d55: 5b pop %ebx 8048d56: 5e pop %esi //c 8048d57: 5d pop %ebp 8048d58: c3 ret
08048d59 <phase_4>: 8048d59: 55 push %ebp 8048d5a: 89 e5 mov %esp,%ebp 8048d5c: 83 ec 18 sub $0x18,%esp 8048d5f: 65 a1 14 00 00 00 mov %gs:0x14,%eax 8048d65: 89 45 f4 mov %eax,-0xc(%ebp) 8048d68: 31 c0 xor %eax,%eax 8048d6a: 8d 45 f0 lea -0x10(%ebp),%eax 8048d6d: 50 push %eax 8048d6e: 8d 45 ec lea -0x14(%ebp),%eax 8048d71: 50 push %eax 8048d72: 68 af a2 04 08 push $0x804a2af 8048d77: ff 75 08 pushl 0x8(%ebp) 8048d7a: e8 91 fa ff ff call 8048810 <__isoc99_sscanf@plt> //读入两个数,第一个在-0x14(%ebp),二-0x10(%ebp) 8048d7f: 83 c4 10 add $0x10,%esp 8048d82: 83 f8 02 cmp $0x2,%eax 8048d85: 75 06 jne 8048d8d <phase_4+0x34> //需要相等,eax=2 8048d87: 83 7d ec 0e cmpl $0xe,-0x14(%ebp) 8048d8b: 76 05 jbe 8048d92 <phase_4+0x39> //-0x14(%ebp)小于等于15 8048d8d: e8 b8 03 00 00 call 804914a <explode_bomb> 8048d92: 83 ec 04 sub $0x4,%esp 8048d95: 6a 0e push $0xe //14 8048d97: 6a 00 push $0x0 //0 8048d99: ff 75 ec pushl -0x14(%ebp) //调用函数传参 14 0 第一个数 8048d9c: e8 61 ff ff ff call 8048d02 <func4> 8048da1: 83 c4 10 add $0x10,%esp 8048da4: 83 f8 12 cmp $0x12,%eax //返回值为18 8048da7: 75 06 jne 8048daf <phase_4+0x56> 8048da9: 83 7d f0 12 cmpl $0x12,-0x10(%ebp) //18相等于-0x10(%ebp)(第二个数) 8048dad: 74 05 je 8048db4 <phase_4+0x5b> 8048daf: e8 96 03 00 00 call 804914a <explode_bomb> 8048db4: 8b 45 f4 mov -0xc(%ebp),%eax 8048db7: 65 33 05 14 00 00 00 xor %gs:0x14,%eax 8048dbe: 74 05 je 8048dc5 <phase_4+0x6c> 8048dc0: e8 cb f9 ff ff call 8048790 <__stack_chk_fail@plt> 8048dc5: c9 leave 8048dc6: c3 ret
|

大概意思就是读入两个数,第一个数是func4()函数的一个参数,要求func4()返回值为18;第二个数为18就行了。
具体的细节我写到注释里面了。
需要把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
| #include<bits/stdc++.h> using namespace std; int wzynb(int c,int b,int a){ int s; int f=b+c; if(f<0){ f-=1; }else{ f+=1; } f/=2;
if(f<a){ int na=f; int rt=wzynb(c,f+1,a); f+=rt; }else if(f==a){ }else{ int nw=f-1; int rt=wzynb(nw,b,a); f+=rt; } return f; } int main() { for(int i=0;i<=15;i++){ if(wzynb(14,0,i)==18){ cout<<i<<endl; } } }
|
运行结果:11

答案:11 18
(DrEvil是后面进入隐藏关的内容)

phase_5
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
| 08048dc7 <phase_5>: 8048dc7: 55 push %ebp 8048dc8: 89 e5 mov %esp,%ebp 8048dca: 53 push %ebx 8048dcb: 83 ec 10 sub $0x10,%esp 8048dce: 8b 5d 08 mov 0x8(%ebp),%ebx //ebx为读入数据的地址 8048dd1: 53 push %ebx 8048dd2: e8 4e 02 00 00 call 8049025 <string_length> 8048dd7: 83 c4 10 add $0x10,%esp 8048dda: 83 f8 06 cmp $0x6,%eax //读入字符串长度为6 8048ddd: 74 05 je 8048de4 <phase_5+0x1d> 8048ddf: e8 66 03 00 00 call 804914a <explode_bomb> 8048de4: 89 d8 mov %ebx,%eax //eax为读入数据的地址 8048de6: 83 c3 06 add $0x6,%ebx //ebx+6 8048de9: b9 00 00 00 00 mov $0x0,%ecx //ecx=0
8048dee: 0f b6 10 movzbl (%eax),%edx //将edx后面四位 = eax字符数组的第i个 i为第几次循环 8048df1: 83 e2 0f and $0xf,%edx //取edx后四位 8048df4: 03 0c 95 60 a1 04 08 add 0x804a160(,%edx,4),%ecx //ecx+= (edx*4+0x804a160) 8048dfb: 83 c0 01 add $0x1,%eax //eax++ 8048dfe: 39 d8 cmp %ebx,%eax //循环六次 8048e00: 75 ec jne 8048dee <phase_5+0x27>
8048e02: 83 f9 37 cmp $0x37,%ecx //ecx=55 一种凑法:edx=2,3,4,5,6,C 8048e05: 74 05 je 8048e0c <phase_5+0x45> //一个答案是: //b(0x62),c(0x63),d(0x64),e(0x65),f(0x66),l(0x6C) 8048e07: e8 3e 03 00 00 call 804914a <explode_bomb> //bcdefl 8048e0c: 8b 5d fc mov -0x4(%ebp),%ebx 8048e0f: c9 leave 8048e10: c3 ret
|
大概意思是读入六个字符,将它们的后面四位作为变址,使得所对应的六个数加起来等于55,一种凑法如下。

后四位:2,3,4,5,6,C
一个答案是:b(0x62),c(0x63),d(0x64),e(0x65),f(0x66),l(0x6C)

phase_6
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
| 08048e11 <phase_6>: //一 8048e11: 55 push %ebp 8048e12: 89 e5 mov %esp,%ebp 8048e14: 56 push %esi 8048e15: 53 push %ebx 8048e16: 83 ec 48 sub $0x48,%esp 8048e19: 65 a1 14 00 00 00 mov %gs:0x14,%eax 8048e1f: 89 45 f4 mov %eax,-0xc(%ebp) 8048e22: 31 c0 xor %eax,%eax 8048e24: 8d 45 c4 lea -0x3c(%ebp),%eax 8048e27: 50 push %eax 8048e28: ff 75 08 pushl 0x8(%ebp) 8048e2b: e8 42 03 00 00 call 8049172 <read_six_numbers> //读入六个数 8048e30: 83 c4 10 add $0x10,%esp 8048e33: be 00 00 00 00 mov $0x0,%esi //esi=0 //二 8048e38: 8b 44 b5 c4 mov -0x3c(%ebp,%esi,4),%eax 8048e3c: 83 e8 01 sub $0x1,%eax 8048e3f: 83 f8 05 cmp $0x5,%eax 8048e42: 76 05 jbe 8048e49 <phase_6+0x38> //数据全部<=6 8048e44: e8 01 03 00 00 call 804914a <explode_bomb> 8048e49: 83 c6 01 add $0x1,%esi //esi++ 8048e4c: 83 fe 06 cmp $0x6,%esi 8048e4f: 74 33 je 8048e84 <phase_6+0x73>//esi==6时跳出循环,到第五块 8048e51: 89 f3 mov %esi,%ebx //ebx=esi 8048e53: 8b 44 9d c4 mov -0x3c(%ebp,%ebx,4),%eax //eax装入第i个数 8048e57: 39 44 b5 c0 cmp %eax,-0x40(%ebp,%esi,4) //a[i]与a[i-1]比较 8048e5b: 75 05 jne 8048e62 <phase_6+0x51> //不能相等 8048e5d: e8 e8 02 00 00 call 804914a <explode_bomb> 8048e62: 83 c3 01 add $0x1,%ebx //ebx=i+1 8048e65: 83 fb 05 cmp $0x5,%ebx 8048e68: 7e e9 jle 8048e53 <phase_6+0x42> //i+1小于或等于5 8048e6a: eb cc jmp 8048e38 <phase_6+0x27> //这是一个双重for循环,要求每个数组元素不相同 //三 8048e6c: 8b 52 08 mov 0x8(%edx),%edx //下一个节点的首地址 8048e6f: 83 c0 01 add $0x1,%eax //eax+1(从1开始) 8048e72: 39 c8 cmp %ecx,%eax //ecx=eax 8048e74: 75 f6 jne 8048e6c <phase_6+0x5b> //不相等继续,直到eax=a[i],相当于进入了链表第a[i]个元素 //四 8048e76: 89 54 b5 dc mov %edx,-0x24(%ebp,%esi,4) //记录下地址 8048e7a: 83 c3 01 add $0x1,%ebx //i++ 8048e7d: 83 fb 06 cmp $0x6,%ebx 8048e80: 75 07 jne 8048e89 <phase_6+0x78> //i不等于6跳五块 8048e82: eb 1c jmp 8048ea0 <phase_6+0x8f> //否则跳六块 //五 8048e84: bb 00 00 00 00 mov $0x0,%ebx //ebx=esi=0,从零开始 8048e89: 89 de mov %ebx,%esi 8048e8b: 8b 4c 9d c4 mov -0x3c(%ebp,%ebx,4),%ecx //ecx=a[i] 8048e8f: b8 01 00 00 00 mov $0x1,%eax //eax=1 8048e94: ba 3c c1 04 08 mov $0x804c13c,%edx //链表头地址 8048e99: 83 f9 01 cmp $0x1,%ecx 8048e9c: 7f ce jg 8048e6c <phase_6+0x5b> //a[i]>1跳第三块 8048e9e: eb d6 jmp 8048e76 <phase_6+0x65> //a[i]<=1跳第四块 //六 8048ea0: 8b 5d dc mov -0x24(%ebp),%ebx //ebx链表地址 8048ea3: 8d 45 dc lea -0x24(%ebp),%eax //eax头地址 8048ea6: 8d 75 f0 lea -0x10(%ebp),%esi //esi尾地址 8048ea9: 89 d9 mov %ebx,%ecx //ecx链表地址 8048eab: 8b 50 04 mov 0x4(%eax),%edx 8048eae: 89 51 08 mov %edx,0x8(%ecx) 8048eb1: 83 c0 04 add $0x4,%eax 8048eb4: 89 d1 mov %edx,%ecx 8048eb6: 39 f0 cmp %esi,%eax 8048eb8: 75 f1 jne 8048eab <phase_6+0x9a> //七 8048eba: c7 42 08 00 00 00 00 movl $0x0,0x8(%edx) 8048ec1: be 05 00 00 00 mov $0x5,%esi 8048ec6: 8b 43 08 mov 0x8(%ebx),%eax 8048ec9: 8b 00 mov (%eax),%eax 8048ecb: 39 03 cmp %eax,(%ebx) 8048ecd: 7e 05 jle 8048ed4 <phase_6+0xc3> //如果前面的数更大,就爆炸 8048ecf: e8 76 02 00 00 call 804914a <explode_bomb> 8048ed4: 8b 5b 08 mov 0x8(%ebx),%ebx //检测现在的链表是否从小到大排序 8048ed7: 83 ee 01 sub $0x1,%esi 8048eda: 75 ea jne 8048ec6 <phase_6+0xb5> 8048edc: 8b 45 f4 mov -0xc(%ebp),%eax 8048edf: 65 33 05 14 00 00 00 xor %gs:0x14,%eax 8048ee6: 74 05 je 8048eed <phase_6+0xdc> 8048ee8: e8 a3 f8 ff ff call 8048790 <__stack_chk_fail@plt> 8048eed: 8d 65 f8 lea -0x8(%ebp),%esp 8048ef0: 5b pop %ebx 8048ef1: 5e pop %esi 8048ef2: 5d pop %ebp 8048ef3: c3 ret
|
一步步分析发现:读入六个数,首先是一个二重循环,保证要求每个数组元素不相同,有一个链表,根据我们输入的数字进行排序,最后需要保证节点中的数字从小到大。

通过调试得到:每个节点有三个数据,第一个是改节点中的数字,第二个是初始编号,第三个是指向下一个节点的指针。按照从小到大的顺序为:4 1 2 5 3 6

secret_phase
Ctrl+f搜索secret_phase,发现是从phase_defused函数进入的,而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 35 36 37 38 39 40 41 42 43 44 45 46 47
| 080492af <phase_defused>: 80492af: 55 push %ebp 80492b0: 89 e5 mov %esp,%ebp 80492b2: 83 ec 68 sub $0x68,%esp 80492b5: 65 a1 14 00 00 00 mov %gs:0x14,%eax 80492bb: 89 45 f4 mov %eax,-0xc(%ebp) 80492be: 31 c0 xor %eax,%eax 80492c0: 83 3d cc c3 04 08 06 cmpl $0x6,0x804c3cc //比较输入的字符串数目是否等于6,不等于则跳转至程序结束 80492c7: 75 6f jne 8049338 <phase_defused+0x89> 80492c9: 83 ec 0c sub $0xc,%esp 80492cc: 8d 45 a4 lea -0x5c(%ebp),%eax 80492cf: 50 push %eax 80492d0: 8d 45 a0 lea -0x60(%ebp),%eax 80492d3: 50 push %eax 80492d4: 8d 45 9c lea -0x64(%ebp),%eax 80492d7: 50 push %eax 80492d8: 68 09 a3 04 08 push $0x804a309 //%d %d %s 前两个数是第四关的答案,后面的字符串是进入隐藏关卡的关键 80492dd: 68 d0 c4 04 08 push $0x804c4d0 //print (char*)0x804c4d0 80492e2: e8 29 f5 ff ff call 8048810 <__isoc99_sscanf@plt> 80492e7: 83 c4 20 add $0x20,%esp 80492ea: 83 f8 03 cmp $0x3,%eax //判断返回值eax(即正确匹配的通配符个数)是否为3 80492ed: 75 39 jne 8049328 <phase_defused+0x79> 80492ef: 83 ec 08 sub $0x8,%esp 80492f2: 68 12 a3 04 08 push $0x804a312 //DrEvil 80492f7: 8d 45 a4 lea -0x5c(%ebp),%eax 80492fa: 50 push %eax 80492fb: e8 47 fd ff ff call 8049047 <strings_not_equal> 8049300: 83 c4 10 add $0x10,%esp //输入的字符串应和“DrEvil”相等 8049303: 85 c0 test %eax,%eax 8049305: 75 21 jne 8049328 <phase_defused+0x79> 8049307: 83 ec 0c sub $0xc,%esp 804930a: 68 d8 a1 04 08 push $0x804a1d8 //Curses, you've found the secret phase! 804930f: e8 ac f4 ff ff call 80487c0 <puts@plt> 8049314: c7 04 24 00 a2 04 08 movl $0x804a200,(%esp) 804931b: e8 a0 f4 ff ff call 80487c0 <puts@plt> 8049320: e8 21 fc ff ff call 8048f46 <secret_phase> 8049325: 83 c4 10 add $0x10,%esp 8049328: 83 ec 0c sub $0xc,%esp 804932b: 68 38 a2 04 08 push $0x804a238 //Congratulations! You've defused the bomb! 8049330: e8 8b f4 ff ff call 80487c0 <puts@plt> 8049335: 83 c4 10 add $0x10,%esp 8049338: 8b 45 f4 mov -0xc(%ebp),%eax 804933b: 65 33 05 14 00 00 00 xor %gs:0x14,%eax 8049342: 74 05 je 8049349 <phase_defused+0x9a> 8049344: e8 47 f4 ff ff call 8048790 <__stack_chk_fail@plt> 8049349: c9 leave 804934a: c3 ret
|
gdb查看使用到的内容:




可以看出进入secret_phase需要以下条件:
通过前六关
在第四关后面再输入一个字符串:DrEvil
进入隐藏关:

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
| 08048ef4 <fun7>: 8048ef4: 55 push %ebp 8048ef5: 89 e5 mov %esp,%ebp 8048ef7: 53 push %ebx 8048ef8: 83 ec 04 sub $0x4,%esp 8048efb: 8b 55 08 mov 0x8(%ebp),%edx 8048efe: 8b 4d 0c mov 0xc(%ebp),%ecx 8048f01: 85 d2 test %edx,%edx 8048f03: 74 37 je 8048f3c <fun7+0x48> //测试传入的edx是否为0,是就跳转至结束并返回0xffffffff 8048f05: 8b 1a mov (%edx),%ebx //取出edx地址的值,即该节点的值,赋给ebx 8048f07: 39 cb cmp %ecx,%ebx //比较 8048f09: 7e 13 jle 8048f1e <fun7+0x2a> //ebx <= ecx(读入的数字),跳转 8048f0b: 83 ec 08 sub $0x8,%esp 8048f0e: 51 push %ecx //还是读入的那个数 8048f0f: ff 72 04 pushl 0x4(%edx) //地址+4,相当于前往右节点 8048f12: e8 dd ff ff ff call 8048ef4 <fun7> //执行递归 8048f17: 83 c4 10 add $0x10,%esp 8048f1a: 01 c0 add %eax,%eax //eax*2 8048f1c: eb 23 jmp 8048f41 <fun7+0x4d> //跳转到返回的地方,直接eax 8048f1e: b8 00 00 00 00 mov $0x0,%eax //eax=0 8048f23: 39 cb cmp %ecx,%ebx 8048f25: 74 1a je 8048f41 <fun7+0x4d> //如果ecx,ebx相等就跳转并返回0 8048f27: 83 ec 08 sub $0x8,%esp 8048f2a: 51 push %ecx //还是读入的那个数 8048f2b: ff 72 08 pushl 0x8(%edx) //地址+8,相当于前往左节点 8048f2e: e8 c1 ff ff ff call 8048ef4 <fun7> 8048f33: 83 c4 10 add $0x10,%esp 8048f36: 8d 44 00 01 lea 0x1(%eax,%eax,1),%eax //eax=eax*2+1 8048f3a: eb 05 jmp 8048f41 <fun7+0x4d> 8048f3c: b8 ff ff ff ff mov $0xffffffff,%eax 8048f41: 8b 5d fc mov -0x4(%ebp),%ebx 8048f44: c9 leave 8048f45: c3 ret
08048f46 <secret_phase>: 8048f46: 55 push %ebp 8048f47: 89 e5 mov %esp,%ebp 8048f49: 53 push %ebx 8048f4a: 83 ec 04 sub $0x4,%esp 8048f4d: e8 5a 02 00 00 call 80491ac <read_line> //读入一行 8048f52: 83 ec 04 sub $0x4,%esp 8048f55: 6a 0a push $0xa //10进制 8048f57: 6a 00 push $0x0 8048f59: 50 push %eax 8048f5a: e8 21 f9 ff ff call 8048880 <strtol@plt> //用于字符串转long //声明: long int strtol(const char *str, char **endptr, int base) 8048f5f: 89 c3 mov %eax,%ebx //ebx=eax 8048f61: 8d 40 ff lea -0x1(%eax),%eax //eax-- 8048f64: 83 c4 10 add $0x10,%esp // 8048f67: 3d e8 03 00 00 cmp $0x3e8,%eax 8048f6c: 76 05 jbe 8048f73 <secret_phase+0x2d> //结果必须<=1000 8048f6e: e8 d7 01 00 00 call 804914a <explode_bomb> 8048f73: 83 ec 08 sub $0x8,%esp 8048f76: 53 push %ebx 8048f77: 68 88 c0 04 08 push $0x804c088 8048f7c: e8 73 ff ff ff call 8048ef4 <fun7>//传入一个地址,一个输入的数字 8048f81: 83 c4 10 add $0x10,%esp 8048f84: 85 c0 test %eax,%eax 8048f86: 74 05 je 8048f8d <secret_phase+0x47> //fun7返回值需要为0 8048f88: e8 bd 01 00 00 call 804914a <explode_bomb> 8048f8d: 83 ec 0c sub $0xc,%esp 8048f90: 68 f8 a0 04 08 push $0x804a0f8 8048f95: e8 26 f8 ff ff call 80487c0 <puts@plt> 8048f9a: e8 10 03 00 00 call 80492af <phase_defused> 8048f9f: 83 c4 10 add $0x10,%esp 8048fa2: 8b 5d fc mov -0x4(%ebp),%ebx 8048fa5: c9 leave 8048fa6: c3 ret
|
隐藏关是输入一个数,传入fun7()中,使得fun7()返回值为0。
一步步分析fun7(),用高级语言写出对于程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| struct Node { int val; Node* left; Node* right; };
int fun7(Node *root,int ans){ if(root->val<=ans){ if(root->val==ans){ return 0; }else{ return 2*fun7(root->left,ans); } }else{ return 2*fun7(root->right,ans)+1; } }
|

这是一颗二叉树,准确来说是一棵BST二叉搜索树,即满足右子节点<根节点<左子节点。
这道题虽然涉及的数据结构较复杂,但是要求比较仁慈,因为只需要输入的数字对于根节点的值就可以返回0啦。



四、实验总结
很开心,学到了很多东西,加深了对汇编语言的理解和掌握,掌握了gdb调试的各种方法。