计算机系统基础的实验课,引用了国外的实验,非常好
文章目录
一、实验目的:
逆向工程拆除“二进制炸弹”程序
增强对程序机器级表示、汇编语言、调试器和逆向工程等 理解。
一个“Binary Bombs”(二进制炸弹,简称炸弹)是一个 Linux可执行C程序,包含phase1~phase6共6个阶段。
炸弹运行各阶段要求输入一个字符串,若输入符合程序预 期,该阶段炸弹被“拆除”,否则“爆炸”。
你需要拆除尽可能多的炸弹
二、实验要求
拆弹装备:
熟练使用gdb调试器和objdump;
单步跟踪调试每一阶段的机器代码;
理解汇编语言代码的行为或作用;
“推断”拆除炸弹所需的目标字符串。
在各阶段的开始代码前和引爆炸弹函数前设置断点,便于调试。
每个炸弹阶段考察机器级语言程序不同方面,难度递增
阶段1:字符串比较
阶段2:循环
阶段3:条件/分支:含switch语句
阶段4:递归调用和栈
阶段5:指针
阶段6:链表/指针/结构
隐藏阶段,第4阶段之后附加特定字符串后出现
三、实验内容(所修改函数代码,功能以及重要代码的解释):
1.phase_1
8048b30: 55 push %ebp
8048b31: 89 e5 mov %esp,%ebp
8048b33: 83 ec 10 sub $0x10,%esp
8048b36: 68 44 a0 04 08 push $0x804a044
8048b3b: ff 75 08 pushl 0x8(%ebp)
8048b3e: e8 9a 04 00 00 call 8048fdd <strings_not_equal>
8048b43: 83 c4 10 add $0x10,%esp
8048b46: 85 c0 test %eax,%eax
8048b48: 74 05 je 8048b4f <phase_1+0x1f>
8048b4a: e8 91 05 00 00 call 80490e0 <explode_bomb>
8048b4f: c9 leave
8048b50: c3 ret
观察汇编代码,第6行,可知输入为一个字符串,4 5行压入参数时有两个参数,用gdb参看0x804a044中的内容
可知,call的函数判断我们输入的参数与该字符串是否相等,返回值会存入eax中 ,test指令与,若两字符串相等,返回0,跳过炸弹
第一题答案为“Border relations with Canada have never been better.”
2.phase_2
8048b51: 55 push %ebp
8048b52: 89 e5 mov %esp,%ebp
8048b54: 53 push %ebx
8048b55: 83 ec 2c sub $0x2c,%esp//下移11个字节
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 99 05 00 00 call 8049108 <read_six_numbers>//读入6个输入
8048b6f: 83 c4 10 add $0x10,%esp//栈顶指针下移
8048b72: 83 7d dc 00 cmpl $0x0,-0x24(%ebp)//将-0x24(%ebx)的内容与0比较(将第一个输入与0比较)
8048b76: 79 05 jns 8048b7d <phase_2+0x2c>//若数据比0大,跳转
8048b78: e8 63 05 00 00 call 80490e0 <explode_bomb>
8048b7d: bb 01 00 00 00 mov $0x1,%ebx //令ebx存1
8048b82: 89 d8 mov %ebx,%eax //eax存ebx的内容(存i)
8048b84: 03 44 9d d8 add -0x28(%ebp,%ebx,4),%eax //eax存自己加上ebp移位11个字节后的内容(i+第i-1个输入)
8048b88: 39 44 9d dc cmp %eax,-0x24(%ebp,%ebx,4) //比较此时eax结果与ebp移位10个字节后的内容(第i个输入与i+i-1输入相等)
8048b8c: 74 05 je 8048b93 <phase_2+0x42> //若eax与比较数相等则跳转
8048b8e: e8 4d 05 00 00 call 80490e0 <explode_bomb>
8048b93: 83 c3 01 add $0x1,%ebx //令ebx内容+1
8048b96: 83 fb 06 cmp $0x6,%ebx //比较6与1
8048b99: 75 e7 jne 8048b82 <phase_2+0x31> //不相等跳转循环,循环 6次后ebx与6相等
8048b9b: 8b 45 f4 mov -0xc(%ebp),%eax
8048b9e: 65 33 05 14 00 00 00 xor %gs:0x14,%eax
8048ba5: 74 05 je 8048bac <phase_2+0x5b>
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
由11行分析知6个输入数字,根据17和24行可判断是一个6次的循环,可知如果要安全通过这6次循环,需要在第19行时满足某个条件,根据上下文分析,条件为第i个数与第i-1个数与i相加进行比较,则我们需要输入的6个数满足这样一个数学公式 a i = a i − 1 + i a_i=a_{i-1}+i ai=ai−1+i
又根据第13行确定第一个输入为0 则可退出我们的输入为 0 1 3 6 10 15
3.phase_3
8048bb1: 55 push %ebp
8048bb2: 89 e5 mov %esp,%ebp //使ebp指向新的栈底
8048bb4: 83 ec 18 sub $0x18,%esp //分配栈空间,6个字节
8048bb7: 65 a1 14 00 00 00 mov %gs:0x14,%eax//赋值eax
8048bbd: 89 45 f4 mov %eax,-0xc(%ebp)//令第3个字节赋值
8048bc0: 31 c0 xor %eax,%eax
8048bc2: 8d 45 f0 lea -0x10(%ebp),%eax
8048bc5: 50 push %eax
8048bc6: 8d 45 ec lea -0x14(%ebp),%eax
8048bc9: 50 push %eax
8048bca: 68 2f a2 04 08 push $0x804a22f
8048bcf: ff 75 08 pushl 0x8(%ebp)
8048bd2: e8 39 fc ff ff call 8048810 <__isoc99_sscanf@plt>//eax存返回值
8048bd7: 83 c4 10 add $0x10,%esp//栈顶上移4字节
8048bda: 83 f8 01 cmp $0x1,%eax//eax参数与1比较
8048bdd: 7f 05 jg 8048be4 <phase_3+0x33>//eax大于1跳
8048bdf: e8 fc 04 00 00 call 80490e0 <explode_bomb>
8048be4: 83 7d ec 07 cmpl $0x7,-0x14(%ebp)//比较参数与7
8048be8: 77 3b ja 8048c25 <phase_3+0x74>//参数比7大时跳到爆炸(要比7小)
8048bea: 8b 45 ec mov -0x14(%ebp),%eax//eax赋值参数
8048bed: ff 24 85 c0 a0 04 08 jmp *0x804a0c0(,%eax,4)//从0x804a0c0取eax个字节的数然后加上当前的地址跳转,比1大,输2即可,输2跳到bfb
8048bf4: b8 92 01 00 00 mov $0x192,%eax //eax赋值0x192=402
8048bf9: eb 3b jmp 8048c36 <phase_3+0x85>//跳
8048bfb: b8 ff 02 00 00 mov $0x2ff,%eax//eax=767
8048c00: eb 34 jmp 8048c36 <phase_3+0x85>//跳
8048c02: b8 9b 00 00 00 mov $0x9b,%eax
8048c07: eb 2d jmp 8048c36 <phase_3+0x85>//跳
8048c09: b8 c7 02 00 00 mov $0x2c7,%eax
8048c0e: eb 26 jmp 8048c36 <phase_3+0x85>//跳
8048c10: b8 f9 00 00 00 mov $0xf9,%eax
8048c15: eb 1f jmp 8048c36 <phase_3+0x85>//跳
8048c17: b8 41 02 00 00 mov $0x241,%eax
8048c1c: eb 18 jmp 8048c36 <phase_3+0x85>//跳
8048c1e: b8 0c 02 00 00 mov $0x20c,%eax
8048c23: eb 11 jmp 8048c36 <phase_3+0x85>//跳
8048c25: e8 b6 04 00 00 call 80490e0 <explode_bomb>
8048c2a: b8 00 00 00 00 mov $0x0,%eax
8048c2f: eb 05 jmp 8048c36 <phase_3+0x85>
8048c31: b8 5a 03 00 00 mov $0x35a,%eax
8048c36: 3b 45 f0 cmp -0x10(%ebp),%eax //比较eax和第 4个参数
8048c39: 74 05 je 8048c40 <phase_3+0x8f> //比较是否相等,相等跳转通关
8048c3b: e8 a0 04 00 00 call 80490e0 <explode_bomb>
8048c40: 8b 45 f4 mov -0xc(%ebp),%eax
8048c43: 65 33 05 14 00 00 00 xor %gs:0x14,%eax
8048c4a: 74 05 je 8048c51 <phase_3+0xa0>
8048c4c: e8 3f fb ff ff call 8048790 <__stack_chk_fail@plt>
8048c51: c9 leave
8048c52: c3 ret
第三题是条件分支判断,先看第11行,压入了一个地址,我们看看这个地址对应的东西是什么
结合第13行可推测该程序利用scanf读取两个整数输入,然后根据第19行可知输入的第一个参数要比7小,然后根据21行可知,程序回跳到下面的某个指令,具体跳到那条与eax寄存器的值有关,也就是我们输入的第一个参数,观察发现下方指令都有
jmp 8048c36 <phase_3+0x85>//跳
可推测这是一个带break的switch语句的汇编,跳到下方后会比较eax与我们第二个参数的大小,相等则进入下一关,这里的答案可以有很多组,我将第一个参数设为2,参看跳到第24行给eax寄存器赋的值,发现为767
答案为 2 767 成功
4.phase_4
8048c9e: 55 push %ebp
8048c9f: 89 e5 mov %esp,%ebp
8048ca1: 83 ec 18 sub $0x18,%esp //创建新栈区
8048ca4: 65 a1 14 00 00 00 mov %gs:0x14,%eax
8048caa: 89 45 f4 mov %eax,-0xc(%ebp)//把eax内容赋给第三个参数
8048cad: 31 c0 xor %eax,%eax//异或,eax赋值0
8048caf: 8d 45 ec lea -0x14(%ebp),%eax //参数2
8048cb2: 50 push %eax //压入eax到栈中
8048cb3: 8d 45 f0 lea -0x10(%ebp),%eax //参数 1
8048cb6: 50 push %eax //压入新的eax值
8048cb7: 68 2f a2 04 08 push $0x804a22f //压入该个地址,输入两个数
8048cbc: ff 75 08 pushl 0x8(%ebp) //把ebp向上数第二个字节
8048cbf: e8 4c fb ff ff call 8048810 <__isoc99_sscanf@plt> //调用函数,eax存返回值
8048cc4: 83 c4 10 add $0x10,%esp //栈顶下移4个字节
8048cc7: 83 f8 02 cmp $0x2,%eax //比较2
8048cca: 75 0b jne 8048cd7 <phase_4+0x39> //不等于2爆炸
8048ccc: 8b 45 ec mov -0x14(%ebp),%eax//赋值 (密钥)参数2
8048ccf: 83 e8 02 sub $0x2,%eax//eax减二
8048cd2: 83 f8 02 cmp $0x2,%eax//比较2与eax,
8048cd5: 76 05 jbe 8048cdc <phase_4+0x3e>//eax-2小于等于2跳到下方
8048cd7: e8 04 04 00 00 call 80490e0 <explode_bomb>//否则爆炸
8048cdc: 83 ec 08 sub $0x8,%esp //栈顶下移14
8048cdf: ff 75 ec pushl -0x14(%ebp) //压入参数1
8048ce2: 6a 07 push $0x7 //压栈立即数
8048ce4: e8 6a ff ff ff call 8048c53 <func4> //跳,进入递归函数
8048ce9: 83 c4 10 add $0x10,%esp//栈顶上移
8048cec: 3b 45 f0 cmp -0x10(%ebp),%eax//比较参数1与eax
8048cef: 74 05 je 8048cf6 <phase_4+0x58>//eax相等,进入下一关
8048cf1: e8 ea 03 00 00 call 80490e0 <explode_bomb>//爆炸
8048cf6: 8b 45 f4 mov -0xc(%ebp),%eax
8048cf9: 65 33 05 14 00 00 00 xor %gs:0x14,%eax
8048d00: 74 05 je 8048d07 <phase_4+0x69>
8048d02: e8 89 fa ff ff call 8048790 <__stack_chk_fail@plt>
8048d07: c9 leave
8048d08: c3 ret
第4题是递归,首先发现第11行是这个地址,跳过去看看是什么东西
发现和上一个题一模一样,同样是输入两个整数,观察17到20行 可知要求我们输入的第二个参数-2之后的值要小于等于2才行,有根据第21行是有符号数的比较,可知输入可为2 3 4
第25行是一个函数,顺利通关的条件是我们输入的参数1与这个函数的返回值相同,我们看看第25行的函数是啥
08048c53 <func4>: //递归函数
8048c53: 55 push %ebp
8048c54: 89 e5 mov %esp,%ebp//存上一个栈顶
8048c56: 57 push %edi//寄存器d
8048c57: 56 push %esi//寄存器s
8048c58: 53 push %ebx//寄存器b
8048c59: 83 ec 0c sub $0xc,%esp //移动栈顶3字节
8048c5c: 8b 5d 08 mov 0x8(%ebp),%ebx //b= 7
8048c5f: 8b 7d 0c mov 0xc(%ebp),%edi //d=参数2
8048c62: 85 db test %ebx,%ebx //比较
8048c64: 7e 2b jle 8048c91 <func4+0x3e> //参数 1小于等于0跳转
8048c66: 89 f8 mov %edi,%eax //eax=参数2
8048c68: 83 fb 01 cmp $0x1,%ebx //比较1和参数1
8048c6b: 74 29 je 8048c96 <func4+0x43> //相等跳转
8048c6d: 83 ec 08 sub $0x8,%esp //栈顶减2个字节
8048c70: 57 push %edi //入栈
8048c71: 8d 43 ff lea -0x1(%ebx),%eax //取地址
8048c74: 50 push %eax
8048c75: e8 d9 ff ff ff call 8048c53 <func4> //递归
8048c7a: 83 c4 08 add $0x8,%esp //上移2字节
8048c7d: 8d 34 07 lea (%edi,%eax,1),%esi
8048c80: 57 push %edi
8048c81: 83 eb 02 sub $0x2,%ebx
8048c84: 53 push %ebx
8048c85: e8 c9 ff ff ff call 8048c53 <func4>
8048c8a: 83 c4 10 add $0x10,%esp
8048c8d: 01 f0 add %esi,%eax
8048c8f: eb 05 jmp 8048c96 <func4+0x43>
8048c91: b8 00 00 00 00 mov $0x0,%eax
8048c96: 8d 65 f4 lea -0xc(%ebp),%esp
8048c99: 5b pop %ebx
8048c9a: 5e pop %esi
8048c9b: 5f pop %edi
8048c9c: 5d pop %ebp
8048c9d: c3 ret
发现是一个递归函数,这个递归函数自己看是在是太难受了,试着将这个递归函数用C语言写出来,人工翻译后的结果如下
int func4(int x,int y)
{
int b=x;
int d=y;
if(b<=0)
{
return 0;
}
else
{
int a=d;
if(b==1) {
return a;
}
else
{
a=b-1;
int k=func4(a,d);
int s=d+k;
b=b-2;
int m=func4(b,d);
m+=s;
return m;
}
}
}
我们由fun4的汇编指令的第8行和第4关汇编的第24行可知输入参数中参数1固定为7,于是向我们的C语言代码中输入7,4得到结果为132
答案不止一组,对应第2个参数为2 3 4均有一组答案,我们这里输入132 4 成功通关
5.phase_5的求解
08048d09 <phase_5>:
8048d09: 55 push %ebp
8048d0a: 89 e5 mov %esp,%ebp
8048d0c: 83 ec 18 sub $0x18,%esp
8048d0f: 65 a1 14 00 00 00 mov %gs:0x14,%eax
8048d15: 89 45 f4 mov %eax,-0xc(%ebp)
8048d18: 31 c0 xor %eax,%eax
8048d1a: 8d 45 f0 lea -0x10(%ebp),%eax //参数2
8048d1d: 50 push %eax
8048d1e: 8d 45 ec lea -0x14(%ebp),%eax //参数1
8048d21: 50 push %eax
8048d22: 68 2f a2 04 08 push $0x804a22f//输入两个整数
8048d27: ff 75 08 pushl 0x8(%ebp)
8048d2a: e8 e1 fa ff ff call 8048810 <__isoc99_sscanf@plt>
8048d2f: 83 c4 10 add $0x10,%esp
8048d32: 83 f8 01 cmp $0x1,%eax //比较1与eax
8048d35: 7f 05 jg 8048d3c <phase_5+0x33> //eax比1大跳转
8048d37: e8 a4 03 00 00 call 80490e0 <explode_bomb>//否则爆炸
8048d3c: 8b 45 ec mov -0x14(%ebp),%eax //赋值参数1
8048d3f: 83 e0 0f and $0xf,%eax//低四位与
8048d42: 89 45 ec mov %eax,-0x14(%ebp) //参数2改变
8048d45: 83 f8 0f cmp $0xf,%eax //比较eax与15
8048d48: 74 2c je 8048d76 <phase_5+0x6d>//相等爆炸
8048d4a: b9 00 00 00 00 mov $0x0,%ecx//c=0
8048d4f: ba 00 00 00 00 mov $0x0,%edx//d=0
8048d54: 83 c2 01 add $0x1,%edx //d+1
8048d57: 8b 04 85 e0 a0 04 08 mov 0x804a0e0(,%eax,4),%eax//第一次为5
8048d5e: 01 c1 add %eax,%ecx //c+=a
8048d60: 83 f8 0f cmp $0xf,%eax //比较15与eax
8048d63: 75 ef jne 8048d54 <phase_5+0x4b>//不相等跳回
8048d65: c7 45 ec 0f 00 00 00 movl $0xf,-0x14(%ebp) //参数2改成15
8048d6c: 83 fa 0f cmp $0xf,%edx//比较d与15
8048d6f: 75 05 jne 8048d76 <phase_5+0x6d>//不相等爆炸
8048d71: 3b 4d f0 cmp -0x10(%ebp),%ecx//参数1与c
8048d74: 74 05 je 8048d7b <phase_5+0x72>相等通过
8048d76: e8 65 03 00 00 call 80490e0 <explode_bomb>
8048d7b: 8b 45 f4 mov -0xc(%ebp),%eax
8048d7e: 65 33 05 14 00 00 00 xor %gs:0x14,%eax
8048d85: 74 05 je 8048d8c <phase_5+0x83>
8048d87: e8 04 fa ff ff call 8048790 <__stack_chk_fail@plt>
8048d8c: c9 leave
8048d8d: c3 ret
发现第12行有个地址,去看看
发现还是输入两个整数。观察27到33行的代码这部分代码是我们通关的核心代码
跳出循环的条件是eax寄存器中的值为为15,而跳出之后我们观察32行33行,要求条件为edx中的值为15,edx从0开始每次循环+1,所以总结下来就是,经过15次循环之后,eax的值若恰好为15,则能够安全跳出,而eax中的值是我们输入的参数1,ecx存放的是每次循环时eax数的加和。观察第30行,可知eax每次在这地方发生值的变化,查看一下其中内容
根据对应的跳转规律我们画出循环的表
发现从5开始,跳15次,恰能跳到15 ecx中累加的数为115
则答案为 5 115顺利通过
6.phase_6
08048d8e <phase_6>:
8048d8e: 55 push %ebp
8048d8f: 89 e5 mov %esp,%ebp
8048d91: 56 push %esi
8048d92: 53 push %ebx
8048d93: 83 ec 48 sub $0x48,%esp //
8048d96: 65 a1 14 00 00 00 mov %gs:0x14,%eax //
8048d9c: 89 45 f4 mov %eax,-0xc(%ebp)
8048d9f: 31 c0 xor %eax,%eax
8048da1: 8d 45 c4 lea -0x3c(%ebp),%eax
8048da4: 50 push %eax //参数入栈
8048da5: ff 75 08 pushl 0x8(%ebp)//
8048da8: e8 5b 03 00 00 call 8049108 <read_six_numbers> //读6个数
8048dad: 83 c4 10 add $0x10,%esp //
8048db0: be 00 00 00 00 mov $0x0,%esi //s=0
8048db5: 8b 44 b5 c4 mov -0x3c(%ebp,%esi,4),%eax //移位,获取某个输入
8048db9: 83 e8 01 sub $0x1,%eax //减1
8048dbc: 83 f8 05 cmp $0x5,%eax //比较5和a
8048dbf: 76 05 jbe 8048dc6 <phase_6+0x38> //小于等于5不爆炸
8048dc1: e8 1a 03 00 00 call 80490e0 <explode_bomb>
8048dc6: 83 c6 01 add $0x1,%esi //s+1
8048dc9: 83 fe 06 cmp $0x6,%esi //比较s和6
8048dcc: 74 1b je 8048de9 <phase_6+0x5b> //相等跳
8048dce: 89 f3 mov %esi,%ebx //b=s
8048dd0: 8b 44 9d c4 mov -0x3c(%ebp,%ebx,4),%eax //eax赋值
8048dd4: 39 44 b5 c0 cmp %eax,-0x40(%ebp,%esi,4) //比较上一个参数
8048dd8: 75 05 jne 8048ddf <phase_6+0x51> //不相等,否则爆炸
8048dda: e8 01 03 00 00 call 80490e0 <explode_bomb>
8048ddf: 83 c3 01 add $0x1,%ebx //b=b+1
8048de2: 83 fb 05 cmp $0x5,%ebx //比较b和5
8048de5: 7e e9 jle 8048dd0 <phase_6+0x42> //小于等于则跳回
8048de7: eb cc jmp 8048db5 <phase_6+0x27> //跳回输入部分
8048de9: 8d 45 c4 lea -0x3c(%ebp),%eax //eax赋值参数
8048dec: 8d 5d dc lea -0x24(%ebp),%ebx //ebx赋值参数
8048def: b9 07 00 00 00 mov $0x7,%ecx //c=7
8048df4: 89 ca mov %ecx,%edx //d=7
8048df6: 2b 10 sub (%eax),%edx //d=d-eax
8048df8: 89 10 mov %edx,(%eax) //指针
8048dfa: 83 c0 04 add $0x4,%eax //加4
8048dfd: 39 c3 cmp %eax,%ebx //比较ebx和eax的
8048dff: 75 f3 jne 8048df4 <phase_6+0x66> //不相等跳回
8048e01: bb 00 00 00 00 mov $0x0,%ebx //ebx赋值0
8048e06: eb 16 jmp 8048e1e <phase_6+0x90> //跳转
8048e08: 8b 52 08 mov 0x8(%edx),%edx //移位,链表下一个
8048e0b: 83 c0 01 add $0x1,%eax //a=a+1
8048e0e: 39 c8 cmp %ecx,%eax//比较ac
8048e10: 75 f6 jne 8048e08 <phase_6+0x7a>//不相等回跳
8048e12: 89 54 b5 dc mov %edx,-0x24(%ebp,%esi,4)//修改链表中值
8048e16: 83 c3 01 add $0x1,%ebx //b=b+1
8048e19: 83 fb 06 cmp $0x6,%ebx //b与6比较
8048e1c: 74 17 je 8048e35 <phase_6+0xa7>//相等跳出循环,6次
8048e1e: 89 de mov %ebx,%esi//s=b
8048e20: 8b 4c 9d c4 mov -0x3c(%ebp,%ebx,4),%ecx//c=第b个输入参数
8048e24: b8 01 00 00 00 mov $0x1,%eax//a=1
8048e29: ba 3c c1 04 08 mov $0x804c13c,%edx//传地址,edx为链表
8048e2e: 83 f9 01 cmp $0x1,%ecx//c与1
8048e31: 7f d5 jg 8048e08 <phase_6+0x7a>//大于回跳
8048e33: eb dd jmp 8048e12 <phase_6+0x84>//回跳
8048e35: 8b 5d dc mov -0x24(%ebp),%ebx//跳出循环
8048e38: 8d 45 dc lea -0x24(%ebp),%eax//加载参数
8048e3b: 8d 75 f0 lea -0x10(%ebp),%esi//加载参数
8048e3e: 89 d9 mov %ebx,%ecx//c=b
8048e40: 8b 50 04 mov 0x4(%eax),%edx//修改链表起始部分
8048e43: 89 51 08 mov %edx,0x8(%ecx)
8048e46: 83 c0 04 add $0x4,%eax//a=4
8048e49: 89 d1 mov %edx,%ecx//c=d
8048e4b: 39 c6 cmp %eax,%esi//s=i
8048e4d: 75 f1 jne 8048e40 <phase_6+0xb2>//不相等跳转
8048e4f: c7 42 08 00 00 00 00 movl $0x0,0x8(%edx)
8048e56: be 05 00 00 00 mov $0x5,%esi//s=5
8048e5b: 8b 43 08 mov 0x8(%ebx),%eax//eax赋值
8048e5e: 8b 00 mov (%eax),%eax //地址赋值
8048e60: 39 03 cmp %eax,(%ebx) //地址比较,比较i与i-1个节点
8048e62: 7d 05 jge 8048e69 <phase_6+0xdb> //大于等于否则爆炸
8048e64: e8 77 02 00 00 call 80490e0 <explode_bomb>
8048e69: 8b 5b 08 mov 0x8(%ebx),%ebx//b移位。移到下一个
8048e6c: 83 ee 01 sub $0x1,%esi //s=s-1
8048e6f: 75 ea jne 8048e5b <phase_6+0xcd> 查看zf是否为1,否则循环
8048e71: 8b 45 f4 mov -0xc(%ebp),%eax //移位
8048e74: 65 33 05 14 00 00 00 xor %gs:0x14,%eax
8048e7b: 74 05 je 8048e82 <phase_6+0xf4> //相等通过
8048e7d: e8 0e f9 ff ff call 8048790 <__stack_chk_fail@plt>
8048e82: 8d 65 f8 lea -0x8(%ebp),%esp
8048e85: 5b pop %ebx
8048e86: 5e pop %esi
8048e87: 5d pop %ebp
8048e88: c3 ret
第6关是关于链表的题目,从第13行可以看出是需要输入6个数,观察到第55行有个地址,我们进去看看
观察发现对应了一个链表地址,每个链表有3个参数,第一个为当前节点的值,第二个为在链表中的序号,第三个为指向的下一个链表的地址,观察第35行到40行这是对我们输入的6个数进行翻转,用7进行反转,而刚好有6个节点**,可以推测输入的这6个数是对链表的某种操作**,观察59到80行,可知通关条件是链表中节点顺序与储存的值按从大到小的顺序排列。从38行到56行是对链表进行重组,根据我们给出的6个参数。于是将链表画出进行重排
由于输入的数字会进行反转,所以按从小到大的顺序输入
答案为 1 4 3 2 6 5 顺利通过
7·Secret_phase
在汇编代码中还有一个隐藏关卡,这个隐藏关卡存放在phase_defused()函数中,在 bomb.c程序中,每次通过一关就会调用该方法一次
我们观察phase_defused可以得出
08049245 <phase_defused>:
8049245: 55 push %ebp
8049246: 89 e5 mov %esp,%ebp
8049248: 83 ec 68 sub $0x68,%esp
804924b: 65 a1 14 00 00 00 mov %gs:0x14,%eax
8049251: 89 45 f4 mov %eax,-0xc(%ebp)
8049254: 31 c0 xor %eax,%eax
8049256: 83 3d cc c3 04 08 06 cmpl $0x6,0x804c3cc
804925d: 75 6f jne 80492ce <phase_defused+0x89>
804925f: 83 ec 0c sub $0xc,%esp
8049262: 8d 45 a4 lea -0x5c(%ebp),%eax
8049265: 50 push %eax
8049266: 8d 45 a0 lea -0x60(%ebp),%eax
8049269: 50 push %eax
804926a: 8d 45 9c lea -0x64(%ebp),%eax
804926d: 50 push %eax
804926e: 68 89 a2 04 08 push $0x804a289
8049273: 68 d0 c4 04 08 push $0x804c4d0
8049278: e8 93 f5 ff ff call 8048810 <__isoc99_sscanf@plt>
804927d: 83 c4 20 add $0x20,%esp
8049280: 83 f8 03 cmp $0x3,%eax
8049283: 75 39 jne 80492be <phase_defused+0x79>
8049285: 83 ec 08 sub $0x8,%esp
8049288: 68 92 a2 04 08 push $0x804a292
804928d: 8d 45 a4 lea -0x5c(%ebp),%eax
8049290: 50 push %eax
8049291: e8 47 fd ff ff call 8048fdd <strings_not_equal>
8049296: 83 c4 10 add $0x10,%esp
8049299: 85 c0 test %eax,%eax
804929b: 75 21 jne 80492be <phase_defused+0x79>
804929d: 83 ec 0c sub $0xc,%esp
80492a0: 68 58 a1 04 08 push $0x804a158
80492a5: e8 16 f5 ff ff call 80487c0 <puts@plt>
80492aa: c7 04 24 80 a1 04 08 movl $0x804a180,(%esp)
80492b1: e8 0a f5 ff ff call 80487c0 <puts@plt>
80492b6: e8 20 fc ff ff call 8048edb <secret_phase>
80492bb: 83 c4 10 add $0x10,%esp
80492be: 83 ec 0c sub $0xc,%esp
80492c1: 68 b8 a1 04 08 push $0x804a1b8
80492c6: e8 f5 f4 ff ff call 80487c0 <puts@plt>
80492cb: 83 c4 10 add $0x10,%esp
80492ce: 8b 45 f4 mov -0xc(%ebp),%eax
80492d1: 65 33 05 14 00 00 00 xor %gs:0x14,%eax
80492d8: 74 05 je 80492df <phase_defused+0x9a>
80492da: e8 b1 f4 ff ff call 8048790 <__stack_chk_fail@plt>
80492df: c9 leave
80492e0: c3 ret
观察第8行可知启动隐藏关卡的第一个条件是前面6关都通过了,观察24行到27行,可知另外一个启动条件是输入一个字符串,然后进行比较,我们进到比较地址看看
找到了隐藏的字符串
我们接着往这个地址前面看看有什么,这样可以确定输入在那个部分
发现输入字符串前还有俩数字,可知我们第3 4 5 关都有这个部分,因此我们在第四关后加入这个字符串,发现成功进入隐藏关卡
08048edb <secret_phase>:
8048edb: 55 push %ebp
8048edc: 89 e5 mov %esp,%ebp
8048ede: 53 push %ebx
8048edf: 83 ec 04 sub $0x4,%esp
8048ee2: e8 5b 02 00 00 call 8049142 <read_line>
8048ee7: 83 ec 04 sub $0x4,%esp
8048eea: 6a 0a push $0xa //压入a
8048eec: 6a 00 push $0x0 //压入0
8048eee: 50 push %eax //压入eax
8048eef: e8 8c f9 ff ff call 8048880 <strtol@plt>//返回eax的string转long值
8048ef4: 89 c3 mov %eax,%ebx //转入eax
8048ef6: 8d 40 ff lea -0x1(%eax),%eax=eax-1
8048ef9: 83 c4 10 add $0x10,%esp
8048efc: 3d e8 03 00 00 cmp $0x3e8,%eax //比较eax与立即数 1000
8048f01: 76 05 jbe 8048f08 <secret_phase+0x2d> //小于等于跳转
8048f03: e8 d8 01 00 00 call 80490e0 <explode_bomb>
8048f08: 83 ec 08 sub $0x8,%esp
8048f0b: 53 push %ebx //参数2
8048f0c: 68 88 c0 04 08 push $0x804c088//参数1
8048f11: e8 73 ff ff ff call 8048e89 <fun7>//进入fun7函数
8048f16: 83 c4 10 add $0x10,%esp
8048f19: 83 f8 05 cmp $0x5,%eax//返回值为5通关
8048f1c: 74 05 je 8048f23 <secret_phase+0x48>
8048f1e: e8 bd 01 00 00 call 80490e0 <explode_bomb>
8048f23: 83 ec 0c sub $0xc,%esp
8048f26: 68 7c a0 04 08 push $0x804a07c//
8048f2b: e8 90 f8 ff ff call 80487c0 <puts@plt>
8048f30: e8 10 03 00 00 call 8049245 <phase_defused>
8048f35: 83 c4 10 add $0x10,%esp
8048f38: 8b 5d fc mov -0x4(%ebp),%ebx
8048f3b: c9 leave
8048f3c: c3 ret
观察隐藏关卡,在第21行进入fun7,当返回值为5时能够通关,我们来进入fun7看看
08048e89 <fun7>:
8048e89: 55 push %ebp
8048e8a: 89 e5 mov %esp,%ebp
8048e8c: 53 push %ebx
8048e8d: 83 ec 04 sub $0x4,%esp
8048e90: 8b 55 08 mov 0x8(%ebp),%edx//获取输入参数1 当前树的节点
8048e93: 8b 4d 0c mov 0xc(%ebp),%ecx//获取输入参数2
8048e96: 85 d2 test %edx,%edx //edx是否为0
8048e98: 74 37 je 8048ed1 <fun7+0x48>//为0跳转
8048e9a: 8b 1a mov (%edx),%ebx //ebx赋值参数1
8048e9c: 39 cb cmp %ecx,%ebx //比较参数2和参数1
8048e9e: 7e 13 jle 8048eb3 <fun7+0x2a> //根节点小于等于参数2跳转
8048ea0: 83 ec 08 sub $0x8,%esp //
8048ea3: 51 push %ecx //参数2入栈
8048ea4: ff 72 04 pushl 0x4(%edx) //树节点的左子树入栈
8048ea7: e8 dd ff ff ff call 8048e89 <fun7> //递归
8048eac: 83 c4 10 add $0x10,%esp
8048eaf: 01 c0 add %eax,%eax //eax*2
8048eb1: eb 23 jmp 8048ed6 <fun7+0x4d> //返回eax
8048eb3: b8 00 00 00 00 mov $0x0,%eax //a=0
8048eb8: 39 cb cmp %ecx,%ebx //比较根节点与参数2
8048eba: 74 1a je 8048ed6 <fun7+0x4d>//相等跳转,返回0
8048ebc: 83 ec 08 sub $0x8,%esp//
8048ebf: 51 push %ecx //压入第二个参数
8048ec0: ff 72 08 pushl 0x8(%edx) //节点的右子树
8048ec3: e8 c1 ff ff ff call 8048e89 <fun7>//递归调用
8048ec8: 83 c4 10 add $0x10,%esp
8048ecb: 8d 44 00 01 lea 0x1(%eax,%eax,1),%eax//eax=a+a+1
8048ecf: eb 05 jmp 8048ed6 <fun7+0x4d>
8048ed1: b8 ff ff ff ff mov $0xffffffff,%eax//返回值为0
8048ed6: 8b 5d fc mov -0x4(%ebp),%ebx
8048ed9: c9 leave
8048eda: c3 ret
哇塞,好复杂的递归,我试着将他换成c语言写出来,但是发现不行,在调用fun7前,也就是在隐藏关时传入的参数是一个地址,而递归的内容全部基于该地址操作,我们看看这个地址里面存了什么东西
有点看不懂,后面同学告诉我这关是个树的结构,准确一点说这棵树是个二叉树,一个位置存了树节点的值,第二个位置存了左子树的地址,第三个位置存了右子树的位置,画出图来
递归看不懂呀,直接暴力破解,把树节点一个个输进去,最后试出来是47
四、实验截图
答案
通关截图
五、实验总结
太强啦,我的同学都把lab3做了,最近没啥时间,就不做lab3了,应该问题不大(手动狗头)