实验题目

bomblab

实验目的

  1. 使用gdb工具反汇编出汇编代码,结合c语言文件找到每个关卡的入口函数。然后分析汇编代码,分析得到每一关的通关密码。
  2. 进一步加深对linux指令的理解,对gdb调试的一些基本操作以及高级操作有所了解。
  3. 熟悉汇编程序,懂得如何利用汇编程序写出C语言程序伪代码,熟悉并掌握函数调用过程中的栈帧结构的变化,熟悉汇编程序及其调试方法。

实验环境

个人PC、Linux 32位操作系统、Ubuntu16.04

实验内容

准备阶段

  • 将实验压缩包解压并找到本人所用到的实验文件夹bomb7,复制到linux系统中,打开文件夹得到bombbomb.cREADME文件;
  • 阅读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
  • 解题思路
  1. 先把重要的反汇编代码意义标注出来(注释如上)
  2. 采用逆向思维法,先找出炸弹什么时候会爆炸。
8048bab:	e8 65 05 00 00       	call   8049115 <explode_bomb> //call <explode_bomb>

执行这一步就会爆炸。

  1. 找到跳过爆炸这一步的条件。
 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指令,跳过爆炸。

  1. 顺藤摸瓜,找出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的条件。

  1. 找出传入<strings_not_equal>的参数。
 8048b93:	c7 44 24 04 44 a1 04 	movl   $0x804a144,0x4(%esp) //0x0804a144 -> M(esp+4),将内存空间放入esp寄存器

所求的字符串即是0x804a144里面的值。

  1. 进入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."
  1. 输入找出来的字符串,验证答案。
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?
  1. 炸弹一拆除!
<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      
  • 解题思路
  1. 先把重要的反汇编代码意义标注出来(注释如上)
  2. 找到炸弹爆炸的汇编代码。
 8048bd3:	e8 3d 05 00 00       	call   8049115 <explode_bomb> //Bomb!
 8048be6:	e8 2a 05 00 00       	call   8049115 <explode_bomb> //Bomb!
  1. 找到跳过第一个炸弹的方法。
 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。

  1. 顺着跳转指令寻找重要寄存器的值。
 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。

  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。

  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跳出循环。

  1. 总体描述输入内容。

输入第一个数只要满足大于等于0即可,第二个数等于第一个数加1,第三个数等于第二个数加2,第四个数等于第三个数加3……,依此类推,最后输进去6个数即可。

  1. 输进去满足上述关系的6个数,发现答案确实如此。
Phase 1 defused. How about the next one?
1 2 4 7 11 16
That's number 2.  Keep going!
  1. 炸弹二拆除!
<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    
  • 解题思路
  1. 先把重要的反汇编代码意义标注出来(注释如上)
  2. 分析第一个爆炸点
 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

  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  
  • 解题思路
  1. 先把重要的反汇编代码意义标注出来(注释如上)
  2. 分析第一个爆炸点
 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。

  1. 进一步确认输入内容。
 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"

的确是输进去两个十进制数字。

  1. 顺着跳转指令,找到最近的一个函数调用,整段分析。
 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)。

  1. 分析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    
  1. 找出函数返回值需要满足的条件
 8048d74:	83 f8 06             	cmp    $0x6,%eax //cmp
 8048d77:	75 07                	jne    8048d80 <phase_4+0x60> //if eax!=6, jump to Bomb

返回值需要等于6

  1. 根据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.

  1. 继续找第二个数
 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.

  1. 输入验证
6 6
So you got that one.  Try this one.
  1. 炸弹四拆除!
<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   
  • 解题思路
  1. 先把重要的反汇编代码意义标注出来(注释如上)
  2. 根据汇编代码功能进行分块(其实基本可以说是按照爆炸点来分块)。
  3. 分析第一块内容。
 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!

根据调用的函数以及跳转条件可以知道,第一块的含义应该是输入一个六个字符的字符串。

  1. 分析第二块内容–循环部分
 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

循环六次

  1. 分析第二块内容–取字符部分
 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,方能跳过爆炸。

  1. 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
  1. 从中组合出结果为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是四个字节的)。

  1. 去ascii表中找低四位为0,1,2,3,4,4的字符
二进制十进制十六进制字符
0111000011270p
0111000111371q
0111001011472r
0111001111573s
0111010011674t
  1. 输入验证答案
pqrstt
Good work!  On to the next...
  1. 炸弹五拆除!
<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     
  • 解题思路
  1. 先把重要的反汇编代码意义标注出来(注释如上)
  2. 将代码根据功能分块(大致是根据跳转指令来划分)
  3. 分析输入指令
 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个数字。

  1. 输入指令接下来一般是限制部分代码,限制输入的数的个数范围满足条件等等,带着这样的思想去找接下来的代码。
 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个数必须互不相等,否则原地爆炸。

  1. 接下来就是链表重排序内容。

具体分析如下:

处理首地址:

 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其实就是取到当前节点中的“下一节点地址”这一部分,接着根据地址取出下一节点里面的数据,然后与当前节点的数据比较大小,判断了重排序之后的链表的数据域需要满足递减的关系。

  1. 分析完链表重排序内容后,找到原来链表的内容,根据从大到小排序之后,输进去对应的编号,即可解除炸弹。
(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  //数据域-编号
  1. 输入验证
6 1 5 2 3 4
Congratulations! You've defused the bomb!
  1. 炸弹六拆除!
<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
  • 解题思路
  1. 先把重要的反汇编代码意义标注出来(注释如上)
  2. 将代码根据功能分块(大致是根据跳转指令来划分
  3. 分析输入指令:
 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>

输入并读取一行字符,将字符转换为长整型,并规定了长整型的大小。

  1. 调用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!
  1. 分析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.

  1. 查看二叉树里面存储的值
(gdb) x/4wx 0x804c088 //查看根节点
0x804c088 <n1>: 	0x00000024	0x0804c094	0x0804c0a0	0x00000008
(gdb) x/4wx 0x0804c0a0 //查看右子节点
0x804c0a0 <n22>:	0x00000032	0x0804c0b8	0x0804c0d0	0x00000016

7.取出数据转换为十进制

0x32 -> 50
  1. 输入验证
50
Wow! You've defused the secret stage!
Congratulations! You've defused the bomb!
  1. 隐藏炸弹拆除!

已经结束咧

实验总结

  • 通过这次实验,掌握了更加丰富的gdb调试方法,学会了如何精确跟踪每一步的走向,找寻对应寄存器的值。
  • 对汇编代码有了非常深入的了解,也明白了循环结构以及链表等等这些复杂结构在汇编代码中的存储方式。
  • 学会了这种根据汇编代码解题的总体思路:逆推法
  • 学会了抓住重点看待问题,在做前两题之前都是一行一行汇编代码分析,追踪每个寄存器以及对应地址里面的值,但随着代码的加长,这样效率显然会变得越来越低。所以我开始直接找爆炸点,然后向前找到调用函数的那一行,从这一段开始整体分析即可,前面的mov、lea指令都是寄存器以及地址之间的转换,到时候要找出寄存器或地址了,再来溯源即可。
  • 深深的体会到了汇编代码的巧妙以及天才般的设计
Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐