实验题目:LAB3(拆炸弹)
实验目的:
程序运行在linux环境中。程序运行中有6个关卡(6个phase),每个phase需要用户在终端上输入特定的字符或者数字才能通关,否则会引爆炸弹!那么如何才能知道输入什么内容呢?这需要你使用gdb工具反汇编出汇编代码,结合c语言文件找到每个关卡的入口函数。然后分析汇编代码,找到在每个phase程序段中,引导程序跳转到“explode_bomb”程序段的地方,并分析其成功跳转的条件,以此为突破口寻找应该在命令行输入何种字符通关。
实验环境:windows7操作系统下ubuntu虚拟机
实验内容及操作步骤:
实验前的准备
1、 在LAB3目录下,使用cat bomb.c查看源程序的c语言代码,但是这里只有主函数,每一个函数的代码并没有;
2、 输入反汇编命令objdump –d bomb,出现了大量的汇编代码;
3、 在终端输入objdump –d bomb > 1.txt,将汇编程序输到服务器上一个自动生成的叫做1.txt文件当中;
4、 然后就可以打开1.txt查看源程序的汇编语言代码,进而开始拆炸弹的操作
实验过程
1、main函数函数调用部分
8048a78: e8 89 07 00 00 call 8049206 <read_line>
8048a7d: 89 04 24 mov %eax,(%esp)
8048a80: e8 dc 04 00 00 call 8048f61 <phase_1>
8048a85: e8 8a 05 00 00 call 8049014 <phase_defused>
8048a8a: c7 44 24 04 a0 a0 04 movl $0x804a0a0,0x4(%esp)
8048a91: 08
8048a92: c7 04 24 01 00 00 00 movl $0x1,(%esp)
8048a99: e8 d2 fd ff ff call 8048870 __printf_chk@plt
8048a9e: e8 63 07 00 00 call 8049206 <read_line>
8048aa3: 89 04 24 mov %eax,(%esp)
8048aa6: e8 bf 02 00 00 call 8048d6a <phase_2>
8048aab: e8 64 05 00 00 call 8049014 <phase_defused>
8048ab0: c7 44 24 04 cc a0 04 movl $0x804a0cc,0x4(%esp)
8048ab7: 08
8048ab8: c7 04 24 01 00 00 00 movl $0x1,(%esp)
8048abf: e8 ac fd ff ff call 8048870 __printf_chk@plt
8048ac4: e8 3d 07 00 00 call 8049206 <read_line>
8048ac9: 89 04 24 mov %eax,(%esp)
8048acc: e8 d0 03 00 00 call 8048ea1 <phase_3>
8048ad1: e8 3e 05 00 00 call 8049014 <phase_defused>
8048ad6: c7 44 24 04 21 a0 04 movl $0x804a021,0x4(%esp)
8048add: 08
8048ade: c7 04 24 01 00 00 00 movl $0x1,(%esp)
8048ae5: e8 86 fd ff ff call 8048870 __printf_chk@plt
8048aea: e8 17 07 00 00 call 8049206 <read_line>
8048aef: 89 04 24 mov %eax,(%esp)
8048af2: e8 37 03 00 00 call 8048e2e <phase_4>
8048af7: e8 18 05 00 00 call 8049014 <phase_defused>
8048afc: c7 44 24 04 ec a0 04 movl $0x804a0ec,0x4(%esp)
8048b03: 08
8048b04: c7 04 24 01 00 00 00 movl $0x1,(%esp)
8048b0b: e8 60 fd ff ff call 8048870 __printf_chk@plt
8048b10: e8 f1 06 00 00 call 8049206 <read_line>
8048b15: 89 04 24 mov %eax,(%esp)
8048b18: e8 9b 02 00 00 call 8048db8 <phase_5>
8048b1d: e8 f2 04 00 00 call 8049014 <phase_defused>
8048b22: c7 44 24 04 14 a1 04 movl $0x804a114,0x4(%esp)
8048b29: 08
8048b2a: c7 04 24 01 00 00 00 movl $0x1,(%esp)
8048b31: e8 3a fd ff ff call 8048870 __printf_chk@plt
8048b36: e8 cb 06 00 00 call 8049206 <read_line>
8048b3b: 89 04 24 mov %eax,(%esp)
8048b3e: e8 46 01 00 00 call 8048c89 <phase_6>
8048b43: e8 cc 04 00 00 call 8049014 <phase_defused>
由上面的汇编代码可以看出一共有6个关卡,每到一个关卡都会先调用read_line函数从命令窗口读入一串字符或者数字,然后再调用该关卡。在每一个关卡中都有可能会调用到explore_bomb函数,这个函数是爆炸函数,我们的目的是让read_line中读入的字符或者数字能够阻止对炸弹函数的调用,所以我们能够通过使炸弹不爆炸的条件倒推出我们应该输入的数字。
2、第一关
08048f61 <phase_1>:
8048f61: 55 push %ebp
8048f62: 89 e5 mov %esp,%ebp
8048f64: 83 ec 18 sub $0x18,%esp
8048f67: c7 44 24 04 5c a1 04 movl $0x804a15c,0x4(%esp) #将esp寄存器地址指向$0x804a15c
8048f6e: 08
8048f6f: 8b 45 08 mov 0x8(%ebp),%eax
8048f72: 89 04 24 mov %eax,(%esp) #$0x804a15c中的数据
8048f75: e8 31 00 00 00 call 8048fab <strings_not_equal>
8048f7a: 85 c0 test %eax,%eax #eax!=0的时候调用<explode_bomb>
8048f7c: 74 05 je 8048f83 <phase_1+0x22>
8048f7e: e8 4e 01 00 00 call 80490d1 <explode_bomb>
8048f83: c9 leave
8048f84: c3 ret
8048f85: 90 nop
8048f86: 90 nop
8048f87: 90 nop
8048f88: 90 nop
8048f89: 90 nop
8048f8a: 90 nop
8048f8b: 90 nop
8048f8c: 90 nop
8048f8d: 90 nop
8048f8e: 90 nop
8048f8f: 90 nop
容易看出在8048f75处程序调用了一个函数string_not_equal,即字符串不相等函数。先撇开其他的部分,我们可以看到string_not_equal函数一共有两个参数,一个是$0x804a15c这个地址,另外一个则是8(%esp)也就是上一个函数调用phrase_1传入的参数。可以清楚的看出这两个参数与我们要进行比较的两个string字符串有关系,第一个参数是$0x804a15c,是一个固定的地址,gdb x/s 0x804a15c中打印得到的内存是We have to stand with our North Korean allies.可以知道这是一个固定的字符串,而另外一个字符串肯定是要自己从窗口输入的,两个字符串进行对比,相同的时候才会运行成功,所以要输入这个字符串,运行成功
3、第二关
8048d6a: 55 push %ebp
8048d6b: 89 e5 mov %esp,%ebp
8048d6d: 56 push %esi
8048d6e: 53 push %ebx
8048d6f: 83 ec 30 sub $0x30,%esp
8048d72: 8d 45 e0 lea -0x20(%ebp),%eax
8048d75: 89 44 24 04 mov %eax,0x4(%esp) #6个数的首地址
8048d79: 8b 45 08 mov 0x8(%ebp),%eax
8048d7c: 89 04 24 mov %eax,(%esp)
8048d7f: e8 87 03 00 00 call 804910b <read_six_numbers>#六个数字
8048d84: 83 7d e0 00 cmpl $0x0,-0x20(%ebp) #第一个数0
8048d88: 75 06 jne 8048d90 <phase_2+0x26>
8048d8a: 83 7d e4 01 cmpl $0x1,-0x1c(%ebp) #第二个数1
8048d8e: 74 05 je 8048d95 <phase_2+0x2b>
8048d90: e8 3c 03 00 00 call 80490d1 <explode_bomb>
8048d95: 8d 5d e8 lea -0x18(%ebp),%ebx #第三个数的地址
8048d98: 8d 75 f8 lea -0x8(%ebp),%esi #esi指向第六个的下一个
8048d9b: 8b 43 fc mov -0x4(%ebx),%eax #第二个数1给eax
8048d9e: 03 43 f8 add -0x8(%ebx),%eax #前两个0,1给eax
8048da1: 39 03 cmp %eax,(%ebx) #第三个数和eax相比
8048da3: 74 05 je 8048daa <phase_2+0x40> #二者相等
8048da5: e8 27 03 00 00 call 80490d1 <explode_bomb>
8048daa: 83 c3 04 add $0x4,%ebx #ebx改指向第四个数字
8048dad: 39 f3 cmp %esi,%ebx
8048daf: 75 ea jne 8048d9b <phase_2+0x31>#跳回循环,斐波拉契
8048db1: 83 c4 30 add $0x30,%esp
8048db4: 5b pop %ebx
8048db5: 5e pop %esi
8048db6: 5d pop %ebp
8048db7: c3 ret
分析过程和前面一关相似,先看程序第一个要进行调用的函数read_six_numbers,读取六个数字,知道了要从命令窗口输入4个数字,而-0x20(%ebp)作为数组的第一个元素的地址,
然后可以判断第一个元素等于0,第二个元素等于1,然后进入一个循环,将当前元素和前面一个元素的和存在另外一个寄存器里,并且为了防止爆炸,判断这个寄存器里面存储的数值与当前元素的地址+4的位置存储的值一样,也就是当前元素的下一个元素,这样就完成了下一元素等于前面两个元素之和的功能,这个操作一直执行到第六个数就可以得到我们要输入的六个数字,这六个数字是0 1 1 2 3 4,斐波拉契数列,输入相应字符,验证正确
4、 第三关
8048ea1: 55 push %ebp
8048ea2: 89 e5 mov %esp,%ebp
8048ea4: 83 ec 28 sub $0x28,%esp #开辟0x28个字节
8048ea7: 8d 45 f0 lea -0x10(%ebp),%eax
8048eaa: 89 44 24 0c mov %eax,0xc(%esp) #第二个的地址-0x10,y
8048eae: 8d 45 f4 lea -0xc(%ebp),%eax
8048eb1: 89 44 24 08 mov %eax,0x8(%esp) #第一个的地址-0xc,x
8048eb5: c7 44 24 04 3e a2 04 movl $0x804a23e,0x4(%esp) #第三个参数地址,两个%d
8048ebc: 08 #说明两个整数
8048ebd: 8b 45 08 mov 0x8(%ebp),%eax
8048ec0: 89 04 24 mov %eax,(%esp)
8048ec3: e8 78 f9 ff ff call 8048840 __isoc99_sscanf@plt
8048ec8: 83 f8 01 cmp $0x1,%eax
8048ecb: 7f 05 jg 8048ed2 <phase_3+0x31> #两个参数
8048ecd: e8 ff 01 00 00 call 80490d1 <explode_bomb>
8048ed2: 83 7d f4 07 cmpl $0x7,-0xc(%ebp) x<=7
8048ed6: 77 6b ja 8048f43 <phase_3+0xa2>
8048ed8: 8b 45 f4 mov -0xc(%ebp),%eax
8048edb: ff 24 85 a0 a1 04 08 jmp 0x804a1a0(,%eax,4) #跳转表$1 = 0x8048f12
8048ee2: b8 00 00 00 00 mov $0x0,%eax
8048ee7: eb 53 jmp 8048f3c <phase_3+0x9b>
8048ee9: b8 00 00 00 00 mov $0x0,%eax
8048eee: 66 90 xchg %ax,%ax
8048ef0: eb 45 jmp 8048f37 <phase_3+0x96>
8048ef2: b8 00 00 00 00 mov $0x0,%eax
8048ef7: eb 39 jmp 8048f32 <phase_3+0x91>
8048ef9: b8 00 00 00 00 mov $0x0,%eax
8048efe: 66 90 xchg %ax,%ax
8048f00: eb 2b jmp 8048f2d <phase_3+0x8c>
8048f02: b8 00 00 00 00 mov $0x0,%eax
8048f07: eb 1f jmp 8048f28 <phase_3+0x87>
8048f09: b8 00 00 00 00 mov $0x0,%eax
8048f0e: 66 90 xchg %ax,%ax
8048f10: eb 11 jmp 8048f23 <phase_3+0x82>
8048f12: b8 14 03 00 00 mov $0x314,%eax #314
8048f17: eb 05 jmp 8048f1e <phase_3+0x7d>
8048f19: b8 00 00 00 00 mov $0x0,%eax
8048f1e: 2d 5a 03 00 00 sub $0x35a,%eax #-0x46
8048f23: 05 ef 02 00 00 add $0x2ef,%eax
8048f28: 2d 16 02 00 00 sub $0x216,%eax #0x93
8048f2d: 05 16 02 00 00 add $0x216,%eax
8048f32: 2d 16 02 00 00 sub $0x216,%eax
8048f37: 05 16 02 00 00 add $0x216,%eax
8048f3c: 2d 16 02 00 00 sub $0x216,%eax
8048f41: eb 0a jmp 8048f4d <phase_3+0xac>
8048f43: e8 89 01 00 00 call 80490d1 <explode_bomb>
8048f48: b8 00 00 00 00 mov $0x0,%eax
8048f4d: 83 7d f4 05 cmpl $0x5,-0xc(%ebp)
8048f51: 7f 05 jg 8048f58 <phase_3+0xb7> #x<=5
8048f53: 3b 45 f0 cmp -0x10(%ebp),%eax #y=0x93=147
8048f56: 74 05 je 8048f5d <phase_3+0xbc>
8048f58: e8 74 01 00 00 call 80490d1 <explode_bomb>
8048f5d: c9 leave
8048f5e: 66 90 xchg %ax,%ax
8048f60: c3 ret
先查看本程序的第一个函数调用,发现这个函数一共调用了4个参数,后面的两个参数是两个地址,暂且先不理会,看下面的参数,将$0x804a23e传到了第三个参数的位置,通过gdb查看这个地址,得到了字符串“%d %d”,明显的可以知道这里要输入两个整型数据,而这两个数会存储在上面的两个地址当中。为了防止爆炸,调用scanf后的返回值一定大于1,所以返回值应该等于2,说明了应该输入两个数据,和上面的结论相对应。然后往下看0x804a1a0(,%eax,4),可以知道这肯定是一个跳转表,跳转表的跳转指令的参数是输入的第一个参数,我先假设跳转指令参数为0,即传入的第一个参数是0,这样就会跳转到$1 = 0x8048f12,经过一系列运算得到运算的结果0x93,转换成十进制是147,下面为了防止爆炸,必须使输入的第二个参数是147,所以得到了输入值 0 147,其实不止有这么一种搭配,可能会有其他的答案,这里不再赘述。
5、 第四关
8048e2e: 55 push %ebp
8048e2f: 89 e5 mov %esp,%ebp
8048e31: 83 ec 28 sub $0x28,%esp
8048e34: 8d 45 f0 lea -0x10(%ebp),%eax #y
8048e37: 89 44 24 0c mov %eax,0xc(%esp)
8048e3b: 8d 45 f4 lea -0xc(%ebp),%eax
8048e3e: 89 44 24 08 mov %eax,0x8(%esp) #x
8048e42: c7 44 24 04 3e a2 04 movl $0x804a23e,0x4(%esp) #两个整数
8048e49: 08
8048e4a: 8b 45 08 mov 0x8(%ebp),%eax
8048e4d: 89 04 24 mov %eax,(%esp)
8048e50: e8 eb f9 ff ff call 8048840 __isoc99_sscanf@plt
8048e55: 83 f8 02 cmp $0x2,%eax #2个参数
8048e58: 75 0c jne 8048e66 <phase_4+0x38>
8048e5a: 8b 45 f4 mov -0xc(%ebp),%eax
8048e5d: 85 c0 test %eax,%eax
8048e5f: 78 05 js 8048e66 <phase_4+0x38>
8048e61: 83 f8 0e cmp $0xe,%eax
8048e64: 7e 05 jle 8048e6b <phase_4+0x3d> #0<x<=14
8048e66: e8 66 02 00 00 call 80490d1 <explode_bomb>
8048e6b: c7 44 24 08 0e 00 00 movl $0xe,0x8(%esp)
8048e72: 00
8048e73: c7 44 24 04 00 00 00 movl $0x0,0x4(%esp)
8048e7a: 00
8048e7b: 8b 45 f4 mov -0xc(%ebp),%eax
8048e7e: 89 04 24 mov %eax,(%esp)
8048e81: e8 da fc ff ff call 8048b60 #调用func(x);
8048e86: 83 f8 01 cmp $0x1,%eax #func(x)=1;
8048e89: 75 06 jne 8048e91 <phase_4+0x63>
8048e8b: 83 7d f0 01 cmpl $0x1,-0x10(%ebp) #y=1;
8048e8f: 74 0c je 8048e9d <phase_4+0x6f>
8048e91: 8d b4 26 00 00 00 00 lea 0x0(%esi,%eiz,1),%esi
8048e98: e8 34 02 00 00 call 80490d1 <explode_bomb>
8048e9d: c9 leave
8048e9e: 66 90 xchg %ax,%ax
8048ea0: c3 ret
Func4
8048b60: 55 push %ebp
8048b61: 89 e5 mov %esp,%ebp
8048b63: 83 ec 18 sub $0x18,%esp
8048b66: 89 5d f8 mov %ebx,-0x8(%ebp)
8048b69: 89 75 fc mov %esi,-0x4(%ebp)
8048b6c: 8b 55 08 mov 0x8(%ebp),%edx #x
8048b6f: 8b 45 0c mov 0xc(%ebp),%eax #0
8048b72: 8b 5d 10 mov 0x10(%ebp),%ebx #e
8048b75: 89 d9 mov %ebx,%ecx #e
8048b77: 29 c1 sub %eax,%ecx #e-0
8048b79: 89 ce mov %ecx,%esi #e-0
8048b7b: c1 ee 1f shr $0x1f,%esi #1
8048b7e: 8d 0c 0e lea (%esi,%ecx,1),%ecx #0xf
8048b81: d1 f9 sar %ecx #0x7
8048b83: 01 c1 add %eax,%ecx #7
8048b85: 39 d1 cmp %edx,%ecx #7和x比较
8048b87: 7e 17 jle 8048ba0 <func4+0x40> #若(e-0)/2+0<=x
8048b89: 83 e9 01 sub $0x1,%ecx
8048b8c: 89 4c 24 08 mov %ecx,0x8(%esp)
8048b90: 89 44 24 04 mov %eax,0x4(%esp)
8048b94: 89 14 24 mov %edx,(%esp)
8048b97: e8 c4 ff ff ff call 8048b60 #func4(x,0,(14-0)/2+0-1)
8048b9c: 01 c0 add %eax,%eax 0
8048b9e: eb 20 jmp 8048bc0 <func4+0x60>
8048ba0: b8 00 00 00 00 mov $0x0,%eax eax=0
8048ba5: 39 d1 cmp %edx,%ecx
8048ba7: 7d 17 jge 8048bc0 <func4+0x60> #若(e-0)/2+0>=x
8048ba9: 89 5c 24 08 mov %ebx,0x8(%esp) e
8048bad: 83 c1 01 add $0x1,%ecx 7+1
8048bb0: 89 4c 24 04 mov %ecx,0x4(%esp) 8
8048bb4: 89 14 24 mov %edx,(%esp) x
8048bb7: e8 a4 ff ff ff call 8048b60 func(x,8,e)
8048bbc: 8d 44 00 01 lea 0x1(%eax,%eax,1),%eax 1
8048bc0: 8b 5d f8 mov -0x8(%ebp),%ebx
8048bc3: 8b 75 fc mov -0x4(%ebp),%esi
8048bc6: 89 ec mov %ebp,%esp
8048bc8: 5d pop %ebp
8048bc9: c3 ret
第四关第一个调用的函数是scanf函数,是要从命令窗口输入字符,函数调用之前,传入了两个地址,并且传入$0x804a23e在参数的第三个位置,第四个参数依旧是8(%ebp),即上面一个函数的压栈的参数。在gdb中用x/s 0x804a23e查看其中的东西,发现时“%d %d”,从而知道这个函数也是输入两个参数,在下面的语句中可以判断这个函数的第一个数值参数是大于0而小于或者等于14的,下面调用了func(x,0,14)函数,并且可以判断返回值是1,然后为了防止炸弹爆炸,要使第二个参数的值为1。进入func4()函数,func4()函数是一个递归函数,利用递归建立一个二叉检索树。为了使返回值是1,倒推得到x输入的初始值可以是8,然后将8 1输入到源程序中,成功
6、第五关
8048db8: 55 push %ebp
8048db9: 89 e5 mov %esp,%ebp
8048dbb: 56 push %esi
8048dbc: 53 push %ebx
8048dbd: 83 ec 20 sub $0x20,%esp
8048dc0: 8d 45 f0 lea -0x10(%ebp),%eax
8048dc3: 89 44 24 0c mov %eax,0xc(%esp) #第二个参数 y
8048dc7: 8d 45 f4 lea -0xc(%ebp),%eax
8048dca: 89 44 24 08 mov %eax,0x8(%esp) #第一个参数 x
8048dce: c7 44 24 04 3e a2 04 movl $0x804a23e,0x4(%esp) #两个参数
8048dd5: 08
8048dd6: 8b 45 08 mov 0x8(%ebp),%eax
8048dd9: 89 04 24 mov %eax,(%esp)
8048ddc: e8 5f fa ff ff call 8048840 __isoc99_sscanf@plt
8048de1: 83 f8 01 cmp $0x1,%eax
8048de4: 7f 05 jg 8048deb <phase_5+0x33> #输入两个参数
8048de6: e8 e6 02 00 00 call 80490d1 <explode_bomb>
8048deb: 8b 45 f4 mov -0xc(%ebp),%eax
8048dee: 83 e0 0f and $0xf,%eax #x=x&f,取低4位
8048df1: 89 45 f4 mov %eax,-0xc(%ebp)
8048df4: 83 f8 0f cmp $0xf,%eax
8048df7: 74 29 je 8048e22 <phase_5+0x6a> x低四位不等于f
8048df9: b9 00 00 00 00 mov $0x0,%ecx 0
8048dfe: ba 00 00 00 00 mov $0x0,%edx 0
8048e03: bb c0 a1 04 08 mov $0x804a1c0,%ebx 数组起始地址
8048e08: 83 c2 01 add $0x1,%edx edx=1
8048e0b: 8b 04 83 mov (%ebx,%eax,4),%eax 首地址+4*eax给eax
8048e0e: 01 c1 add %eax,%ecx 数组前eax个元素之和
8048e10: 83 f8 0f cmp $0xf,%eax 数组第eax个元素与f比较
8048e13: 75 f3 jne 8048e08 <phase_5+0x50> 不等于,执行循环
8048e15: 89 45 f4 mov %eax,-0xc(%ebp) f
8048e18: 83 fa 0f cmp $0xf,%edx edx=15,执行15次
8048e1b: 75 05 jne 8048e22 <phase_5+0x6a>
8048e1d: 39 4d f0 cmp %ecx,-0x10(%ebp)
8048e20: 74 05 je 8048e27 <phase_5+0x6f>
8048e22: e8 aa 02 00 00 call 80490d1 <explode_bomb>
8048e27: 83 c4 20 add $0x20,%esp
8048e2a: 5b pop %ebx
8048e2b: 5e pop %esi
8048e2c: 5d pop %ebp
8048e2d: c3 ret
按照与之前相同的步骤,找到程序的第一个函数的调用的地方,发现了这个函数的参数一共有四个,后面的两个是地址,第三个参数是一个地址的值,用同样的gdb的方法看到这里还是“%d %d”,意思是我们需要输入两个参数,存放在后面两个地址参数当中。接着往下面看,限制了输入的第一个参数的低四位不是15。下面执行了一个循环,这个循环最多只能执行15次,但是其中还有一个判断条件是读取到的数不能是15,因此为了循环能够进行15次,我们必须要让判断当前的数据不能与15这个结论至少能循环15次,所以我要让第15次才能够读取到15个数。从汇编代码中仔细分析的话,我们不难发现这个循环每次会让上面读到的数据作为下面要进行读取的数据的相对地址,所以我们要由果溯因找到输入的第一个参数的相对地址。打印$0x804a1c处数组,得到了这样一个数组$1={10,2,14,7,8,12,15,11,0,4,1,13,3,9,6,5},将其与下标进行对比的结果
由果溯因得到输入值的变化为:5 12 3 7 11 13 9 4 8 0 10 1 2 14 6 15,所以第一个输入的x的值经过处理之后的结果是5,不过这是经过处理的值,只要低四位等于5就能够满足要求,所以这一题是一个多解题。接着看后面的程序会发现有一个寄存器一直在累加查看到的数据的值,一直到查看到最后一个数据,最后一个也就是5,所以这个寄存器最终的值就是前面15个元素的值,计算结果是115.将5 115输入,成功
7、 第六关
8048c89: 55 push %ebp
8048c8a: 89 e5 mov %esp,%ebp
8048c8c: 57 push %edi
8048c8d: 56 push %esi
8048c8e: 53 push %ebx
8048c8f: 83 ec 5c sub $0x5c,%esp
8048c92: 8d 45 d0 lea -0x30(%ebp),%eax #数组首地址
8048c95: 89 44 24 04 mov %eax,0x4(%esp)
8048c99: 8b 45 08 mov 0x8(%ebp),%eax
8048c9c: 89 04 24 mov %eax,(%esp)
8048c9f: e8 67 04 00 00 call 804910b <read_six_numbers> #读取6个数字
8048ca4: be 00 00 00 00 mov $0x0,%esi
8048ca9: 8d 7d d0 lea -0x30(%ebp),%edi
8048cac: 8b 04 b7 mov (%edi,%esi,4),%eax #第esi+1个数字给eax
8048caf: 83 e8 01 sub $0x1,%eax #eax–
8048cb2: 83 f8 05 cmp $0x5,%eax #eax<=5
8048cb5: 76 05 jbe 8048cbc <phase_6+0x33>
8048cb7: e8 15 04 00 00 call 80490d1 <explode_bomb>
8048cbc: 83 c6 01 add $0x1,%esi #esi++
8048cbf: 83 fe 06 cmp $0x6,%esi #
8048cc2: 74 22 je 8048ce6 <phase_6+0x5d> #if(esi==6)
8048cc4: 8d 1c b7 lea (%edi,%esi,4),%ebx #第esi+1个数给ebx
8048cc7: 89 75 b4 mov %esi,-0x4c(%ebp) #esi
8048cca: 8b 44 b7 fc mov -0x4(%edi,%esi,4),%eax #esi个数给eax
8048cce: 3b 03 cmp (%ebx),%eax #a[esi-1]!=a[esi]
8048cd0: 75 05 jne 8048cd7 <phase_6+0x4e>
8048cd2: e8 fa 03 00 00 call 80490d1 <explode_bomb>
8048cd7: 83 45 b4 01 addl $0x1,-0x4c(%ebp) #esi++
8048cdb: 83 c3 04 add $0x4,%ebx #ebx+=4
8048cde: 83 7d b4 05 cmpl $0x5,-0x4c(%ebp)
8048ce2: 7e e6 jle 8048cca <phase_6+0x41> if(esi<=5),循环6-esi次
8048ce4: eb c6 jmp 8048cac <phase_6+0x23>
8048ce6: bb 00 00 00 00 mov $0x0,%ebx #ebx=0
8048ceb: 8d 7d d0 lea -0x30(%ebp),%edi #首地址
8048cee: eb 16 jmp 8048d06 <phase_6+0x7d>
8048cf0: 8b 52 08 mov 0x8(%edx),%edx #双层for循环,调整链表中6个元素的位置
8048cf3: 83 c0 01 add $0x1,%eax eax=2
8048cf6: 39 c8 cmp %ecx,%eax 2!=ebx
8048cf8: 75 f6 jne 8048cf0 <phase_6+0x67>
8048cfa: 89 54 b5 b8 mov %edx,-0x48(%ebp,%esi,4) #数组首地址
8048cfe: 83 c3 01 add $0x1,%ebx ebx=1
8048d01: 83 fb 06 cmp $0x6,%ebx
8048d04: 74 16 je 8048d1c <phase_6+0x93>
8048d06: 89 de mov %ebx,%esi #esi=0 1
8048d08: 8b 0c 9f mov (%edi,%ebx,4),%ecx #a[ebx]
8048d0b: ba c4 c0 04 08 mov $0x804c0c4,%edx #链表首地址
8048d10: b8 01 00 00 00 mov $0x1,%eax eax=1
8048d15: 83 f9 01 cmp $0x1,%ecx
8048d18: 7f d6 jg 8048cf0 <phase_6+0x67> a[ebx]>1
8048d1a: eb de jmp 8048cfa <phase_6+0x71>
8048d1c: 8b 5d b8 mov -0x48(%ebp),%ebx #链表首地址
8048d1f: 8b 45 bc mov -0x44(%ebp),%eax
8048d22: 89 43 08 mov %eax,0x8(%ebx) #指针4个字节
8048d25: 8b 55 c0 mov -0x40(%ebp),%edx
8048d28: 89 50 08 mov %edx,0x8(%eax)
8048d2b: 8b 45 c4 mov -0x3c(%ebp),%eax
8048d2e: 89 42 08 mov %eax,0x8(%edx)
8048d31: 8b 55 c8 mov -0x38(%ebp),%edx
8048d34: 89 50 08 mov %edx,0x8(%eax)
8048d37: 8b 45 cc mov -0x34(%ebp),%eax -0x48~-0x34指针
8048d3a: 89 42 08 mov %eax,0x8(%edx)
8048d3d: c7 40 08 00 00 00 00 movl $0x0,0x8(%eax) #对链表节点的值进行判断
8048d44: be 00 00 00 00 mov $0x0,%esi
8048d49: 8b 43 08 mov 0x8(%ebx),%eax #第二个
8048d4c: 8b 13 mov (%ebx),%edx #第一个
8048d4e: 3b 10 cmp (%eax),%edx #第一个大于第二个
8048d50: 7d 05 jge 8048d57 <phase_6+0xce>
8048d52: e8 7a 03 00 00 call 80490d1 <explode_bomb>
8048d57: 8b 5b 08 mov 0x8(%ebx),%ebx #下一个循环
8048d5a: 83 c6 01 add $0x1,%esi
8048d5d: 83 fe 05 cmp $0x5,%esi
8048d60: 75 e7 jne 8048d49 <phase_6+0xc0>
8048d62: 83 c4 5c add $0x5c,%esp
8048d65: 5b pop %ebx
8048d66: 5e pop %esi
8048d67: 5f pop %edi
8048d68: 5d pop %ebp
8048d69: c3 ret
依然先行查看第一个函数,发现这个函数是read_six_numbers,简单的猜想这个关卡需要输入六个数字。代码中传入了两个参数,一个是-0x30(%ebp)开始的一片内存,另外一个是0x8(ebp)开始的一片内存,猜想两个都是数组,一个是输入的整型数组,另外一个是原来存在的数组。下面的代码对输入数据数组进行了一个限制,即数组中每两个数字是不相同的,并且这些数字都是小于或者等于6的,所以输入的数字一定是1-6范围内的,并且不重复。下面有一个较为复杂的循环,他会从头读取你输入的数字,然后在原来内存中的数组中找到这个数字对应小标的数的数据,并把它存在一片新的以-0x48(%ebp)开始的存储空间,然后进行下一个循环,直到将原始内存中的数据全部按照输入的数组的值将原始内存数组以一种全新的顺序存储到另外一片连续的存储空间当中。 下面要进行的操作将新建立的数组按照顺序建立一个链表,即前面一个数据的值加上8就是后面一个数据的值,关于为什么加8,我猜想可能是一个链表的节点可能是用4个字节来存储数据,另外四个数据来存储指针。建立好链表之后,有一个对链表节点的遍历,要求每一个节点都要比他后面的节点的值要大,所以这就要求我们输入的数组要能够将原始内存中的数据排成一个从大到小的数据排列的数组,查看原始数组是1ac 6c 155 187 3bd 255,所以自己输入的数组必须是5 6 1 4 3 2,将原始数组进行一个排列,将数据输入到终端,成功
程序运行过程中bomb了太多次,这里不再列举,总体还是算成功的。但是程序中好像有一个隐藏关卡,由于不太熟悉,就没有在报告中显示出来。
实验结果及分析:
收获与体会:
1、 一开始看到思路指导的时候,就一路按照思路指导上面的操作来做了,但是昨晚前面两个之后就发现后面的不一样了,直接按照思路知道上面的思路没法做的出来,所以不得不又把前面的两题仔仔细细的看了一遍,分析了一下思路才懂得这类题该以怎样的方式去做。
2、 懂了逆向思维的方法,从结果或者后面的部分入手,可以推出前面应该有怎样的条件或者是输入什么样的值才能得到这样的结果,由果溯因的思维有的时候可以解决普通思维方式不能解决的问题。
3、 关卡中常常有非常复杂的程序调用语句,往往看着看着就乱套了,所以也就丧失了信心,此时不妨将原来的推理数据在程序中以注释的方式记录下来,这样一来下一次在看到程序的时候就能够很快的熟悉而并不需要浪费时间了。