《深入理解计算机系统》Lab3 Bomblab

准备工作

在终端中输入objdump -d bomb > bomb.asm,将bomb反汇编并保存到文件bomb.asm中,相较于在gdb中输入disassemble命令更便于阅读。

080489e4 <main>:
 80489e4:	55                   	push   %ebp
 80489e5:	89 e5                	mov    %esp,%ebp
 80489e7:	53                   	push   %ebx
 80489e8:	83 e4 f0             	and    $0xfffffff0,%esp
 80489eb:	83 ec 10             	sub    $0x10,%esp
 80489ee:	8b 45 08             	mov    0x8(%ebp),%eax
 80489f1:	8b 5d 0c             	mov    0xc(%ebp),%ebx
 80489f4:	83 f8 01             	cmp    $0x1,%eax
 80489f7:	75 0c                	jne    8048a05 <main+0x21>
 80489f9:	a1 a4 c3 04 08       	mov    0x804c3a4,%eax
 80489fe:	a3 d0 c3 04 08       	mov    %eax,0x804c3d0
 8048a03:	eb 74                	jmp    8048a79 <main+0x95>
 8048a05:	83 f8 02             	cmp    $0x2,%eax
 8048a08:	75 49                	jne    8048a53 <main+0x6f>
 8048a0a:	c7 44 24 04 68 a0 04 	movl   $0x804a068,0x4(%esp)
 8048a11:	08 
 8048a12:	8b 43 04             	mov    0x4(%ebx),%eax
 8048a15:	89 04 24             	mov    %eax,(%esp)
 8048a18:	e8 63 fe ff ff       	call   8048880 <fopen@plt>
 8048a1d:	a3 d0 c3 04 08       	mov    %eax,0x804c3d0
 8048a22:	85 c0                	test   %eax,%eax
 8048a24:	75 53                	jne    8048a79 <main+0x95>
 8048a26:	8b 43 04             	mov    0x4(%ebx),%eax
 8048a29:	89 44 24 0c          	mov    %eax,0xc(%esp)
 8048a2d:	8b 03                	mov    (%ebx),%eax
 8048a2f:	89 44 24 08          	mov    %eax,0x8(%esp)
 8048a33:	c7 44 24 04 6a a0 04 	movl   $0x804a06a,0x4(%esp)
 8048a3a:	08 
 8048a3b:	c7 04 24 01 00 00 00 	movl   $0x1,(%esp)
 8048a42:	e8 59 fe ff ff       	call   80488a0 <__printf_chk@plt>
 8048a47:	c7 04 24 08 00 00 00 	movl   $0x8,(%esp)
 8048a4e:	e8 ed fd ff ff       	call   8048840 <exit@plt>
 8048a53:	8b 03                	mov    (%ebx),%eax
 8048a55:	89 44 24 08          	mov    %eax,0x8(%esp)
 8048a59:	c7 44 24 04 87 a0 04 	movl   $0x804a087,0x4(%esp)
 8048a60:	08 
 8048a61:	c7 04 24 01 00 00 00 	movl   $0x1,(%esp)
 8048a68:	e8 33 fe ff ff       	call   80488a0 <__printf_chk@plt>
 8048a6d:	c7 04 24 08 00 00 00 	movl   $0x8,(%esp)
 8048a74:	e8 c7 fd ff ff       	call   8048840 <exit@plt>
 8048a79:	e8 bd 05 00 00       	call   804903b <initialize_bomb>
 8048a7e:	c7 04 24 ec a0 04 08 	movl   $0x804a0ec,(%esp)
 8048a85:	e8 76 fd ff ff       	call   8048800 <puts@plt>
 8048a8a:	c7 04 24 28 a1 04 08 	movl   $0x804a128,(%esp)
 8048a91:	e8 6a fd ff ff       	call   8048800 <puts@plt>
 8048a96:	e8 62 06 00 00       	call   80490fd <read_line>
 8048a9b:	89 04 24             	mov    %eax,(%esp)
 8048a9e:	e8 ad 00 00 00       	call   8048b50 <phase_1>
 8048aa3:	e8 b3 07 00 00       	call   804925b <phase_defused>
 8048aa8:	c7 04 24 54 a1 04 08 	movl   $0x804a154,(%esp)
 8048aaf:	e8 4c fd ff ff       	call   8048800 <puts@plt>
 8048ab4:	e8 44 06 00 00       	call   80490fd <read_line>
 8048ab9:	89 04 24             	mov    %eax,(%esp)
 8048abc:	e8 b3 00 00 00       	call   8048b74 <phase_2>
 8048ac1:	e8 95 07 00 00       	call   804925b <phase_defused>
 8048ac6:	c7 04 24 a1 a0 04 08 	movl   $0x804a0a1,(%esp)
 8048acd:	e8 2e fd ff ff       	call   8048800 <puts@plt>
 8048ad2:	e8 26 06 00 00       	call   80490fd <read_line>
 8048ad7:	89 04 24             	mov    %eax,(%esp)
 8048ada:	e8 e5 00 00 00       	call   8048bc4 <phase_3>
 8048adf:	e8 77 07 00 00       	call   804925b <phase_defused>
 8048ae4:	c7 04 24 bf a0 04 08 	movl   $0x804a0bf,(%esp)
 8048aeb:	e8 10 fd ff ff       	call   8048800 <puts@plt>
 8048af0:	e8 08 06 00 00       	call   80490fd <read_line>
 8048af5:	89 04 24             	mov    %eax,(%esp)
 8048af8:	e8 e2 01 00 00       	call   8048cdf <phase_4>
 8048afd:	e8 59 07 00 00       	call   804925b <phase_defused>
 8048b02:	c7 04 24 80 a1 04 08 	movl   $0x804a180,(%esp)
 8048b09:	e8 f2 fc ff ff       	call   8048800 <puts@plt>
 8048b0e:	e8 ea 05 00 00       	call   80490fd <read_line>
 8048b13:	89 04 24             	mov    %eax,(%esp)
 8048b16:	e8 26 02 00 00       	call   8048d41 <phase_5>
 8048b1b:	e8 3b 07 00 00       	call   804925b <phase_defused>
 8048b20:	c7 04 24 ce a0 04 08 	movl   $0x804a0ce,(%esp)
 8048b27:	e8 d4 fc ff ff       	call   8048800 <puts@plt>
 8048b2c:	e8 cc 05 00 00       	call   80490fd <read_line>
 8048b31:	89 04 24             	mov    %eax,(%esp)
 8048b34:	e8 51 02 00 00       	call   8048d8a <phase_6>
 8048b39:	e8 1d 07 00 00       	call   804925b <phase_defused>
 8048b3e:	b8 00 00 00 00       	mov    $0x0,%eax
 8048b43:	8b 5d fc             	mov    -0x4(%ebp),%ebx
 8048b46:	c9                   	leave  
 8048b47:	c3                   	ret    
 8048b48:	90                   	nop
 8048b49:	90                   	nop
 8048b4a:	90                   	nop
 8048b4b:	90                   	nop
 8048b4c:	90                   	nop
 8048b4d:	90                   	nop
 8048b4e:	90                   	nop
 8048b4f:	90                   	nop

首先分析main函数,观察到main函数中,read_line函数的意义是读取一行字符串。phase_1~phase_6,read_line之后都会将返回值%eax传给(%esp),也就是read_line读取的字符串的首地址作为接下来执行phase_n的第一个参数。

 

Phase_1

密钥:I can see Russia from my house!

08048b50 <phase_1>:
 8048b50:	83 ec 1c             	sub    $0x1c,%esp
 8048b53:	c7 44 24 04 a4 a1 04 	movl   $0x804a1a4,0x4(%esp)
 8048b5a:	08 
 8048b5b:	8b 44 24 20          	mov    0x20(%esp),%eax
 8048b5f:	89 04 24             	mov    %eax,(%esp)
 8048b62:	e8 5d 04 00 00       	call   8048fc4 <strings_not_equal>
 8048b67:	85 c0                	test   %eax,%eax
 8048b69:	74 05                	je     8048b70 <phase_1+0x20>
 8048b6b:	e8 66 05 00 00       	call   80490d6 <explode_bomb>
 8048b70:	83 c4 1c             	add    $0x1c,%esp
 8048b73:	c3                   	ret   

分析

阅读phase_1的前5行代码,观察到数据$0x804a1a4和0x20(%esp)对应的read_line读入的字符串首地址作为参数被传入栈帧中,结合第6行的strings_not_equal函数,可以推测,$0x804a1a4处存储的也是一个字符串的首地址,并且将要与此前读入的字符串进行比较。阅读strings_not_equal的汇编代码,得知该函数比较两个字符串是否相等,若相等则返回0,保存在%eax中。再看第7、8、9行,可知若%eax为0,则跳过第9行至<phase_1+0x20>处,否则执行第9行。第9行的指令进入explode_bomb函数,执行此行命令将会引爆炸弹,所以必须令程序在第8行时进行跳转。结合以上信息,得知,我们必须令输入的字符串与$0x804a1a4处的字符串相等,才能通过phase_1。

通过gdb中的print命令,打印出$0x804a1a4处的字符串,出现了如图所示的字符串“I can see Russia from my house!”,这便是phase_1的通关密钥。运行bomb,输入该字符串,成功通过phase_1。

 

Phase_2

密钥:0 1 1 2 3 5

08048b74 <phase_2>:
 8048b74:	56                   	push   %esi
 8048b75:	53                   	push   %ebx
 8048b76:	83 ec 34             	sub    $0x34,%esp
 8048b79:	8d 44 24 18          	lea    0x18(%esp),%eax
 8048b7d:	89 44 24 04          	mov    %eax,0x4(%esp)
 8048b81:	8b 44 24 40          	mov    0x40(%esp),%eax
 8048b85:	89 04 24             	mov    %eax,(%esp)
 8048b88:	e8 7e 06 00 00       	call   804920b <read_six_numbers>
 8048b8d:	83 7c 24 18 00       	cmpl   $0x0,0x18(%esp)
 8048b92:	75 07                	jne    8048b9b <phase_2+0x27>
 8048b94:	83 7c 24 1c 01       	cmpl   $0x1,0x1c(%esp)
 8048b99:	74 05                	je     8048ba0 <phase_2+0x2c>
 8048b9b:	e8 36 05 00 00       	call   80490d6 <explode_bomb>
 8048ba0:	8d 5c 24 20          	lea    0x20(%esp),%ebx
 8048ba4:	8d 74 24 30          	lea    0x30(%esp),%esi
 8048ba8:	8b 43 f8             	mov    -0x8(%ebx),%eax
 8048bab:	03 43 fc             	add    -0x4(%ebx),%eax
 8048bae:	39 03                	cmp    %eax,(%ebx)
 8048bb0:	74 05                	je     8048bb7 <phase_2+0x43>
 8048bb2:	e8 1f 05 00 00       	call   80490d6 <explode_bomb>
 8048bb7:	83 c3 04             	add    $0x4,%ebx
 8048bba:	39 f3                	cmp    %esi,%ebx
 8048bbc:	75 ea                	jne    8048ba8 <phase_2+0x34>
 8048bbe:	83 c4 34             	add    $0x34,%esp
 8048bc1:	5b                   	pop    %ebx
 8048bc2:	5e                   	pop    %esi
 8048bc3:	c3                   	ret    

分析

观察到phase_2调用了read_six_numbers这个函数,根据phase_1的经验,phase_n函数内调用的函数很可能就是突破口。第4~7行配置read_six_numbers的参数,其中根据main函数可知0x40(%esp)中的内容为输入的字符串的首地址,结合阅读read_six_numbers函数的代码,得知该函数读取6个整型数字,并将读取的数字从左到右依次保存至0x18(%esp)~0x2c(%esp)中。若读取的数字不足6个,将会引爆炸弹。然而,并非随意输入6个数字即可。通过第9~13行得知,0x18(%esp)中的内容,即第一个数字必须等于0,而0x1c(%esp)中的内容,即第二个数字必须等于1,否则都将进入explode_bomb函数导致炸弹引爆。而15~24行是一个循环结构,翻译成伪代码为

pos = %edx; end_pos = %esi;
a[] = 读取的数字
while(pos != end_pos)
{
    if(a[pos] + a[pos+1] != a[pos+2])
        explode_bomb();
    pos++;
}

于是,可以得知,要输入的6个数字,就是斐波那契数列的前六个数字:0 1 1 2 3 5。

 

Phase_3

密钥:0 392 1 -213 2 -47 3 -878 4 0 5 -878

08048bc4 <phase_3>:
 8048bc4:	83 ec 2c             	sub    $0x2c,%esp
 8048bc7:	8d 44 24 1c          	lea    0x1c(%esp),%eax
 8048bcb:	89 44 24 0c          	mov    %eax,0xc(%esp)
 8048bcf:	8d 44 24 18          	lea    0x18(%esp),%eax
 8048bd3:	89 44 24 08          	mov    %eax,0x8(%esp)
 8048bd7:	c7 44 24 04 c3 a3 04 	movl   $0x804a3c3,0x4(%esp)
 8048bde:	08 
 8048bdf:	8b 44 24 30          	mov    0x30(%esp),%eax
 8048be3:	89 04 24             	mov    %eax,(%esp)
 8048be6:	e8 85 fc ff ff       	call   8048870 <__isoc99_sscanf@plt>
 8048beb:	83 f8 01             	cmp    $0x1,%eax
 8048bee:	7f 05                	jg     8048bf5 <phase_3+0x31>
 8048bf0:	e8 e1 04 00 00       	call   80490d6 <explode_bomb>
 8048bf5:	83 7c 24 18 07       	cmpl   $0x7,0x18(%esp)
 8048bfa:	77 66                	ja     8048c62 <phase_3+0x9e>
 8048bfc:	8b 44 24 18          	mov    0x18(%esp),%eax
 8048c00:	ff 24 85 00 a2 04 08 	jmp    *0x804a200(,%eax,4)
 8048c07:	b8 00 00 00 00       	mov    $0x0,%eax
 8048c0c:	eb 05                	jmp    8048c13 <phase_3+0x4f>
 8048c0e:	b8 5d 02 00 00       	mov    $0x25d,%eax
 8048c13:	2d a6 00 00 00       	sub    $0xa6,%eax
 8048c18:	eb 05                	jmp    8048c1f <phase_3+0x5b>
 8048c1a:	b8 00 00 00 00       	mov    $0x0,%eax
 8048c1f:	05 3f 03 00 00       	add    $0x33f,%eax
 8048c24:	eb 05                	jmp    8048c2b <phase_3+0x67>
 8048c26:	b8 00 00 00 00       	mov    $0x0,%eax
 8048c2b:	2d 6e 03 00 00       	sub    $0x36e,%eax
 8048c30:	eb 05                	jmp    8048c37 <phase_3+0x73>
 8048c32:	b8 00 00 00 00       	mov    $0x0,%eax
 8048c37:	05 6e 03 00 00       	add    $0x36e,%eax
 8048c3c:	eb 05                	jmp    8048c43 <phase_3+0x7f>
 8048c3e:	b8 00 00 00 00       	mov    $0x0,%eax
 8048c43:	2d 6e 03 00 00       	sub    $0x36e,%eax
 8048c48:	eb 05                	jmp    8048c4f <phase_3+0x8b>
 8048c4a:	b8 00 00 00 00       	mov    $0x0,%eax
 8048c4f:	05 6e 03 00 00       	add    $0x36e,%eax
 8048c54:	eb 05                	jmp    8048c5b <phase_3+0x97>
 8048c56:	b8 00 00 00 00       	mov    $0x0,%eax
 8048c5b:	2d 6e 03 00 00       	sub    $0x36e,%eax
 8048c60:	eb 0a                	jmp    8048c6c <phase_3+0xa8>
 8048c62:	e8 6f 04 00 00       	call   80490d6 <explode_bomb>
 8048c67:	b8 00 00 00 00       	mov    $0x0,%eax
 8048c6c:	83 7c 24 18 05       	cmpl   $0x5,0x18(%esp)
 8048c71:	7f 06                	jg     8048c79 <phase_3+0xb5>
 8048c73:	3b 44 24 1c          	cmp    0x1c(%esp),%eax
 8048c77:	74 05                	je     8048c7e <phase_3+0xba>
 8048c79:	e8 58 04 00 00       	call   80490d6 <explode_bomb>
 8048c7e:	83 c4 2c             	add    $0x2c,%esp
 8048c81:	c3                   	ret    

分析

第2~9行是为scanf函数配置参数以及执行scanf函数的过程,用print查看$0x804a3c3中的内容,发现是“%d %d”,说明scanf读取两个整型数,即密钥由两个整型数构成。第13~14行,0x18(%esp)中是读取的第一个数,ja 指令意味着无符号数的大小比较,即第一个数的取值范围在[0, 7]中。第15~16行是变址寻址的地址跳转,%eax中是我们输入的第一个数,跳转的地址是*(0x804a200+(%eax)*4)。这里以输入的第一个数是0为例,跳转的地址为*0x804a200,查看该地址中的内容,发现是0x8048c0e,于是跳转至该处的指令上。指令不停执行,直到倒数第6~7行,我们发现,该处指令对第一个数的范围做了新的限制,即将第一个数的范围缩小到[0, 5]。倒数第5行,将%eax与输入的第二个数进行比较,若不相等则引爆炸弹。%eax是根据此前的操作得来的,这时利用print命令查看%eax的内容,发现为392,即密钥为0 392。但由于第一个数可取0,1,2,3,4,5,故依次测试其余5个数作为第一个数,可得到不同第一个数对应的第二个数。所有可能的结果为0 392 或 1 -213 或 2 -47 或 3 -878 或 4 0 或5 -878。

 

Phase_4

密钥:40 2 60 3 80 4

08048c82 <func4>:
 8048c82:	83 ec 1c             	sub    $0x1c,%esp
 8048c85:	89 5c 24 10          	mov    %ebx,0x10(%esp)
 8048c89:	89 74 24 14          	mov    %esi,0x14(%esp)
 8048c8d:	89 7c 24 18          	mov    %edi,0x18(%esp)
 8048c91:	8b 74 24 20          	mov    0x20(%esp),%esi
 8048c95:	8b 5c 24 24          	mov    0x24(%esp),%ebx
 8048c99:	85 f6                	test   %esi,%esi
 8048c9b:	7e 2b                	jle    8048cc8 <func4+0x46>
 8048c9d:	83 fe 01             	cmp    $0x1,%esi
 8048ca0:	74 2b                	je     8048ccd <func4+0x4b>
 8048ca2:	89 5c 24 04          	mov    %ebx,0x4(%esp)
 8048ca6:	8d 46 ff             	lea    -0x1(%esi),%eax
 8048ca9:	89 04 24             	mov    %eax,(%esp)
 8048cac:	e8 d1 ff ff ff       	call   8048c82 <func4>
 8048cb1:	8d 3c 18             	lea    (%eax,%ebx,1),%edi
 8048cb4:	89 5c 24 04          	mov    %ebx,0x4(%esp)
 8048cb8:	83 ee 02             	sub    $0x2,%esi
 8048cbb:	89 34 24             	mov    %esi,(%esp)
 8048cbe:	e8 bf ff ff ff       	call   8048c82 <func4>
 8048cc3:	8d 1c 07             	lea    (%edi,%eax,1),%ebx
 8048cc6:	eb 05                	jmp    8048ccd <func4+0x4b>
 8048cc8:	bb 00 00 00 00       	mov    $0x0,%ebx
 8048ccd:	89 d8                	mov    %ebx,%eax
 8048ccf:	8b 5c 24 10          	mov    0x10(%esp),%ebx
 8048cd3:	8b 74 24 14          	mov    0x14(%esp),%esi
 8048cd7:	8b 7c 24 18          	mov    0x18(%esp),%edi
 8048cdb:	83 c4 1c             	add    $0x1c,%esp
 8048cde:	c3                   	ret    

08048cdf <phase_4>:
 8048cdf:	83 ec 2c             	sub    $0x2c,%esp
 8048ce2:	8d 44 24 18          	lea    0x18(%esp),%eax
 8048ce6:	89 44 24 0c          	mov    %eax,0xc(%esp)
 8048cea:	8d 44 24 1c          	lea    0x1c(%esp),%eax
 8048cee:	89 44 24 08          	mov    %eax,0x8(%esp)
 8048cf2:	c7 44 24 04 c3 a3 04 	movl   $0x804a3c3,0x4(%esp)
 8048cf9:	08 
 8048cfa:	8b 44 24 30          	mov    0x30(%esp),%eax
 8048cfe:	89 04 24             	mov    %eax,(%esp)
 8048d01:	e8 6a fb ff ff       	call   8048870 <__isoc99_sscanf@plt>
 8048d06:	83 f8 02             	cmp    $0x2,%eax
 8048d09:	75 0e                	jne    8048d19 <phase_4+0x3a>
 8048d0b:	8b 44 24 18          	mov    0x18(%esp),%eax
 8048d0f:	83 f8 01             	cmp    $0x1,%eax
 8048d12:	7e 05                	jle    8048d19 <phase_4+0x3a>
 8048d14:	83 f8 04             	cmp    $0x4,%eax
 8048d17:	7e 05                	jle    8048d1e <phase_4+0x3f>
 8048d19:	e8 b8 03 00 00       	call   80490d6 <explode_bomb>
 8048d1e:	8b 44 24 18          	mov    0x18(%esp),%eax
 8048d22:	89 44 24 04          	mov    %eax,0x4(%esp)
 8048d26:	c7 04 24 06 00 00 00 	movl   $0x6,(%esp)
 8048d2d:	e8 50 ff ff ff       	call   8048c82 <func4>
 8048d32:	3b 44 24 1c          	cmp    0x1c(%esp),%eax
 8048d36:	74 05                	je     8048d3d <phase_4+0x5e>
 8048d38:	e8 99 03 00 00       	call   80490d6 <explode_bomb>
 8048d3d:	83 c4 2c             	add    $0x2c,%esp
 8048d40:	c3                   	ret    

分析

第2~9行是为scanf函数配置参数以及执行scanf函数的过程,用print查看$0x804a3c3中的内容,发现是“%d %d”,说明scanf读取两个整型数,即密钥由两个整型数构成,这也可以通过第10~11行,scanf的返回值不为2时则引爆炸弹看出。第12~16行,0x18(%esp)中的内容是输入的第二个数(与phase_3不同是因为写入的位置调换了),可以看出第二个数的取值范围为(1, 4]。第18~20行,为func4函数配置参数。接下来我们进入func4函数,func4是一个递归函数,为了便于解释,在此将func4函数翻译成c语言。func4函数翻译成c语言为

int func4(int k, int n)
{
    if(k <= 0) return 0;
    if(k == 1) return n;
    return func4(k-1, n) + func4(k-2, n) + n;
}

由于第二个数的取值范围为(1, 4],故第二个数可取2,3,4,这里以2为例。代码中传入的参数为k = 6, n = 2(即以2为例的第二个数),经过计算得到func4(6, 2)的结果为40。接着观察到第21~22行,需要比较func4的结果是否与0x1c(%esp),即输入的第一个数相等,若不相等则引爆炸弹。于是,便可得知第一个数即是func(6, 第二个数)的值,当第二个数为2时,第一个数为40,故密钥为40 2。当第二个数取3/4时,还可以生成密钥60 3和80 4。

 

Phase_5

密钥:`adejn abdejl abejmn adefin adefjm adekln adijln aefjkn aeflmn aegjln bdefln beijln cdejln defgjn defijl dehjmn deijkn deilmn dfjlmn efhjln efijmn ejklmn 更多

08048d41 <phase_5>:
 8048d41:	53                   	push   %ebx
 8048d42:	83 ec 18             	sub    $0x18,%esp
 8048d45:	8b 5c 24 20          	mov    0x20(%esp),%ebx
 8048d49:	89 1c 24             	mov    %ebx,(%esp)
 8048d4c:	e8 5a 02 00 00       	call   8048fab <string_length>
 8048d51:	83 f8 06             	cmp    $0x6,%eax
 8048d54:	74 05                	je     8048d5b <phase_5+0x1a>
 8048d56:	e8 7b 03 00 00       	call   80490d6 <explode_bomb>
 8048d5b:	ba 00 00 00 00       	mov    $0x0,%edx
 8048d60:	b8 00 00 00 00       	mov    $0x0,%eax
 8048d65:	0f be 0c 03          	movsbl (%ebx,%eax,1),%ecx
 8048d69:	83 e1 0f             	and    $0xf,%ecx
 8048d6c:	03 14 8d 20 a2 04 08 	add    0x804a220(,%ecx,4),%edx
 8048d73:	83 c0 01             	add    $0x1,%eax
 8048d76:	83 f8 06             	cmp    $0x6,%eax
 8048d79:	75 ea                	jne    8048d65 <phase_5+0x24>
 8048d7b:	83 fa 45             	cmp    $0x45,%edx
 8048d7e:	74 05                	je     8048d85 <phase_5+0x44>
 8048d80:	e8 51 03 00 00       	call   80490d6 <explode_bomb>
 8048d85:	83 c4 18             	add    $0x18,%esp
 8048d88:	5b                   	pop    %ebx
 8048d89:	c3                   	ret    

分析

由第3~6行可知,程序将输入的字符串作为参数执行了string_length函数,若输入的字符串长度不为6,则引爆炸弹。接下来是一个循环结构,遍历输入的字符串,将当前遍历的字节与0xf相与得到一个数,这个数将作为首地址在0x804a220处的整型数组的下标,并将访问到的整型数组中的数进行累加,若和为0x45,则该字符串即是密钥。为了便于解释,将汇编代码翻译成c代码如下:

char s[6] = 输入的字符串
int a[16] = 首地址在0x804a220处的数组,由于和0xf相与,故下标最大值为15,数组长度为16
int sum = 0; //累加和
for(int i = 0; i < 6; ++i)
{
    int pos = (int)s[i] & 0xf;
    sum += a[pos];
}
if(sum != 0x45)
    explode_bomb();

于是,我们要找到密钥,就要先找到总和为0x45的6个数的下标。为了方便找到这样的6个数的集合,可以编写程序,得到这16个数中,共有22组这样的6个数的集合。这22组数在数组中的下标如下:

0 1 4 5 10 14

1 2 4 5 10 12

1 2 5 10 13 14

1 4 5 6 9 14

1 4 5 6 10 13

1 4 5 11 12 14

1 4 9 10 12 14

1 5 6 10 11 14

1 5 6 12 13 14

1 5 7 10 12 14

2 4 5 6 12 14

2 5 9 10 12 14

3 4 5 10 12 14

4 5 6 7 10 14

4 5 6 9 10 12

4 5 8 10 13 14

4 5 9 10 11 14

4 5 9 12 13 14

4 6 10 12 13 14

5 6 8 10 12 14

5 6 9 10 13 14

5 10 11 12 13 14

那么,现在的问题变成了如何构造满足条件的字符串了。因为下标的范围为[0, 15],ASCII码为0~15的字符都是控制字符,是不可打印的字符,所以不能够直接输入[0, 15]对应的字符组成的字符串。考虑到输出英文字母比较直观,且字母’a’和’A’的ASCII码分别为1100001和1000001,后4位是0001,故可以将下标加上’a’-1或’A’-1,将得到的字符组成字符串即为密钥。如下标为5时,其二进制为0101,加上’a’-1为1100101,和0xf(1111)相与后仍为0101。

通过这种方法,更换下标的加数或调换下标的顺序,可以产生大量的密钥。

 

Phase_6

密钥:5 3 2 4 6 1

08048d8a <phase_6>:
 8048d8a:	56                   	push   %esi
 8048d8b:	53                   	push   %ebx
 8048d8c:	83 ec 44             	sub    $0x44,%esp
 8048d8f:	8d 44 24 10          	lea    0x10(%esp),%eax
 8048d93:	89 44 24 04          	mov    %eax,0x4(%esp)
 8048d97:	8b 44 24 50          	mov    0x50(%esp),%eax
 8048d9b:	89 04 24             	mov    %eax,(%esp)
 8048d9e:	e8 68 04 00 00       	call   804920b <read_six_numbers>
 8048da3:	be 00 00 00 00       	mov    $0x0,%esi
 8048da8:	8b 44 b4 10          	mov    0x10(%esp,%esi,4),%eax
 8048dac:	83 e8 01             	sub    $0x1,%eax
 8048daf:	83 f8 05             	cmp    $0x5,%eax
 8048db2:	76 05                	jbe    8048db9 <phase_6+0x2f>
 8048db4:	e8 1d 03 00 00       	call   80490d6 <explode_bomb>
 8048db9:	83 c6 01             	add    $0x1,%esi
 8048dbc:	83 fe 06             	cmp    $0x6,%esi
 8048dbf:	74 33                	je     8048df4 <phase_6+0x6a>
 8048dc1:	89 f3                	mov    %esi,%ebx
 8048dc3:	8b 44 9c 10          	mov    0x10(%esp,%ebx,4),%eax
 8048dc7:	39 44 b4 0c          	cmp    %eax,0xc(%esp,%esi,4)
 8048dcb:	75 05                	jne    8048dd2 <phase_6+0x48>
 8048dcd:	e8 04 03 00 00       	call   80490d6 <explode_bomb>
 8048dd2:	83 c3 01             	add    $0x1,%ebx
 8048dd5:	83 fb 05             	cmp    $0x5,%ebx
 8048dd8:	7e e9                	jle    8048dc3 <phase_6+0x39>
 8048dda:	eb cc                	jmp    8048da8 <phase_6+0x1e>
 8048ddc:	8b 52 08             	mov    0x8(%edx),%edx
 8048ddf:	83 c0 01             	add    $0x1,%eax
 8048de2:	39 c8                	cmp    %ecx,%eax
 8048de4:	75 f6                	jne    8048ddc <phase_6+0x52>
 8048de6:	89 54 b4 28          	mov    %edx,0x28(%esp,%esi,4)
 8048dea:	83 c3 01             	add    $0x1,%ebx
 8048ded:	83 fb 06             	cmp    $0x6,%ebx
 8048df0:	75 07                	jne    8048df9 <phase_6+0x6f>
 8048df2:	eb 1c                	jmp    8048e10 <phase_6+0x86>
 8048df4:	bb 00 00 00 00       	mov    $0x0,%ebx
 8048df9:	89 de                	mov    %ebx,%esi
 8048dfb:	8b 4c 9c 10          	mov    0x10(%esp,%ebx,4),%ecx
 8048dff:	b8 01 00 00 00       	mov    $0x1,%eax
 8048e04:	ba 3c c1 04 08       	mov    $0x804c13c,%edx
 8048e09:	83 f9 01             	cmp    $0x1,%ecx
 8048e0c:	7f ce                	jg     8048ddc <phase_6+0x52>
 8048e0e:	eb d6                	jmp    8048de6 <phase_6+0x5c>
 8048e10:	8b 5c 24 28          	mov    0x28(%esp),%ebx
 8048e14:	8b 44 24 2c          	mov    0x2c(%esp),%eax
 8048e18:	89 43 08             	mov    %eax,0x8(%ebx)
 8048e1b:	8b 54 24 30          	mov    0x30(%esp),%edx
 8048e1f:	89 50 08             	mov    %edx,0x8(%eax)
 8048e22:	8b 44 24 34          	mov    0x34(%esp),%eax
 8048e26:	89 42 08             	mov    %eax,0x8(%edx)
 8048e29:	8b 54 24 38          	mov    0x38(%esp),%edx
 8048e2d:	89 50 08             	mov    %edx,0x8(%eax)
 8048e30:	8b 44 24 3c          	mov    0x3c(%esp),%eax
 8048e34:	89 42 08             	mov    %eax,0x8(%edx)
 8048e37:	c7 40 08 00 00 00 00 	movl   $0x0,0x8(%eax)
 8048e3e:	be 05 00 00 00       	mov    $0x5,%esi
 8048e43:	8b 43 08             	mov    0x8(%ebx),%eax
 8048e46:	8b 10                	mov    (%eax),%edx
 8048e48:	39 13                	cmp    %edx,(%ebx)
 8048e4a:	7d 05                	jge    8048e51 <phase_6+0xc7>
 8048e4c:	e8 85 02 00 00       	call   80490d6 <explode_bomb>
 8048e51:	8b 5b 08             	mov    0x8(%ebx),%ebx
 8048e54:	83 ee 01             	sub    $0x1,%esi
 8048e57:	75 ea                	jne    8048e43 <phase_6+0xb9>
 8048e59:	83 c4 44             	add    $0x44,%esp
 8048e5c:	5b                   	pop    %ebx
 8048e5d:	5e                   	pop    %esi
 8048e5e:	c3                   	ret    

分析

第2~5行读取6个整型数,由6~11行可知,读入的6个数的范围都必须在区间[1, 6]中。第12~22行的作用是判断读入的6个数是否两两互不相同,若否则会引爆炸弹。接下来的部分是将存储在0x804c13c处的内容存储到栈帧中,其中0x804c13c处存储的数据类型是node,node是一个由两个int(一个记录数值,一个记录node编号)、一个node*构成的结构体(实际上,node是一个链表),共占12个字节。存储node的方式是:按照读入的6个数(假定为a[i]),将编号为a[i]的node存储到第i个位置。然后从栈帧中的存储的第一个node开始,每个node与其后一个node中的数值进行比较,若后大于前,则会引爆炸弹。

综合该函数的行为,可以发现,函数要求将6个node在栈帧中根据数值从大到小排列,而输入的6个数,即是从大到小排列的node的编号。

以下是6个node的信息:

(gdb) x 0x804c13c

0x804c13c <node1>:     0x00000036

0x804c140 <node1+4>: 0x00000001

0x804c144 <node1+8>: 0x0804c148

0x804c148 <node2>:     0x000000c1  

0x804c14c <node2+4>: 0x00000002

0x804c150 <node2+8>: 0x0804c154

0x804c154 <node3>:     0x00000273   

0x804c158 <node3+4>: 0x00000003

0x804c15c <node3+8>: 0x0804c160

0x804c160 <node4>:     0x00000089  

0x804c164 <node4+4>: 0x00000004

0x804c168 <node4+8>: 0x0804c16c

0x804c16c <node5>:     0x000003d7   

0x804c170 <node5+4>: 0x00000005

0x804c174 <node5+8>: 0x0804c178

0x804c178 <node6>:     0x0000006c   

0x804c17c <node6+4>: 0x00000006

0x804c180 <node6+8>: 0x00000000

可见,6个node根据数值从大到小排列的编号为5 3 2 4 6 1,即为密钥。

 

Secret_phase

密钥:107

0804925b <phase_defused>:
 804925b:	81 ec 8c 00 00 00    	sub    $0x8c,%esp
 8049261:	65 a1 14 00 00 00    	mov    %gs:0x14,%eax
 8049267:	89 44 24 7c          	mov    %eax,0x7c(%esp)
 804926b:	31 c0                	xor    %eax,%eax
 804926d:	83 3d cc c3 04 08 06 	cmpl   $0x6,0x804c3cc
 8049274:	75 72                	jne    80492e8 <phase_defused+0x8d>
 8049276:	8d 44 24 2c          	lea    0x2c(%esp),%eax
 804927a:	89 44 24 10          	mov    %eax,0x10(%esp)
 804927e:	8d 44 24 28          	lea    0x28(%esp),%eax
 8049282:	89 44 24 0c          	mov    %eax,0xc(%esp)
 8049286:	8d 44 24 24          	lea    0x24(%esp),%eax
 804928a:	89 44 24 08          	mov    %eax,0x8(%esp)
 804928e:	c7 44 24 04 c9 a3 04 	movl   $0x804a3c9,0x4(%esp)
 8049295:	08 
 8049296:	c7 04 24 d0 c4 04 08 	movl   $0x804c4d0,(%esp)
 804929d:	e8 ce f5 ff ff       	call   8048870 <__isoc99_sscanf@plt>
 80492a2:	83 f8 03             	cmp    $0x3,%eax
 80492a5:	75 35                	jne    80492dc <phase_defused+0x81>
 80492a7:	c7 44 24 04 d2 a3 04 	movl   $0x804a3d2,0x4(%esp)
 80492ae:	08 
 80492af:	8d 44 24 2c          	lea    0x2c(%esp),%eax
 80492b3:	89 04 24             	mov    %eax,(%esp)
 80492b6:	e8 09 fd ff ff       	call   8048fc4 <strings_not_equal>
 80492bb:	85 c0                	test   %eax,%eax
 80492bd:	75 1d                	jne    80492dc <phase_defused+0x81>
 80492bf:	c7 04 24 98 a2 04 08 	movl   $0x804a298,(%esp)
 80492c6:	e8 35 f5 ff ff       	call   8048800 <puts@plt>
 80492cb:	c7 04 24 c0 a2 04 08 	movl   $0x804a2c0,(%esp)
 80492d2:	e8 29 f5 ff ff       	call   8048800 <puts@plt>
 80492d7:	e8 d4 fb ff ff       	call   8048eb0 <secret_phase>
 80492dc:	c7 04 24 f8 a2 04 08 	movl   $0x804a2f8,(%esp)
 80492e3:	e8 18 f5 ff ff       	call   8048800 <puts@plt>
 80492e8:	8b 44 24 7c          	mov    0x7c(%esp),%eax
 80492ec:	65 33 05 14 00 00 00 	xor    %gs:0x14,%eax
 80492f3:	74 05                	je     80492fa <phase_defused+0x9f>
 80492f5:	e8 d6 f4 ff ff       	call   80487d0 <__stack_chk_fail@plt>
 80492fa:	81 c4 8c 00 00 00    	add    $0x8c,%esp
 8049300:	c3                   	ret    
 8049301:	90                   	nop
 8049302:	90                   	nop
 8049303:	90                   	nop
 8049304:	90                   	nop
 8049305:	90                   	nop
 8049306:	90                   	nop
 8049307:	90                   	nop
 8049308:	90                   	nop
 8049309:	90                   	nop
 804930a:	90                   	nop
 804930b:	90                   	nop
 804930c:	90                   	nop
 804930d:	90                   	nop
 804930e:	90                   	nop
 804930f:	90                   	nop

08048e5f <fun7>:
 8048e5f:	53                   	push   %ebx
 8048e60:	83 ec 18             	sub    $0x18,%esp
 8048e63:	8b 54 24 20          	mov    0x20(%esp),%edx
 8048e67:	8b 4c 24 24          	mov    0x24(%esp),%ecx
 8048e6b:	85 d2                	test   %edx,%edx
 8048e6d:	74 37                	je     8048ea6 <fun7+0x47>
 8048e6f:	8b 1a                	mov    (%edx),%ebx
 8048e71:	39 cb                	cmp    %ecx,%ebx
 8048e73:	7e 13                	jle    8048e88 <fun7+0x29>
 8048e75:	89 4c 24 04          	mov    %ecx,0x4(%esp)
 8048e79:	8b 42 04             	mov    0x4(%edx),%eax
 8048e7c:	89 04 24             	mov    %eax,(%esp)
 8048e7f:	e8 db ff ff ff       	call   8048e5f <fun7>
 8048e84:	01 c0                	add    %eax,%eax
 8048e86:	eb 23                	jmp    8048eab <fun7+0x4c>
 8048e88:	b8 00 00 00 00       	mov    $0x0,%eax
 8048e8d:	39 cb                	cmp    %ecx,%ebx
 8048e8f:	74 1a                	je     8048eab <fun7+0x4c>
 8048e91:	89 4c 24 04          	mov    %ecx,0x4(%esp)
 8048e95:	8b 42 08             	mov    0x8(%edx),%eax
 8048e98:	89 04 24             	mov    %eax,(%esp)
 8048e9b:	e8 bf ff ff ff       	call   8048e5f <fun7>
 8048ea0:	8d 44 00 01          	lea    0x1(%eax,%eax,1),%eax
 8048ea4:	eb 05                	jmp    8048eab <fun7+0x4c>
 8048ea6:	b8 ff ff ff ff       	mov    $0xffffffff,%eax
 8048eab:	83 c4 18             	add    $0x18,%esp
 8048eae:	5b                   	pop    %ebx
 8048eaf:	c3                   	ret    

08048eb0 <secret_phase>:
 8048eb0:	53                   	push   %ebx
 8048eb1:	83 ec 18             	sub    $0x18,%esp
 8048eb4:	e8 44 02 00 00       	call   80490fd <read_line>
 8048eb9:	c7 44 24 08 0a 00 00 	movl   $0xa,0x8(%esp)
 8048ec0:	00 
 8048ec1:	c7 44 24 04 00 00 00 	movl   $0x0,0x4(%esp)
 8048ec8:	00 
 8048ec9:	89 04 24             	mov    %eax,(%esp)
 8048ecc:	e8 0f fa ff ff       	call   80488e0 <strtol@plt>
 8048ed1:	89 c3                	mov    %eax,%ebx
 8048ed3:	8d 40 ff             	lea    -0x1(%eax),%eax
 8048ed6:	3d e8 03 00 00       	cmp    $0x3e8,%eax
 8048edb:	76 05                	jbe    8048ee2 <secret_phase+0x32>
 8048edd:	e8 f4 01 00 00       	call   80490d6 <explode_bomb>
 8048ee2:	89 5c 24 04          	mov    %ebx,0x4(%esp)
 8048ee6:	c7 04 24 88 c0 04 08 	movl   $0x804c088,(%esp)
 8048eed:	e8 6d ff ff ff       	call   8048e5f <fun7>
 8048ef2:	83 f8 03             	cmp    $0x3,%eax
 8048ef5:	74 05                	je     8048efc <secret_phase+0x4c>
 8048ef7:	e8 da 01 00 00       	call   80490d6 <explode_bomb>
 8048efc:	c7 04 24 c4 a1 04 08 	movl   $0x804a1c4,(%esp)
 8048f03:	e8 f8 f8 ff ff       	call   8048800 <puts@plt>
 8048f08:	e8 4e 03 00 00       	call   804925b <phase_defused>
 8048f0d:	83 c4 18             	add    $0x18,%esp
 8048f10:	5b                   	pop    %ebx
 8048f11:	c3                   	ret    
 8048f12:	90                   	nop
 8048f13:	90                   	nop
 8048f14:	90                   	nop
 8048f15:	90                   	nop
 8048f16:	90                   	nop
 8048f17:	90                   	nop
 8048f18:	90                   	nop
 8048f19:	90                   	nop
 8048f1a:	90                   	nop
 8048f1b:	90                   	nop
 8048f1c:	90                   	nop
 8048f1d:	90                   	nop
 8048f1e:	90                   	nop
 8048f1f:	90                   	nop

分析

在阅读汇编代码时,发现phase_6之下还有一个secret_phase,直接输入6个密钥会发现直接退出了运行,而不会进入这个秘密关卡,于是我启用搜索,看看是什么函数调用了这个secret_phase。

在代码中搜索secret_phase,会发现phase_defused函数调用了secret_phase,而这个phase_defused函数是每个phase通过之后会执行的函数,于是我联想到是否需要在某个phase输入特定的信息才会进入这个secret_phase。阅读phase_defused的汇编代码,会发现出现了几个内存的地址,查看这些地址的内容,会发现这的确是secret_phase的入口。

其中,DrEvil出现在0x804a3d2中,而phase_defused调用了strings_not_equal函数来比较DrEvil和一个应该是我们输入的字符串的地方,这说明,我们开启secret_phase需要输入的信息是DrEvil!

80492a7: c7 44 24 04 d2 a3 04     movl   $0x 804a3d2,0x4(%esp)
 80492ae:      08
 80492af:      8d 44 24 2c           lea    0x2c(%esp),%eax
 80492b3:      89 04 24                    mov    %eax,(%esp)
 80492b6:      e8 09 fd ff ff            call   8048fc4 <strings_not_equal>
 80492bb:      85 c0                      test   %eax,%eax
 80492bd:      75 1d                      jne    80492dc <phase_defused+0x81>

那么这个DrEvil该什么时候输入呢?

804929d: e8 ce f5 ff ff            call   8048870 <__isoc99_sscanf@plt>
80492a2:      83 f8 03             cmp    $0x3,%eax
80492a5: 75 35                     jne    80492dc <phase_defused+0x81>

通过以上这3行可以看到,包括DrEvil在内,输入的参数的数量需要是3,且分别为%d %d %s否则将会跳过secret_phase的调用。可以发现,只有phase_4的密钥加上DrEvil满足这个条件。经测试,在phase_4的密钥后加上DrEvil后,在phase_6通过后,果然出现了secret_phase。

进入secret_phase,函数先读取我们输入的密钥,然后调用了c语言的strtol函数,这个函数将字符串转为长整型数,其中参数0xa意味着将其转为十进制。接下来函数会比较经转换后的数自减1后与0x3e8(十进制为1000)的无符号数大小,这意味着我们输入的数必须在[1, 1001]中。接着,函数会调用一个递归函数func7,参数为0x804c088和我们输入的数,func7的伪代码如下

int func7(int* k, int n)
{
       if(k <= 0) return -1;
       int kn = *k;
       if(kn == n) return 0;
       else if(kn < n) return 2*func7(k+2, n) + 1;
       else return 2*func7(k+1, n);
}

又由以下两个指令知,func7(0x804c088, 我们输入的数)的返回值必须为3,否则将引爆炸弹。

8048ef2: 83 f8 03              cmp    $0x3,%eax
8048ef5: 74 05                 je     8048efc <secret_phase+0x4c>

观察内存中地址为0x804c088的内容,是一棵二叉树:第一个地址存储数据,第二、三个地址存储下子结点的地址。于是我们只要分析func7的行为,并根据0x804c088中的内容,即可确定要输入的数。

首先,函数要求func7最终返回3。要构造一个3,根据func7的特性,可以这样构造:2*(2*0+1)+1,这意味着,最后一次要返回0,倒数第二次(或第二次)要返回2*func7(k+2, n) + 1,倒数第三次(或第一次)要返回2*func7(k+2, n) + 1。或者说,要返回的值是二叉树查找时比较的次数。要满足这样的关系,假如我们把第1~3次比较的数设为k1、k2、k3,那么,我们输入的数n要满足的条件是n < k1且n < k2且n == k3。要找到k3,查找0x804c088处的内存即可,经查找,为0x6b,转换为十进制为107,即为密钥。

(gdb) x/x 0x804c088

0x804c088 <n1>:   0x00000024

0x804c08c <n1+4>:      0x0804c094

0x804c090 <n1+8>:      0x0804c0a0

0x804c094 <n21>: 0x00000008

0x804c098 <n21+4>:    0x0804c0c4

0x804c09c <n21+8>:    0x0804c0ac

0x804c0a0 <n22>: 0x00000032

0x804c0a4 <n22+4>:    0x0804c0b8

0x804c0a8 <n22+8>:    0x0804c0d0

0x804c0ac <n32>: 0x00000016

0x804c0b0 <n32+4>:    0x0804c118

0x804c0b4 <n32+8>:    0x0804c100

0x804c0b8 <n33>: 0x0000002d

0x804c0bc <n33+4>:    0x0804c0dc

0x804c0c0 <n33+8>:    0x0804c124

0x804c0c4 <n31>: 0x00000006

0x804c0c8 <n31+4>:    0x0804c0e8

0x804c0cc <n31+8>:     0x0804c10c

0x804c0d0 <n34>: 0x0000006b

0x804c0d4 <n34+4>:    0x0804c0f4

0x804c0d8 <n34+8>:    0x0804c130

0x804c0dc <n45>: 0x00000028

0x804c0e0 <n45+4>:    0x00000000

0x804c0e4 <n45+8>:    0x00000000

0x804c0e8 <n41>: 0x00000001

0x804c0ec <n41+4>:     0x00000000

0x804c0f0 <n41+8>:     0x00000000

0x804c0f4 <n47>:  0x00000063

0x804c0f8 <n47+4>:     0x00000000

0x804c0fc <n47+8>:     0x00000000

0x804c100 <n44>: 0x00000023

0x804c104 <n44+4>:    0x00000000

0x804c108 <n44+8>:    0x00000000

0x804c10c <n42>: 0x00000007

0x804c110 <n42+4>:    0x00000000

0x804c114 <n42+8>:    0x00000000

0x804c118 <n43>: 0x00000014

0x804c11c <n43+4>:     0x00000000

0x804c120 <n43+8>:    0x00000000

0x804c124 <n46>: 0x0000002f

0x804c128 <n46+4>:    0x00000000

0x804c12c <n46+8>:    0x00000000

0x804c130 <n48>: 0x000003e9

0x804c134 <n48+4>:    0x00000000

0x804c138 <n48+8>:    0x00000000

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值