实验题目
bomblab
实验目的
- 使用gdb工具反汇编出汇编代码,结合c语言文件找到每个关卡的入口函数。然后分析汇编代码,分析得到每一关的通关密码。
- 进一步加深对linux指令的理解,对gdb调试的一些基本操作以及高级操作有所了解。
- 熟悉汇编程序,懂得如何利用汇编程序写出C语言程序伪代码,熟悉并掌握函数调用过程中的栈帧结构的变化,熟悉汇编程序及其调试方法。
实验环境
个人PC、Linux 32位操作系统、Ubuntu16.04
实验内容
准备阶段
- 将实验压缩包解压并找到本人所用到的实验文件夹bomb7,复制到linux系统中,打开文件夹得到bomb、bomb.c、README文件;
- 阅读README等实验相关材料,通过各种方式了解实验的相关内容和过程;
- 检查bomb实验中的两个文件,发现bomb.c文件残缺,无法运行;而bomb文件可以正常运行,实验主要部分与bomb.c文件关系不大;
- 了解实验的要求,该实验共有7个关卡,包括6个普通关卡和1个隐藏关卡。
- 将bomb文件反汇编并生成 .txt 文件(
objdump bomb -d > my_bomb.txt
),这样可以在my_bomb.txt中分析汇编代码,而bomb文件则可结合gdb进行调试分析;
分析阶段
<phase_1>
- 反汇编代码
08048b90 <phase_1>:
8048b90: 83 ec 1c sub $0x1c,%esp //esp-28 -> esp,申请栈空间
8048b93: c7 44 24 04 44 a1 04 movl $0x804a144,0x4(%esp) //0x0804a144 -> M(esp+4),将内存空间放入esp寄存器
8048b9a: 08
8048b9b: 8b 44 24 20 mov 0x20(%esp),%eax //M(esp+32) -> eax
8048b9f: 89 04 24 mov %eax,(%esp) //eax -> M(esp)
8048ba2: e8 63 04 00 00 call 804900a <strings_not_equal> //调用函数判断string是否相等
8048ba7: 85 c0 test %eax,%eax //test if eax is empty,if empty,ZF = 1.
8048ba9: 74 05 je 8048bb0 <phase_1+0x20> //jump if ZF = 1
8048bab: e8 65 05 00 00 call 8049115 <explode_bomb> //call <explode_bomb>
8048bb0: 83 c4 1c add $0x1c,%esp //esp+12 -> esp
8048bb3: c3 ret //return
- 解题思路
- 先把重要的反汇编代码意义标注出来(注释如上)
- 采用逆向思维法,先找出炸弹什么时候会爆炸。
8048bab: e8 65 05 00 00 call 8049115 <explode_bomb> //call <explode_bomb>
执行这一步就会爆炸。
- 找到跳过爆炸这一步的条件。
8048ba7: 85 c0 test %eax,%eax //test if eax is empty,if empty,ZF = 1.
8048ba9: 74 05 je 8048bb0 <phase_1+0x20> //jump if ZF = 1
只要eax为0,则执行je指令,跳过爆炸。
- 顺藤摸瓜,找出eax为0的条件。
8048b9b: 8b 44 24 20 mov 0x20(%esp),%eax //M(esp+32) -> eax
8048b9f: 89 04 24 mov %eax,(%esp) //eax -> M(esp)
8048ba2: e8 63 04 00 00 call 804900a <strings_not_equal> //调用函数判断string是否相等
追溯到 mov 0x20(%esp),%eax指令,把我们输入的参数放进%eax中,然后放进(%esp) ,再调用函数<strings_not_equal>,如果输入的内容与传入的字符串相等,则返回0,这个就是eax为0的条件。
- 找出传入<strings_not_equal>的参数。
8048b93: c7 44 24 04 44 a1 04 movl $0x804a144,0x4(%esp) //0x0804a144 -> M(esp+4),将内存空间放入esp寄存器
所求的字符串即是0x804a144里面的值。
- 进入gdb调试模式,找到0x804a144地址格子里面的东西
gates@ubuntu:~/bomb7$ gdb -q bomb
Reading symbols from bomb...
(gdb) x/s 0x804a144
0x804a144: "Brownie, you are doing a heck of a job."
- 输入找出来的字符串,验证答案。
gates@ubuntu:~/bomb7$ ./bomb
Welcome to my fiendish little bomb. You have 6 phases with
which to blow yourself up. Have a nice day!
Brownie, you are doing a heck of a job.
Phase 1 defused. How about the next one?
- 炸弹一拆除!
<phase_2>
- 反汇编代码
08048bb4 <phase_2>:
8048bb4: 53 push %ebx //save old edx
8048bb5: 83 ec 38 sub $0x38,%esp //open new stack
8048bb8: 8d 44 24 18 lea 0x18(%esp),%eax //esp+24->eax
8048bbc: 89 44 24 04 mov %eax,0x4(%esp) //eax -> M(esp+4)
8048bc0: 8b 44 24 40 mov 0x40(%esp),%eax //M(esp+64) -> eax
8048bc4: 89 04 24 mov %eax,(%esp) //eax -> M(esp)
8048bc7: e8 70 05 00 00 call 804913c <read_six_numbers>
8048bcc: 83 7c 24 18 00 cmpl $0x0,0x18(%esp) //cmp
8048bd1: 79 22 jns 8048bf5 <phase_2+0x41> //M(esp+24-0)>=0
8048bd3: e8 3d 05 00 00 call 8049115 <explode_bomb> //Bomb!
8048bd8: eb 1b jmp 8048bf5 <phase_2+0x41> //
8048bda: 89 d8 mov %ebx,%eax //ebx -> eax
8048bdc: 03 44 9c 14 add 0x14(%esp,%ebx,4),%eax //eax+M(20+esp+4*ebx) -> eax
8048be0: 39 44 9c 18 cmp %eax,0x18(%esp,%ebx,4) //cmp
8048be4: 74 05 je 8048beb <phase_2+0x37> //jump if eax==M(24+esp+4*ebx)
8048be6: e8 2a 05 00 00 call 8049115 <explode_bomb> //Bomb!
8048beb: 83 c3 01 add $0x1,%ebx //ebx+1 -> ebx
8048bee: 83 fb 06 cmp $0x6,%ebx //cmp
8048bf1: 75 e7 jne 8048bda <phase_2+0x26> //jump if ebx!=6
8048bf3: eb 07 jmp 8048bfc <phase_2+0x48> //jump
8048bf5: bb 01 00 00 00 mov $0x1,%ebx //1 -> ebx
8048bfa: eb de jmp 8048bda <phase_2+0x26> //jump
8048bfc: 83 c4 38 add $0x38,%esp //esp+56 -> esp
8048bff: 5b pop %ebx //pop
8048c00: c3 ret
- 解题思路
- 先把重要的反汇编代码意义标注出来(注释如上)
- 找到炸弹爆炸的汇编代码。
8048bd3: e8 3d 05 00 00 call 8049115 <explode_bomb> //Bomb!
8048be6: e8 2a 05 00 00 call 8049115 <explode_bomb> //Bomb!
- 找到跳过第一个炸弹的方法。
804913c <read_six_numbers>
8048bcc: 83 7c 24 18 00 cmpl $0x0,0x18(%esp) //cmp
8048bd1: 79 22 jns 8048bf5 <phase_2+0x41> //M(esp+24-0)>=0
<read_six_numbers>是读取输入的六个数字,cmpl、jns语句结合,判断M(esp+24)与0x0的大小,满足M(esp+24)>=0x0则可跳过第一个炸弹,由此可知,输入的第一个数应该要大于等于0。
- 顺着跳转指令寻找重要寄存器的值。
8048bf5: bb 01 00 00 00 mov $0x1,%ebx //1 -> ebx
8048bfa: eb de jmp 8048bda <phase_2+0x26> //jump
8048bda: 89 d8 mov %ebx,%eax //ebx -> eax
ebx=1,eax=ebx=1。
- 找到跳过第二个炸弹的方法。
8048bdc: 03 44 9c 14 add 0x14(%esp,%ebx,4),%eax //eax+M(20+esp+4*ebx) -> eax
8048be0: 39 44 9c 18 cmp %eax,0x18(%esp,%ebx,4) //cmp
8048be4: 74 05 je 8048beb <phase_2+0x37> //jump if eax==M(24+esp+4*ebx)
分析这一段代码可以知道,这是把第一个数与eax(此处是1)相加,得到的结果与第二个数相比较,如果等于第二个数方可跳过炸弹。所以第二个数就是第一个数加1。
- 顺藤摸瓜,找到跳出循环的条件。
8048beb: 83 c3 01 add $0x1,%ebx //ebx+1 -> ebx
8048bee: 83 fb 06 cmp $0x6,%ebx //cmp
8048bf1: 75 e7 jne 8048bda <phase_2+0x26> //jump if ebx!=6
8048bf3: eb 07 jmp 8048bfc <phase_2+0x48> //jump
由此段代码可以知道,ebx每次加1,然后跳到上面的一步,直到等于6跳出循环。
- 总体描述输入内容。
输入第一个数只要满足大于等于0即可,第二个数等于第一个数加1,第三个数等于第二个数加2,第四个数等于第三个数加3……,依此类推,最后输进去6个数即可。
- 输进去满足上述关系的6个数,发现答案确实如此。
Phase 1 defused. How about the next one?
1 2 4 7 11 16
That's number 2. Keep going!
- 炸弹二拆除!
<phase_3>
- 反汇编代码
08048c01 <phase_3>:
8048c01: 83 ec 2c sub $0x2c,%esp //
8048c04: 8d 44 24 1c lea 0x1c(%esp),%eax //
8048c08: 89 44 24 0c mov %eax,0xc(%esp) //为输入内容做准备
8048c0c: 8d 44 24 18 lea 0x18(%esp),%eax //
8048c10: 89 44 24 08 mov %eax,0x8(%esp) //为输入内容做准备
8048c14: c7 44 24 04 0f a3 04 movl $0x804a30f,0x4(%esp) //%d%d
8048c1b: 08
8048c1c: 8b 44 24 30 mov 0x30(%esp),%eax //
8048c20: 89 04 24 mov %eax,(%esp) //传递参数
8048c23: e8 38 fc ff ff call 8048860 <__isoc99_sscanf@plt> //
8048c28: 83 f8 01 cmp $0x1,%eax //eax与1比较
8048c2b: 7f 05 jg 8048c32 <phase_3+0x31> //eax>1则跳过爆炸
8048c2d: e8 e3 04 00 00 call 8049115 <explode_bomb> //Bomb!
8048c32: 83 7c 24 18 07 cmpl $0x7,0x18(%esp) //
8048c37: 77 66 ja 8048c9f <phase_3+0x9e> //M(0x18+exp)>0x7则跳转爆炸
8048c39: 8b 44 24 18 mov 0x18(%esp),%eax //M(esp+0x18) -> eax
8048c3d: ff 24 85 a0 a1 04 08 jmp *0x804a1a0(,%eax,4) //jump to *(0x804a1a0+4*eax)
8048c44: b8 00 00 00 00 mov $0x0,%eax //0 -> eax
8048c49: eb 05 jmp 8048c50 <phase_3+0x4f> //jump
8048c4b: b8 4c 02 00 00 mov $0x24c,%eax //0x24c -> eax
8048c50: 2d 31 03 00 00 sub $0x331,%eax //eax-0x331 -> eax
8048c55: eb 05 jmp 8048c5c <phase_3+0x5b> //jump
8048c57: b8 00 00 00 00 mov $0x0,%eax //0x0 -> eax
8048c5c: 05 05 03 00 00 add $0x305,%eax //eax+0x305 -> eax
8048c61: eb 05 jmp 8048c68 <phase_3+0x67> //jump
8048c63: b8 00 00 00 00 mov $0x0,%eax //0x0 -> eax
8048c68: 2d 9e 00 00 00 sub $0x9e,%eax //eax-0x9e -> eax
8048c6d: eb 05 jmp 8048c74 <phase_3+0x73> //jump
8048c6f: b8 00 00 00 00 mov $0x0,%eax //0x0 -> eax
8048c74: 05 9e 00 00 00 add $0x9e,%eax //eax+0x9e -> eax
8048c79: eb 05 jmp 8048c80 <phase_3+0x7f> //jump
8048c7b: b8 00 00 00 00 mov $0x0,%eax //0x0 -> eax
8048c80: 2d 9e 00 00 00 sub $0x9e,%eax //eax-0x9e -> eax
8048c85: eb 05 jmp 8048c8c <phase_3+0x8b> //jump
8048c87: b8 00 00 00 00 mov $0x0,%eax //0x0 --> eax
8048c8c: 05 9e 00 00 00 add $0x9e,%eax //eax+0x9e -> eax
8048c91: eb 05 jmp 8048c98 <phase_3+0x97> //jump
8048c93: b8 00 00 00 00 mov $0x0,%eax //0x0 -> eax
8048c98: 2d 9e 00 00 00 sub $0x9e,%eax //eax-0x9e -> eax
8048c9d: eb 0a jmp 8048ca9 <phase_3+0xa8> //jump
8048c9f: e8 71 04 00 00 call 8049115 <explode_bomb> //Bomb!
8048ca4: b8 00 00 00 00 mov $0x0,%eax //0x0 -> eax
8048ca9: 83 7c 24 18 05 cmpl $0x5,0x18(%esp) //cmp 0x5,M(0x18+esp)
8048cae: 7f 06 jg 8048cb6 <phase_3+0xb5> //if greater, jump to Bomb
8048cb0: 3b 44 24 1c cmp 0x1c(%esp),%eax //cmp eax,M(0x1c+esp)
8048cb4: 74 05 je 8048cbb <phase_3+0xba> //if equal, jump, not Bomb
8048cb6: e8 5a 04 00 00 call 8049115 <explode_bomb> //Bomb!
8048cbb: 83 c4 2c add $0x2c,%esp //
8048cbe: c3 ret
- 解题思路
- 先把重要的反汇编代码意义标注出来(注释如上)
- 分析第一个爆炸点
8048c23: e8 38 fc ff ff call 8048860 <__isoc99_sscanf@plt> //
8048c28: 83 f8 01 cmp $0x1,%eax //eax与1比较
8048c2b: 7f 05 jg 8048c32 <phase_3+0x31> //eax>1则跳过爆炸
8048c2d: e8 e3 04 00 00 call 8049115 <explode_bomb> //Bomb!
跳过炸弹的条件是eax>1
- 追溯eax
8048c04: 8d 44 24 1c lea 0x1c(%esp),%eax //
8048c08: 89 44 24 0c mov %eax,0xc(%esp) //为输入内容做准备
8048c0c: 8d 44 24 18 lea 0x18(%esp),%eax //
8048c10: 89 44 24 08 mov %eax,0x8(%esp) //为输入内容做准备
8048c14: c7 44 24 04 0f a3 04 movl $0x804a30f,0x4(%esp) //%d%d
8048c1b: 08
8048c1c: 8b 44 24 30 mov 0x30(%esp),%eax //
8048c20: 89 04 24 mov %eax,(%esp) //传递参数
观察汇编代码格式,lea、mov结合,猜测是传参,再查看代码中出现的$0x804a30f里面的内容。
(gdb) x/s 0x804a30f
0x804a30f: "%d %d"
果然如此,传进去两个十进制参数,同时再观察下面代码有没有其它传参函数。发现下面没有其它函数调用了,所以已经可以确定拆除这个炸弹的方法就是传进去两个符合条件的十进制数。
- 分析要传进去怎么样的两个数。
8048c32: 83 7c 24 18 07 cmpl $0x7,0x18(%esp) //
8048c37: 77 66 ja 8048c9f <phase_3+0x9e> //M(0x18+exp)>0x7则跳转爆炸
0x18(%esp),这个符号在第二个炸弹也出现过,所以可以猜测这应该也是传进去的第一个参数,若要不爆炸(即不跳转到爆炸函数)那么这个参数需要满足的条件是0<=x<=7。
8048c3d: ff 24 85 a0 a1 04 08 jmp *0x804a1a0(,%eax,4) //jump to *(0x804a1a0+4*eax)
继续执行 jmp *0x804a1a0(,%eax,4),这是典型的switch跳转语句,即跳转到以地址*0x804a1a0为基址的跳转表中。
(gdb) p/x *0x804a1a0
$1 = 0x8048c4b
先假设输入的第一个数是0,查看对应地址内容,找到内容中对应指令。
8048c4b: b8 4c 02 00 00 mov $0x24c,%eax
8048c50: 2d 31 03 00 00 sub $0x331,%eax
8048c55: eb 05 jmp 8048c5c <phase_3+0x5b>
8048c5c: 05 05 03 00 00 add $0x305,%eax
8048c61: eb 05 jmp 8048c68 <phase_3+0x67>
8048c68: 2d 9e 00 00 00 sub $0x9e,%eax
8048c6d: eb 05 jmp 8048c74 <phase_3+0x73>
8048c74: 05 9e 00 00 00 add $0x9e,%eax
8048c79: eb 05 jmp 8048c80 <phase_3+0x7f>
8048c80: 2d 9e 00 00 00 sub $0x9e,%eax
8048c85: eb 05 jmp 8048c8c <phase_3+0x8b>
8048c8c: 05 9e 00 00 00 add $0x9e,%eax
8048c91: eb 05 jmp 8048c98 <phase_3+0x97>
8048c98: 2d 9e 00 00 00 sub $0x9e,%eax
8048c9d: eb 0a jmp 8048ca9 <phase_3+0xa8>
8048ca9: 83 7c 24 18 05 cmpl $0x5,0x18(%esp)
8048cae: 7f 06 jg 8048cb6 <phase_3+0xb5>
计算:24C-331+305-9E+9E-9E+9E-9E=0x182=386
- 输入验证
0 386
Halfway there!
本题答案不唯一,只要第一个数满足条件,然后根据跳转条件去计算第二个数即可。
- 炸弹三拆除!
<phase_4>
- 反汇编代码
08048d20 <phase_4>:
8048d20: 83 ec 2c sub $0x2c,%esp
8048d23: 8d 44 24 1c lea 0x1c(%esp),%eax //esp+0x1c -> eax
8048d27: 89 44 24 0c mov %eax,0xc(%esp) //eax -> M(0xc+esp)
8048d2b: 8d 44 24 18 lea 0x18(%esp),%eax //esp+0x18 -> eax
8048d2f: 89 44 24 08 mov %eax,0x8(%esp) //eax -> M(0x8+esp)
8048d33: c7 44 24 04 0f a3 04 movl $0x804a30f,0x4(%esp) //0x804a30f -> M(0x4+esp)
8048d3a: 08
8048d3b: 8b 44 24 30 mov 0x30(%esp),%eax //M(0x30+esp) -> eax
8048d3f: 89 04 24 mov %eax,(%esp) //eax -> M(esp)
8048d42: e8 19 fb ff ff call 8048860 <__isoc99_sscanf@plt>
8048d47: 83 f8 02 cmp $0x2,%eax //cmp
8048d4a: 75 07 jne 8048d53 <phase_4+0x33> //jump if eax!=0x2
8048d4c: 83 7c 24 18 0e cmpl $0xe,0x18(%esp) //cmp
8048d51: 76 05 jbe 8048d58 <phase_4+0x38> //jump if M(0x18+esp)<=0xe
8048d53: e8 bd 03 00 00 call 8049115 <explode_bomb> //Bomb!
8048d58: c7 44 24 08 0e 00 00 movl $0xe,0x8(%esp) //0xe -> M(esp+0x8)
8048d5f: 00
8048d60: c7 44 24 04 00 00 00 movl $0x0,0x4(%esp) //0x0 -> M(esp+0x4)
8048d67: 00
8048d68: 8b 44 24 18 mov 0x18(%esp),%eax //M(0x18+esp) -> eax
8048d6c: 89 04 24 mov %eax,(%esp) //eax -> M(esp)
8048d6f: e8 4b ff ff ff call 8048cbf <func4> //func4(x,0,14)
8048d74: 83 f8 06 cmp $0x6,%eax //cmp
8048d77: 75 07 jne 8048d80 <phase_4+0x60> //if eax!=6, jump to Bomb
8048d79: 83 7c 24 1c 06 cmpl $0x6,0x1c(%esp) //cmp
8048d7e: 74 05 je 8048d85 <phase_4+0x65> //jump if M(0x1c+esp)==6
8048d80: e8 90 03 00 00 call 8049115 <explode_bomb> //Bomb!
8048d85: 83 c4 2c add $0x2c,%esp
8048d88: c3 ret
- 解题思路
- 先把重要的反汇编代码意义标注出来(注释如上)
- 分析第一个爆炸点
8048d42: e8 19 fb ff ff call 8048860 <__isoc99_sscanf@plt>
8048d47: 83 f8 02 cmp $0x2,%eax
8048d4a: 75 07 jne 8048d53 <phase_4+0x33>
8048d4c: 83 7c 24 18 0e cmpl $0xe,0x18(%esp)
8048d51: 76 05 jbe 8048d58 <phase_4+0x38>
8048d53: e8 bd 03 00 00 call 8049115 <explode_bomb>
此处不难看出,这里的形式与第三个炸弹的汇编代码是类似的,所以再结合这段汇编代码前面的两组lea、mov指令可以基本断定,本题的要求也是输入两个数字,并且第一个数字不能大于0xe,也就是14。
- 进一步确认输入内容。
8048d23: 8d 44 24 1c lea 0x1c(%esp),%eax //esp+0x1c -> eax
8048d27: 89 44 24 0c mov %eax,0xc(%esp) //eax -> M(0xc+esp)
8048d2b: 8d 44 24 18 lea 0x18(%esp),%eax //esp+0x18 -> eax
8048d2f: 89 44 24 08 mov %eax,0x8(%esp) //eax -> M(0x8+esp)
8048d33: c7 44 24 04 0f a3 04 movl $0x804a30f,0x4(%esp) //0x804a30f -> M(0x4+esp)
(gdb) x/s 0x804a30f
0x804a30f: "%d %d"
的确是输进去两个十进制数字。
- 顺着跳转指令,找到最近的一个函数调用,整段分析。
8048d58: c7 44 24 08 0e 00 00 movl $0xe,0x8(%esp) //0xe -> M(esp+0x8)
8048d5f: 00
8048d60: c7 44 24 04 00 00 00 movl $0x0,0x4(%esp) //0x0 -> M(esp+0x4)
8048d67: 00
8048d68: 8b 44 24 18 mov 0x18(%esp),%eax //M(0x18+esp) -> eax
8048d6c: 89 04 24 mov %eax,(%esp) //eax -> M(esp)
8048d6f: e8 4b ff ff ff call 8048cbf <func4> //func4(x,0,14)
有函数调用,就要看传参,盯紧mov指令,发现传了3个参数,从左往右依次是x,0,14,故调用的函数是func4(x,0,14)。
- 分析func4函数汇编代码。
08048cbf <func4>:
8048cbf: 56 push %esi
8048cc0: 53 push %ebx
8048cc1: 83 ec 14 sub $0x14,%esp //开辟栈帧
8048cc4: 8b 54 24 20 mov 0x20(%esp),%edx //设esp+0x20存储x=>edx=x
8048cc8: 8b 44 24 24 mov 0x24(%esp),%eax //设esp+0x24存储y=>eax=y
8048ccc: 8b 5c 24 28 mov 0x28(%esp),%ebx //设esp+0x28存储z=>ebx=z
8048cd0: 89 d9 mov %ebx,%ecx //ecx=z
8048cd2: 29 c1 sub %eax,%ecx //ecx=z-y
8048cd4: 89 ce mov %ecx,%esi //esi=z-y
8048cd6: c1 ee 1f shr $0x1f,%esi //逻辑右移,esi=(z-y)>>31,得到符号位s
8048cd9: 01 f1 add %esi,%ecx //ecx=esi+ecx=s+z-y
8048cdb: d1 f9 sar %ecx //算术右移,ecx=(s+z-y)>>1
8048cdd: 01 c1 add %eax,%ecx //ecx=eax+ecx=y+(s+z-y)>>1
8048cdf: 39 d1 cmp %edx,%ecx //cmp
8048ce1: 7e 17 jle 8048cfa <func4+0x3b> //jump if ecx<=edx
8048ce3: 83 e9 01 sub $0x1,%ecx //ecx=ecx-1=y+(s+z-y)>>1-1
8048ce6: 89 4c 24 08 mov %ecx,0x8(%esp) //传参
8048cea: 89 44 24 04 mov %eax,0x4(%esp) //传参
8048cee: 89 14 24 mov %edx,(%esp) //传参
8048cf1: e8 c9 ff ff ff call 8048cbf <func4> //func4(x,y,y+(s+z-y)>>1-1)
8048cf6: 01 c0 add %eax,%eax //eax=2*eax
8048cf8: eb 20 jmp 8048d1a <func4+0x5b> //jump
8048cfa: b8 00 00 00 00 mov $0x0,%eax //eax=0
8048cff: 39 d1 cmp %edx,%ecx //cmp
8048d01: 7d 17 jge 8048d1a <func4+0x5b> //jump if ecx>=edx
8048d03: 89 5c 24 08 mov %ebx,0x8(%esp) //传参
8048d07: 83 c1 01 add $0x1,%ecx //ecx=ecx+1=y+(s+z-y)>>1+1
8048d0a: 89 4c 24 04 mov %ecx,0x4(%esp) //传参
8048d0e: 89 14 24 mov %edx,(%esp) //传参
8048d11: e8 a9 ff ff ff call 8048cbf <func4> //func4(x,y+(s+z-y)>>1+1,z)
8048d16: 8d 44 00 01 lea 0x1(%eax,%eax,1),%eax //eax=2eax+1
8048d1a: 83 c4 14 add $0x14,%esp
8048d1d: 5b pop %ebx
8048d1e: 5e pop %esi
8048d1f: c3 ret
- 找出函数返回值需要满足的条件
8048d74: 83 f8 06 cmp $0x6,%eax //cmp
8048d77: 75 07 jne 8048d80 <phase_4+0x60> //if eax!=6, jump to Bomb
返回值需要等于6
- 根据func4函数反汇编写出c语言程序,找出递归返回值等于
6的输入第一个数x的值。
#include<stdio.h>
int func4(x,y,z){
int a=(z-y)>>31; //s
int t=y+(a+(z-y))/2;
if(t<=x){ //ecx<=edx
if(t==x) return 0;
else return 2*func4(x,t+1,z)+1;
}
else return 2*func4(x,y,t-1);
}
int main(){
int i=0;
for(i=0;i<=14;i++){
printf("func4(%d,0,14)=%d ",i,func4(i,0,14));
if((i+1)%5==0) printf("\n");
}
return 0;
}
func4(0,0,14)=0 func4(1,0,14)=0 func4(2,0,14)=4 func4(3,0,14)=0 func4(4,0,14)=2
func4(5,0,14)=2 func4(6,0,14)=6 func4(7,0,14)=0 func4(8,0,14)=1 func4(9,0,14)=1
func4(10,0,14)=5 func4(11,0,14)=1 func4(12,0,14)=3 func4(13,0,14)=3 func4(14,0,14)=7
由此可知,输入的第一个数应该是6.
- 继续找第二个数
8048d79: 83 7c 24 1c 06 cmpl $0x6,0x1c(%esp) //cmp
8048d7e: 74 05 je 8048d85 <phase_4+0x65> //jump if M(0x1c+esp)==6
8048d80: e8 90 03 00 00 call 8049115 <explode_bomb> //Bomb!
由此可知,输入的第二个数必须是6.
- 输入验证
6 6
So you got that one. Try this one.
- 炸弹四拆除!
<phase_5>
- 反汇编代码
08048d89 <phase_5>:
8048d89: 53 push %ebx
8048d8a: 83 ec 18 sub $0x18,%esp
8048d8d: 8b 5c 24 20 mov 0x20(%esp),%ebx //M(0x20+esp) -> ebx
8048d91: 89 1c 24 mov %ebx,(%esp) //ebx -> M(esp)
8048d94: e8 52 02 00 00 call 8048feb <string_length> //get string length
8048d99: 83 f8 06 cmp $0x6,%eax //eax=6?
8048d9c: 74 05 je 8048da3 <phase_5+0x1a> //jump if eax==6
8048d9e: e8 72 03 00 00 call 8049115 <explode_bomb> //Bomb!
8048da3: ba 00 00 00 00 mov $0x0,%edx //0 -> edx
8048da8: b8 00 00 00 00 mov $0x0,%eax //0 -> eax
8048dad: 0f b6 0c 03 movzbl (%ebx,%eax,1),%ecx //M(ebx+eax) -> ecx,无符号扩展
8048db1: 83 e1 0f and $0xf,%ecx //取后四位
8048db4: 03 14 8d c0 a1 04 08 add 0x804a1c0(,%ecx,4),%edx //M(0x804a1c0+4*ecx)+edx -> edx
8048dbb: 83 c0 01 add $0x1,%eax //eax+1 -> eax
8048dbe: 83 f8 06 cmp $0x6,%eax //eax==6?
8048dc1: 75 ea jne 8048dad <phase_5+0x24> //jump if eax!=6
8048dc3: 83 fa 2b cmp $0x2b,%edx //edx==0x2b?
8048dc6: 74 05 je 8048dcd <phase_5+0x44> //jump if edx==0x2b?
8048dc8: e8 48 03 00 00 call 8049115 <explode_bomb> //Bomb!
8048dcd: 83 c4 18 add $0x18,%esp
8048dd0: 5b pop %ebx
8048dd1: c3 ret
- 解题思路
- 先把重要的反汇编代码意义标注出来(注释如上)
- 根据汇编代码功能进行分块(其实基本可以说是按照爆炸点来分块)。
- 分析第一块内容。
8048d8d: 8b 5c 24 20 mov 0x20(%esp),%ebx //M(0x20+esp) -> ebx
8048d91: 89 1c 24 mov %ebx,(%esp) //ebx -> M(esp)
8048d94: e8 52 02 00 00 call 8048feb <string_length> //get string length
8048d99: 83 f8 06 cmp $0x6,%eax //eax=6?
8048d9c: 74 05 je 8048da3 <phase_5+0x1a> //jump if eax==6
8048d9e: e8 72 03 00 00 call 8049115 <explode_bomb> //Bomb!
根据调用的函数以及跳转条件可以知道,第一块的含义应该是输入一个六个字符的字符串。
- 分析第二块内容–循环部分
8048da8: b8 00 00 00 00 mov $0x0,%eax //0 -> eax
......
8048dbb: 83 c0 01 add $0x1,%eax //eax+1 -> eax
8048dbe: 83 f8 06 cmp $0x6,%eax //eax==6?
8048dc1: 75 ea jne 8048dad <phase_5+0x24> //jump if eax!=6
循环六次
- 分析第二块内容–取字符部分
8048dad: 0f b6 0c 03 movzbl (%ebx,%eax,1),%ecx //M(ebx+eax) -> ecx,无符号扩展
8048db1: 83 e1 0f and $0xf,%ecx //取低四位
8048db4: 03 14 8d c0 a1 04 08 add 0x804a1c0(,%ecx,4),%edx //M(0x804a1c0+4*ecx)+edx -> edx
......
8048dc3: 83 fa 2b cmp $0x2b,%edx //edx==0x2b?
8048dc6: 74 05 je 8048dcd <phase_5+0x44> //jump if edx==0x2b?
8048dc8: e8 48 03 00 00 call 8049115 <explode_bomb> //Bomb!
将长度为6的字符串的第eax个字符存入ecx中。取ecx中的低4位,利用基址比例变址寻址的方式,到数组中找值,并累和在寄存器edx中,累和得到的值必须为0x2b,方能跳过爆炸。
- gdb调试输入数组首地址,找到数组的内容。
(gdb) x/32bx 0x804a1c0
0x804a1c0 <array.3143>: 0x02 0x00 0x00 0x00 0x0a 0x00 0x00 0x00
0x804a1c8 <array.3143+8>: 0x06 0x00 0x00 0x00 0x01 0x00 0x00 0x00
0x804a1d0 <array.3143+16>: 0x0c 0x00 0x00 0x00 0x10 0x00 0x00 0x00
0x804a1d8 <array.3143+24>: 0x09 0x00 0x00 0x00 0x03 0x00 0x00 0x00
- 从中组合出结果为0x2b的6个数以及对应数组下标。
0x02 0x0a 0x06 0x01 0x0c 0x0c
0x20在array[0],0x0a在array[1],0x06在array[2],0x01在array[3],0x0c在array[4],0x0c在array[4],(此处每个array是四个字节的)。
- 去ascii表中找低四位为0,1,2,3,4,4的字符
二进制 | 十进制 | 十六进制 | 字符 |
---|---|---|---|
01110000 | 112 | 70 | p |
01110001 | 113 | 71 | q |
01110010 | 114 | 72 | r |
01110011 | 115 | 73 | s |
01110100 | 116 | 74 | t |
- 输入验证答案
pqrstt
Good work! On to the next...
- 炸弹五拆除!
<phase_6>
- 反汇编代码
08048dd2 <phase_6>:
8048dd2: 56 push %esi
8048dd3: 53 push %ebx
8048dd4: 83 ec 44 sub $0x44,%esp
8048dd7: 8d 44 24 10 lea 0x10(%esp),%eax //esp+0x10 -> eax
8048ddb: 89 44 24 04 mov %eax,0x4(%esp) //eax -> M(esp+0x4),参数2
8048ddf: 8b 44 24 50 mov 0x50(%esp),%eax //M(esp+0x50) -> eax
8048de3: 89 04 24 mov %eax,(%esp) //eax -> M(esp),参数1
8048de6: e8 51 03 00 00 call 804913c <read_six_numbers> //call
8048deb: be 00 00 00 00 mov $0x0,%esi //0 -> esi
8048df0: 8b 44 b4 10 mov 0x10(%esp,%esi,4),%eax //M(0x10+esp+4*esi) -> eax
8048df4: 83 e8 01 sub $0x1,%eax //eax=eax-1
8048df7: 83 f8 05 cmp $0x5,%eax //eax与5比较
8048dfa: 76 05 jbe 8048e01 <phase_6+0x2f> //jump if eax<=5
8048dfc: e8 14 03 00 00 call 8049115 <explode_bomb> //Bomb!
8048e01: 83 c6 01 add $0x1,%esi //esi=esi+1
8048e04: 83 fe 06 cmp $0x6,%esi //esi与6比较
8048e07: 75 07 jne 8048e10 <phase_6+0x3e> //jump if esi!=6
8048e09: bb 00 00 00 00 mov $0x0,%ebx //ebx=0
8048e0e: eb 38 jmp 8048e48 <phase_6+0x76> //jump (if esi==6)
8048e10: 89 f3 mov %esi,%ebx //esi -> ebx
8048e12: 8b 44 9c 10 mov 0x10(%esp,%ebx,4),%eax //M(0x10+esp+4*ebx)->eax
8048e16: 39 44 b4 0c cmp %eax,0xc(%esp,%esi,4) //eax与下一个数M(0xc+esp+4*esi)比较
8048e1a: 75 05 jne 8048e21 <phase_6+0x4f> //jump if eax!=M(0xc+esp+4*esi)
8048e1c: e8 f4 02 00 00 call 8049115 <explode_bomb> //Bomb!
8048e21: 83 c3 01 add $0x1,%ebx //ebx=ebx+1
8048e24: 83 fb 05 cmp $0x5,%ebx //ebx与5比较
8048e27: 7e e9 jle 8048e12 <phase_6+0x40> //jump if ebx<=5
8048e29: eb c5 jmp 8048df0 <phase_6+0x1e> //jump
8048e2b: 8b 52 08 mov 0x8(%edx),%edx //M(0x8+edx) -> edx
8048e2e: 83 c0 01 add $0x1,%eax //eax=eax+1
8048e31: 39 c8 cmp %ecx,%eax //ecx与eax比较
8048e33: 75 f6 jne 8048e2b <phase_6+0x59> //jump if ecx!=eax
8048e35: eb 05 jmp 8048e3c <phase_6+0x6a> //jump
8048e37: ba 3c c1 04 08 mov $0x804c13c,%edx //edx=0x804c13c
8048e3c: 89 54 b4 28 mov %edx,0x28(%esp,%esi,4) //M(0x28+esp+4*esi)=edx
8048e40: 83 c3 01 add $0x1,%ebx //ebx=ebx+1
8048e43: 83 fb 06 cmp $0x6,%ebx //ebx与6比较
8048e46: 74 17 je 8048e5f <phase_6+0x8d> //jump if ebx==6
8048e48: 89 de mov %ebx,%esi //ebx -> esi
8048e4a: 8b 4c 9c 10 mov 0x10(%esp,%ebx,4),%ecx //ecx=M(0x10+esp+4*ebx)
8048e4e: 83 f9 01 cmp $0x1,%ecx //ecx与1比较
8048e51: 7e e4 jle 8048e37 <phase_6+0x65> //jump if ecx<=1
8048e53: b8 01 00 00 00 mov $0x1,%eax //eax=1
8048e58: ba 3c c1 04 08 mov $0x804c13c,%edx //edx=0x804c13c
8048e5d: eb cc jmp 8048e2b <phase_6+0x59> //jump
8048e5f: 8b 5c 24 28 mov 0x28(%esp),%ebx //M(0x28+esp) -> ebx
8048e63: 8d 44 24 2c lea 0x2c(%esp),%eax //esp+0x2c -> eax
8048e67: 8d 74 24 40 lea 0x40(%esp),%esi //esp+0x40 -> esi
8048e6b: 89 d9 mov %ebx,%ecx //ecx=ebx
8048e6d: 8b 10 mov (%eax),%edx //edx=M(eax)
8048e6f: 89 51 08 mov %edx,0x8(%ecx) //M(ecx+0x8)=edx
8048e72: 83 c0 04 add $0x4,%eax //eax=eax+4
8048e75: 39 f0 cmp %esi,%eax //eax与esi比较
8048e77: 74 04 je 8048e7d <phase_6+0xab> //jump if eax=esi
8048e79: 89 d1 mov %edx,%ecx //ecx=edx
8048e7b: eb f0 jmp 8048e6d <phase_6+0x9b> //jump
8048e7d: c7 42 08 00 00 00 00 movl $0x0,0x8(%edx) //M(edx+0x8)=0
8048e84: be 05 00 00 00 mov $0x5,%esi //esi=5
8048e89: 8b 43 08 mov 0x8(%ebx),%eax //eax=M(ebx+0x8)
8048e8c: 8b 00 mov (%eax),%eax //eax=M(eax)
8048e8e: 39 03 cmp %eax,(%ebx) //M(ebx)与eax比较
8048e90: 7d 05 jge 8048e97 <phase_6+0xc5> //jump if M(ebx)>=eax
8048e92: e8 7e 02 00 00 call 8049115 <explode_bomb> //Bomb!
8048e97: 8b 5b 08 mov 0x8(%ebx),%ebx //ebx=M(0x8+ebx)
8048e9a: 83 ee 01 sub $0x1,%esi //esi=esi-1
8048e9d: 75 ea jne 8048e89 <phase_6+0xb7> //jump if M(ebx)!=eax
8048e9f: 83 c4 44 add $0x44,%esp
8048ea2: 5b pop %ebx
8048ea3: 5e pop %esi
8048ea4: c3 ret
- 解题思路
- 先把重要的反汇编代码意义标注出来(注释如上)
- 将代码根据功能分块(大致是根据跳转指令来划分)
- 分析输入指令
8048dd7: 8d 44 24 10 lea 0x10(%esp),%eax //esp+0x10 -> eax
8048ddb: 89 44 24 04 mov %eax,0x4(%esp) //eax -> M(esp+0x4),参数2
8048ddf: 8b 44 24 50 mov 0x50(%esp),%eax //M(esp+0x50) -> eax
8048de3: 89 04 24 mov %eax,(%esp) //eax -> M(esp),参数1
8048de6: e8 51 03 00 00 call 804913c <read_six_numbers> //call
根据调用函数的名字就可以猜出这是要输进去6个数字。
- 输入指令接下来一般是限制部分代码,限制输入的数的个数、范围、满足条件等等,带着这样的思想去找接下来的代码。
8048deb: be 00 00 00 00 mov $0x0,%esi //0 -> esi
8048df0: 8b 44 b4 10 mov 0x10(%esp,%esi,4),%eax //M(0x10+esp+4*esi) -> eax
8048df4: 83 e8 01 sub $0x1,%eax //eax=eax-1
8048df7: 83 f8 05 cmp $0x5,%eax //eax与5比较
8048dfa: 76 05 jbe 8048e01 <phase_6+0x2f> //jump if eax<=5
8048dfc: e8 14 03 00 00 call 8049115 <explode_bomb> //Bomb!
8048e01: 83 c6 01 add $0x1,%esi //esi=esi+1
8048e04: 83 fe 06 cmp $0x6,%esi //esi与6比较
8048e07: 75 07 jne 8048e10 <phase_6+0x3e> //jump if esi!=6
8048e09: bb 00 00 00 00 mov $0x0,%ebx //ebx=0
8048e0e: eb 38 jmp 8048e48 <phase_6+0x76> //jump (if esi==6),跳出循环
8048e10: 89 f3 mov %esi,%ebx //esi -> ebx
8048e12: 8b 44 9c 10 mov 0x10(%esp,%ebx,4),%eax //M(0x10+esp+4*ebx)->eax
8048e16: 39 44 b4 0c cmp %eax,0xc(%esp,%esi,4) //eax与M(0x0c+esp+4*esi)比较
8048e1a: 75 05 jne 8048e21 <phase_6+0x4f> //jump if eax!=M(0xc+esp+4*esi)
8048e1c: e8 f4 02 00 00 call 8049115 <explode_bomb> //Bomb!
8048e21: 83 c3 01 add $0x1,%ebx //ebx=ebx+1
8048e24: 83 fb 05 cmp $0x5,%ebx //ebx与5比较
8048e27: 7e e9 jle 8048e12 <phase_6+0x40> //jump if ebx<=5
8048e29: eb c5 jmp 8048df0 <phase_6+0x1e> //jump
为方便解释,编写伪代码如下:
for(int i=0;i<6;i++){
int temp=i+1;
if(a[i]>6) Bomb;
for(int j=temp;j<6;j++){
if(a[j]==a[i]) Bomb;
}
}
这个函数是利用一个嵌套for循环结构,限制输进去的6个数的范围(1-6),以及条件:6个数必须互不相等,否则原地爆炸。
- 接下来就是链表重排序内容。
具体分析如下:
处理首地址:
8048e09: bb 00 00 00 00 mov $0x0,%ebx //ebx=0
8048e0e: eb 38 jmp 8048e48 <phase_6+0x76> //这是上一步跳出循环的内容
……
8048e37: ba 3c c1 04 08 mov $0x804c13c,%edx //edx=0x804c13c
8048e3c: 89 54 b4 28 mov %edx,0x28(%esp,%esi,4) //M(0x28+esp+4*esi)=edx
……
8048e48: 89 de mov %ebx,%esi //ebx -> esi
8048e4a: 8b 4c 9c 10 mov 0x10(%esp,%ebx,4),%ecx //ecx=M(0x10+esp+4*ebx)
8048e4e: 83 f9 01 cmp $0x1,%ecx //ecx与1比较
8048e51: 7e e4 jle 8048e37 <phase_6+0x65> //jump if ecx<=1
假设从esi=ebx=0开始分析的话,这一段所做的工作就是遍历并找到原来链表中的的第一个节点,并且开辟一个新数组,把节点的首地址存进去,
mov $0x804c13c,%edx //edx=0x804c13c mov %edx,0x28(%esp,%esi,4) //M(0x28+esp+4*esi)=edx
依次把你输进去的数在链表中对应的节点的地址写进数组:
8048e2b: 8b 52 08 mov 0x8(%edx),%edx //M(0x8+edx) -> edx
8048e2e: 83 c0 01 add $0x1,%eax //eax=eax+1
8048e31: 39 c8 cmp %ecx,%eax //ecx与eax比较
8048e33: 75 f6 jne 8048e2b <phase_6+0x59> //jump if ecx!=eax
8048e35: eb 05 jmp 8048e3c <phase_6+0x6a> //jump
……
8048e3c: 89 54 b4 28 mov %edx,0x28(%esp,%esi,4) //M(0x28+esp+4*esi)=edx
8048e40: 83 c3 01 add $0x1,%ebx //ebx=ebx+1
8048e43: 83 fb 06 cmp $0x6,%ebx //ebx与6比较
8048e46: 74 17 je 8048e5f <phase_6+0x8d> //jump if ebx==6
8048e48: 89 de mov %ebx,%esi //ebx -> esi
8048e4a: 8b 4c 9c 10 mov 0x10(%esp,%ebx,4),%ecx //ecx=M(0x10+esp+4*ebx)
8048e4e: 83 f9 01 cmp $0x1,%ecx //ecx与1比较
8048e51: 7e e4 jle 8048e37 <phase_6+0x65> //jump if ecx<=1
8048e53: b8 01 00 00 00 mov $0x1,%eax //eax=1
8048e58: ba 3c c1 04 08 mov $0x804c13c,%edx //edx=0x804c13c
8048e5d: eb cc jmp 8048e2b <phase_6+0x59> //jump
每个节点的组成是<数据域,编号,下一节点地址>,所以M(0x8+edx)是用来寻找下一节点地址的,利用遍历找到之后存到对应位置。
总的来说,这里代码所做的其实是把你输进去的六个数字(1-6)在原来链表中对应节点的地址依次存储进了一个数组里面,这类似于一个结构体。
更新每个节点的下一节点地址:
8048e5f: 8b 5c 24 28 mov 0x28(%esp),%ebx //M(0x28+esp) -> ebx
8048e63: 8d 44 24 2c lea 0x2c(%esp),%eax //esp+0x2c -> eax
8048e67: 8d 74 24 40 lea 0x40(%esp),%esi //esp+0x40 -> esi
8048e6b: 89 d9 mov %ebx,%ecx //ecx=ebx
8048e6d: 8b 10 mov (%eax),%edx //edx=M(eax)
8048e6f: 89 51 08 mov %edx,0x8(%ecx) //M(ecx+0x8)=edx
8048e72: 83 c0 04 add $0x4,%eax //eax=eax+4
8048e75: 39 f0 cmp %esi,%eax //eax与esi比较
8048e77: 74 04 je 8048e7d <phase_6+0xab> //jump if eax=esi
8048e79: 89 d1 mov %edx,%ecx //ecx=edx
8048e7b: eb f0 jmp 8048e6d <phase_6+0x9b> //jump
这一部分代码所做的工作就是根据你输进去的序列以及上面所构建的节点地址数组,将序列中每个节点里面的“下一节点地址”这一部分更新为你输进去的序列的下一个节点所对应的地址,重新构建链表。
至此,链表已经构建完毕,下面我们就要看看你新构建的链表需要满足什么样的关系。
满足关系:
8048e84: be 05 00 00 00 mov $0x5,%esi //esi=5
8048e89: 8b 43 08 mov 0x8(%ebx),%eax //eax=M(ebx+0x8)
8048e8c: 8b 00 mov (%eax),%eax //eax=M(eax)
8048e8e: 39 03 cmp %eax,(%ebx) //M(ebx)与eax比较
8048e90: 7d 05 jge 8048e97 <phase_6+0xc5> //jump if M(ebx)>=eax
8048e92: e8 7e 02 00 00 call 8049115 <explode_bomb> //Bomb!
8048e97: 8b 5b 08 mov 0x8(%ebx),%ebx //ebx=M(0x8+ebx)
8048e9a: 83 ee 01 sub $0x1,%esi //esi=esi-1
8048e9d: 75 ea jne 8048e89 <phase_6+0xb7> //jump if M(ebx)!=eax
这是一个循环,每次加0x8其实就是取到当前节点中的“下一节点地址”这一部分,接着根据地址取出下一节点里面的数据,然后与当前节点的数据比较大小,判断了重排序之后的链表的数据域需要满足递减的关系。
- 分析完链表重排序内容后,找到原来链表的内容,根据从大到小排序之后,输进去对应的编号,即可解除炸弹。
(gdb) x/d 0x804c13c
0x804c13c <node1>: 471
(gdb) x/d 0x804c148
0x804c148 <node2>: 236
(gdb) x/d 0x804c154
0x804c154 <node3>: 154
(gdb) x/d 0x804c160
0x804c160 <node4>: 61
(gdb) x/d 0x804c16c
0x804c16c <node5>: 346
(gdb) x/d 0x804c178
0x804c178 <node6>: 733
排序之后应该是:
733-6 471-1 346-5 236-2 154-3 61-4 //数据域-编号
- 输入验证
6 1 5 2 3 4
Congratulations! You've defused the bomb!
- 炸弹六拆除!
<secret_phase>:
- 入口寻找
在第六关汇编代码下面还有一个fun7函数以及secret_phase部分,这很明显就说明了有隐藏关卡。我们观察给出的c语言代码可以发现每个关卡函数里面都有一个函数调用 phase_defused();找到该函数汇编代码,分析可知当输入两个数字和一个字符串的组合时就调用了隐藏关卡,第四关是输入两个数字的,所以我们需要在后面再输入某个字符串,那这个字符串从哪里找呢,我们知道隐藏关卡是在结束第六关才会出现的,所以字符的密码就隐藏在第六关,设置断点,查看需要传入的字符串,是 DrEvil,在第四关输入两个数字之后输入DrEvil,结束第六关之后,成功跳出隐藏关卡。 - 反汇编代码
08048ef6 <secret_phase>:
8048ef6: 53 push %ebx
8048ef7: 83 ec 18 sub $0x18,%esp
8048efa: e8 8d 02 00 00 call 804918c <read_line> //读一行内容
8048eff: c7 44 24 08 0a 00 00 movl $0xa,0x8(%esp)
8048f06: 00
8048f07: c7 44 24 04 00 00 00 movl $0x0,0x4(%esp)
8048f0e: 00
8048f0f: 89 04 24 mov %eax,(%esp) //以上几行为取出字符
8048f12: e8 b9 f9 ff ff call 80488d0 <strtol@plt> //将输入字符转换成对应长整型
8048f17: 89 c3 mov %eax,%ebx
8048f19: 8d 40 ff lea -0x1(%eax),%eax
8048f1c: 3d e8 03 00 00 cmp $0x3e8,%eax // 将eax与1001比较
8048f21: 76 05 jbe 8048f28 <secret_phase+0x32> // jump if eax<=1001
8048f23: e8 ed 01 00 00 call 8049115 <explode_bomb>
8048f28: 89 5c 24 04 mov %ebx,0x4(%esp) //传参2
8048f2c: c7 04 24 88 c0 04 08 movl $0x804c088,(%esp) //传参1
8048f33: e8 6d ff ff ff call 8048ea5 <fun7> //call fun7
8048f38: 83 f8 01 cmp $0x1,%eax //比较fun7返回值与1
8048f3b: 74 05 je 8048f42 <secret_phase+0x4c> //返回值必须是1
8048f3d: e8 d3 01 00 00 call 8049115 <explode_bomb> //Bomb!
8048f42: c7 04 24 6c a1 04 08 movl $0x804a16c,(%esp)
8048f49: e8 a2 f8 ff ff call 80487f0 <puts@plt>
8048f4e: e8 33 03 00 00 call 8049286 <phase_defused> //已经结束咧
8048f53: 83 c4 18 add $0x18,%esp
8048f56: 5b pop %ebx
8048f57: c3 ret
8048f58: 66 90 xchg %ax,%ax
8048f5a: 66 90 xchg %ax,%ax
8048f5c: 66 90 xchg %ax,%ax
8048f5e: 66 90 xchg %ax,%ax
- 解题思路
- 先把重要的反汇编代码意义标注出来(注释如上)
- 将代码根据功能分块(大致是根据跳转指令来划分
- 分析输入指令:
8048ef7: 83 ec 18 sub $0x18,%esp
8048efa: e8 8d 02 00 00 call 804918c <read_line> //读一行内容
8048eff: c7 44 24 08 0a 00 00 movl $0xa,0x8(%esp)
8048f06: 00
8048f07: c7 44 24 04 00 00 00 movl $0x0,0x4(%esp)
8048f0e: 00
8048f0f: 89 04 24 mov %eax,(%esp) //以上几行为取出字符
8048f12: e8 b9 f9 ff ff call 80488d0 <strtol@plt> //将输入字符转换成对应长整型
8048f17: 89 c3 mov %eax,%ebx
8048f19: 8d 40 ff lea -0x1(%eax),%eax
8048f1c: 3d e8 03 00 00 cmp $0x3e8,%eax // 将eax与1001比较
8048f21: 76 05 jbe 8048f28 <secret_phase+0x32> // jump if eax<=1001
8048f23: e8 ed 01 00 00 call 8049115 <explode_bomb>
输入并读取一行字符,将字符转换为长整型,并规定了长整型的大小。
- 调用fun7函数,并且返回值必须是1.
8048f28: 89 5c 24 04 mov %ebx,0x4(%esp) //传参2
8048f2c: c7 04 24 88 c0 04 08 movl $0x804c088,(%esp) //传参1
8048f33: e8 6d ff ff ff call 8048ea5 <fun7> //call fun7
8048f38: 83 f8 01 cmp $0x1,%eax //比较fun7返回值与1
8048f3b: 74 05 je 8048f42 <secret_phase+0x4c> //返回值必须是1
8048f3d: e8 d3 01 00 00 call 8049115 <explode_bomb> //Bomb!
- 分析fun7函数
08048ea5 <fun7>:
8048ea5: 53 push %ebx
8048ea6: 83 ec 18 sub $0x18,%esp
8048ea9: 8b 54 24 20 mov 0x20(%esp),%edx //参数1 node=$0x804c088
8048ead: 8b 4c 24 24 mov 0x24(%esp),%ecx //参数2 x
8048eb1: 85 d2 test %edx,%edx //按位与,测试非空
8048eb3: 74 37 je 8048eec <fun7+0x47> // 空则跳转
8048eb5: 8b 1a mov (%edx),%ebx //ebx=M(edx)
8048eb7: 39 cb cmp %ecx,%ebx //ebx与ecx比较
8048eb9: 7e 13 jle 8048ece <fun7+0x29> //jump if M(edx)<=ecx
8048ebb: 89 4c 24 04 mov %ecx,0x4(%esp) //M(esp+4)=ecx,传参2 ecx=x
8048ebf: 8b 42 04 mov 0x4(%edx),%eax //eax=M(edx+4)
8048ec2: 89 04 24 mov %eax,(%esp) //M(esp)=eax,传参1' M(edx+4)
8048ec5: e8 db ff ff ff call 8048ea5 <fun7> //递归调用 fun7(node->left,x)
8048eca: 01 c0 add %eax,%eax //eax = 2*eax = 2*fun7(node->left,x)
8048ecc: eb 23 jmp 8048ef1 <fun7+0x4c> //jump
8048ece: b8 00 00 00 00 mov $0x0,%eax //eax=0
8048ed3: 39 cb cmp %ecx,%ebx //M(edx)与ecx比较
8048ed5: 74 1a je 8048ef1 <fun7+0x4c> //jump if M(edx)==ecx
8048ed7: 89 4c 24 04 mov %ecx,0x4(%esp) //(if M(edx)<ecx)传参2 ecx=x
8048edb: 8b 42 08 mov 0x8(%edx),%eax //
8048ede: 89 04 24 mov %eax,(%esp) //传参1'' M(edx+8)
8048ee1: e8 bf ff ff ff call 8048ea5 <fun7> //递归调用 fun7(node->right,x)
8048ee6: 8d 44 00 01 lea 0x1(%eax,%eax,1),%eax //eax=2*eax+1=2*fun7(node->right,x)
8048eea: eb 05 jmp 8048ef1 <fun7+0x4c> //jump
8048eec: b8 ff ff ff ff mov $0xffffffff,%eax //return -1
8048ef1: 83 c4 18 add $0x18,%esp
8048ef4: 5b pop %ebx
8048ef5: c3 ret
这是一个递归调用函数,其结构类似于二叉树,其中首次传进去的这一个地址0x804a16c就是根节点地址。
为方便解释,写出该二叉树伪代码:
int fun7(Node* addr, int x){
if(addr == 0){
return -1;
}
int v = addr->value;
if (v == x){
return 0;
}
else if( v>x ){ //访问左子节点
return 2*fun7(addr->left, x);
}
else{ //访问右子节点
return 2*func7( addr->right, x)+1;
}
}
可知,在根节点非空的情况下,对于左子结点,返回值是上一步返回值乘以2,对于右子结点是上一步返回值乘以2再加1,那如何得到1呢,只需要调用1次右节点,返回值即为fun7=2*0+1=1.文章来源:https://www.toymoban.com/news/detail-419188.html
- 查看二叉树里面存储的值
(gdb) x/4wx 0x804c088 //查看根节点
0x804c088 <n1>: 0x00000024 0x0804c094 0x0804c0a0 0x00000008
(gdb) x/4wx 0x0804c0a0 //查看右子节点
0x804c0a0 <n22>: 0x00000032 0x0804c0b8 0x0804c0d0 0x00000016
7.取出数据转换为十进制文章来源地址https://www.toymoban.com/news/detail-419188.html
0x32 -> 50
- 输入验证
50
Wow! You've defused the secret stage!
Congratulations! You've defused the bomb!
- 隐藏炸弹拆除!
已经结束咧
实验总结
- 通过这次实验,掌握了更加丰富的gdb调试方法,学会了如何精确跟踪每一步的走向,找寻对应寄存器的值。
- 对汇编代码有了非常深入的了解,也明白了循环结构以及链表等等这些复杂结构在汇编代码中的存储方式。
- 学会了这种根据汇编代码解题的总体思路:逆推法。
- 学会了抓住重点看待问题,在做前两题之前都是一行一行汇编代码分析,追踪每个寄存器以及对应地址里面的值,但随着代码的加长,这样效率显然会变得越来越低。所以我开始直接找爆炸点,然后向前找到调用函数的那一行,从这一段开始整体分析即可,前面的mov、lea指令都是寄存器以及地址之间的转换,到时候要找出寄存器或地址了,再来溯源即可。
- 深深的体会到了汇编代码的巧妙以及天才般的设计。
到了这里,关于计算机系统实验二——bomblab(炸弹实验)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!