文章目录
- 前言
- 实验内容
- phase_1
- phase_2
- phase_3
- phase_4
- phase_5
- phase_6
- secret_phase
前言
刚做了csapp lab2,记录一下。
我这里用的的系统环境是Ubuntu22.04
,是64位系统,与用32位系统可能有所差异。
实验共包括七个阶段,每个阶段考察机器级语言程序的不同方面,难度递增
- 阶段一:字符串比较
- 阶段二:循环
- 阶段三:条件/分支,含switch语句
- 阶段四:递归调用和栈
- 阶段五:指针
- 阶段六:链表/指针/结构
- 隐藏阶段:第四阶段之后附加特定字符串后出现
在实验过程中gdb调试起到了相当重要的作用,下面是我调试过程中频繁使用的几条指令,不熟悉gdb的小伙伴一定要熟悉一下:
-
r ans.txt
- 开始运行,ans.txt为命令行参数,可以省略 -
c
- 运行至下一个断点处 -
b \*addr
- 在addr处的指令打断点 -
d num
- 删除断点num -
display /x %reg
- 每次执行完指令时打印寄存器rgx的值 -
undisplay num
- 删除监视变量num -
ni
- 执行下一条指令 -
x/nws addr
- x是examine,表示进行内存检查;n表示显示n个数据项;w表示以8个字节为单位进行显示;s是以字符串的形式显示解释,可以替换成x,以十六进制的形式进行解释,可以替换成u,以十进制的形式进行解释
为了方便,我们创建一个
ans.txt
文件,每做出来一题就把答案写进去,这样就避免了一条一条输入答案。一题占一行,要注意最后要多一个空行,目的是最后一题的输入以 换行符结尾,保证是正确输入:
实验内容
phase_1
找到 phase_1
函数在 main
函数中被调用的位置:
8048a6d: c7 04 24 70 a0 04 08 movl $0x804a070,(%esp)
8048a74: e8 47 fd ff ff call 80487c0 <puts@plt>
8048a79: e8 69 07 00 00 call 80491e7 <read_line>
8048a7e: 89 04 24 mov %eax,(%esp)
8048a81: e8 aa 00 00 00 call 8048b30 <phase_1>
8048a86: e8 5f 08 00 00 call 80492ea <phase_defused>
在反汇编文件中继续查找 phase_1
的位置:
08048b30 <phase_1>:
8048b30: 55 push %ebp
8048b31: 89 e5 mov %esp,%ebp
8048b33: 83 ec 10 sub $0x10,%esp
8048b36: 68 ec a0 04 08 push $0x804a0ec
8048b3b: ff 75 08 push 0x8(%ebp)
8048b3e: e8 3f 05 00 00 call 8049082 <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 36 06 00 00 call 8049185 <explode_bomb>
8048b4f: c9 leave
8048b50: c3 ret
$0x804a0ec
: 数据区地址,也是 strings_not_equal
的第二个参数
0x8(%ebp)
: phase_1
的参数,也是 strings_not_equal
的第一个参数
在main()
函数的汇编代码中,可以进一步找到:
8048a79: e8 69 07 00 00 call 80491e7 <read_line>
8048a7e: 89 04 24 mov %eax,(%esp)
%eax
里存储的是调用 read_line
函数的返回值,也是用户输入的字符串首地址
推测拆弹密码字符串的存储地址为 $0x804a0ec
,因为调用 strings_not_equal
前有语句:
8048b36: 68 ec a0 04 08 push $0x804a0ec
执行 gdb bomb
找到答案When a problem comes along, you must zip it!
:
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 push 0x8(%ebp)
8048b6a: e8 3e 06 00 00 call 80491ad <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 08 06 00 00 call 8049185 <explode_bomb>
8048b7d: bb 01 00 00 00 mov $0x1,%ebx
8048b82: 89 d8 mov %ebx,%eax
8048b84: 03 44 9d d8 add -0x28(%ebp,%ebx,4),%eax
8048b88: 39 44 9d dc cmp %eax,-0x24(%ebp,%ebx,4)
8048b8c: 74 05 je 8048b93 <phase_2+0x42>
8048b8e: e8 f2 05 00 00 call 8049185 <explode_bomb>
8048b93: 83 c3 01 add $0x1,%ebx
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
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
关注到8048b6a: e8 3e 06 00 00 call 80491ad <read_six_numbers>
,这题应该是要输入六个数字。
观察这部分汇编代码:
8048b7d: bb 01 00 00 00 mov $0x1,%ebx
8048b82: 89 d8 mov %ebx,%eax
8048b84: 03 44 9d d8 add -0x28(%ebp,%ebx,4),%eax
8048b88: 39 44 9d dc cmp %eax,-0x24(%ebp,%ebx,4)
8048b8c: 74 05 je 8048b93 <phase_2+0x42>
8048b8e: e8 f2 05 00 00 call 8049185 <explode_bomb>
8048b93: 83 c3 01 add $0x1,%ebx
8048b96: 83 fb 06 cmp $0x6,%ebx
8048b99: 75 e7 jne 8048b82 <phase_2+0x31>
可以看出这就是一段循环,循环次数为5,每次循环是做一次比较,%ebx
是循环计数器,每轮循环结束后 +1。循环内会进行一次比较,如果两个数不相等就会爆炸。那么关键就是待比较的两个数。
ebx | 比较数1 | 比较数2 |
1 | 1 + ebp-0x28+1*4 | ebp-0x24+1*4 |
2 | 2 + ebp-0x28+2*4 | ebp-0x24+2*4 |
3 | 3 + ebp-0x28+3*4 | ebp-0x24+3*4 |
4 | 4 + ebp-0x28+4*4 | ebp-0x24+4*4 |
5 | 5 + ebp-0x28+5*4 | ebp-0x24+5*4 |
经过观察和不断地调试发现比较数1是第一个输入的数+ebx,比较数2是第二个输入的数。
经过大量试错调试,最终得出这一题的答案:输入六个数,每两个数之间的差依次为1/2/3/4/5
。
所以这样得到本题的一组答案:1 2 4 7 11 16
,当然答案不唯一,比如符合条件的2 3 5 8 12 17
也是可以通 过的。
部分调试记录截图:
这题后面的比较循环一开始看到以6结尾,想当然地认为循环比较六次,这样就多出来一个输入的数,百思不得其解,在网上查阅相关教程发现题目也不一样。最终就逐步调试,发现只是循环了5次,这样就对上了。在调试过程中也找到了本题的答案。
phase_3
先把phase_3
的代码贴出来:
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 42 a1 04 08 push $0x804a142
8048bd3: ff 75 08 push 0x8(%ebp)
8048bd6: e8 35 fc ff ff call 8048810 <__isoc99_sscanf@plt>
8048bdb: 83 c4 20 add $0x20,%esp
8048bde: 83 f8 02 cmp $0x2,%eax
8048be1: 7f 05 jg 8048be8 <phase_3+0x37>
8048be3: e8 9d 05 00 00 call 8049185 <explode_bomb>
8048be8: 83 7d ec 07 cmpl $0x7,-0x14(%ebp)
8048bec: 0f 87 ef 00 00 00 ja 8048ce1 <phase_3+0x130>
8048bf2: 8b 45 ec mov -0x14(%ebp),%eax
8048bf5: ff 24 85 54 a1 04 08 jmp *0x804a154(,%eax,4)
8048bfc: b8 78 00 00 00 mov $0x78,%eax
8048c01: 81 7d f0 44 01 00 00 cmpl $0x144,-0x10(%ebp)
8048c08: 0f 84 dd 00 00 00 je 8048ceb <phase_3+0x13a>
8048c0e: e8 72 05 00 00 call 8049185 <explode_bomb>
8048c13: b8 78 00 00 00 mov $0x78,%eax
8048c18: e9 ce 00 00 00 jmp 8048ceb <phase_3+0x13a>
8048c1d: b8 69 00 00 00 mov $0x69,%eax
8048c22: 81 7d f0 99 01 00 00 cmpl $0x199,-0x10(%ebp)
8048c29: 0f 84 bc 00 00 00 je 8048ceb <phase_3+0x13a>
8048c2f: e8 51 05 00 00 call 8049185 <explode_bomb>
8048c34: b8 69 00 00 00 mov $0x69,%eax
8048c39: e9 ad 00 00 00 jmp 8048ceb <phase_3+0x13a>
8048c3e: b8 74 00 00 00 mov $0x74,%eax
8048c43: 81 7d f0 f6 02 00 00 cmpl $0x2f6,-0x10(%ebp)
8048c4a: 0f 84 9b 00 00 00 je 8048ceb <phase_3+0x13a>
8048c50: e8 30 05 00 00 call 8049185 <explode_bomb>
8048c55: b8 74 00 00 00 mov $0x74,%eax
8048c5a: e9 8c 00 00 00 jmp 8048ceb <phase_3+0x13a>
8048c5f: b8 68 00 00 00 mov $0x68,%eax
8048c64: 81 7d f0 37 02 00 00 cmpl $0x237,-0x10(%ebp)
8048c6b: 74 7e je 8048ceb <phase_3+0x13a>
8048c6d: e8 13 05 00 00 call 8049185 <explode_bomb>
8048c72: b8 68 00 00 00 mov $0x68,%eax
8048c77: eb 72 jmp 8048ceb <phase_3+0x13a>
8048c79: b8 79 00 00 00 mov $0x79,%eax
8048c7e: 81 7d f0 1c 03 00 00 cmpl $0x31c,-0x10(%ebp)
8048c85: 74 64 je 8048ceb <phase_3+0x13a>
8048c87: e8 f9 04 00 00 call 8049185 <explode_bomb>
8048c8c: b8 79 00 00 00 mov $0x79,%eax
8048c91: eb 58 jmp 8048ceb <phase_3+0x13a>
8048c93: b8 62 00 00 00 mov $0x62,%eax
8048c98: 81 7d f0 bd 01 00 00 cmpl $0x1bd,-0x10(%ebp)
8048c9f: 74 4a je 8048ceb <phase_3+0x13a>
8048ca1: e8 df 04 00 00 call 8049185 <explode_bomb>
8048ca6: b8 62 00 00 00 mov $0x62,%eax
8048cab: eb 3e jmp 8048ceb <phase_3+0x13a>
8048cad: b8 64 00 00 00 mov $0x64,%eax
8048cb2: 81 7d f0 5a 03 00 00 cmpl $0x35a,-0x10(%ebp)
8048cb9: 74 30 je 8048ceb <phase_3+0x13a>
8048cbb: e8 c5 04 00 00 call 8049185 <explode_bomb>
8048cc0: b8 64 00 00 00 mov $0x64,%eax
8048cc5: eb 24 jmp 8048ceb <phase_3+0x13a>
8048cc7: b8 65 00 00 00 mov $0x65,%eax
8048ccc: 81 7d f0 50 02 00 00 cmpl $0x250,-0x10(%ebp)
8048cd3: 74 16 je 8048ceb <phase_3+0x13a>
8048cd5: e8 ab 04 00 00 call 8049185 <explode_bomb>
8048cda: b8 65 00 00 00 mov $0x65,%eax
8048cdf: eb 0a jmp 8048ceb <phase_3+0x13a>
8048ce1: e8 9f 04 00 00 call 8049185 <explode_bomb>
8048ce6: b8 6b 00 00 00 mov $0x6b,%eax
8048ceb: 3a 45 eb cmp -0x15(%ebp),%al
8048cee: 74 05 je 8048cf5 <phase_3+0x144>
8048cf0: e8 90 04 00 00 call 8049185 <explode_bomb>
8048cf5: 8b 45 f4 mov -0xc(%ebp),%eax
8048cf8: 65 33 05 14 00 00 00 xor %gs:0x14,%eax
8048cff: 74 05 je 8048d06 <phase_3+0x155>
8048d01: e8 8a fa ff ff call 8048790 <__stack_chk_fail@plt>
8048d06: c9 leave
8048d07: c3 ret
首先观察到phase_3
的代码中有一段:
8048bce: 68 42 a1 04 08 push $0x804a142
8048bd3: ff 75 08 push 0x8(%ebp)
8048bd6: e8 35 fc ff ff call 8048810 <__isoc99_sscanf@plt>
其中$0x804a142
是输入格式字符串,这里要作为参数压栈,传递给sscanf
,查看一下:
(gdb) x/s 0x804a142
0x804a142: "%d %c %d"
这题的输入就是两个整数中间用字符隔开。
紧接着的一段代码也印证了这一点:
8048bdb: 83 c4 20 add $0x20,%esp
8048bde: 83 f8 02 cmp $0x2,%eax
8048be1: 7f 05 jg 8048be8 <phase_3+0x37>
8048be3: e8 9d 05 00 00 call 8049185 <explode_bomb>
eax
也就是sscanf
的返回值,也就是输入的变量的个数,如果小于等于2就bomb。
紧接着有一个判断:
8048be8: 83 7d ec 07 cmpl $0x7,-0x14(%ebp)
8048bec: 0f 87 ef 00 00 00 ja 8048ce1 <phase_3+0x130>
如果-0x14(%ebp) > 0x7
,就会跳转,这里会跳转到call <explode_bomb>
,所以要求-0x14(%ebp) <= 7
,那它是什么呢?观察调用sscanf
之前的一段代码:
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
根据参数传递的顺序应该是我们输入的第一个数,这也就要求我们输入的第一个数小于等于7。
继续往下看,发现程序会跳转,并且跳转的位置和我们输入的数相关联:
8048bf2: 8b 45 ec mov -0x14(%ebp),%eax
8048bf5: ff 24 85 54 a1 04 08 jmp *0x804a154(,%eax,4)
用第一个输入的数是1进行下一步调试:
跳转到了:
8048c1d: b8 69 00 00 00 mov $0x69,%eax
8048c22: 81 7d f0 99 01 00 00 cmpl $0x199,-0x10(%ebp)
8048c29: 0f 84 bc 00 00 00 je 8048ceb <phase_3+0x13a>
8048c2f: e8 51 05 00 00 call 8049185 <explode_bomb>
继续看,这里把eax
更新成了0x69
,并且比较了我们的第三个输入,也就是第二个整数和0x199
也就是409
,所以确定了我们要输入的第二个数是409
,接下来程序跳转到了8048ceb
:
8048ceb: 3a 45 eb cmp -0x15(%ebp),%al
8048cee: 74 05 je 8048cf5 <phase_3+0x144>
8048cf0: e8 90 04 00 00 call 8049185 <explode_bomb>
8048cf5: 8b 45 f4 mov -0xc(%ebp),%eax
8048cf8: 65 33 05 14 00 00 00 xor %gs:0x14,%eax
8048cff: 74 05 je 8048d06 <phase_3+0x155>
8048d01: e8 8a fa ff ff call 8048790 <__stack_chk_fail@plt>
8048d06: c9 leave
8048d07: c3 ret
这里比较的是我们输入的字符和%al
,%al
是累加器寄存器eax
的低八位,如下图所示,转成十进制就是105
,所以我们要输入的字符的ascii码值是105
,也就是i
。这样就得到了最终的答案1i409
!
不过这道题应该有8个答案,因为输入的第一个整数有8个取值,所以会有八个分支,对应八个答案。
phase_4
先贴代码:
08048d08 <func4>:
8048d08: 55 push %ebp
8048d09: 89 e5 mov %esp,%ebp
8048d0b: 56 push %esi
8048d0c: 53 push %ebx
8048d0d: 8b 55 08 mov 0x8(%ebp),%edx
8048d10: 8b 4d 0c mov 0xc(%ebp),%ecx
8048d13: 8b 75 10 mov 0x10(%ebp),%esi
8048d16: 89 f0 mov %esi,%eax
8048d18: 29 c8 sub %ecx,%eax
8048d1a: 89 c3 mov %eax,%ebx
8048d1c: c1 eb 1f shr $0x1f,%ebx
8048d1f: 01 d8 add %ebx,%eax
8048d21: d1 f8 sar %eax
8048d23: 8d 1c 08 lea (%eax,%ecx,1),%ebx
8048d26: 39 d3 cmp %edx,%ebx
8048d28: 7e 15 jle 8048d3f <func4+0x37>
8048d2a: 83 ec 04 sub $0x4,%esp
8048d2d: 8d 43 ff lea -0x1(%ebx),%eax
8048d30: 50 push %eax
8048d31: 51 push %ecx
8048d32: 52 push %edx
8048d33: e8 d0 ff ff ff call 8048d08 <func4>
8048d38: 83 c4 10 add $0x10,%esp
8048d3b: 01 d8 add %ebx,%eax
8048d3d: eb 19 jmp 8048d58 <func4+0x50>
8048d3f: 89 d8 mov %ebx,%eax
8048d41: 39 d3 cmp %edx,%ebx
8048d43: 7d 13 jge 8048d58 <func4+0x50>
8048d45: 83 ec 04 sub $0x4,%esp
8048d48: 56 push %esi
8048d49: 8d 43 01 lea 0x1(%ebx),%eax
8048d4c: 50 push %eax
8048d4d: 52 push %edx
8048d4e: e8 b5 ff ff ff call 8048d08 <func4>
8048d53: 83 c4 10 add $0x10,%esp
8048d56: 01 d8 add %ebx,%eax
8048d58: 8d 65 f8 lea -0x8(%ebp),%esp
8048d5b: 5b pop %ebx
8048d5c: 5e pop %esi
8048d5d: 5d pop %ebp
8048d5e: c3 ret
08048d5f <phase_4>:
8048d5f: 55 push %ebp
8048d60: 89 e5 mov %esp,%ebp
8048d62: 83 ec 18 sub $0x18,%esp
8048d65: 65 a1 14 00 00 00 mov %gs:0x14,%eax
8048d6b: 89 45 f4 mov %eax,-0xc(%ebp)
8048d6e: 31 c0 xor %eax,%eax
8048d70: 8d 45 f0 lea -0x10(%ebp),%eax
8048d73: 50 push %eax
8048d74: 8d 45 ec lea -0x14(%ebp),%eax
8048d77: 50 push %eax
8048d78: 68 93 a2 04 08 push $0x804a293
8048d7d: ff 75 08 push 0x8(%ebp)
8048d80: e8 8b fa ff ff call 8048810 <__isoc99_sscanf@plt>
8048d85: 83 c4 10 add $0x10,%esp
8048d88: 83 f8 02 cmp $0x2,%eax
8048d8b: 75 06 jne 8048d93 <phase_4+0x34>
8048d8d: 83 7d ec 0e cmpl $0xe,-0x14(%ebp)
8048d91: 76 05 jbe 8048d98 <phase_4+0x39>
8048d93: e8 ed 03 00 00 call 8049185 <explode_bomb>
8048d98: 83 ec 04 sub $0x4,%esp
8048d9b: 6a 0e push $0xe
8048d9d: 6a 00 push $0x0
8048d9f: ff 75 ec push -0x14(%ebp)
8048da2: e8 61 ff ff ff call 8048d08 <func4>
8048da7: 83 c4 10 add $0x10,%esp
8048daa: 83 f8 13 cmp $0x13,%eax
8048dad: 75 06 jne 8048db5 <phase_4+0x56>
8048daf: 83 7d f0 13 cmpl $0x13,-0x10(%ebp)
8048db3: 74 05 je 8048dba <phase_4+0x5b>
8048db5: e8 cb 03 00 00 call 8049185 <explode_bomb>
8048dba: 8b 45 f4 mov -0xc(%ebp),%eax
8048dbd: 65 33 05 14 00 00 00 xor %gs:0x14,%eax
8048dc4: 74 05 je 8048dcb <phase_4+0x6c>
8048dc6: e8 c5 f9 ff ff call 8048790 <__stack_chk_fail@plt>
8048dcb: c9 leave
8048dcc: c3 ret
还是先观察调用sscanf
之前的代码确定输入格式:
8048d70: 8d 45 f0 lea -0x10(%ebp),%eax
8048d73: 50 push %eax
8048d74: 8d 45 ec lea -0x14(%ebp),%eax
8048d77: 50 push %eax
8048d78: 68 93 a2 04 08 push $0x804a293
8048d7d: ff 75 08 push 0x8(%ebp)
8048d80: e8 8b fa ff ff call 8048810 <__isoc99_sscanf@plt>
8048d85: 83 c4 10 add $0x10,%esp
8048d88: 83 f8 02 cmp $0x2,%eax
8048d8b: 75 06 jne 8048d93 <phase_4+0x34>
输入格式是两个整数,中间用空格隔开。
其中第一个数存在-0x14(%ebp)
,第二个数存在-0x10(%ebp)
。
继续向下看:
8048d8d: 83 7d ec 0e cmpl $0xe,-0x14(%ebp)
8048d91: 76 05 jbe 8048d98 <phase_4+0x39>
8048d93: e8 ed 03 00 00 call 8049185 <explode_bomb>
这里比较了输入的第一个数和0xe
,也就是14
,说明第一个数要小于等于14
,这里还是用输入1来进一步调试。继续看代码:
8048d98: 83 ec 04 sub $0x4,%esp
8048d9b: 6a 0e push $0xe
8048d9d: 6a 00 push $0x0
8048d9f: ff 75 ec push -0x14(%ebp)
8048da2: e8 61 ff ff ff call 8048d08 <func4>
调用了func4
,先分析一下参数:第一个参数是输入的第一个整数,第二个参数是 0
,第三个参数是0xe
也就是14
。继续分析func4
的代码,先看参数的处理:
8048d0d: 8b 55 08 mov 0x8(%ebp),%edx
8048d10: 8b 4d 0c mov 0xc(%ebp),%ecx
8048d13: 8b 75 10 mov 0x10(%ebp),%esi
这里把第一个参数,也就是输入的第一个整数1
赋给了edx
,把0
赋给了ecx
,14
赋给了esi
,继续看代码:
8048d16: 89 f0 mov %esi,%eax
8048d18: 29 c8 sub %ecx,%eax
8048d1a: 89 c3 mov %eax,%ebx
8048d1c: c1 eb 1f shr $0x1f,%ebx
8048d1f: 01 d8 add %ebx,%eax
8048d21: d1 f8 sar %eax
8048d23: 8d 1c 08 lea (%eax,%ecx,1),%ebx
8048d26: 39 d3 cmp %edx,%ebx
8048d28: 7e 15 jle 8048d3f <func4+0x37>
一直执行到比较之前,各个寄存器存放的值如下:
接下来过不了比较,会继续向下执行:
8048d2a: 83 ec 04 sub $0x4,%esp
8048d2d: 8d 43 ff lea -0x1(%ebx),%eax
8048d30: 50 push %eax
8048d31: 51 push %ecx
8048d32: 52 push %edx
8048d33: e8 d0 ff ff ff call 8048d08 <func4>
这样就开始了递归,先看一下此时递归前各个寄存器存放的值:
从左到右三个参数依次为1、0、1,与上一次调用相比第三个参数发生了变化,继续调试到比较:
此时仍无法结束递归,下面会进行第三次调用,调用前各个寄存器的值如下:
第三个参数变成了2,继续调试到比较:
此时终于符合条件ebx <= edx
,跳转到新阶段:
8048d3f: 89 d8 mov %ebx,%eax
8048d41: 39 d3 cmp %edx,%ebx
8048d43: 7d 13 jge 8048d58 <func4+0x50>
接下来有个判断,要求ebx >= edx
,先看一下此时各个寄存器的值:
符合条件,跳转到新位置:
8048d58: 8d 65 f8 lea -0x8(%ebp),%esp
8048d5b: 5b pop %ebx
8048d5c: 5e pop %esi
8048d5d: 5d pop %ebp
8048d5e: c3 ret
此时跳出第三次调用,来到第二次调用的栈帧:
8048d33: e8 d0 ff ff ff call 8048d08 <func4>
8048d38: 83 c4 10 add $0x10,%esp
8048d3b: 01 d8 add %ebx,%eax
8048d3d: eb 19 jmp 8048d58 <func4+0x50>
跳转之前各个寄存器的值如下:
第二次调用随之也结束,来到第一次调用的栈帧,还是与第二次一样,返回phase_4
之前eax
变为11
。
之后回到phase_4
的栈帧:
8048da2: e8 61 ff ff ff call 8048d08 <func4>
8048da7: 83 c4 10 add $0x10,%esp
8048daa: 83 f8 13 cmp $0x13,%eax
8048dad: 75 06 jne 8048db5 <phase_4+0x56>
8048daf: 83 7d f0 13 cmpl $0x13,-0x10(%ebp)
8048db3: 74 05 je 8048dba <phase_4+0x5b>
8048db5: e8 cb 03 00 00 call 8049185 <explode_bomb>
此时各个寄存器的值如下:
此时eax(11) != 0x13(19)
,所以会爆炸,所以拆弹的关键来到了eax
。在经过函数递归之后,eax
的值要变为19
,那就观察一下eax
的值是如何变化的。不过这里还能看出第二个输入的整数应该是0x13(19)
在分析eax的过程中被绕晕了,索性摆烂,反正最大不超过14
,一个个试,试出了正确答案4
。
所以这一题的最终答案就是:4 19
phase_5
贴代码:
08048dcd <phase_5>:
8048dcd: 55 push %ebp
8048dce: 89 e5 mov %esp,%ebp
8048dd0: 53 push %ebx
8048dd1: 83 ec 20 sub $0x20,%esp
8048dd4: 8b 5d 08 mov 0x8(%ebp),%ebx
8048dd7: 65 a1 14 00 00 00 mov %gs:0x14,%eax
8048ddd: 89 45 f4 mov %eax,-0xc(%ebp)
8048de0: 31 c0 xor %eax,%eax
8048de2: 53 push %ebx
8048de3: e8 78 02 00 00 call 8049060 <string_length>
8048de8: 83 c4 10 add $0x10,%esp
8048deb: 83 f8 06 cmp $0x6,%eax
8048dee: 74 05 je 8048df5 <phase_5+0x28>
8048df0: e8 90 03 00 00 call 8049185 <explode_bomb>
8048df5: b8 00 00 00 00 mov $0x0,%eax
8048dfa: 0f b6 14 03 movzbl (%ebx,%eax,1),%edx
8048dfe: 83 e2 0f and $0xf,%edx
8048e01: 0f b6 92 74 a1 04 08 movzbl 0x804a174(%edx),%edx
8048e08: 88 54 05 ed mov %dl,-0x13(%ebp,%eax,1)
8048e0c: 83 c0 01 add $0x1,%eax
8048e0f: 83 f8 06 cmp $0x6,%eax
8048e12: 75 e6 jne 8048dfa <phase_5+0x2d>
8048e14: c6 45 f3 00 movb $0x0,-0xd(%ebp)
8048e18: 83 ec 08 sub $0x8,%esp
8048e1b: 68 4b a1 04 08 push $0x804a14b
8048e20: 8d 45 ed lea -0x13(%ebp),%eax
8048e23: 50 push %eax
8048e24: e8 59 02 00 00 call 8049082 <strings_not_equal>
8048e29: 83 c4 10 add $0x10,%esp
8048e2c: 85 c0 test %eax,%eax
8048e2e: 74 05 je 8048e35 <phase_5+0x68>
8048e30: e8 50 03 00 00 call 8049185 <explode_bomb>
8048e35: 8b 45 f4 mov -0xc(%ebp),%eax
8048e38: 65 33 05 14 00 00 00 xor %gs:0x14,%eax
8048e3f: 74 05 je 8048e46 <phase_5+0x79>
8048e41: e8 4a f9 ff ff call 8048790 <__stack_chk_fail@plt>
8048e46: 8b 5d fc mov -0x4(%ebp),%ebx
8048e49: c9 leave
8048e4a: c3 ret
先看部分代码:
8048de3: e8 78 02 00 00 call 8049060 <string_length>
8048de8: 83 c4 10 add $0x10,%esp
8048deb: 83 f8 06 cmp $0x6,%eax
8048dee: 74 05 je 8048df5 <phase_5+0x28>
8048df0: e8 90 03 00 00 call 8049185 <explode_bomb>
一开始调用了一次string_length
,并且比较了一下字符串的长度和6,如果不相等就会爆炸,需要知道string_length
是计算的谁的长度:
经过测试发现是我们输入的字符串的长度,这样就确定了我们要输入的字符串的长度是6
。
接下来是一段循环:
8048df5: b8 00 00 00 00 mov $0x0,%eax
8048dfa: 0f b6 14 03 movzbl (%ebx,%eax,1),%edx
8048dfe: 83 e2 0f and $0xf,%edx
8048e01: 0f b6 92 74 a1 04 08 movzbl 0x804a174(%edx),%edx
8048e08: 88 54 05 ed mov %dl,-0x13(%ebp,%eax,1)
8048e0c: 83 c0 01 add $0x1,%eax
8048e0f: 83 f8 06 cmp $0x6,%eax
8048e12: 75 e6 jne 8048dfa <phase_5+0x2d>
eax
从0
开始,每次+1
,直到等于6
。分析一下这个循环做了什么。
首先给edx
赋值,且这个值随着循环次数改变而改变,然后保留edx
低4位,把某个地址加上第四位作为偏移量,得到的地址处的值赋给edx
,然后把这个值赋给-0x13(%ebp,%eax,1)
,只占一个字节空间,猜测每一次取了一个字符,并且后面调用了strings_not_equal
:
8048e1b: 68 4b a1 04 08 push $0x804a14b
8048e20: 8d 45 ed lea -0x13(%ebp),%eax
8048e23: 50 push %eax
8048e24: e8 59 02 00 00 call 8049082 <strings_not_equal>
六次循环得到一个字符串,并把这个字符串的首地址作为参数传给strings_not_equal
,所以这六个字符串是什么呢,答案在0x804a14b
:
所以要通过循环构造出这么一个字符串。循环是从0x804a174
加偏移得到的,先看一下0x804a174
指向的内容:
很显然要从maduiersnfotvbylSo
通过加偏移量的方式依次构造出f l a m e s
,偏移量依次是9 15 1 0 5 7
,偏移量从哪来呢:
8048dd4: 8b 5d 08 mov 0x8(%ebp),%ebx
很显然是传过来的参数,这个参数就是我们输入的字符串,所以是取我们输入的字符串对应的ascii
码值的低四位作为偏移量,所以答案的要求就出来了:低四位分别是1001(9) 1111(15) 0001(1) 0000(0) 0101(5) 0111(7)
:
令高位为0100(64)
,得到一组解I(73) O(79) A(65) @(64) E(69) G(71)
,所以正确答案就是IOA@EG
。
phase_6
贴代码:
08048e4b <phase_6>:
8048e4b: 55 push %ebp
8048e4c: 89 e5 mov %esp,%ebp
8048e4e: 56 push %esi
8048e4f: 53 push %ebx
8048e50: 83 ec 48 sub $0x48,%esp
8048e53: 65 a1 14 00 00 00 mov %gs:0x14,%eax
8048e59: 89 45 f4 mov %eax,-0xc(%ebp)
8048e5c: 31 c0 xor %eax,%eax
8048e5e: 8d 45 c4 lea -0x3c(%ebp),%eax
8048e61: 50 push %eax
8048e62: ff 75 08 push 0x8(%ebp)
8048e65: e8 43 03 00 00 call 80491ad <read_six_numbers>
8048e6a: 83 c4 10 add $0x10,%esp
8048e6d: be 00 00 00 00 mov $0x0,%esi
8048e72: 8b 44 b5 c4 mov -0x3c(%ebp,%esi,4),%eax
8048e76: 83 e8 01 sub $0x1,%eax
8048e79: 83 f8 05 cmp $0x5,%eax
8048e7c: 76 05 jbe 8048e83 <phase_6+0x38>
8048e7e: e8 02 03 00 00 call 8049185 <explode_bomb>
8048e83: 83 c6 01 add $0x1,%esi
8048e86: 83 fe 06 cmp $0x6,%esi
8048e89: 74 33 je 8048ebe <phase_6+0x73>
8048e8b: 89 f3 mov %esi,%ebx
8048e8d: 8b 44 9d c4 mov -0x3c(%ebp,%ebx,4),%eax
8048e91: 39 44 b5 c0 cmp %eax,-0x40(%ebp,%esi,4)
8048e95: 75 05 jne 8048e9c <phase_6+0x51>
8048e97: e8 e9 02 00 00 call 8049185 <explode_bomb>
8048e9c: 83 c3 01 add $0x1,%ebx
8048e9f: 83 fb 05 cmp $0x5,%ebx
8048ea2: 7e e9 jle 8048e8d <phase_6+0x42>
8048ea4: eb cc jmp 8048e72 <phase_6+0x27>
8048ea6: 8b 52 08 mov 0x8(%edx),%edx
8048ea9: 83 c0 01 add $0x1,%eax
8048eac: 39 c8 cmp %ecx,%eax
8048eae: 75 f6 jne 8048ea6 <phase_6+0x5b>
8048eb0: 89 54 b5 dc mov %edx,-0x24(%ebp,%esi,4)
8048eb4: 83 c3 01 add $0x1,%ebx
8048eb7: 83 fb 06 cmp $0x6,%ebx
8048eba: 75 07 jne 8048ec3 <phase_6+0x78>
8048ebc: eb 1c jmp 8048eda <phase_6+0x8f>
8048ebe: bb 00 00 00 00 mov $0x0,%ebx
8048ec3: 89 de mov %ebx,%esi
8048ec5: 8b 4 c 9d c4 mov -0x3c(%ebp,%ebx,4),%ecx
8048ec9: b8 01 00 00 00 mov $0x1,%eax
8048ece: ba 3c c1 04 08 mov $0x804c13c,%edx
8048ed3: 83 f9 01 cmp $0x1,%ecx
8048ed6: 7f ce jg 8048ea6 <phase_6+0x5b>
8048ed8: eb d6 jmp 8048eb0 <phase_6+0x65>
8048eda: 8b 5d dc mov -0x24(%ebp),%ebx
8048edd: 8d 45 dc lea -0x24(%ebp),%eax
8048ee0: 8d 75 f0 lea -0x10(%ebp),%esi
8048ee3: 89 d9 mov %ebx,%ecx
8048ee5: 8b 50 04 mov 0x4(%eax),%edx
8048ee8: 89 51 08 mov %edx,0x8(%ecx)
8048eeb: 83 c0 04 add $0x4,%eax
8048eee: 89 d1 mov %edx,%ecx
8048ef0: 39 f0 cmp %esi,%eax
8048ef2: 75 f1 jne 8048ee5 <phase_6+0x9a>
8048ef4: c7 42 08 00 00 00 00 movl $0x0,0x8(%edx)
8048efb: be 05 00 00 00 mov $0x5,%esi
8048f00: 8b 43 08 mov 0x8(%ebx),%eax
8048f03: 8b 00 mov (%eax),%eax
8048f05: 39 03 cmp %eax,(%ebx)
8048f07: 7d 05 jge 8048f0e <phase_6+0xc3>
8048f09: e8 77 02 00 00 call 8049185 <explode_bomb>
8048f0e: 8b 5b 08 mov 0x8(%ebx),%ebx
8048f11: 83 ee 01 sub $0x1,%esi
8048f14: 75 ea jne 8048f00 <phase_6+0xb5>
8048f16: 8b 45 f4 mov -0xc(%ebp),%eax
8048f19: 65 33 05 14 00 00 00 xor %gs:0x14,%eax
8048f20: 74 05 je 8048f27 <phase_6+0xdc>
8048f22: e8 69 f8 ff ff call 8048790 <__stack_chk_fail@plt>
8048f27: 8d 65 f8 lea -0x8(%ebp),%esp
8048f2a: 5b pop %ebx
8048f2b: 5e pop %esi
8048f2c: 5d pop %ebp
8048f2d: c3 ret
先看:
8048e5e: 8d 45 c4 lea -0x3c(%ebp),%eax
8048e61: 50 push %eax
8048e62: ff 75 08 push 0x8(%ebp)
8048e65: e8 43 03 00 00 call 80491ad <read_six_numbers>
通过开始的一段代码可知是输入六个数字,并且首个元素存放地址为-0x3c(%ebp)
,假设输入的六个数字分别是a[0], a[1], a[2], a[3], a[4], a[5]
。
之后有一个判断:
8048e6a: 83 c4 10 add $0x10,%esp
8048e6d: be 00 00 00 00 mov $0x0,%esi
8048e72: 8b 44 b5 c4 mov -0x3c(%ebp,%esi,4),%eax
8048e76: 83 e8 01 sub $0x1,%eax
8048e79: 83 f8 05 cmp $0x5,%eax
8048e7c: 76 05 jbe 8048e83 <phase_6+0x38>
8048e7e: e8 02 03 00 00 call 8049185 <explode_bomb>
跳转条件是(a[0] - 1 <= 5
),这就限制了a[0] <= 6
,后面以输入1进行尝试。
8048e83: 83 c6 01 add $0x1,%esi
8048e86: 83 fe 06 cmp $0x6,%esi
8048e89: 74 33 je 8048ebe <phase_6+0x73>
8048e8b: 89 f3 mov %esi,%ebx
8048e8d: 8b 44 9d c4 mov -0x3c(%ebp,%ebx,4),%eax
8048e91: 39 44 b5 c0 cmp %eax,-0x40(%ebp,%esi,4)
8048e95: 75 05 jne 8048e9c <phase_6+0x51>
8048e97: e8 e9 02 00 00 call 8049185 <explode_bomb>
接下来esi + 1
与6
进行比较,可以猜想出这是一个循环,esi
是计数器,循环六次。接下来又比较了a[0]和a[1]
,要求两个数不能相等。
8048e9c: 83 c3 01 add $0x1,%ebx
8048e9f: 83 fb 05 cmp $0x5,%ebx
8048ea2: 7e e9 jle 8048e8d <phase_6+0x42>
8048ea4: eb cc jmp 8048e72 <phase_6+0x27>
然后ebx
成了新计数器,循环5
次,这里是把a[0]
依次与a[1] ~ a[5]
各比较一次,保证不相等。可以发现其实这是两层循环,先判断循环到的a[i]
是否小于等于6
,然后判断它与后面几个数是否相等。
整个大循环结束后来到新部分:
8048ea6: 8b 52 08 mov 0x8(%edx),%edx
8048ea9: 83 c0 01 add $0x1,%eax
8048eac: 39 c8 cmp %ecx,%eax
8048eae: 75 f6 jne 8048ea6 <phase_6+0x5b>
8048eb0: 89 54 b5 dc mov %edx,-0x24(%ebp,%esi,4)
8048eb4: 83 c3 01 add $0x1,%ebx
8048eb7: 83 fb 06 cmp $0x6,%ebx
8048eba: 75 07 jne 8048ec3 <phase_6+0x78>
8048ebc: eb 1c jmp 8048eda <phase_6+0x8f>
8048ebe: bb 00 00 00 00 mov $0x0,%ebx
8048ec3: 89 de mov %ebx,%esi
8048ec5: 8b 4 c 9d c4 mov -0x3c(%ebp,%ebx,4),%ecx
8048ec9: b8 01 00 00 00 mov $0x1,%eax
8048ece: ba 3c c1 04 08 mov $0x804c13c,%edx
8048ed3: 83 f9 01 cmp $0x1,%ecx
8048ed6: 7f ce jg 8048ea6 <phase_6+0x5b>
8048ed8: eb d6 jmp 8048eb0 <phase_6+0x65>
程序从8048ebe
开始执行,这里的计数器是ebx
,从0开始循环到6,共循环7次。
第一次cmp
跳转之前各个寄存器的值如图:
比较的是ecx
和1
,ecx
是a[ebx]
,第一次是a[0],我输入的是1 2 3 4 5 6
,显然jg
不会跳转,程序来到了8048eb0
。接下来把edx
给到了-0x24(%ebp,%esi,4)
地址处,推测ebp-0x24
地址处存放的是一个指针804c13c
。
可以看一下这个地址处存放的内容:
推测是链表,进一步查看:
链表一共有六个节点,每个节点存放三个数据,显然第三个数据是下一个节点的地址。第二个数据从1~6
,推测是链表的节点编号,那第一个数据应该就是链表存放的有效数据。
mov 0x8(%edx), %edx
操作其实是把edx
更新成下个节点的指针,当eax
与ecx
相等时把节点指针压栈,
否则会进入一个循环:eax++; edx继续指向下个指针,直到eax == ecx
,这也就意味着六个节点压栈的顺序与我们输入的六个数字相关,如果输入的2 4 1 3 6 5
,则六个节点在栈中存放的顺序依次为node2 node4 node1 node3 node6 node5
,存放地址依次为%ebp-0x24、%ebp-0x20、%ebp-0x1c、%ebp-0x18、%ebp-0x14、%ebp-0x10
。其实就是node[a[0]] ~ node[a[6]]
。
继续看代码:
8048eda: 8b 5d dc mov -0x24(%ebp),%ebx
8048edd: 8d 45 dc lea -0x24(%ebp),%eax
8048ee0: 8d 75 f0 lea -0x10(%ebp),%esi
8048ee3: 89 d9 mov %ebx,%ecx
8048ee5: 8b 50 04 mov 0x4(%eax),%edx
8048ee8: 89 51 08 mov %edx,0x8(%ecx)
8048eeb: 83 c0 04 add $0x4,%eax
8048eee: 89 d1 mov %edx,%ecx
8048ef0: 39 f0 cmp %esi,%eax
8048ef2: 75 f1 jne 8048ee5 <phase_6+0x9a>
这段代码也是循环,会改变链表指向,通过多次调试可以发现:如果输入的是1 2 3 4 5 6
,则链表指向不变;如果输入的是6 5 4 3 2 1
,则链表指向变为6>5>4>3>2>1>2
,最后有个环;如果输入的是1 3 5 2 4 6
,则链表指向变为1>3>5>2>4>6
。
部分调试记录如下:
以上发现了链表指向顺序会跟输入的六个数有关。
来到最后一段代码:
8048ef4: c7 42 08 00 00 00 00 movl $0x0,0x8(%edx)
8048efb: be 05 00 00 00 mov $0x5,%esi
8048f00: 8b 43 08 mov 0x8(%ebx),%eax
8048f03: 8b 00 mov (%eax),%eax
8048f05: 39 03 cmp %eax,(%ebx)
8048f07: 7d 05 jge 8048f0e <phase_6+0xc3>
8048f09: e8 77 02 00 00 call 8049185 <explode_bomb>
8048f0e: 8b 5b 08 mov 0x8(%ebx),%ebx
8048f11: 83 ee 01 sub $0x1,%esi
8048f14: 75 ea jne 8048f00 <phase_6+0xb5>
这段代码其实就是遍历链表,遍历顺序就是刚刚重新排列好的顺序,然后依次取相邻两个链表的值进行比较,这一点可以调试到 8048f05 cmp %eax, (%ebx)
之前,看一下eax
的值和ebx
指向的值,如果前者大于后者就能通过,然后继续遍历,否则bomb!
所以链表的正确排列顺序就有依据了,先记录一下各个链表存放的数据都是多少:
1-995 2-959 3-779 4-921 5-853 6-363
要求前者大于后者,所以正确顺序也就是正确答案应该是:1 2 4 5 3 6
。
secret_phase
还有一个函数名叫secret_phase
的,代码如下:
08048f80 <secret_phase>:
8048f80: 55 push %ebp
8048f81: 89 e5 mov %esp,%ebp
8048f83: 53 push %ebx
8048f84: 83 ec 04 sub $0x4,%esp
8048f87: e8 5b 02 00 00 call 80491e7 <read_line>
8048f8c: 83 ec 04 sub $0x4,%esp
8048f8f: 6a 0a push $0xa
8048f91: 6a 00 push $0x0
8048f93: 50 push %eax
8048f94: e8 e7 f8 ff ff call 8048880 <strtol@plt>
8048f99: 89 c3 mov %eax,%ebx
8048f9b: 8d 40 ff lea -0x1(%eax),%eax
8048f9e: 83 c4 10 add $0x10,%esp
8048fa1: 3d e8 03 00 00 cmp $0x3e8,%eax
8048fa6: 76 05 jbe 8048fad <secret_phase+0x2d>
8048fa8: e8 d8 01 00 00 call 8049185 <explode_bomb>
8048fad: 83 ec 08 sub $0x8,%esp
8048fb0: 53 push %ebx
8048fb1: 68 88 c0 04 08 push $0x804c088
8048fb6: e8 73 ff ff ff call 8048f2e <fun7>
8048fbb: 83 c4 10 add $0x10,%esp
8048fbe: 83 f8 03 cmp $0x3,%eax
8048fc1: 74 05 je 8048fc8 <secret_phase+0x48>
8048fc3: e8 bd 01 00 00 call 8049185 <explode_bomb>
8048fc8: 83 ec 0c sub $0xc,%esp
8048fcb: 68 1c a1 04 08 push $0x804a11c
8048fd0: e8 eb f7 ff ff call 80487c0 <puts@plt>
8048fd5: e8 10 03 00 00 call 80492ea <phase_defused>
8048fda: 83 c4 10 add $0x10,%esp
8048fdd: 8b 5d fc mov -0x4(%ebp),%ebx
8048fe0: c9 leave
8048fe1: c3 ret
在phase_defused
中有调用,所以贴一下phase_defused
的代码:
080492ea <phase_defused>:
80492ea: 55 push %ebp
80492eb: 89 e5 mov %esp,%ebp
80492ed: 83 ec 68 sub $0x68,%esp
80492f0: 65 a1 14 00 00 00 mov %gs:0x14,%eax
80492f6: 89 45 f4 mov %eax,-0xc(%ebp)
80492f9: 31 c0 xor %eax,%eax
80492fb: 83 3d cc c3 04 08 06 cmpl $0x6,0x804c3cc
8049302: 75 6f jne 8049373 <phase_defused+0x89>
8049304: 83 ec 0c sub $0xc,%esp
8049307: 8d 45 a4 lea -0x5c(%ebp),%eax
804930a: 50 push %eax
804930b: 8d 45 a0 lea -0x60(%ebp),%eax
804930e: 50 push %eax
804930f: 8d 45 9c lea -0x64(%ebp),%eax
8049312: 50 push %eax
8049313: 68 ed a2 04 08 push $0x804a2ed
8049318: 68 d0 c4 04 08 push $0x804c4d0
804931d: e8 ee f4 ff ff call 8048810 <__isoc99_sscanf@plt>
8049322: 83 c4 20 add $0x20,%esp
8049325: 83 f8 03 cmp $0x3,%eax
8049328: 75 39 jne 8049363 <phase_defused+0x79>
804932a: 83 ec 08 sub $0x8,%esp
804932d: 68 f6 a2 04 08 push $0x804a2f6
8049332: 8d 45 a4 lea -0x5c(%ebp),%eax
8049335: 50 push %eax
8049336: e8 47 fd ff ff call 8049082 <strings_not_equal>
804933b: 83 c4 10 add $0x10,%esp
804933e: 85 c0 test %eax,%eax
8049340: 75 21 jne 8049363 <phase_defused+0x79>
8049342: 83 ec 0c sub $0xc,%esp
8049345: 68 bc a1 04 08 push $0x804a1bc
804934a: e8 71 f4 ff ff call 80487c0 <puts@plt>
804934f: c7 04 24 e4 a1 04 08 movl $0x804a1e4,(%esp)
8049356: e8 65 f4 ff ff call 80487c0 <puts@plt>
804935b: e8 20 fc ff ff call 8048f80 <secret_phase>
8049360: 83 c4 10 add $0x10,%esp
8049363: 83 ec 0c sub $0xc,%esp
8049366: 68 1c a2 04 08 push $0x804a21c
804936b: e8 50 f4 ff ff call 80487c0 <puts@plt>
8049370: 83 c4 10 add $0x10,%esp
8049373: 8b 45 f4 mov -0xc(%ebp),%eax
8049376: 65 33 05 14 00 00 00 xor %gs:0x14,%eax
804937d: 74 05 je 8049384 <phase_defused+0x9a>
804937f: e8 0c f4 ff ff call 8048790 <__stack_chk_fail@plt>
8049384: c9 leave
8049385: c3 ret
在phase_defused
的代码中发现了这句:
804935b: e8 20 fc ff ff call 8048f80 <secret_phase>
这也就意味着每次拆除炸弹都有可能触发secret_phase
,继续对phase_defused
的代码进行分析,寻找触发条件,着重关注最这行:
8049312: 50 push %eax
8049313: 68 ed a2 04 08 push $0x804a2ed
8049318: 68 d0 c4 04 08 push $0x804c4d0
804931d: e8 ee f4 ff ff call 8048810 <__isoc99_sscanf@plt>
8049322: 83 c4 20 add $0x20,%esp
8049325: 83 f8 03 cmp $0x3,%eax
8049328: 75 39 jne 8049363 <phase_defused+0x79>
调用sscanf
之前先看一下输入格式:
两个整数一个字符串,这里会判断输入了几个,如果输入的是两个数据并不会bomb,而是正常结束,输入两个的正好是phase_4
,猜测phase_4
多输一个字符串会触发secret_phase
,下面继续分析这个字符串应该是什么:
804932a: 83 ec 08 sub $0x8,%esp
804932d: 68 f6 a2 04 08 push $0x804a2f6
8049332: 8d 45 a4 lea -0x5c(%ebp),%eax
8049335: 50 push %eax
8049336: e8 47 fd ff ff call 8049082 <strings_not_equal>
804933b: 83 c4 10 add $0x10,%esp
804933e: 85 c0 test %eax,%eax
8049340: 75 21 jne 8049363 <phase_defused+0x79>
看到一个关键地址0x804a2f6
,查看内容:
这就找到了我们要多输入的字符串:DrEvil
。
输入进去之后程序最后输出了小彩蛋,印证了它就是目标字符串;
回过头来对secret_phase
进行分析:
8048f8f: 6a 0a push $0xa
8048f91: 6a 00 push $0x0
8048f93: 50 push %eax
8048f94: e8 e7 f8 ff ff call 8048880 <strtol@plt>
8048f99: 89 c3 mov %eax,%ebx
8048f9b: 8d 40 ff lea -0x1(%eax),%eax
8048f9e: 83 c4 10 add $0x10,%esp
8048fa1: 3d e8 03 00 00 cmp $0x3e8,%eax
8048fa6: 76 05 jbe 8048fad <secret_phase+0x2d>
8048fa8: e8 d8 01 00 00 call 8049185 <explode_bomb>
开始会调用一个strtol
函数,将字符串转成long
,这里要转的字符串的首地址是eax
,也就是上一次调用的函数的返回值,上一个函数是read_line
,所以也就是把我们的输入转成long
,-1
后与0x3e8(1000)
进行比较,要求小于等于1000
,也就意味着我们要输入一个数字,并且这个数字要小于等于1001
。
继续向下看;
8048fb0: 53 push %ebx
8048fb1: 68 88 c0 04 08 push $0x804c088
8048fb6: e8 73 ff ff ff call 8048f2e <fun7>
8048fbb: 83 c4 10 add $0x10,%esp
8048fbe: 83 f8 03 cmp $0x3,%eax
8048fc1: 74 05 je 8048fc8 <secret_phase+0x48>
8048fc3: e8 bd 01 00 00 call 8049185 <explode_bomb>
8048fbb: 83 c4 10 add $0x10,%esp
8048fbe: 83 f8 03 cmp $0x3,%eax
8048fc1: 74 05 je 8048fc8 <secret_phase+0x48>
8048fc3: e8 bd 01 00 00 call 8049185 <explode_bomb>
看最后三句可以看出当调用完fun7
后,eax==3
时才不会爆炸,secret_phase
也就会结束,所以关键看eax
在fun7
中的变化。
调用fun7
之前传递的参数为ebx
和0x804c088
,ebx
根据之前的代码可知是我们输入的整数,看一下0x804c088
存的是什么(64是显示64个单位,u是以十进制格式输出,换成x就是以十六进制格式输出,w是以8个字节为单位,我一开始是用默认的4个字节为单位,结果什么也看不出来,耽误了好久):
数据有nxx
,后面还有nodex
,其实就是phase_6
中的链表节点结构。不难看出nxx
也有着异曲同工之妙。再看一眼,能看出nxx
是以三个单位为一组,第一个存放的是一个较小的数,猜测为有效数据,后两个显然是指针,并且前七个节点中的两个指针都有指向,猜测为二叉树结构,一共15个节点,8个叶子结点,根据各个节点之间的连接关系和对应地址处的标识,可以推断出这棵树的形状如下:
整理出来发现这是一颗平衡搜索二叉树,左节点都比父节点小,右节点都比父节点大。
那么fun7
的参数就有了,第一个参数是这颗二叉树根节点的地址,第二个参数是我们输入的数,接下来可以继续分析fun7
,先打出完整代码,然后顺着翻译一下:
08048f2e <fun7>:
8048f2e: 55 push %ebp
8048f2f: 89 e5 mov %esp,%ebp
8048f31: 53 push %ebx
8048f32: 83 ec 04 sub $0x4,%esp
8048f35: 8b 55 08 mov 0x8(%ebp),%edx
8048f38: 8b 4d 0c mov 0xc(%ebp),%ecx
8048f3b: 85 d2 test %edx,%edx
8048f3d: 74 37 je 8048f76 <fun7+0x48>
8048f3f: 8b 1a mov (%edx),%ebx
8048f41: 39 cb cmp %ecx,%ebx
8048f43: 7e 13 jle 8048f58 <fun7+0x2a>
8048f45: 83 ec 08 sub $0x8,%esp
8048f48: 51 push %ecx
8048f49: ff 72 04 push 0x4(%edx)
8048f4c: e8 dd ff ff ff call 8048f2e <fun7>
8048f51: 83 c4 10 add $0x10,%esp
8048f54: 01 c0 add %eax,%eax
8048f56: eb 23 jmp 8048f7b <fun7+0x4d>
8048f58: b8 00 00 00 00 mov $0x0,%eax
8048f5d: 39 cb cmp %ecx,%ebx
8048f5f: 74 1a je 8048f7b <fun7+0x4d>
8048f61: 83 ec 08 sub $0x8,%esp
8048f64: 51 push %ecx
8048f65: ff 72 08 push 0x8(%edx)
8048f68: e8 c1 ff ff ff call 8048f2e <fun7>
8048f6d: 83 c4 10 add $0x10,%esp
8048f70: 8d 44 00 01 lea 0x1(%eax,%eax,1),%eax
8048f74: eb 05 jmp 8048f7b <fun7+0x4d>
8048f76: b8 ff ff ff ff mov $0xffffffff,%eax
8048f7b: 8b 5d fc mov -0x4(%ebp),%ebx
8048f7e: c9 leave
8048f7f: c3 ret
顺着思路翻译一下还是很简单的:
fun7 (node* root, int num) {
if (root == null) {
eax = 0xffffffff;
return
}
if (root->val <= num) {
eax = 0; # ①
if (root->val == num)
return;
fun7(root->right, num);
eax = eax * 2 + 1; # ②
return;
}
fun7(root->left, num);
eax *= 2; # ③
return;
}
本来是想一步步分析汇编的,但递归实在太绕了:
还是顺着把汇编翻译一下比较简单。
最终目标是让eax
变为3
。这里有关eax的有效操作有①eax = 0
;②eax = eax * 2 + 1
;③eax *= 2
。
所以一个可行的顺序是①②②,,对应的在递归第一层要执行②,第二层要执行②,第三层执行①。
来到第一层,此时root->val = 36
,第一层要走到②之前那一句进入二层调用,这就要求num > 36
。
来到第二层,此时root->val = 50
,仍需要走到②之前那一句进入三层调用,这就要求num > 50
。
来到第三层,此时root->val = 107
,这次需要走到①处然后返回,要求num == 107
。
这样就推理出了最终答案:107
!
测试一下完全通过: