CSAPP LAB Binary bombs实验报告
文章目录
更好的阅读体验
一、实验目的:
逆向工程拆除“二进制炸弹”程序
增强对程序机器级表示、汇编语言、调试器和逆向工程等 理解。
一个“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函数,代码和我的注释如下
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主要考察循环,具体代码和注释如下
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语句,具体代码和注释如下
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
注意到:
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指向的字符串,发现是读入两个数,一个字符
又因为:
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
相当于Switch语句,跳转到0x804a140+%eax4,所以查看0x804a140+%eax4的内容
不妨让第一个数为0,跳到0x8048bfc
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
最后:
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
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()函数用高级语言写出来,再枚举输入来得到答案:
#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主要考察指针相关知识
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主要涉及链表
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就是我们关过后调用的函数。具体分析写在注释里了
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
进入隐藏关:
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(),用高级语言写出对于程序:
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啦。
四、实验总结
ebp),%ebx
8048fa5: c9 leave
8048fa6: c3 ret
隐藏关是输入一个数,传入fun7()中,使得fun7()返回值为0。
一步步分析fun7(),用高级语言写出对于程序:
```c++
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;
}
}
[外链图片转存中…(img-bIqbIZmE-1654332562085)]
这是一颗二叉树,准确来说是一棵BST二叉搜索树,即满足右子节点<根节点<左子节点。
这道题虽然涉及的数据结构较复杂,但是要求比较仁慈,因为只需要输入的数字对于根节点的值就可以返回0啦。
[外链图片转存中…(img-IpRzgoCD-1654332562085)]
[外链图片转存中…(img-ilaIBuPZ-1654332562086)]
[外链图片转存中…(img-XwQcG3Wp-1654332562086)]
四、实验总结
很开心,学到了很多东西,加深了对汇编语言的理解和掌握,掌握了gdb调试的各种方法。