前言
内容仅作记录,请谨慎参考,思路可能差不多,但每个人的炸弹是不一样的
第一个
08048b90 <phase_1>:
8048b90: 83 ec 1c sub $0x1c,%esp
//栈顶指针%esp-0x1c,即开辟一个大小为0x1c字节的栈空间
8048b93: c7 44 24 04 5c a1 04 movl $0x804a15c,0x4(%esp)
//将地址0x804a15c中的值赋给*(%esp+0x4)
8048b9a: 08
8048b9b: 8b 44 24 20 mov 0x20(%esp),%eax
//将*(%esp+0x20)赋给寄存器%eax
8048b9f: 89 04 24 mov %eax,(%esp)
//将寄存器%eax中的值赋给*(esp)
8048ba2: e8 93 04 00 00 call 804903a <strings_not_equal>
//执行函数,比较两字符串是否相等
8048ba7: 85 c0 test %eax,%eax
//test即为做“与”运算
8048ba9: 74 05 je 8048bb0 <phase_1+0x20>
//结果为0则跳转,即%eax=0,则跳转到8048bb0
8048bab: e8 95 05 00 00 call 8049145 <explode_bomb>
//结果为1则执行这一句,炸弹爆炸
8048bb0: 83 c4 1c add $0x1c,%esp
//将栈顶指针往栈底移动0x1c,回收栈空间
8048bb3: c3 ret
通过分析汇编代码,我们可以推出寄存器%eax存放的是我们所输入的字符串,同时我们可以看到,$0x804a15c是一个立即数,那么很有可能该地址处存放的就是用于比较的字符串,使用gdb进行调试,输入x/s 0x804a15c查看地址中的值可以看到其内容为:
And they have no disregard for human life.
运行bomb程序,输入这段字符串可以看到第一个炸弹被成功拆除:
第二个
08048bb4 <phase_2>:
8048bb4: 56 push %esi
8048bb5: 53 push %ebx
8048bb6: 83 ec 34 sub $0x34,%esp
//栈顶指针%esp-0x34,即开辟一个大小为0x34字节的栈空间
8048bb9: 8d 44 24 18 lea 0x18(%esp),%eax
//将地址%esp+0x18赋给%eax
8048bbd: 89 44 24 04 mov %eax,0x4(%esp)
//将寄存器%eax中的值赋给*(%esp+0x4)
8048bc1: 8b 44 24 40 mov 0x40(%esp),%eax
//将*(%esp+40)赋给寄存器%eax
8048bc5: 89 04 24 mov %eax,(%esp)
//将寄存器%eax中的值赋给*(esp)
8048bc8: e8 9f 05 00 00 call 804916c <read_six_numbers>
//执行函数
8048bcd: 83 7c 24 18 01 cmpl $0x1,0x18(%esp)
//将0x1与*(%esp+0x18)进行比较
8048bd2: 74 1e je 8048bf2 <phase_2+0x3e>
//如果相等,则跳转到8048bf2
8048bd4: e8 6c 05 00 00 call 8049145 <explode_bomb>
//如果不相等则执行这一句,炸弹爆炸
8048bd9: eb 17 jmp 8048bf2 <phase_2+0x3e>
//跳转到8048bf2
8048bdb: 8b 43 fc mov -0x4(%ebx),%eax
//将*(%ebx-0x4)赋给寄存器%eax
8048bde: 01 c0 add %eax,%eax
//将寄存器%eax中的值的二倍赋给%eax
8048be0: 39 03 cmp %eax,(%ebx)
//将%eax与*(%ebx)进行比较
8048be2: 74 05 je 8048be9 <phase_2+0x35>
//如果相等,则跳转到8048be9
8048be4: e8 5c 05 00 00 call 8049145 <explode_bomb>
//如果不相等则执行这一句,炸弹爆炸
8048be9: 83 c3 04 add $0x4,%ebx
//将寄存器%ebx中的值加上0x4结果放到寄存器%ebx中
8048bec: 39 f3 cmp %esi,%ebx
//将%esi与%ebx中的值进行比较
8048bee: 75 eb jne 8048bdb <phase_2+0x27>
//如果不相等,则跳转到8048bdb
8048bf0: eb 0a jmp 8048bfc <phase_2+0x48>
//跳转到8048bfc
8048bf2: 8d 5c 24 1c lea 0x1c(%esp),%ebx
//将地址%esp+0x1c赋给%ebx
8048bf6: 8d 74 24 30 lea 0x30(%esp),%esi
//将地址%esp+0x30赋给%esi
8048bfa: eb df jmp 8048bdb <phase_2+0x27>
//跳转到8048bdb
8048bfc: 83 c4 34 add $0x34,%esp
//将寄存器%esp中的值加上0x34结果放到寄存器%esp中
8048bff: 5b pop %ebx
8048c00: 5e pop %esi
//弹栈
8048c01: c3 ret
通过分析汇编代码,开辟栈空间之后结合<read_six_numbers>我们可以推出%esp+0x18、
%esp+0x1c、%esp+0x20、%esp+0x24、%esp+0x28、%esp+0x2c中存放的是要进行比较的六个数。同时,通过cmpl $0x1,0x18(%esp)可以知道第一个数为1,之后将会进行一个循环,通过add %eax,%eax可以知道每次循环的结果为原来的数的二倍,故最终得到的六个数为:1、2、4、8、16、32
运行bomb,输入这六个数可以看到第二个炸弹被拆除:
第三个
08048c02 <phase_3>:
8048c02: 83 ec 2c sub $0x2c,%esp
//栈顶指针%esp-0x2c,即开辟一个大小为0x2c字节的栈空间
8048c05: 8d 44 24 1c lea 0x1c(%esp),%eax
//将地址%esp+0x1c赋给%eax
8048c09: 89 44 24 0c mov %eax,0xc(%esp)
//将寄存器%eax中的值赋给*(%esp+0xc)
8048c0d: 8d 44 24 18 lea 0x18(%esp),%eax
//将地址%esp+0x18赋给寄存器%eax
8048c11: 89 44 24 08 mov %eax,0x8(%esp)
//将寄存器%eax中的值赋给*(%esp+0x8)
8048c15: c7 44 24 04 f7 a2 04 movl $0x804a2f7,0x4(%esp)
//将立即数0x804a2f7赋给*(%esp+0x4)
8048c1c: 08
8048c1d: 8b 44 24 30 mov 0x30(%esp),%eax
//将*(%esp+0x30)赋给寄存器%eax
8048c21: 89 04 24 mov %eax,(%esp)
//将寄存器%eax中的值赋给*(%esp)
8048c24: e8 37 fc ff ff call 8048860 <__isoc99_sscanf@plt>
//执行函数
8048c29: 83 f8 01 cmp $0x1,%eax
//比较0x1与寄存器%eax中的值的大小
8048c2c: 7f 05 jg 8048c33 <phase_3+0x31>
//%eax中的值大于0x1则跳转到8048c33
8048c2e: e8 12 05 00 00 call 8049145 <explode_bomb>
//没有跳转成功则炸弹爆炸
8048c33: 83 7c 24 18 07 cmpl $0x7,0x18(%esp)
//将0x7与*(%esp+0x18)中的值进行比较
8048c38: 77 3c ja 8048c76 <phase_3+0x74>
//如果*(%esp+0x18)中的值大于0x7则跳转到8048c76
8048c3a: 8b 44 24 18 mov 0x18(%esp),%eax
//将*(%esp+0x18)中的值赋给寄存器%eax
8048c3e: ff 24 85 b8 a1 04 08 jmp *0x804a1b8(,%eax,4)
//跳转到%eax*4+*0x804a1b8(使用x/s查看得到为8048c45)
8048c45: b8 f1 00 00 00 mov $0xf1,%eax
//将立即数0xf1赋给寄存器%eax
8048c4a: eb 3b jmp 8048c87 <phase_3+0x85>
//跳转到8048c87
8048c4c: b8 8c 01 00 00 mov $0x18c,%eax
//将立即数0x18c赋给寄存器%eax
8048c51: eb 34 jmp 8048c87 <phase_3+0x85>
//跳转到8048c87
8048c53: b8 c2 00 00 00 mov $0xc2,%eax
//将立即数0xc2赋给寄存器%eax
8048c58: eb 2d jmp 8048c87 <phase_3+0x85>
//跳转到8048c87
8048c5a: b8 bd 02 00 00 mov $0x2bd,%eax
//将立即数0x2bd赋给寄存器%eax
8048c5f: eb 26 jmp 8048c87 <phase_3+0x85>
//跳转到8048c87
8048c61: b8 09 03 00 00 mov $0x309,%eax
//将立即数0x309赋给寄存器%eax
8048c66: eb 1f jmp 8048c87 <phase_3+0x85>
//跳转到8048c87
8048c68: b8 92 02 00 00 mov $0x292,%eax
//将立即数0x292赋给寄存器%eax
8048c6d: eb 18 jmp 8048c87 <phase_3+0x85>
//跳转到8048c87
8048c6f: b8 53 02 00 00 mov $0x253,%eax
//将立即数0x253赋给寄存器%eax
8048c74: eb 11 jmp 8048c87 <phase_3+0x85>
//跳转到8048c87
8048c76: e8 ca 04 00 00 call 8049145 <explode_bomb>
//炸弹爆炸
8048c7b: b8 00 00 00 00 mov $0x0,%eax
//将立即数0x0赋给寄存器%eax
8048c80: eb 05 jmp 8048c87 <phase_3+0x85>
//跳转到8048c87
8048c82: b8 fc 02 00 00 mov $0x2fc,%eax
//将立即数0x2fc赋给寄存器%eax
8048c87: 3b 44 24 1c cmp 0x1c(%esp),%eax
//比较*(%esp+0x1c)与寄存器%eax中的值的大小
8048c8b: 74 05 je 8048c92 <phase_3+0x90>
//如果相等则跳转到8048c92
8048c8d: e8 b3 04 00 00 call 8049145 <explode_bomb>
//炸弹爆炸
8048c92: 83 c4 2c add $0x2c,%esp
//将寄存器%esp加0x2c,用于回收开辟的栈空间
8048c95: c3 ret
分析汇编代码,我们注意到,movl $0x804a2f7,0x4(%esp)这里传递了一个立即数,使用gdb调试,x/s 0x804a2f7查看该地址的内容可以得到:
结合后面的call 8048860 <__isoc99_sscanf@plt>可以推出输入的为两个数,后面的将0x1与%eax进行比较也说明了要输入两个参数
接着,将0x7与%esp+0x18中的值进行了比较,只有当这个值不大于7时炸弹才不会爆炸
通过jmp * 0x804a1b8(,%eax,4)可以知道这是一个跳转表,结合刚才的分析可以推出第一个输入即为跳转的参数0、1、2、3、4、5、6、7,对应8个不同的地址
使用gdb查看*0x804a1b8+0、+4、+8、+12、+16、+20、+24、+28中的内容:
可以看到对应了8个不同的跳转地址,进而得到8组输入为:
运行bomb,输入结果:
可以看到确实是8组答案均可通过(还真有人这么闲都试一遍啊)
第四个
第四关代码
08048cf3 <phase_4>:
8048cf3: 83 ec 2c sub $0x2c,%esp
//栈顶指针%esp-0x2c,即开辟一个大小为0x2c字节的栈空间
8048cf6: 8d 44 24 1c lea 0x1c(%esp),%eax
//将地址%esp+0x1c赋给寄存器%eax
8048cfa: 89 44 24 0c mov %eax,0xc(%esp)
//将%eax中的值赋给*(%esp+0xc)
8048cfe: 8d 44 24 18 lea 0x18(%esp),%eax
//将地址%esp+0x18赋给寄存器%eax
8048d02: 89 44 24 08 mov %eax,0x8(%esp)
//将寄存器%eax中的值赋给*(%esp+0x8)
8048d06: c7 44 24 04 f7 a2 04 movl $0x804a2f7,0x4(%esp)
//将立即数0x804a2f7赋给*(%esp+0x4)
8048d0d: 08
8048d0e: 8b 44 24 30 mov 0x30(%esp),%eax
//将*(%esp+0x30)赋给寄存器%eax
8048d12: 89 04 24 mov %eax,(%esp)
//将寄存器%eax中的值赋给*(%esp)
8048d15: e8 46 fb ff ff call 8048860 <__isoc99_sscanf@plt>
//执行函数,可以推出是进行输入
8048d1a: 83 f8 02 cmp $0x2,%eax
//比较0x2与寄存器%eax中的值
8048d1d: 75 07 jne 8048d26 <phase_4+0x33>
//如果不相等则跳转到8048d26
8048d1f: 83 7c 24 18 0e cmpl $0xe,0x18(%esp)
//比较0xe与*(%esp+0x18)
8048d24: 76 05 jbe 8048d2b <phase_4+0x38>
//如果小于等于则跳转到8048d2b
8048d26: e8 1a 04 00 00 call 8049145 <explode_bomb>
//炸弹爆炸
8048d2b: c7 44 24 08 0e 00 00 movl $0xe,0x8(%esp)
//将0xe赋给*(%esp+0x8),即函数的第三个参数
8048d32: 00
8048d33: c7 44 24 04 00 00 00 movl $0x0,0x4(%esp)
//将0x0赋给*(%esp+0x4),即函数的第二个参数
8048d3a: 00
8048d3b: 8b 44 24 18 mov 0x18(%esp),%eax
//将*(%esp+0x18)赋给寄存器%eax,即函数的第一个参数(我们的第一个输入)
8048d3f: 89 04 24 mov %eax,(%esp)
//将寄存器%eax中的值赋给*(%esp)
8048d42: e8 4f ff ff ff call 8048c96 <func4>
//执行函数func4
8048d47: 83 f8 2b cmp $0x2b,%eax
//比较0x2b与寄存器%eax中的值的大小
8048d4a: 75 07 jne 8048d53 <phase_4+0x60>
//如果不相等则跳转到8048d53
8048d4c: 83 7c 24 1c 2b cmpl $0x2b,0x1c(%esp)
//比较0x2b与*(%esp+0x1c)的大小
8048d51: 74 05 je 8048d58 <phase_4+0x65>
//如果相等则跳转到8048d58
8048d53: e8 ed 03 00 00 call 8049145 <explode_bomb>
//炸弹爆炸
8048d58: 83 c4 2c add $0x2c,%esp
//将寄存器%esp加上0x2c用于回收栈空间
8048d5b: c3 ret
func4函数
func4:
08048c96 <func4>:
8048c96: 56 push %esi
8048c97: 53 push %ebx
8048c98: 83 ec 14 sub $0x14,%esp
8048c9b: 8b 54 24 20 mov 0x20(%esp),%edx
8048c9f: 8b 44 24 24 mov 0x24(%esp),%eax
8048ca3: 8b 74 24 28 mov 0x28(%esp),%esi
8048ca7: 89 f1 mov %esi,%ecx
//可以推出以上传递函数用到的三个参数(假设为x、y、z)给寄存器
8048ca9: 29 c1 sub %eax,%ecx
//将%ecx中的值减去%eax中的值,即z-y
8048cab: 89 cb mov %ecx,%ebx
8048cad: c1 eb 1f shr $0x1f,%ebx
//将z-y的值右移31位,即取其符号位
8048cb0: 01 d9 add %ebx,%ecx
//z-y为负则加上1,否则加上0
8048cb2: d1 f9 sar %ecx
//算术左移一位,即%ecx中的值/2
8048cb4: 8d 1c 01 lea (%ecx,%eax,1),%ebx
//y+(z-y+(1))/2赋给寄存器%ebx
8048cb7: 39 d3 cmp %edx,%ebx
//将x与寄存器%ebx中的值进行比较
8048cb9: 7e 17 jle 8048cd2 <func4+0x3c>
//小于等于则跳转到8048cd2
8048cbb: 8d 4b ff lea -0x1(%ebx),%ecx
8048cbe: 89 4c 24 08 mov %ecx,0x8(%esp)
8048cc2: 89 44 24 04 mov %eax,0x4(%esp)
8048cc6: 89 14 24 mov %edx,(%esp)
8048cc9: e8 c8 ff ff ff call 8048c96 <func4>
8048cce: 01 d8 add %ebx,%eax
//如果x<result则返回result+func4(x,y,result-1)
8048cd0: eb 1b jmp 8048ced <func4+0x57>
//跳转到8048ced
8048cd2: 89 d8 mov %ebx,%eax
//x=result则返回result
8048cd4: 39 d3 cmp %edx,%ebx
//比较x与result的大小
8048cd6: 7d 15 jge 8048ced <func4+0x57>
//大于等于则跳转到8048ced
8048cd8: 89 74 24 08 mov %esi,0x8(%esp)
8048cdc: 8d 43 01 lea 0x1(%ebx),%eax
8048cdf: 89 44 24 04 mov %eax,0x4(%esp)
8048ce3: 89 14 24 mov %edx,(%esp)
8048ce6: e8 ab ff ff ff call 8048c96 <func4>
8048ceb: 01 d8 add %ebx,%eax
//如果x>result则执行result+func4(x,result+1,z)
8048ced: 83 c4 14 add $0x14,%esp
8048cf0: 5b pop %ebx
8048cf1: 5e pop %esi
8048cf2: c3 ret
通过查看gdb查看0x804a2f7中的值可以得到:
结合之后的函数名的特点,以及cmp $0x2 %eax可以推出输入的是两个数,然后传递了函数func4的三个参数:输入、0、14执行函数func4
通过分析func4的汇编代码我们可以推出其对应的c程序:
int func4(int x,int y,int z)
{
int result=y+(z-y)/2;
if(x<result)
return result+func4(x,y,result-1);
else if(x>result)
return result+func4(x,result+1,z);
else
return result;
}
其中参数x为我们的输入,y=0,z=0xe(14)
函数执行完之后,通过汇编代码可以看到,程序将函数返回结果与0x2b(43)进行了比较,同时这个比较的值也是我们的第二个输入,也就是说第一个输入要使函数返回结果为43,编写一段程序,不难得到当函数第一个参数为12时,函数返回结果为43,输入12、43:
可以看到结果正确
第五个
08048d5c <phase_5>:
8048d5c: 53 push %ebx
//将寄存器%ebx压栈
8048d5d: 83 ec 28 sub $0x28,%esp
//将栈顶指针%esp-0x28,即开辟大小为0x28的栈空间
8048d60: 8b 5c 24 30 mov 0x30(%esp),%ebx
//将*(%esp+0x30)赋给寄存器%ebx
8048d64: 65 a1 14 00 00 00 mov %gs:0x14,%eax
8048d6a: 89 44 24 1c mov %eax,0x1c(%esp)
//将寄存器%eax中的值赋给*(%esp+0x1c)
8048d6e: 31 c0 xor %eax,%eax
//将寄存器%eax中的值进行异或
8048d70: 89 1c 24 mov %ebx,(%esp)
//将寄存器%ebx中的值赋给*(%esp)
8048d73: e8 a3 02 00 00 call 804901b <string_length>
//执行函数
8048d78: 83 f8 06 cmp $0x6,%eax
//比较0x6与寄存器%eax中的值的大小
8048d7b: 74 45 je 8048dc2 <phase_5+0x66>
//如果相等则跳转到8048dc2
8048d7d: e8 c3 03 00 00 call 8049145 <explode_bomb>
//炸弹爆炸
8048d82: eb 3e jmp 8048dc2 <phase_5+0x66>
//跳转到8048dc2
8048d84: 0f b6 14 03 movzbl (%ebx,%eax,1),%edx
//将寄存器%eax和寄存器%ebx中的值相加作0扩展赋给寄存器%edx
8048d88: 83 e2 0f and $0xf,%edx
//将0xf与寄存器%edx进行与运算
8048d8b: 0f b6 92 d8 a1 04 08 movzbl 0x804a1d8(%edx),%edx
//将寄存器%edx中的值加上0x804ald8作0扩展赋给寄存器%edx
8048d92: 88 54 04 15 mov %dl,0x15(%esp,%eax,1)
//将寄存器%edx最后一个字节的值赋给*(%esp+%eax+0x15)
8048d96: 83 c0 01 add $0x1,%eax
//寄存器%eax中的值加1
8048d99: 83 f8 06 cmp $0x6,%eax
//比较0x6与寄存器%eax的值的大小
8048d9c: 75 e6 jne 8048d84 <phase_5+0x28>
//如果不相等则跳转到8048d84
8048d9e: c6 44 24 1b 00 movb $0x0,0x1b(%esp)
//将0x0赋给*(%esp+0x1b)
8048da3: c7 44 24 04 ae a1 04 movl $0x804a1ae,0x4(%esp)
//将0x804a1ae赋给*(%esp+0x4)
8048daa: 08
8048dab: 8d 44 24 15 lea 0x15(%esp),%eax
//将地址%esp+0x15赋给寄存器eax
8048daf: 89 04 24 mov %eax,(%esp)
//将寄存器%eax中的值赋给*(esp)
8048db2: e8 83 02 00 00 call 804903a <strings_not_equal>
//执行函数
8048db7: 85 c0 test %eax,%eax
//做与运算
8048db9: 74 0e je 8048dc9 <phase_5+0x6d>
//结果为0则跳转到8048dc9
8048dbb: e8 85 03 00 00 call 8049145 <explode_bomb>
//炸弹爆炸
8048dc0: eb 07 jmp 8048dc9 <phase_5+0x6d>
//跳转到8048dc9
8048dc2: b8 00 00 00 00 mov $0x0,%eax
//将0x0赋给寄存器%eax
8048dc7: eb bb jmp 8048d84 <phase_5+0x28>
//跳转到8048d84
8048dc9: 8b 44 24 1c mov 0x1c(%esp),%eax
//将*(%esp+0x1c)赋给寄存器%eax
8048dcd: 65 33 05 14 00 00 00 xor %gs:0x14,%eax
8048dd4: 74 05 je 8048ddb <phase_5+0x7f>
//如果相等则跳转到8048ddb
8048dd6: e8 e5 f9 ff ff call 80487c0 <__stack_chk_fail@plt>
//执行函数
8048ddb: 83 c4 28 add $0x28,%esp
//将%esp加上0x28,用于回收栈空间
8048dde: 5b pop %ebx
8048ddf: 90 nop
8048de0: c3 ret
通过分析汇编代码call 804901b <string_length>以及cmp $0x6,%eax可以推出输入为一个长度为6的字符串,由之后的movzbl (%ebx,%eax,1),%edx和and $0xf,%edx可以推出是取的每个字符的低4位(二进制表示的话),由movzbl 0x804a1d8(%edx),%edx可以知道以0x804a1d8为基址进行了查找,使用gdb输入x/s 0x804a1d8得到:
可以知道0x804a1d8为一个字符串的首地址,以每个字符的低4为下标查找对应的字符,循环6次,将最终结果放到了寄存器%edx中
之后比较了0x804a1ae中的值与刚刚所得到的结果,使用gdb输入x/s 0x804a1ae得到:
通过以上分析可以知道,该程序的大致执行过程为:
输入一个长度为6的字符串,将每个字符的低4位(二进制表示)作为索引在字符串
maduiersnfotvbyl中查找对应的字符,最终得到“flames”
字符串flames在字符串maduiersnfotvbyl中对应的位置:
可以知道答案是并不唯一的,输入一组答案:
第六个
08048de1 <phase_6>:
8048de1: 56 push %esi
8048de2: 53 push %ebx
8048de3: 83 ec 44 sub $0x44,%esp
8048de6: 8d 44 24 10 lea 0x10(%esp),%eax
8048dea: 89 44 24 04 mov %eax,0x4(%esp)
8048dee: 8b 44 24 50 mov 0x50(%esp),%eax
8048df2: 89 04 24 mov %eax,(%esp)
8048df5: e8 72 03 00 00 call 804916c <read_six_numbers>
//输入6个数
8048dfa: be 00 00 00 00 mov $0x0,%esi
8048dff: 8b 44 b4 10 mov 0x10(%esp,%esi,4),%eax
8048e03: 83 e8 01 sub $0x1,%eax
8048e06: 83 f8 05 cmp $0x5,%eax
8048e09: 76 05 jbe 8048e10 <phase_6+0x2f>
8048e0b: e8 35 03 00 00 call 8049145 <explode_bomb>
8048e10: 83 c6 01 add $0x1,%esi
8048e13: 83 fe 06 cmp $0x6,%esi
8048e16: 74 1b je 8048e33 <phase_6+0x52>
//这里可以看到对输入的六个数的每个数进行了判断,每个数的大小不能大于6
8048e18: 89 f3 mov %esi,%ebx
8048e1a: 8b 44 9c 10 mov 0x10(%esp,%ebx,4),%eax
8048e1e: 39 44 b4 0c cmp %eax,0xc(%esp,%esi,4)
8048e22: 75 05 jne 8048e29 <phase_6+0x48>
8048e24: e8 1c 03 00 00 call 8049145 <explode_bomb>
8048e29: 83 c3 01 add $0x1,%ebx
8048e2c: 83 fb 05 cmp $0x5,%ebx
8048e2f: 7e e9 jle 8048e1a <phase_6+0x39>
8048e31: eb cc jmp 8048dff <phase_6+0x1e>
//这是内层循环要求输入的六个数的大小不能相等
8048e33: 8d 44 24 10 lea 0x10(%esp),%eax
8048e37: 8d 5c 24 28 lea 0x28(%esp),%ebx
8048e3b: b9 07 00 00 00 mov $0x7,%ecx
8048e40: 89 ca mov %ecx,%edx
8048e42: 2b 10 sub (%eax),%edx
8048e44: 89 10 mov %edx,(%eax)
8048e46: 83 c0 04 add $0x4,%eax
8048e49: 39 d8 cmp %ebx,%eax
8048e4b: 75 f3 jne 8048e40 <phase_6+0x5f>
//这里对输入的每个数做了处理7-a[i]
8048e4d: bb 00 00 00 00 mov $0x0,%ebx
8048e52: eb 1d jmp 8048e71 <phase_6+0x90>
//开始一个循环
8048e54: 8b 52 08 mov 0x8(%edx),%edx
//每次偏移0x8
8048e57: 83 c0 01 add $0x1,%eax
8048e5a: 39 c8 cmp %ecx,%eax
//?
8048e5c: 75 f6 jne 8048e54 <phase_6+0x73>
8048e5e: eb 05 jmp 8048e65 <phase_6+0x84>
8048e60: ba 3c c1 04 08 mov $0x804c13c,%edx
8048e65: 89 54 b4 28 mov %edx,0x28(%esp,%esi,4)
//将链表中的每个值放到一块新的空间
8048e69: 83 c3 01 add $0x1,%ebx
8048e6c: 83 fb 06 cmp $0x6,%ebx
8048e6f: 74 17 je 8048e88 <phase_6+0xa7>
8048e71: 89 de mov %ebx,%esi
8048e73: 8b 4c 9c 10 mov 0x10(%esp,%ebx,4),%ecx
//依次获得输入的6个数
8048e77: 83 f9 01 cmp $0x1,%ecx
8048e7a: 7e e4 jle 8048e60 <phase_6+0x7f>
//<=1则跳回开头
8048e7c: b8 01 00 00 00 mov $0x1,%eax
8048e81: ba 3c c1 04 08 mov $0x804c13c,%edx
8048e86: eb cc jmp 8048e54 <phase_6+0x73>
//?
8048e88: 8b 5c 24 28 mov 0x28(%esp),%ebx //链表的第一个
8048e8c: 8d 44 24 2c lea 0x2c(%esp),%eax //链表的第二个
8048e90: 8d 74 24 40 lea 0x40(%esp),%esi //链表的第七个(边界)
8048e94: 89 d9 mov %ebx,%ecx
//开始循环
8048e96: 8b 10 mov (%eax),%edx //相当于节点->next
8048e98: 89 51 08 mov %edx,0x8(%ecx) //n[i]->next=n[i+1]
8048e9b: 83 c0 04 add $0x4,%eax //增加4
8048e9e: 39 f0 cmp %esi,%eax
8048ea0: 74 04 je 8048ea6 <phase_6+0xc5>
//%eax为链表最后一个节点的地址时停止
8048ea2: 89 d1 mov %edx,%ecx
8048ea4: eb f0 jmp 8048e96 <phase_6+0xb5>
8048ea6: c7 42 08 00 00 00 00 movl $0x0,0x8(%edx)
//将各个节点按照我们的输入的7-a[i]的顺序链接起来
8048ead: be 05 00 00 00 mov $0x5,%esi
8048eb2: 8b 43 08 mov 0x8(%ebx),%eax
8048eb5: 8b 00 mov (%eax),%eax
8048eb7: 39 03 cmp %eax,(%ebx)
//比较前一个节点与后一个节点的值的大小
8048eb9: 7d 05 jge 8048ec0 <phase_6+0xdf>
8048ebb: e8 85 02 00 00 call 8049145 <explode_bomb>
//前面的值比后面的值大则炸弹爆炸
8048ec0: 8b 5b 08 mov 0x8(%ebx),%ebx //移动指针
8048ec3: 83 ee 01 sub $0x1,%esi
8048ec6: 75 ea jne 8048eb2 <phase_6+0xd1>//循环
8048ec8: 83 c4 44 add $0x44,%esp
8048ecb: 5b pop %ebx
8048ecc: 5e pop %esi
8048ecd: c3 ret
这个的汇编代码较为复杂,采用了逐段分析的方式,程序大致执行过程如下:
首先由我们输入6个数,这6个数必须满足不能大于6同时互不相等,假设为a[]
接着对每个输入的数进行运算7-a[i]
然后我们从一个地址拷贝了数据,这个地址为0x804c13c,使用gdb查看:
可以看到,以三个为一组可以分成6组数据:
结合后面的分析可以得知这是一个链表的数据结构
之后通过调整next的指向将链表按照7-a[i]的顺序进行了链接
在调整链表顺序之后,其值的大小必须满足降序排列
综上,我们可以知道,7-a[0]、7-a[1]…顺序排列的链表必须满足降序
将链表降序排列得到:
得到7-a[i]的序列为:5、3、6、4、1、2
则a[i]的序列为:2、4、1、3、6、5
输入该序列:
隐藏关
寻找隐藏关
080492b6 <phase_defused>:
80492b6: 81 ec 8c 00 00 00 sub $0x8c,%esp
80492bc: 65 a1 14 00 00 00 mov %gs:0x14,%eax
80492c2: 89 44 24 7c mov %eax,0x7c(%esp)
80492c6: 31 c0 xor %eax,%eax
80492c8: 83 3d c8 c3 04 08 06 cmpl $0x6,0x804c3c8
80492cf: 75 72 jne 8049343 <phase_defused+0x8d>
80492d1: 8d 44 24 2c lea 0x2c(%esp),%eax
80492d5: 89 44 24 10 mov %eax,0x10(%esp)
80492d9: 8d 44 24 28 lea 0x28(%esp),%eax
80492dd: 89 44 24 0c mov %eax,0xc(%esp)
80492e1: 8d 44 24 24 lea 0x24(%esp),%eax
80492e5: 89 44 24 08 mov %eax,0x8(%esp)
80492e9: c7 44 24 04 51 a3 04 movl $0x804a351,0x4(%esp)
80492f0: 08
80492f1: c7 04 24 d0 c4 04 08 movl $0x804c4d0,(%esp)
80492f8: e8 63 f5 ff ff call 8048860 <__isoc99_sscanf@plt>
80492fd: 83 f8 03 cmp $0x3,%eax
//该输入函数同样出现在第四关,这里判断第四关输入了几个值
8049300: 75 35 jne 8049337 <phase_defused+0x81>
8049302: c7 44 24 04 5a a3 04 movl $0x804a35a,0x4(%esp)
//使用gdb查看该地址中的内容为”DrEvil”
8049309: 08
804930a: 8d 44 24 2c lea 0x2c(%esp),%eax
804930e: 89 04 24 mov %eax,(%esp)
8049311: e8 24 fd ff ff call 804903a <strings_not_equal>
8049316: 85 c0 test %eax,%eax
8049318: 75 1d jne 8049337 <phase_defused+0x81>
804931a: c7 04 24 20 a2 04 08 movl $0x804a220,(%esp)
//使用gdb查看该地址中的内容“Curses, you've found the secret phase!”
8049321: e8 ca f4 ff ff call 80487f0 <puts@plt>
8049326: c7 04 24 48 a2 04 08 movl $0x804a248,(%esp)
//使用gdb查看该地址中的内容“But finding it and solving it are quite different...”
804932d: e8 be f4 ff ff call 80487f0 <puts@plt>
8049332: e8 e8 fb ff ff call 8048f1f <secret_phase>
//成功进入隐藏关卡
8049337: c7 04 24 80 a2 04 08 movl $0x804a280,(%esp)
804933e: e8 ad f4 ff ff call 80487f0 <puts@plt>
8049343: 8b 44 24 7c mov 0x7c(%esp),%eax
8049347: 65 33 05 14 00 00 00 xor %gs:0x14,%eax
804934e: 74 05 je 8049355 <phase_defused+0x9f>
8049350: e8 6b f4 ff ff call 80487c0 <__stack_chk_fail@plt>
8049355: 81 c4 8c 00 00 00 add $0x8c,%esp
804935b: c3 ret
804935c: 66 90 xchg %ax,%ax
804935e: 66 90 xchg %ax,%ax
通过分析这一段汇编代码我们可以得出,在第四关输入值的时候,我们是可以输入三个值的,而且我们输入的第三个值将会作为我们能否进入隐藏关的钥匙,通过gdb查看用来比较的值:
我们可以猜测,这把钥匙就是字符串“DrEvil”,在第四关额外输入该字符串:
果然成功进入了隐藏关!
隐藏关代码
08048f1f <secret_phase>:
8048f1f: 53 push %ebx
8048f20: 83 ec 18 sub $0x18,%esp
8048f23: e8 94 02 00 00 call 80491bc <read_line>
8048f28: c7 44 24 08 0a 00 00 movl $0xa,0x8(%esp)
8048f2f: 00
8048f30: c7 44 24 04 00 00 00 movl $0x0,0x4(%esp)
8048f37: 00
8048f38: 89 04 24 mov %eax,(%esp)
//这里传递函数的三个参数
8048f3b: e8 90 f9 ff ff call 80488d0 <strtol@plt>
//了解函数strtol的使用可以知道其标准格式为strtol(char*ch1,char*ch2,int base)
//具体作用就是将*ch1字符串当作base进制的返回,这里传入0xa说明输入一个十进制数
8048f40: 89 c3 mov %eax,%ebx
8048f42: 8d 40 ff lea -0x1(%eax),%eax
8048f45: 3d e8 03 00 00 cmp $0x3e8,%eax
8048f4a: 76 05 jbe 8048f51 <secret_phase+0x32>
//这里控制输入的值不能大于0x3e8,这里其实与后面是有关联的
8048f4c: e8 f4 01 00 00 call 8049145 <explode_bomb>
8048f51: 89 5c 24 04 mov %ebx,0x4(%esp)
8048f55: c7 04 24 88 c0 04 08 movl $0x804c088,(%esp)
//传入函数参数,第二个为我们的输入,第一个为一个固定值
8048f5c: e8 6d ff ff ff call 8048ece <fun7>
//调用函数func7
8048f61: 83 f8 04 cmp $0x4,%eax
8048f64: 74 05 je 8048f6b <secret_phase+0x4c>
8048f66: e8 da 01 00 00 call 8049145 <explode_bomb>
8048f6b: c7 04 24 88 a1 04 08 movl $0x804a188,(%esp)
8048f72: e8 79 f8 ff ff call 80487f0 <puts@plt>
8048f77: e8 3a 03 00 00 call 80492b6 <phase_defused>
8048f7c: 83 c4 18 add $0x18,%esp
8048f7f: 5b pop %ebx
8048f80: c3 ret
8048f81: 66 90 xchg %ax,%ax
8048f83: 66 90 xchg %ax,%ax
8048f85: 66 90 xchg %ax,%ax
8048f87: 66 90 xchg %ax,%ax
8048f89: 66 90 xchg %ax,%ax
8048f8b: 66 90 xchg %ax,%ax
8048f8d: 66 90 xchg %ax,%ax
8048f8f: 90 nop
func7函数
08048ece <fun7>:
8048ece: 53 push %ebx
8048ecf: 83 ec 18 sub $0x18,%esp
8048ed2: 8b 54 24 20 mov 0x20(%esp),%edx
8048ed6: 8b 4c 24 24 mov 0x24(%esp),%ecx
//传入函数的两个参数,假设叫*p和n
8048eda: 85 d2 test %edx,%edx
8048edc: 74 37 je 8048f15 <fun7+0x47>
//为零则直接结束
8048ede: 8b 1a mov (%edx),%ebx
8048ee0: 39 cb cmp %ecx,%ebx
8048ee2: 7e 13 jle 8048ef7 <fun7+0x29>
//*p<=n跳转到8048ef7
8048ee4: 89 4c 24 04 mov %ecx,0x4(%esp)
8048ee8: 8b 42 04 mov 0x4(%edx),%eax
//传入函数的两个参数*(p+4)、n
8048eeb: 89 04 24 mov %eax,(%esp)
8048eee: e8 db ff ff ff call 8048ece <fun7>
8048ef3: 01 c0 add %eax,%eax
//返回2*func7(*(p+4), n),这里对应*p>n的情况
8048ef5: eb 23 jmp 8048f1a <fun7+0x4c>
8048ef7: b8 00 00 00 00 mov $0x0,%eax
8048efc: 39 cb cmp %ecx,%ebx
8048efe: 74 1a je 8048f1a <fun7+0x4c>
//如果*p=n则跳转到8048f1a(结束),这里对应*p==n的情况
8048f00: 89 4c 24 04 mov %ecx,0x4(%esp)
8048f04: 8b 42 08 mov 0x8(%edx),%eax
//传入函数的两个参数*(p+8)、n
8048f07: 89 04 24 mov %eax,(%esp)
8048f0a: e8 bf ff ff ff call 8048ece <fun7>
8048f0f: 8d 44 00 01 lea 0x1(%eax,%eax,1),%eax
//返回2*func7(*(p+8), n)+1,这里对应*p<n的情况
8048f13: eb 05 jmp 8048f1a <fun7+0x4c>
8048f15: b8 ff ff ff ff mov $0xffffffff,%eax
8048f1a: 83 c4 18 add $0x18,%esp
8048f1d: 5b pop %ebx
8048f1e: c3 ret
这时可以大致分析出func7函数的程序代码:
int func7(? *p, int n)
{
if(*p==0) return 0;
if(*p==n) return 0;
else if(*p<n) return 2*func7(*(p+8), n)+1;
else return 2*func7(*(p+4), n);
}
这时我们会疑惑* p到底是什么,* (p+4)、* (p+8)作为参数是什么意思?
回到隐藏关代码,使用gdb查看以地址0x804c088开始的内容:
可以看到,将他们以三个为一组,可以分成15组,而且都是数据,地址,地址的格式
不断的寻找这些地址之间的关系,可以发现:
这时这个数据结构就一目了然了,它居然是一个二叉树!
那么也就可以解释为什么输入不能大于0x3e9了,因为该值就是这个二叉树所有值中的最大值了,其次,我们也可以解释*(p+4)、*(p+8)的含义了,这两个写法正对应了二叉树查找左子树和右子树的过程。
之后我们可以看到,将返回结果与0x4进行了比较,相等的话可以通过这个隐藏关
如何使返回结果为0x4? 分析func7函数我们知道,查找到目标值时将会返回0,查找一次左子树返回结果的2倍,查找一次右子树返回结果的2倍加1。
由于最多查找3次,那么就是0->2x0+1->2x1->2x2得到4
也就是查找两次左子树,一次右子树,对应值为0x7,由于输入为10进制,输入7:
炸弹成功拆除!