CSAPP BombLab

目录

实验内容

反汇编

phase_1

phase_2

phase_3

phase_4

phase_5

phase_6

secret_phase

结果


实验内容

给定一个二进制文件bomb,需要输入特定的几段字符或者数字才可通过,需要利用调试工具和反汇编找出这些特定的输入,各个阶段信息如下:

Each bomb phase tests a different aspect of machine language programs:
  Phase 1: string comparison
  Phase 2: loops
  Phase 3: conditionals/switches
  Phase 4: recursive calls and the stack discipline
  Phase 5: pointers
  Phase 6: linked lists/pointers/structs

Phases get progressively harder. There is also a "secret phase" that
only appears if students append a certain string to the solution to
Phase 4. 

注:DataLab和BombLab写的是旧版。

反汇编

objdump -d bomb > bomb.txt

利用objdump -d可以将指定可执行文件反汇编,并可以反汇编结果输出到指定文件中。

进入bomb.txt后可以找到main函数,其中包含了phase_1 ~ phase_6共6个阶段,这些phase即为需要拆除的炸弹。

phase_1

main函数中关于phase_1的代码如下,在进入phase_1前的read_line函数,用于读取一串字符。

 8048a52:	e8 a5 07 00 00       	call   80491fc <read_line>
 8048a57:	83 c4 f4             	add    $0xfffffff4,%esp
 8048a5a:	50                   	push   %eax
 8048a5b:	e8 c0 00 00 00       	call   8048b20 <phase_1>

phase_1中调用了strings_not_equal函数,同时根据该函数的返回值%eax判断是否进入explode_bomb函数引爆炸弹。

08048b20 <phase_1>: 
......

 8048b23:	83 ec 08             	sub    $0x8,%esp
 8048b26:	8b 45 08             	mov    0x8(%ebp),%eax
 8048b29:	83 c4 f8             	add    $0xfffffff8,%esp
 8048b2c:	68 c0 97 04 08       	push   $0x80497c0
 8048b31:	50                   	push   %eax

 8048b32:	e8 f9 04 00 00       	call   8049030 <strings_not_equal>
 8048b37:	83 c4 10             	add    $0x10,%esp
                                
 8048b3a:	85 c0                	test   %eax,%eax                                
 8048b3c:	74 05                	je     8048b43 <phase_1+0x23>
 8048b3e:	e8 b9 09 00 00       	call   80494fc <explode_bomb>

 ......

string_not_equal函数中:

08049030 <strings_not_equal>:
...... 

 8049039:	8b 75 08             	mov    0x8(%ebp),%esi
 804903c:	8b 7d 0c             	mov    0xc(%ebp),%edi
 804903f:	83 c4 f4             	add    $0xfffffff4,%esp
 8049042:	56                   	push   %esi

                                    //字符串1的长度存储在%ebx  
 8049043:	e8 d0 ff ff ff       	call   8049018 <string_length>
 8049048:	89 c3                	mov    %eax,%ebx  
 804904a:	83 c4 f4             	add    $0xfffffff4,%esp
 804904d:	57                   	push   %edi

                                    //返回的字符串2的长度存于%eax,与%ebx进行比较
 804904e:	e8 c5 ff ff ff       	call   8049018 <string_length>
 8049053:	39 c3                	cmp    %eax,%ebx                        
 8049055:	74 09                	je     8049060 <strings_not_equal+0x30>

                                    //不匹配,返回1并退出函数
 8049057:	b8 01 00 00 00       	mov    $0x1,%eax
 804905c:	eb 21                	jmp    804907f <strings_not_equal+0x4f>

                                    //判断字符串是否为空
 804905e:	89 f6                	mov    %esi,%esi
 8049060:	89 f2                	mov    %esi,%edx
 8049062:	89 f9                	mov    %edi,%ecx
 8049064:	80 3a 00             	cmpb   $0x0,(%edx)
 8049067:	74 14                	je     804907d <strings_not_equal+0x4d>

                                    //逐个判断字符是否相等,两字符分别为(%edx)和(%ecx)
 8049069:	8d b4 26 00 00 00 00 	lea    0x0(%esi,%eiz,1),%esi
 8049070:	8a 02                	mov    (%edx),%al
 8049072:	3a 01                	cmp    (%ecx),%al
 8049074:	75 e1                	jne    8049057 <strings_not_equal+0x27>
                                    //因为字符只占一个字节,故地址自增1
 8049076:	42                   	inc    %edx
 8049077:	41                   	inc    %ecx
 8049078:	80 3a 00             	cmpb   $0x0,(%edx)
 804907b:	75 f3                	jne    8049070 <strings_not_equal+0x40>

                                    //匹配,返回0
 804907d:	31 c0                	xor    %eax,%eax
 804907f:	8d 65 e8             	lea    -0x18(%ebp),%esp



......

由上可知,在比较长度时,两个字符串的长度分别在%ebx%eax。在逐个比较字符时两字符串的首指针分别在%edx%ecx,为了知道字符具体是多少,下面需要进行gdb断点调试:

输入的字符串为abcde%ebx对应的长度为5,而%eax的长度为29,即为目标串的长度,下面需要重新调试,输入长度29的串才能进入逐个判断字符阶段:

打印%ecx%edx处地址的字符,容易发现%ecx为目标串的指针,选择打印%ecx地址下的29个字符即可得到phase_1的结果为:

Public speaking is very easy.

phase_2

08048b48 <phase_2>:
......
 
                                    //读取六个数字
 8048b5b:	e8 78 04 00 00       	call   8048fd8 <read_six_numbers>
 8048b60:	83 c4 10             	add    $0x10,%esp

                                    //比较第一个数字
 8048b63:	83 7d e8 01          	cmpl   $0x1,-0x18(%ebp)
 8048b67:	74 05                	je     8048b6e <phase_2+0x26>

 8048b69:	e8 8e 09 00 00       	call   80494fc <explode_bomb>

                                    //%eax = %ebx + 1; 
                                    //%eax = [i] * %eax; 
                                    //if (%eax == [i + 1]) ...
 8048b6e:	bb 01 00 00 00       	mov    $0x1,%ebx
 8048b73:	8d 75 e8             	lea    -0x18(%ebp),%esi
 8048b76:	8d 43 01             	lea    0x1(%ebx),%eax
 8048b79:	0f af 44 9e fc       	imul   -0x4(%esi,%ebx,4),%eax
 8048b7e:	39 04 9e             	cmp    %eax,(%esi,%ebx,4)
 8048b81:	74 05                	je     8048b88 <phase_2+0x40>

 8048b83:	e8 74 09 00 00       	call   80494fc <explode_bomb>

                                    //%ebx++;
 8048b88:	43                   	inc    %ebx
                                
                                    //继续循环
 8048b89:	83 fb 05             	cmp    $0x5,%ebx
 8048b8c:	7e e8                	jle    8048b76 <phase_2+0x2e>

......

直接根据代码逻辑可知,需要六个数字,且[1] = 1[i] = [i - 1] * i,易有答案为

1 2 6 24 120 720

phase_3

main函数中phase_3前为read_line读取的字符串,输入的字符串首地址入栈。

08048b98 <phase_3>:
......

                                    //为sscanf函数作参数准备(指针做参数)
 8048b9f:	8b 55 08             	mov    0x8(%ebp),%edx
 8048ba2:	83 c4 f4             	add    $0xfffffff4,%esp
 8048ba5:	8d 45 fc             	lea    -0x4(%ebp),%eax  //&n2
 8048ba8:	50                   	push   %eax
 8048ba9:	8d 45 fb             	lea    -0x5(%ebp),%eax  //&c
 8048bac:	50                   	push   %eax
 8048bad:	8d 45 f4             	lea    -0xc(%ebp),%eax  //&n1
 8048bb0:	50                   	push   %eax
 8048bb1:	68 de 97 04 08       	push   $0x80497de  //"%d, %c, %d"
 8048bb6:	52                   	push   %edx  //%edx即为我们输入的字符串的首地址

                                    //sscanf(%edx, "%d, %c, %d", &n1, &c, &n2)
 8048bb7:	e8 a4 fc ff ff       	call   8048860 <sscanf@plt>

                                    //sscanf返回了读取的数据个数,返回值是否大于两个
 8048bbc:	83 c4 20             	add    $0x20,%esp
 8048bbf:	83 f8 02             	cmp    $0x2,%eax
 8048bc2:	7f 05                	jg     8048bc9 <phase_3+0x31>
 8048bc4:	e8 33 09 00 00       	call   80494fc <explode_bomb>

                                    //n1大于7,则爆炸
 8048bc9:	83 7d f4 07          	cmpl   $0x7,-0xc(%ebp)
 8048bcd:	0f 87 b5 00 00 00    	ja     8048c88 <phase_3+0xf0>
 8048bd3:	8b 45 f4             	mov    -0xc(%ebp),%eax
                                    //*(0x80497e8) = 0x8048be0,根据n1的值进行跳转
 8048bd6:	ff 24 85 e8 97 04 08 	jmp    *0x80497e8(,%eax,4)

                                    //n1 = 0, c = 0x71, n2 = 0x309
 8048bdd:	8d 76 00             	lea    0x0(%esi),%esi
 8048be0:	b3 71                	mov    $0x71,%bl
                                    //n2需要等于0x309
 8048be2:	81 7d fc 09 03 00 00 	cmpl   $0x309,-0x4(%ebp)
 8048be9:	0f 84 a0 00 00 00    	je     8048c8f <phase_3+0xf7>
 8048bef:	e8 08 09 00 00       	call   80494fc <explode_bomb>
 8048bf4:	e9 96 00 00 00       	jmp    8048c8f <phase_3+0xf7>

......

 8048c88:	b3 78                	mov    $0x78,%bl
 8048c8a:	e8 6d 08 00 00       	call   80494fc <explode_bomb>

                                    //c需要等于%bl
 8048c8f:	3a 5d fb             	cmp    -0x5(%ebp),%bl
 8048c92:	74 05                	je     8048c99 <phase_3+0x101>
 8048c94:	e8 63 08 00 00       	call   80494fc <explode_bomb>
 8048c99:	8b 5d e8             	mov    -0x18(%ebp),%ebx

......

phase_3中主要是用sscanf函数从输入的字符串中读取数据,从参数上看可以知道共有三个数据,而读取格式为$0x80497de,转换为字符串即为"%d, %c, %d"。随后根据n1的值进入switch语句,以n1 == 0为例:首先将%bl置为0x71,然后判断n2 == 0x309。接着进入另一段代码,判断c == %bl。故可以得到一个合法的输入为:

0 q 777

类似的,n1 = 1, 2, ...时也可以同样得到若干组数据。

phase_4

phase_4同样利用read_line读取了字符串:

08048ce0 <phase_4>:
 8048ce0:	55                   	push   %ebp
 8048ce1:	89 e5                	mov    %esp,%ebp
 8048ce3:	83 ec 18             	sub    $0x18,%esp
 8048ce6:	8b 55 08             	mov    0x8(%ebp),%edx  //%edx为input的首地址

 8048ce9:	83 c4 fc             	add    $0xfffffffc,%esp
 8048cec:	8d 45 fc             	lea    -0x4(%ebp),%eax  //&n
 8048cef:	50                   	push   %eax
 8048cf0:	68 08 98 04 08       	push   $0x8049808 //"%d"
 8048cf5:	52                   	push   %edx
                                    //sscanf(%edx, "%d", &n)
 8048cf6:	e8 65 fb ff ff       	call   8048860 <sscanf@plt>


 8048cfb:	83 c4 10             	add    $0x10,%esp
 8048cfe:	83 f8 01             	cmp    $0x1,%eax  //返回值为1
 8048d01:	75 06                	jne    8048d09 <phase_4+0x29>
 8048d03:	83 7d fc 00          	cmpl   $0x0,-0x4(%ebp)  //n > 0
 8048d07:	7f 05                	jg     8048d0e <phase_4+0x2e>

 8048d09:	e8 ee 07 00 00       	call   80494fc <explode_bomb>

                                    //调用了func4(n)
 8048d0e:	83 c4 f4             	add    $0xfffffff4,%esp
 8048d11:	8b 45 fc             	mov    -0x4(%ebp),%eax
 8048d14:	50                   	push   %eax
 8048d15:	e8 86 ff ff ff       	call   8048ca0 <func4>
 8048d1a:	83 c4 10             	add    $0x10,%esp
                                    //fuc4(n)的返回值需要为0x37
 8048d1d:	83 f8 37             	cmp    $0x37,%eax
 8048d20:	74 05                	je     8048d27 <phase_4+0x47>

 8048d22:	e8 d5 07 00 00       	call   80494fc <explode_bomb>

 8048d27:	89 ec                	mov    %ebp,%esp
 8048d29:	5d                   	pop    %ebp
 8048d2a:	c3                   	ret    
 8048d2b:	90                   	nop

可以知道输入字符串被读取为数字nn需要满足n > 0func4(&n) == 0x37,进入func4查看:

08048ca0 <func4>:
 8048ca0:	55                   	push   %ebp
 8048ca1:	89 e5                	mov    %esp,%ebp
 8048ca3:	83 ec 10             	sub    $0x10,%esp
 8048ca6:	56                   	push   %esi
 8048ca7:	53                   	push   %ebx

 8048ca8:	8b 5d 08             	mov    0x8(%ebp),%ebx //%ebx = n

                                    //n小于等于1退出函数并返回1
 8048cab:	83 fb 01             	cmp    $0x1,%ebx
 8048cae:	7e 20                	jle    8048cd0 <func4+0x30>

 8048cb0:	83 c4 f4             	add    $0xfffffff4,%esp
 8048cb3:	8d 43 ff             	lea    -0x1(%ebx),%eax  //%eax == n - 1
 8048cb6:	50                      push   %eax                                  
 8048cb7:	e8 e4 ff ff ff       	call   8048ca0 <func4>  //调用了func4(n - 1)
 8048cbc:	89 c6                	mov    %eax,%esi  //func(n - 1)的返回值保存于%esi

 8048cbe:	83 c4 f4             	add    $0xfffffff4,%esp
 8048cc1:	8d 43 fe             	lea    -0x2(%ebx),%eax
 8048cc4:	50                   	push   %eax
 8048cc5:	e8 d6 ff ff ff       	call   8048ca0 <func4>  //调用了func4(n - 2)
                                        
                                    //本函数的返回值设为func(n - 1) + func4(n - 2)
 8048cca:	01 f0                	add    %esi,%eax
 8048ccc:	eb 07                	jmp    8048cd5 <func4+0x35>
 8048cce:	89 f6                	mov    %esi,%esi
                                    
 8048cd0:	b8 01 00 00 00       	mov    $0x1,%eax  //返回1
 8048cd5:	8d 65 e8             	lea    -0x18(%ebp),%esp
 8048cd8:	5b                   	pop    %ebx
 8048cd9:	5e                   	pop    %esi
 8048cda:	89 ec                	mov    %ebp,%esp
 8048cdc:	5d                   	pop    %ebp
 8048cdd:	c3                   	ret    
 8048cde:	89 f6                	mov    %esi,%esi

func4的函数可以知道,func4(n) = func4(n - 1) + func4(n - 2),且func(1) =  func(0) = 1,即斐波那契数列,故容易知道func(9) == 55 == 0x37,答案为:

9

phase_5

phase_5同样输入一字符串:现有一长度为16的字符串S。依次取input的每个字符的低4位,根据低4位的值,取S中对应的字符,构造一个新串,要求新串和目标串相等。

08048d2c <phase_5>:
......
  
 8048d34:	8b 5d 08             	mov    0x8(%ebp),%ebx  //%ebx = input首地址
 8048d37:	83 c4 f4             	add    $0xfffffff4,%esp
 8048d3a:	53                   	push   %ebx
 8048d3b:	e8 d8 02 00 00       	call   8049018 <string_length>
 8048d40:	83 c4 10             	add    $0x10,%esp
 8048d43:	83 f8 06             	cmp    $0x6,%eax  //长度为6
 8048d46:	74 05                	je     8048d4d <phase_5+0x21>
 8048d48:	e8 af 07 00 00       	call   80494fc <explode_bomb>

 8048d4d:	31 d2                	xor    %edx,%edx  //%edx初始化为0

 8048d4f:	8d 4d f8             	lea    -0x8(%ebp),%ecx  //地址存于%ecx
 8048d52:	be 20 b2 04 08       	mov    $0x804b220,%esi  //地址0x804b220存于%esi

                                    //%al = input字符串的第%edx个字符
 8048d57:	8a 04 1a             	mov    (%edx,%ebx,1),%al  
                                    //只取字符的低4位,即半个字节
 8048d5a:	24 0f                	and    $0xf,%al  

                                    //记%al = c,取地址0x804b220偏移c后的字符
                                    //数组首地址为0x804b220,大小为16
 8048d5c:	0f be c0             	movsbl %al,%eax
 8048d5f:	8a 04 30             	mov    (%eax,%esi,1),%al
 8048d62:	88 04 0a             	mov    %al,(%edx,%ecx,1)  
 8048d65:	42                   	inc    %edx  //自增
 8048d66:	83 fa 05             	cmp    $0x5,%edx  //循环共执行6次
 8048d69:	7e ec                	jle    8048d57 <phase_5+0x2b>

 8048d6b:	c6 45 fe 00          	movb   $0x0,-0x2(%ebp)
 8048d6f:	83 c4 f8             	add    $0xfffffff8,%esp
 8048d72:	68 0b 98 04 08       	push   $0x804980b       //目标串
 8048d77:	8d 45 f8             	lea    -0x8(%ebp),%eax  //根据字符数组构造的新串
 8048d7a:	50                   	push   %eax

                                    //phase_1用到的比较字符串的函数
 8048d7b:	e8 b0 02 00 00       	call   8049030 <strings_not_equal>
 8048d80:	83 c4 10             	add    $0x10,%esp
 8048d83:	85 c0                	test   %eax,%eax
 8048d85:	74 05                	je     8048d8c <phase_5+0x60>
 8048d87:	e8 70 07 00 00       	call   80494fc <explode_bomb>

......

从代码中容易知道,input的长度需要为6,S的首地址为0x804b220target的首地址为0x804980b,利用gdb打印出各个值:

"giants"分别出现在S的第15、0、5、11、13、1位置,故input的6个字符的低4位,即半个字节,分别为f、0、5、b、d、1,容易构造出一个字符串:

oPekma

phase_6

phase_6的输入为一个字符串,由字符串构造6个数字,主要分为两个部分:

第一个部分是判断6个数字是否都处于[1, 6]范围内,且没有重复数字,涉及一个双层循环。

8048da1:	8b 55 08             	mov    0x8(%ebp),%edx  //%edx = *input
 8048da4:	c7 45 cc 6c b2 04 08 	movl   $0x804b26c,-0x34(%ebp)  //结构体基地址
 8048dab:	83 c4 f8             	add    $0xfffffff8,%esp
 8048dae:	8d 45 e8             	lea    -0x18(%ebp),%eax  //%ebp - 0x18开始存6个数字
 8048db1:	50                   	push   %eax
 8048db2:	52                   	push   %edx
 8048db3:	e8 20 02 00 00       	call   8048fd8 <read_six_numbers>

                                    //下面是一个双层循环
                                    //判断是否所有无符号数字都小于等于6,且各不相等
 8048db8:	31 ff                	xor    %edi,%edi  //%edi = i = 0
 8048dba:	83 c4 10             	add    $0x10,%esp
 8048dbd:	8d 76 00             	lea    0x0(%esi),%esi

 8048dc0:	8d 45 e8             	lea    -0x18(%ebp),%eax  //%eax = [0]的地址
 8048dc3:	8b 04 b8             	mov    (%eax,%edi,4),%eax  //%eax = [i]的值
 8048dc6:	48                   	dec    %eax  //[i]若为0,减1后变无符号数也会爆炸
 8048dc7:	83 f8 05             	cmp    $0x5,%eax  
 8048dca:	76 05                	jbe    8048dd1 <phase_6+0x39>  //0 < [i] <= 6
 8048dcc:	e8 2b 07 00 00       	call   80494fc <explode_bomb>

 8048dd1:	8d 5f 01             	lea    0x1(%edi),%ebx  //%ebx = j = i + 1
 8048dd4:	83 fb 05             	cmp    $0x5,%ebx  
 8048dd7:	7f 23                	jg     8048dfc <phase_6+0x64>  //j > 5则跳转

 8048dd9:	8d 04 bd 00 00 00 00 	lea    0x0(,%edi,4),%eax  //i * 4
 8048de0:	89 45 c8             	mov    %eax,-0x38(%ebp)  //-0x38(%ebp) = i * 4
 8048de3:	8d 75 e8             	lea    -0x18(%ebp),%esi  //%esi置为6个数字首地址
 8048de6:	8b 55 c8             	mov    -0x38(%ebp),%edx  //%edx = i * 4
                                  
 8048de9:	8b 04 32             	mov    (%edx,%esi,1),%eax  //%eax = [i]                           
 8048dec:	3b 04 9e             	cmp    (%esi,%ebx,4),%eax  //[i + j] =? [i]
 8048def:	75 05                	jne    8048df6 <phase_6+0x5e>  //不相等则跳转
 8048df1:	e8 06 07 00 00       	call   80494fc <explode_bomb>

 8048df6:	43                   	inc    %ebx  //++j
 8048df7:	83 fb 05             	cmp    $0x5,%ebx
 8048dfa:	7e ea                	jle    8048de6 <phase_6+0x4e>  //内层循环
 8048dfc:	47                   	inc    %edi  //++i
 8048dfd:	83 ff 05             	cmp    $0x5,%edi
 8048e00:	7e be                	jle    8048dc0 <phase_6+0x28>  //外层循环

第二部分涉及到节点(结构体)的构造,程序中从0x804b26c开始定义了一个节点数组。通过汇编码可以知道:每个结构体的大小是12字节,其中高地址存储指针,剩余部分存储一个64位数字,再通过gdb打印可以看到每个节点都预先存储了数据:

                                    //开辟一片内存存放节点,从0x804b26c开始
                                    //每个结构体12个字节,高4字节放地址
 8048e02:	31 ff                	xor    %edi,%edi  //%edi = i = 0
 8048e04:	8d 4d e8             	lea    -0x18(%ebp),%ecx  //6个数字的位置
 8048e07:	8d 45 d0             	lea    -0x30(%ebp),%eax
 8048e0a:	89 45 c4             	mov    %eax,-0x3c(%ebp)
 8048e0d:	8d 76 00             	lea    0x0(%esi),%esi  
                                    //%esi = -0x34(%ebp) = 0x804b26c
 8048e10:	8b 75 cc             	mov    -0x34(%ebp),%esi  


 8048e13:	bb 01 00 00 00       	mov    $0x1,%ebx  //%ebx = j = 1
 8048e18:	8d 04 bd 00 00 00 00 	lea    0x0(,%edi,4),%eax  //%eax = 4i
 8048e1f:	89 c2                	mov    %eax,%edx  //%edx = 4i
 8048e21:	3b 1c 08             	cmp    (%eax,%ecx,1),%ebx  //[i] 与 j比较
 8048e24:	7d 12                	jge    8048e38 <phase_6+0xa0>  //[i] <= j则跳转

 8048e26:	8b 04 0a             	mov    (%edx,%ecx,1),%eax  //%eax = [i]
 8048e29:	8d b4 26 00 00 00 00 	lea    0x0(%esi,%eiz,1),%esi

 8048e30:	8b 76 08             	mov    0x8(%esi),%esi  //高地址的是指针,剩下8字节是数据

 8048e33:	43                   	inc    %ebx  //++j
 8048e34:	39 c3                	cmp    %eax,%ebx  //直到[i] == j
 8048e36:	7c f8                	jl     8048e30 <phase_6+0x98>

                                    //若[i] == j
                                    //那么就在%edx + 4 * i的位置存放第j个节点的地址
 8048e38:	8b 55 c4             	mov    -0x3c(%ebp),%edx  //%edx =  %ebp - 0x30
 8048e3b:	89 34 ba             	mov    %esi,(%edx,%edi,4)
 8048e3e:	47                   	inc    %edi
 8048e3f:	83 ff 05             	cmp    $0x5,%edi
 8048e42:	7e cc                	jle    8048e10 <phase_6+0x78>  //继续读取其他数字

(%ebp - 0x30)开始有一个指针数组C,如果[i] == j,那么就把节点数组中第j个节点的地址存在C[i]。例如如果input = {2, 6, 4, 1, 3, 2},设节点数组首地址为m,那么C = {*m[1], *m[5], *m[3], *m[0], *m[2], *m[1]}

第三部分的作用是,按照C的顺序将节点改造为新的链表,并根据链表访问节点,访问时的数据单调递减:

                                    //构造链表的循环
 8048e44:	8b 75 d0             	mov    -0x30(%ebp),%esi  //[0]对应的节点j所在的位置
 8048e47:	89 75 cc             	mov    %esi,-0x34(%ebp)
 8048e4a:	bf 01 00 00 00       	mov    $0x1,%edi
 8048e4f:	8d 55 d0             	lea    -0x30(%ebp),%edx 


 8048e52:	8b 04 ba             	mov    (%edx,%edi,4),%eax  //[1]对应的节点k的位置
 8048e55:	89 46 08             	mov    %eax,0x8(%esi)  //j.next = k
 8048e58:	89 c6                	mov    %eax,%esi
 8048e5a:	47                   	inc    %edi
 8048e5b:	83 ff 05             	cmp    $0x5,%edi
 8048e5e:	7e f2                	jle    8048e52 <phase_6+0xba>  //循环下去
 8048e60:	c7 46 08 00 00 00 00 	movl   $0x0,0x8(%esi)

                                    //新的循环,进行判断
 8048e67:	8b 75 cc             	mov    -0x34(%ebp),%esi  
 8048e6a:	31 ff                	xor    %edi,%edi  
 8048e6c:	8d 74 26 00          	lea    0x0(%esi,%eiz,1),%esi
                                            
 8048e70:	8b 56 08             	mov    0x8(%esi),%edx  //下一个节点的地址  
 8048e73:	8b 06                	mov    (%esi),%eax  //当前节点的数据
 8048e75:	3b 02                	cmp    (%edx),%eax  //要求当前节点数字 >= 下一个节点的数字
 8048e77:	7d 05                	jge    8048e7e <phase_6+0xe6>  
 8048e79:	e8 7e 06 00 00       	call   80494fc <explode_bomb>

 8048e7e:	8b 76 08             	mov    0x8(%esi),%esi
 8048e81:	47                   	inc    %edi 
 8048e82:	83 ff 04             	cmp    $0x4,%edi 
 8048e85:	7e e9                	jle    8048e70 <phase_6+0xd8>
 8048e87:	8d 65 a8             	lea    -0x58(%ebp),%esp

根据gdb打印的每个节点的数据分别为253、725、301、997、212、432,故输入的6个数字为:

4 2 6 3 1 5

phase_6涉及到三个数组,n保存输入的数据,m为节点数组,C为节点地址数组。

secret_phase

phase_defused中有如下一段代码,结合gdb调试可以知道,0x804b770这个地址将分为串s和数字ns == "austinpowers"才可进入secret_phase

0804952c <phase_defused>:
......

 8049533:	83 3d 80 b4 04 08 06 	cmpl   $0x6,0x804b480  //这里记录着已经解决的阶段数
 804953a:	75 63                	jne    804959f <phase_defused+0x73>

 804953c:	8d 5d b0             	lea    -0x50(%ebp),%ebx  //串
 804953f:	53                   	push   %ebx
 8049540:	8d 45 ac             	lea    -0x54(%ebp),%eax  //数字
 8049543:	50                   	push   %eax
 8049544:	68 03 9d 04 08       	push   $0x8049d03  //sscanf参数1
 8049549:	68 70 b7 04 08       	push   $0x804b770  //sscanf参数2
 804954e:	e8 0d f3 ff ff       	call   8048860 <sscanf@plt>
 8049553:	83 c4 10             	add    $0x10,%esp
 8049556:	83 f8 02             	cmp    $0x2,%eax
 8049559:	75 37                	jne    8049592 <phase_defused+0x66>

 804955b:	83 c4 f8             	add    $0xfffffff8,%esp
 804955e:	68 09 9d 04 08       	push   $0x8049d09  //目标串
 8049563:	53                   	push   %ebx  //sscanf读入的串
 8049564:	e8 c7 fa ff ff       	call   8049030 <strings_not_equal>
 8049569:	83 c4 10             	add    $0x10,%esp
 804956c:	85 c0                	test   %eax,%eax
 804956e:	75 22                	jne    8049592 <phase_defused+0x66>

 8049570:	83 c4 f4             	add    $0xfffffff4,%esp
 8049573:	68 20 9d 04 08       	push   $0x8049d20
 8049578:	e8 93 f2 ff ff       	call   8048810 <printf@plt>
 804957d:	83 c4 f4             	add    $0xfffffff4,%esp
 8049580:	68 60 9d 04 08       	push   $0x8049d60
 8049585:	e8 86 f2 ff ff       	call   8048810 <printf@plt>
 804958a:	83 c4 20             	add    $0x20,%esp
 804958d:	e8 56 f9 ff ff       	call   8048ee8 <secret_phase>

在README中已有提示,在phase_4后添加这一串即可。或者也可以在read_line中得到验证,0x804b770就是phase_4input的首地址。

下面是secret_phase,其中strtol_internal(input, NULL, 10, 0) == strtol(input, NULL, 10),即把input转换为以10为基的数并返回n,随后调用了fun7(m, n),m为地址0x804b320

 08048ee8 <secret_phase>:
 8048ee8:	55                   	push   %ebp
 8048ee9:	89 e5                	mov    %esp,%ebp

 8048eeb:	83 ec 14             	sub    $0x14,%esp
 8048eee:	53                   	push   %ebx
 8048eef:	e8 08 03 00 00       	call   80491fc <read_line>

 8048ef4:	6a 00                	push   $0x0
 8048ef6:	6a 0a                	push   $0xa
 8048ef8:	6a 00                	push   $0x0
 8048efa:	50                   	push   %eax
                                    //strtol_internal(input, NULL, 10, 0)
 8048efb:	e8 f0 f8 ff ff       	call   80487f0 <__strtol_internal@plt>

 8048f00:	83 c4 10             	add    $0x10,%esp
 8048f03:	89 c3                	mov    %eax,%ebx
 8048f05:	8d 43 ff             	lea    -0x1(%ebx),%eax  
 8048f08:	3d e8 03 00 00       	cmp    $0x3e8,%eax  //n <= 1001
 8048f0d:	76 05                	jbe    8048f14 <secret_phase+0x2c>
 8048f0f:	e8 e8 05 00 00       	call   80494fc <explode_bomb>

 8048f14:	83 c4 f8             	add    $0xfffffff8,%esp
 8048f17:	53                   	push   %ebx  //n
 8048f18:	68 20 b3 04 08       	push   $0x804b320  //0x804b320
 8048f1d:	e8 72 ff ff ff       	call   8048e94 <fun7>

                                    //要求返回7
 8048f22:	83 c4 10             	add    $0x10,%esp
 8048f25:	83 f8 07             	cmp    $0x7,%eax
 8048f28:	74 05                	je     8048f2f <secret_phase+0x47>
 8048f2a:	e8 cd 05 00 00       	call   80494fc <explode_bomb>
 8048f2f:	83 c4 f4             	add    $0xfffffff4,%esp
 8048f32:	68 20 98 04 08       	push   $0x8049820
 8048f37:	e8 d4 f8 ff ff       	call   8048810 <printf@plt>
 8048f3c:	e8 eb 05 00 00       	call   804952c <phase_defused>

fun7(node, n)是一个递归函数,可以看到传入的node地址指向一个结构体{data, left, right}包括数据和左右指针,根据datan的大小关系进入leftright进行查找。

  • 如果当前节点为NULL,返回-1
  • 如果当前节点找到,返回0
  • 如果在left中找到,返回fun7(left, n) * 2
  • 如果在right中找到,返回fun7(right, n) * 2 + 1
//fun7(node, n)
08048e94 <fun7>:
 8048e94:	55                   	push   %ebp
 8048e95:	89 e5                	mov    %esp,%ebp
 8048e97:	83 ec 08             	sub    $0x8,%esp
 8048e9a:	8b 55 08             	mov    0x8(%ebp),%edx  //node.data
 8048e9d:	8b 45 0c             	mov    0xc(%ebp),%eax  //n

 8048ea0:	85 d2                	test   %edx,%edx
 8048ea2:	75 0c                	jne    8048eb0 <fun7+0x1c>  

                                
 8048ea4:	b8 ff ff ff ff       	mov    $0xffffffff,%eax
 8048ea9:	eb 37                	jmp    8048ee2 <fun7+0x4e>  //node == NULL,返回-1
 8048eab:	90                   	nop
 8048eac:	8d 74 26 00          	lea    0x0(%esi,%eiz,1),%esi


 8048eb0:	3b 02                	cmp    (%edx),%eax  
 8048eb2:	7d 11                	jge    8048ec5 <fun7+0x31>  //n >= node.data

                                //n < m 递归
 8048eb4:	83 c4 f8             	add    $0xfffffff8,%esp
 8048eb7:	50                   	push   %eax
 8048eb8:	8b 42 04             	mov    0x4(%edx),%eax  //新地址为0x4(%edx) = left
 8048ebb:	50                   	push   %eax
 8048ebc:	e8 d3 ff ff ff       	call   8048e94 <fun7>  //fun7(node.left, n)
 8048ec1:	01 c0                	add    %eax,%eax   //返回2 * %eax
 8048ec3:	eb 1d                	jmp    8048ee2 <fun7+0x4e>


 8048ec5:	3b 02                	cmp    (%edx),%eax  // n == m
 8048ec7:	74 17                	je     8048ee0 <fun7+0x4c>  //返回0

                                //n > m 递归
 8048ec9:	83 c4 f8             	add    $0xfffffff8,%esp
 8048ecc:	50                   	push   %eax
 8048ecd:	8b 42 08             	mov    0x8(%edx),%eax  //新地址为0x8(%edx) = right
 8048ed0:	50                   	push   %eax
 8048ed1:	e8 be ff ff ff       	call   8048e94 <fun7>  //fun7(node.right, n)
 8048ed6:	01 c0                	add    %eax,%eax 
 8048ed8:	40                   	inc    %eax  //返回2 * %eax + 1
 8048ed9:	eb 07                	jmp    8048ee2 <fun7+0x4e>
 8048edb:	90                   	nop
 8048edc:	8d 74 26 00          	lea    0x0(%esi,%eiz,1),%esi


 8048ee0:	31 c0                	xor    %eax,%eax  //置返回值为0

 8048ee2:	89 ec                	mov    %ebp,%esp
 8048ee4:	5d                   	pop    %ebp
 8048ee5:	c3                   	ret    
 8048ee6:	89 f6                	mov    %esi,%esi

用gdb把二叉树各个节点打印出来,观察输入的n是哪个即可:

 十六进制下的树为:

 根据返回值的规则:

  • 7 = 2 * 3 + 1
  • 3 = 2 * 1 + 1
  • 1 =0 * 1 + 1

故是最右侧找不到,例如

1001

结果

其中phase_3phase_5phase_secret的结果不唯一。

Public speaking is very easy.

1 2 6 24 120 720

0 q 777

9 austinpowers

oPekma

4 2 6 3 1 5

1001

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值