Young87

SmartCat's Blog

So happy to code my life!

游戏开发交流QQ群号60398951

当前位置:首页 >跨站数据测试

CSAPP二进制炸弹实验 bomb lab详细解析

前段时间刚刚做完bomb lab实验,记录一下我做CSAPP 二进制炸弹实验的详细过程。有什么问题可以在评论中指出,一起进步。

实验准备

首先需要下载相关的资料。
代码:http://csapp.cs.cmu.edu/3e/bomb.tar

GDB命令文档:http://csapp.cs.cmu.edu/3e/docs/gdbnotes-x86-64.pdf

说明:http://csapp.cs.cmu.edu/3e/bomblab.pdf

README:http://csapp.cs.cmu.edu/3e/REA

实验环境是基于linux x86-64,目的是培养看懂反汇编代码的能力,以及利用gdb进行调试的能力。代码文件解压后有一个bomb和bomb.c,bomb是实验的可执行程序,bomb.c是实验的main函数(里面隐藏了若干函数,需要我们通过bomb的可执行文件进行反汇编,去搜索六个关卡的答案)

第一步:objdump -d bomb > bomb.s将反汇编代码放在bomb.s文本中方便查看
第二步:Ctrl+F快捷键搜索phase_1,这是第一关的反汇编代码,我们要基于此进行拆炸弹。
第三步:仔细分析找到的反汇编代码,在终端中执行 gdb bomb,输入r就开启了该实验,需要输入正确的字符串才能过关。
在这里插入图片描述
实验过程需要通过gdb获得一些信息找到答案,列举一下我用到的gdb调试命令:
b:设置断点。 如b phase_1,表示在phase1函数中设断点
r:执行,直到第一个断点处停止。
ni:单步执行。
x/8x 0x400124:以十六进制打印0x400124的8字节内容
x/8d 0x400124:以十进制打印0x400124的8字节内容
x/2s 0x400124:打印地址0x400124开头的字符串
info reg:打印寄存器的值。

好的,现在我们开始第一关的分析。

第一关

1.phase_1主函数:

0000000000400ee0 <phase_1>:
  400ee0:	48 83 ec 08          	sub    $0x8,%rsp
  400ee4:	be 00 24 40 00       	mov    $0x402400,%esi
  400ee9:	e8 4a 04 00 00       	callq  401338 <strings_not_equal> 
// call一个比较字符串的函数,两字符串不相等则返回1,相等则返回0
  400eee:	85 c0                	test   %eax,%eax
  400ef0:	74 05                	je     400ef7 <phase_1+0x17> 
// 若等于0,则正常返回,不然爆炸
  400ef2:	e8 43 05 00 00       	callq  40143a <explode_bomb>
  400ef7:	48 83 c4 08          	add    $0x8,%rsp
  400efb:	c3                   	retq  

phase_1逻辑很清楚,调用strings_not_equal函数,若返回0则成功,返回1则bomb(),所以先分析后面的函数部分。`

2.strings_not_equal 函数:

0000000000401338 <strings_not_equal>:	
  401338:	41 54                	push   %r12
  40133a:	55                   	push   %rbp
  40133b:	53                   	push   %rbx
  40133c:	48 89 fb             	mov    %rdi,%rbx	#rbx=x
  40133f:	48 89 f5             	mov    %rsi,%rbp	#rbp=y	
  401342:	e8 d4 ff ff ff       	callq  40131b <string_length>		
  401347:	41 89 c4             	mov    %eax,%r12d	# r12d=length(x) 
  40134a:	48 89 ef             	mov    %rbp,%rdi	# rdi=y
  40134d:	e8 c9 ff ff ff       	callq  40131b <string_length>		
  401352:	ba 01 00 00 00       	mov    $0x1,%edx	# edx=1
  401357:	41 39 c4             	cmp    %eax,%r12d			
  40135a:	75 3f                	jne    40139b <strings_not_equal+0x63> 
// if(length(y)!=length(x))-->return 1
  40135c:	0f b6 03             	movzbl (%rbx),%eax			#rax=*x
  40135f:	84 c0                	test   %al,%al				
  401361:	74 25                	je     401388 <strings_not_equal+0x50>
// if(*x==0) -->  {401388}
  401363:	3a 45 00             	cmp    0x0(%rbp),%al			
  401366:	74 0a                	je     401372 <strings_not_equal+0x3a> 
// if(*x==*y) --> {401372} 
  401368:	eb 25                	jmp    40138f <strings_not_equal+0x57>
// else --> return 1
  40136a:	3a 45 00             	cmp    0x0(%rbp),%al	#{40136a}
  40136d:	0f 1f 00             	nopl   (%rax)
  401370:	75 24                	jne    401396 <strings_not_equal+0x5e> 
// if (*x!=*y) -> return 1
  401372:	48 83 c3 01          	add    $0x1,%rbx	#rbx+=1 (x++)
  401376:	48 83 c5 01          	add    $0x1,%rbp	#rbp+=1 (y++)
  40137a:	0f b6 03             	movzbl (%rbx),%eax	#rax=*x
  40137d:	84 c0                	test   %al,%al				
  40137f:	75 e9                	jne    40136a <strings_not_equal+0x32>
//if(*x!=0) ->{40136a}
  401381:	ba 00 00 00 00       	mov    $0x0,%edx
  401386:	eb 13                	jmp    40139b <strings_not_equal+0x63>
// return 0
  401388:	ba 00 00 00 00       	mov    $0x0,%edx	#{401388}:
  40138d:	eb 0c                	jmp    40139b <strings_not_equal+0x63> 
// return 0;
  40138f:	ba 01 00 00 00       	mov    $0x1,%edx
  401394:	eb 05                	jmp    40139b <strings_not_equal+0x63> 
// return 1; 
  401396:	ba 01 00 00 00       	mov    $0x1,%edx
  40139b:	89 d0                	mov    %edx,%eax
  40139d:	5b                   	pop    %rbx
  40139e:	5d                   	pop    %rbp
  40139f:	41 5c                	pop    %r12
  4013a1:	c3                   	retq   

将上述汇编代码忠实地实现为C代码为:

bool strings_not_equal(char *x,char *y)
{
	if (length(x)!=length(y))
		return 1;
	if (*x==0)
		return 0;
	if (*x!=*y)
		return 1;
	else
	{
	do
	{
		if (*x!=*y)
			return 1;
		else 
		{
			x++;
			y++;
		}
	}(while (*x!=0))
	}
	return 0;
} 

简化代码后变为:

bool string_not_equal(char *x,char *y)
{
	if (length(x)!=length(y))
			return 1;
	while (*x!=0)
	{
	if (*x!=*y)
		return 1;
	x++;y++;
	}	
	return 0;
}``

3.string_length 函数:

0000000000401338 <strings_not_equal>:	
  401338:	41 54                	push   %r12
  40133a:	55                   	push   %rbp
  40133b:	53                   	push   %rbx
  40133c:	48 89 fb             	mov    %rdi,%rbx	#rbx=x
  40133f:	48 89 f5             	mov    %rsi,%rbp	#rbp=y	
  401342:	e8 d4 ff ff ff       	callq  40131b <string_length>		
  401347:	41 89 c4             	mov    %eax,%r12d	# r12d=length(x) 
  40134a:	48 89 ef             	mov    %rbp,%rdi	# rdi=y
  40134d:	e8 c9 ff ff ff       	callq  40131b <string_length>		
  401352:	ba 01 00 00 00       	mov    $0x1,%edx	# edx=1
  401357:	41 39 c4             	cmp    %eax,%r12d			
  40135a:	75 3f                	jne    40139b <strings_not_equal+0x63>  
// if(length(y)!=length(x))-->return 1
  40135c:	0f b6 03             	movzbl (%rbx),%eax		#rax=*x
  40135f:	84 c0                	test   %al,%al				
  401361:	74 25                	je     401388 <strings_not_equal+0x50>
  401363:	3a 45 00             	cmp    0x0(%rbp),%al			
  401366:	74 0a                	je     401372 <strings_not_equal+0x3a>
  401368:	eb 25                	jmp    40138f <strings_not_equal+0x57>
  40136a:	3a 45 00             	cmp    0x0(%rbp),%al		
  40136d:	0f 1f 00             	nopl   (%rax)
  401370:	75 24                	jne    401396 <strings_not_equal+0x5e>
  401372:	48 83 c3 01          	add    $0x1,%rbx		#rbx+=1 (x++)
  401376:	48 83 c5 01          	add    $0x1,%rbp		#rbp+=1 (y++)
  40137a:	0f b6 03             	movzbl (%rbx),%eax		#rax=*x
  40137d:	84 c0                	test   %al,%al				
  40137f:	75 e9                	jne    40136a <strings_not_equal+0x32>
  401381:	ba 00 00 00 00       	mov    $0x0,%edx
  401386:	eb 13                	jmp    40139b <strings_not_equal+0x63>
  401388:	ba 00 00 00 00       	mov    $0x0,%edx		
  40138d:	eb 0c                	jmp    40139b <strings_not_equal+0x63>
  40138f:	ba 01 00 00 00       	mov    $0x1,%edx
  401394:	eb 05                	jmp    40139b <strings_not_equal+0x63>
  401396:	ba 01 00 00 00       	mov    $0x1,%edx
  40139b:	89 d0                	mov    %edx,%eax
  40139d:	5b                   	pop    %rbx
  40139e:	5d                   	pop    %rbp
  40139f:	41 5c                	pop    %r12
  4013a1:	c3                   	retq   

将上述汇编代码忠实地实现为C代码为:

int string_length(char *x)	//求字符串长度 
{
	if (*x==0)
		return 0;
	x_begin=x;
	else 
	{
		do
		{
			x++;
		}while(*x !=0)
	}
	return (x-xbegin);
}

简化代码后变为:

int string_length(char *x)
{
	x_begin=x;
	while (*x!=0)
		x++;
	return x-x_begin
}

4.最后的求解结果:

实现了两个函数后很清晰:
string_length(char *):用来求字符串长度
string_not_equal(char *,char *):如其名字一样,当两字符串相等时return 0.
至此,可以完成phase_2函数–>

void phase_2(char *x)
{
	// char *y= 0x402400;  这个里面存着Border relations with Canada have never been better.
	if (string_not_equal(x,y)==0)	//如果相等
		return
	else
		bomb();
}

所以很清楚,Phase_1是要在内存中的input字符串中写入与0x402400中相同的字符串,gdb下通过x/1s 命令查询给定地址的字符串为:
在这里插入图片描述

把input字符串的地址放在rdi中以便传入string_not_eqaul中,并确保input字符串与0x402400的字符串相同。
结果:Border relations with Canada have never been better.

第二关

1.phase_2主函数:

//arg1=input (phase_2有一个参数input,是传入的字符串)
0000000000400efc <phase_2>:
  400efc:	55                   	push   %rbp		
  400efd:	53                   	push   %rbx		
  400efe:	48 83 ec 28          	sub    $0x28,%rsp
  400f02:	48 89 e6             	mov    %rsp,%rsi	
// 参数1=input,参数2=rsp地址,调用函数read_six_number。
  400f05:	e8 52 05 00 00       	callq  40145c <read_six_numbers>

将外部传入的input字符串作为第一参数,(input是什么先不用管,后面就知道了) phase_1中分配一段内存空间后,将该内存空间的首地址作为第二参数传给read_six_numbers函数中(猜测这段内存空间是一个数组)
接下来先进入read_six_numbers函数观察里面发生甚摸事了–>

2…read_six_numbers函数:

// arg1=input,arg2=a (a是已经分配好空间的数组)
000000000040145c <read_six_numbers>:
  40145c:	48 83 ec 18         sub    $0x18,%rsp	#24栈帧
  401460:	48 89 f2            mov    %rsi,%rdx	# rdx=rsi=y
  401463:	48 8d 4e 04         lea    0x4(%rsi),%rcx	# rcx=y+4
  401467:	48 8d 46 14         lea    0x14(%rsi),%rax	# rax=y+20
  40146b:	48 89 44 24 08      mov    %rax,0x8(%rsp)	# (rsp+8)=y+20
  401470:	48 8d 46 10         lea    0x10(%rsi),%rax	# rax=y+16
  401474:	48 89 04 24         mov    %rax,(%rsp)		# (rsp+0)=y+16
  401478:	4c 8d 4e 0c         lea    0xc(%rsi),%r9	# r9=y+12
  40147c:	4c 8d 46 08         lea    0x8(%rsi),%r8	# r8=y+8
  401480:	be c3 25 40 00      mov    $0x4025c3,%esi	# rsi=0x4025c3
  401485:	b8 00 00 00 00      mov    $0x0,%eax		# rax=0
  40148a:	e8 61 f7 ff ff      callq  400bf0 <__isoc99_sscanf@plt>	
  40148f:	83 f8 05            cmp    $0x5,%eax			
  401492:	7f 05               jg     401499 <read_six_numbers+0x3d>	 
// if (eax>5),退出
  401494:	e8 a1 ff ff ff      callq  40143a <explode_bomb>		
// 否則爆炸
  401499:	48 83 c4 18         add    $0x18,%rsp
  40149d:	c3                  retq 

read_six_numbers函数中调用了sscanf函数int sscanf(char *input,char *format,arg1,..),用于将input字符串按照format模式串的形式,将字符输出给指定的变量地址,返回成功输入的参数个数。第二个参数(模式串)rsi=0x4025c3,查找0x4025c3的值为"%d %d %d %d %d %d" .
在这里插入图片描述

所以共需要8个参数,其中两个char *类型,6个int类型的地址。
而寄存器传参最多只能传6个,分别存于rdi rsi rdx rcx r8 r9。
第七个参数需要置于栈顶,第八个参数置于第七个参数上方。
纵观整个流程,应该是在phase_2调用前,先要求我们在终端中输入字符串,
将该地址存在某个地址,假设为input,然后调用phase_2(input),其中会调用
sscanf(input,"%d %d %d %d %d %d",rsp+0,rsp+4,rsp+8,rsp+12,rsp+16,rsp+20)存入数据

将read_six_numbers函数简化为C代码:

int read_six_numbers(char *input,int *a)
// input是phase_2函数从其调用者传入的一个字符串,a是栈上分配的一个数组
{	
	return ssacnf(input,"%d %d %d %d %d %d",&a[0],&a[1],&a[2],&a[3],&a[4]);
// 注意:这里没有实际通过寄存器传8个参数,因为限制最多6个,最后两个通过栈传递
} 

3.回到phase_2主函数:

0000000000400efc <phase_2>:
  400efc:	55                  push   %rbp	# push rbp
  400efd:	53                  push   %rbx	# push rbx
  400efe:	48 83 ec 28         sub    $0x28,%rsp	# rsp-=0x28
  400f02:	48 89 e6            mov    %rsp,%rsi	# rsi=rsp
  400f05:	e8 52 05 00 00      callq  40145c <read_six_numbers>
  400f0a:	83 3c 24 01         cmpl   $0x1,(%rsp)	
  400f0e:	74 20               je     400f30 <phase_2+0x34> 
// if (rsp内存的值为1,转到400f30)
  400f10:	e8 25 05 00 00      callq  40143a <explode_bomb>
// 没转则爆炸
  400f15:	eb 19               jmp    400f30 <phase_2+0x34>		 
  400f17:	8b 43 fc            mov    -0x4(%rbx),%eax	
  400f1a:	01 c0               add    %eax,%eax	# eax=2*eax
  400f1c:	39 03               cmp    %eax,(%rbx)			
  400f1e:	74 05               je     400f25 <phase_2+0x29>
// if (rbx内存的值=eax,转到{400f25})
  400f20:	e8 15 05 00 00      callq  40143a <explode_bomb>
//没转则爆炸
  400f25:	48 83 c3 04         add    $0x4,%rbx	
  400f29:	48 39 eb            cmp    %rbp,%rbx			
  400f2c:	75 e9               jne    400f17 <phase_2+0x1b>		 
// if (rbx不等于rbp,转到{400f17})
  400f2e:	eb 0c               jmp    400f3c <phase_2+0x40>
// 否则,转到{400f3c}
  400f30:	48 8d 5c 24 04      lea    0x4(%rsp),%rbx
  400f35:	48 8d 6c 24 18      lea    0x18(%rsp),%rbp
  400f3a:	eb db               jmp    400f17 <phase_2+0x1b>
  400f3c:	48 83 c4 28         add    $0x28,%rsp
  400f40:	5b                  pop    %rbx
  400f41:	5d                  pop    %rbp
  400f42:	c3                  retq   ss

翻译为C代码,大致为:

void phase_2(char *input)
{
	int a[6];
	if (sscanf(input,"%d %d %d %d %d %d",&a[0],&a[1],&a[2],&a[3],&a[4],&a[5])!=6)
		bomb();
	if (a[0]!=1)
		bomb();
begin:
	int *p=a+1;
	int mid=(*(p-1))*2;
	if (mid!=*p) 
		bomb();
	else goto begin;
	// 展示代码大致逻辑<尽管这个代码不正确>
}
所以整个代码的逻辑是,先判断a[0]是否为1,不是1就bomb。之后需要保证a[n+1]=2*a[n],
才不会bomb所以显然写入  "1 2 4 8 16 32".

结果:"1 2 4 8 16 32".

第三关

Phase_3

0000000000400f43 <phase_3>:
// arg1=input (input是一个字符串)
  400f43:	48 83 ec 18          sub    $0x18,%rsp
  400f47:	48 8d 4c 24 0c       lea    0xc(%rsp),%rcx
  400f4c:	48 8d 54 24 08       lea    0x8(%rsp),%rdx
  400f51:	be cf 25 40 00       mov    $0x4025cf,%esi
  400f56:	b8 00 00 00 00       mov    $0x0,%eax
  400f5b:	e8 90 fc ff ff       callq  400bf0 <__isoc99_sscanf@plt>
  400f60:	83 f8 01             cmp    $0x1,%eax
  400f63:	7f 05                jg     400f6a <phase_3+0x27>
  400f65:	e8 d0 04 00 00       callq  40143a <explode_bomb>
  400f6a:	83 7c 24 08 07       cmpl   $0x7,0x8(%rsp)
  400f6f:	77 3c                ja     400fad <phase_3+0x6a>
  400f71:	8b 44 24 08          mov    0x8(%rsp),%eax
  400f75:	ff 24 c5 70 24 40 00 jmpq   *0x402470(,%rax,8)
  400f7c:	b8 cf 00 00 00       mov    $0xcf,%eax
  400f81:	eb 3b                jmp    400fbe <phase_3+0x7b>
  400f83:	b8 c3 02 00 00       mov    $0x2c3,%eax
  400f88:	eb 34                jmp    400fbe <phase_3+0x7b>
  400f8a:	b8 00 01 00 00       mov    $0x100,%eax
  400f8f:	eb 2d                jmp    400fbe <phase_3+0x7b>
  400f91:	b8 85 01 00 00       mov    $0x185,%eax
  400f96:	eb 26                jmp    400fbe <phase_3+0x7b>
  400f98:	b8 ce 00 00 00       mov    $0xce,%eax
  400f9d:	eb 1f                jmp    400fbe <phase_3+0x7b>
  400f9f:	b8 aa 02 00 00       mov    $0x2aa,%eax
  400fa4:	eb 18                jmp    400fbe <phase_3+0x7b>
  400fa6:	b8 47 01 00 00       mov    $0x147,%eax
  400fab:	eb 11                jmp    400fbe <phase_3+0x7b>
  400fad:	e8 88 04 00 00       callq  40143a <explode_bomb>
  400fb2:	b8 00 00 00 00       mov    $0x0,%eax
  400fb7:	eb 05                jmp    400fbe <phase_3+0x7b>
  400fb9:	b8 37 01 00 00       mov    $0x137,%eax
  400fbe:	3b 44 24 0c          cmp    0xc(%rsp),%eax
  400fc2:	74 05                je     400fc9 <phase_3+0x86>
  400fc4:	e8 71 04 00 00       callq  40143a <explode_bomb>
  400fc9:	48 83 c4 18          add    $0x18,%rsp
  400fcd:	c3                   retq   

phase_3中同样调用了sscanf,第二个参数rsi在调用前赋予了0x4025cf
gdb中用 x/1s 0x4025cf查到是:“%d %d”,说明这个是输入两个int整数。在这里插入图片描述然后写入第三、四参数,即rdx,rcx, rcx=rsp+0xcrdx=rsp+0x8
rdx和rcx其实是一个数组,令:rdx<=>&a[0],rcx<=>&a[1]

同样的套路,在调用Phase_3之前先要求按"%d %d"来将数据流写入到char *input
然后调用sscanf(char *input,"%d %d",&a[0],&a[1])

若sscanf的返回值<=1,直接爆炸(说明先确保按正确的格式输入)。跳出sscanf后,if (a[0]<0 或 a[0]>7),也直接爆炸。(保证0 =< a[0] <=7)

后面有一个重要的命令:jmpq *0x402470(,%rax,8),由于rax=a[0],所以表明跳转到地址(0x402470+a[0]*8)中存放的地址(代码)。gdb下输入 x/8gx 0x402470得到:
在这里插入图片描述
这摆明是以rdx为索引的跳转表啊!
图中所写入的8个地址都在phase_3代码段中可查,switch的索引a[0]可以取0,1,2,3,4,5,6,7以及default,转成代码为:

void phase_3(char *input)
{
	int a[2];
	if ( sscanf(input,"%d %d",&a[0],&a[1]) != 2)
		bomb();
	switch (a[0]){
	case 0: goto 0x400f7c; break;
	case 1: goto 0x400fb9; break;
	case 2: goto 0x400f83; break;
	case 3: goto 0x400f8a; break;
	case 4: goto 0x400f91; break;
	case 5: goto 0x400f98; break;
	case 6: goto 0x400f9f; break;
	case 7: goto 0x400fa6; break;
	default:	bomb();
	}	
}

a[0]=0为例,真实的代码存放在0x402470对应的实际代码地址为400f7c:

  400f7c:	b8 cf 00 00 00       	mov    $0xcf,%eax
// eax=0xcf
  400fbe:	3b 44 24 0c          	cmp    0xc(%rsp),%eax			
  400fc2:	74 05                	je     400fc9 <phase_3+0x86>	
// if (eax==a[1])->成功! --> 所以a[1]=0xcf = 
  400fc4:	e8 71 04 00 00       	callq  40143a <explode_bomb>
  400fc9:	48 83 c4 18          	add    $0x18,%rsp	
// 同样的,可以令a[0]取另外7个数,也有对应的a[1]使得关卡通过。

因而,其中一个答案为:"0 207"

第四关

phase4汇编代码:

000000000040100c <phase_4>:
  40100c:	48 83 ec 18          sub    $0x18,%rsp				#rsp-=0x18						
  401010:	48 8d 4c 24 0c       lea    0xc(%rsp),%rcx			#rcx=rsp+0xc
  401015:	48 8d 54 24 08       lea    0x8(%rsp),%rdx			#rdx=rsp+0x8
  40101a:	be cf 25 40 00       mov    $0x4025cf,%esi			#rsi=0x4025cf
  40101f:	b8 00 00 00 00       mov    $0x0,%eax				#eax=0
  401024:	e8 c7 fb ff ff       callq  400bf0 <__isoc99_sscanf@plt>
  #查(gdb)x/1s 0x4025cf"%d %d",所以sscanf两个int型整数,第1个数据x存于0x8(%rsp),2个数据y存于0xc(%rsp)
  401029:	83 f8 02             cmp    $0x2,%eax							
  40102c:	75 07                jne    401035 <phase_4+0x29>	#if (rax!=2)->bomb()
  40102e:	83 7c 24 08 0e       cmpl   $0xe,0x8(%rsp)			#else
  401033:	76 05                jbe    40103a <phase_4+0x2e>	#if (0=<x<=0xe)->{40103a}
  401035:	e8 00 04 00 00       callq  40143a <explode_bomb>	#else bomb()
  40103a:	ba 0e 00 00 00       mov    $0xe,%edx				#edx=0xe
  40103f:	be 00 00 00 00       mov    $0x0,%esi				#esi=0
  401044:	8b 7c 24 08          mov    0x8(%rsp),%edi			#edi=x
  401048:	e8 81 ff ff ff       callq  400fce <func4>			
  40104d:	85 c0                test   %eax,%eax				
  40104f:	75 07                jne    401058 <phase_4+0x4c>	#if(返回值!=0)->bomb()
  401051:	83 7c 24 0c 00       cmpl   $0x0,0xc(%rsp)			#else
  401056:	74 05                je     40105d <phase_4+0x51>	#if (y!=0)->bomb()
  401058:	e8 dd 03 00 00       callq  40143a <explode_bomb>	#else
  40105d:	48 83 c4 18          add    $0x18,%rsp				#->成功

Phase_4思路:
调用ssacnf(input,"%d %d",&x,&y),必须成功输入到&x和&y中,必须使得 x>=0 && x<=0xe,然后置参数1:edi=x ,参数2:esi=0,参数3:edx=0xe 调用func4函数。必须保证返回值为0且y的值为0才成功。所以y的值确定为0,x的值在0~0xe之间,确切可行解需进入func4()分析。

编写成C代码为:

void func4(char *input)
{
	int y;
	int x;
	if (sscanf(input,"%d %d",&x,&y)!=2)
		bomb();
	if (x<0 || x>0xe)
		bomb();
	int result=func4(x,0,0xe,&y);
	if (y!=0 || result!=0)
		bomb();
	return;
} 

现在,就剩下进入func4函数中进行分析:

0000000000400fce <func4>:
// func4(a=x,b=0,c=0xe)
// a,b,c分别存于rdi,rsi,rdx
  400fce:	48 83 ec 08          sub    $0x8,%rsp				#rsp-=8		
  400fd2:	89 d0                mov    %edx,%eax				#rax=0xe
  400fd4:	29 f0                sub    %esi,%eax				#rax-=b
  400fd6:	89 c1                mov    %eax,%ecx				###rcx=rax
  400fd8:	c1 e9 1f             shr    $0x1f,%ecx				
  400fdb:	01 c8                add    %ecx,%eax				#rax+=rax>>0x1f
  400fdd:	d1 f8                sar    %eax					#rax= rax/2
  400fdf:	8d 0c 30             lea    (%rax,%rsi,1),%ecx		#rcx=rax+rsi
  400fe2:	39 f9                cmp    %edi,%ecx				
  400fe4:	7e 0c                jle    400ff2 <func4+0x24>		#if(rcx<=rdi)->{400ff2}
  400fe6:	8d 51 ff             lea    -0x1(%rcx),%edx			#rax=rcx-1
  400fe9:	e8 e0 ff ff ff       callq  400fce <func4>			#callq <func4>
  400fee:	01 c0                add    %eax,%eax
  400ff0:	eb 15                jmp    401007 <func4+0x39>	
  400ff2:	b8 00 00 00 00       mov    $0x0,%eax				#rax=0
  400ff7:	39 f9                cmp    %edi,%ecx
  400ff9:	7d 0c                jge    401007 <func4+0x39>		#if(rcx>=rdi)->{401007}
  400ffb:	8d 71 01             lea    0x1(%rcx),%esi			#else rsi=rcx+1
  400ffe:	e8 cb ff ff ff       callq  400fce <func4>			#callq <func4>
  401003:	8d 44 00 01          lea    0x1(%rax,%rax,1),%eax	#rax=2*rax+1
  401007:	48 83 c4 08          add    $0x8,%rsp				#rsp+=8
  40100b:	c3                   retq 							#return rax 

分析其逻辑,将func4汇编代码的逻辑转化为C代码:

int func4(a,b,c)
{
	int mid=c-b;
	mid=(mid>>31+mid)/2+b;
	if (mid<a)
		return 2*func4(a+mid+1,c)+1;
	else if (mid>a)
		return 2*func4(a,b,mid-1);
	else
		return 0;
}
// 一开始传入的参数为func4(x,0,14)
// 首次传入进入func4的mid值为7,因而若x=7,可以直接return 0
// 其他答案难以直观看出,可编代码试验,在此不做赘述

因而,得到其中一个解:“7 0” .

第五关

一、Phase_5反汇编及分析

0000000000401062 <phase_5>:
// input in rdi
  401062:	53                   	push   %rbx						
  401063:	48 83 ec 20          	sub    $0x20,%rsp		# 分配32空间
  401067:	48 89 fb             	mov    %rdi,%rbx		
  40106a:	64 48 8b 04 25 28 00 	mov    %fs:0x28,%rax		#
  401071:	00 00 
  401073:	48 89 44 24 18       	mov    %rax,0x18(%rsp)	# 0x18(rsp)=rax
  401078:	31 c0                	xor    %eax,%eax		# eax置0
  40107a:	e8 9c 02 00 00       	callq  40131b <string_length>
// 要求返回值为6,所以猜输入的是长度为6的字符串
  40107f:	83 f8 06             	cmp    $0x6,%eax
  401082:	74 4e                	je     4010d2 <phase_5+0x70>	#不然爆炸
  401084:	e8 b1 03 00 00       	callq  40143a <explode_bomb>
  401089:	eb 47                	jmp    4010d2 <phase_5+0x70>
  
 #### 循环起点 ####
  40108b:	0f b6 0c 03          	movzbl (%rbx,%rax,1),%ecx 
// 循环最开始rax=0,rbx是从rdi中导入,是一个字符串,所以将字符串首字符传入ecx,用0补齐
  40108f:	88 0c 24             	mov    %cl,(%rsp)	# 刚传入的字符导入栈顶
  401092:	48 8b 14 24          	mov    (%rsp),%rdx	
  401096:	83 e2 0f             	and    $0xf,%edx	# edx=刚传入的字符高4位归0
  401099:	0f b6 92 b0 24 40 00 	movzbl 0x4024b0(%rdx),%edx	
// 以0x4024b0这一特定地址为基址,以传入的字符为下标找到该地址下的1字节
// 
  4010a0:	88 54 04 10          	mov    %dl,0x10(%rsp,%rax,1)	
// 该字符存于 rsp+0x10+rax 地址中
  4010a4:	48 83 c0 01          	add    $0x1,%rax			
  4010a8:	48 83 f8 06          	cmp    $0x6,%rax
  4010ac:	75 dd                	jne    40108b <phase_5+0x29> #rax!=6->回到循环开始
#### 循环结束 ####
// 这段循环用C代码表示为:
	for (int i=0;i<6;i++){
		a[i] = src(input[i] & 0xf);
	}
//

 其中a是rsp+0x10为地址的字符串,src=0x4024b0,input为phase_5的输入参数

  4010ae:	c6 44 24 16 00       	movb   $0x0,0x16(%rsp)	# (rsp+24)=0
  4010b3:	be 5e 24 40 00       	mov    $0x40245e,%esi		# 参数2=0x40245e,查得"flyers"
  4010b8:	48 8d 7c 24 10       	lea    0x10(%rsp),%rdi	# 参数1=数组a的首元素地址
  4010bd:	e8 76 02 00 00       	callq  401338 <strings_not_equal>
  4010c2:	85 c0                	test   %eax,%eax
  4010c4:	74 13                	je     4010d9 <phase_5+0x77>	# 字符串必须相等
  4010c6:	e8 6f 03 00 00       	callq  40143a <explode_bomb>
  4010cb:	0f 1f 44 00 00       	nopl   0x0(%rax,%rax,1)
  4010d0:	eb 07                	jmp    4010d9 <phase_5+0x77>
  4010d2:	b8 00 00 00 00       	mov    $0x0,%eax		#eax置0
  4010d7:	eb b2                	jmp    40108b <phase_5+0x29>
  
  4010d9:	48 8b 44 24 18       	mov    0x18(%rsp),%rax
  4010de:	64 48 33 04 25 28 00 	xor    %fs:0x28,%rax
  4010e5:	00 00 
  4010e7:	74 05                	je     4010ee <phase_5+0x8c>
  4010e9:	e8 42 fa ff ff       	callq  400b30 <__stack_chk_fail@plt>
  4010ee:	48 83 c4 20          	add    $0x20,%rsp
  4010f2:	5b                   	pop    %rbx
  4010f3:	c3                   	retq   

二、问题求解及结果
对0x4024b0和0x40245e进行查询,查询结果如下表所示:

查询结果
(gdb) x/1s 0x4024b0“maduiersnfotvbylSo you think you can stop the bomb”
(gdb) x/1s 0x40245e“flyers”

由于字符串a和字符串tar="flyers"相等,即a="flyers" 所以src[input[i]&&0xf]=tar[i],得到下表:

tar[i]charinput[i]&0xfinput[i]
tar[0]f9) 9 i y I Y
tar[1]l15/ ? O _ o
tar[2]y14. > N ^ n -
tar[3]e5% e u E U
tar[4]r6& 6 f v F V
tar[5]s77 G W g w

tar=flyers,转换成src字符串的下标分别为9,15,14,5,6,7以该下标作为末四位查Ascii表即可得到input字符串前六个字符。在每个i在input[i]中任取1即可。
其中一个答案为:YONUVW
在这里插入图片描述

第六关

由于第六关的汇编代码太长且复杂,需要非常耐心地进行分析,故将整个汇编代码分为几个部分详细说明。
一、Part1

00000000004010f4 <phase_6>:
// arg1=input(input是从外部传入的字符串)
  4010f4:	41 56              push   %r14
  4010f6:	41 55              push   %r13
  4010f8:	41 54              push   %r12
  4010fa:	55                 push   %rbp
  4010fb:	53                 push   %rbx
  4010fc:	48 83 ec 50        sub    $0x50,%rsp	
  401100:	49 89 e5           mov    %rsp,%r13
  401103:	48 89 e6           mov    %rsp,%rsi
  401106:	e8 51 03 00 00     callq  40145c <read_six_numbers>
  
// read_six_numbers
  int read_six_numbers(input,a)	
  {//input是phase_6的参数1,a是在栈中分配的一个int*数组
  	return (sscanf(input,"%d %d %d %d %d %d",&a[0],&a[1],&a[2],&a[3],&a[4],&a[5]));
  }
//
  40110b:	49 89 e6           mov    %rsp,%r14	
  40110e:	41 bc 00 00 00 00  mov    $0x0,%r12d	
  
>>>>>>>>>>>>>>>>>>>>>>>>>>> 循环开始<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
// 令 r12d<=>i , rbx<=>j (后面注释的i和j代表这两个寄存器的值)
  401114:	4c 89 ed           mov    %r13,%rbp	
  401117:	41 8b 45 00        mov    0x0(%r13),%eax	# eax=a[i]
  40111b:	83 e8 01           sub    $0x1,%eax	# eax -= 1
  40111e:	83 f8 05           cmp    $0x5,%eax
  401121:	76 05              jbe    401128 <phase_6+0x34>
// if (eax>=0 && eax<=5) -> jmp     else -> bomb (所以eax必须在区间[0,5])
  401123:	e8 12 03 00 00     callq  40143a <explode_bomb>
  401128:	41 83 c4 01        add    $0x1,%r12d	# i++
// r12d一开始为0,这边加1,感觉会是个用于循环的计数器
  40112c:	41 83 fc 06        cmp    $0x6,%r12d
  401130:	74 21              je     401153 <phase_6+0x5f>	# 等于6时跳出循环
  401132:	44 89 e3           mov    %r12d,%ebx	# 否则继续,j初始化=i+1

>>>>>>>>>>>>>>>>>>>>>>>>>>> 内循环开始<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
  401135:	48 63 c3           movslq %ebx,%rax
  401138:	8b 04 84           mov    (%rsp,%rax,4),%eax	# 取下一个数到%eax
  40113b:	39 45 00           cmp    %eax,0x0(%rbp) #该数不能与a[i]相同,否则爆炸
  40113e:	75 05              jne    401145 <phase_6+0x51>
  401140:	e8 f5 02 00 00     callq  40143a <explode_bomb>
  401145:	83 c3 01           add    $0x1,%ebx	# ebx+=1
  401148:	83 fb 05           cmp    $0x5,%ebx
  40114b:	7e e8              jle    401135 <phase_6+0x41>
// ebx开始时为i,if (ebx<=5) -> 跳出循环
>>>>>>>>>>>>>>>>>>>>>>>>>>> 内循环结束<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

  40114d:	49 83 c5 04          	add    $0x4,%r13	 
  401151:	eb c1                	jmp    401114 <phase_6+0x20>
>>>>>>>>>>>>>>>>>>>>>>>>>>> 循环结束<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
// 这后面还一大段代码,暂作省略,在后面部分再进行分析

上面是一个多重循环,详细注释已在上写出,分析清楚逻辑不难写成C代码:

for (int i=0; i<6; i++){	
	if (a[i]-1>5 || a[i]-1<0)	bomb();
	for (int j=i+1;j<=5;j++){
		if (a[j] == a[i])	bomb(); 
	}
}

这段代码逻辑很清晰,就是保证6个数都处于区间[1,6]中且不能重复,下面进入代码分析的第二部分。

二、Part2


  401153:	48 8d 74 24 18       	lea    0x18(%rsp),%rsi # 边界&a[6]	
  401158:	4c 89 f0             	mov    %r14,%rax # rax=&a[0]
  40115b:	b9 07 00 00 00       	mov    $0x7,%ecx	
>>>>>>>>>>>>>>>>>>>>>>>>>>> 循环开始<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
  401160:	89 ca                	mov    %ecx,%edx
  401162:	2b 10                	sub    (%rax),%edx # edx =7-a[0]
  401164:	89 10                	mov    %edx,(%rax) # a[0]=7-a[0]
  401166:	48 83 c0 04          	add    $0x4,%rax # rax指向下一个数
  40116a:	48 39 f0             	cmp    %rsi,%rax
  40116d:	75 f1                	jne    401160 <phase_6+0x6c>
  上面这个循环很清楚是将a[i]线性变换为7-a[i].
>>>>>>>>>>>>>>>>>>>>>>>>>>> 循环结束<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

这部分代码很简单,就是将a[i]变换为7-a[i],写成代码就是

for (int i=0;i<6;i++)
	a[i] = 7-a[i];

继续进入第三部分。
三、Part3

  40116f:	be 00 00 00 00     mov    $0x0,%esi
// esi = 0 感觉又是一个计数器 令:esi<=>i
  401174:	eb 21              jmp    401197 <phase_6+0xa3>
>>>>>>>>>>>>>>>>>>>>>>>>>>> 循环开始<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
#############小循环1开始:
  401176:	48 8b 52 08        mov    0x8(%rdx),%rdx
// rdx=mem[rdx+0x8],这种写法很奇怪,猜想rdx=是一个结构体,且0x8偏移为该结构体指针
  40117a:	83 c0 01           add    $0x1,%eax # i++
  40117d:	39 c8              cmp    %ecx,%eax  
// ecx=7,eax一开始=&a[6]
  40117f:	75 f5              jne    401176 <phase_6+0x82>
#############小循环1结束。

  401181:	eb 05              jmp    401188 <phase_6+0x94>
#############小循环2开始:
  401183:	ba d0 32 60 00     mov    $0x6032d0,%edx
  401188:	48 89 54 74 20     mov    %rdx,0x20(%rsp,%rsi,2)
  // mem(a+0x20+2i)=0x4032d0
  40118d:	48 83 c6 04        add    $0x4,%rsi # 另外一个循环
  401191:	48 83 fe 18        cmp    $0x18,%rsi # 保证每个a[i]都运行过
  401195:	74 14              je     4011ab <phase_6+0xb7> # 退出循环
  401197:	8b 0c 34           mov    (%rsp,%rsi,1),%ecx # 指针偏移,依次获取6个数
  40119a:	83 f9 01           cmp    $0x1,%ecx 
  40119d:	7e e4              jle    401183 <phase_6+0x8f> #<=1则跳回开头
#############小循环2结束。

  40119f:	b8 01 00 00 00     mov    $0x1,%eax
  4011a4:	ba d0 32 60 00     mov    $0x6032d0,%edx
  4011a9:	eb cb              jmp    401176 <phase_6+0x82>
>>>>>>>>>>>>>>>>>>>>>>>>>>> 循环结束<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

这部分内容有点复杂且不知所云,那么先用gdb看看里面的 0x6032d0是个啥东西。
在这里插入图片描述这个名字叫node1,再结合上面的mov 0x8(%rdx),%rdx ,猜测这是一个结构体且8字节的位置是这个结构体的指针,我们尝试打印更多信息。
在这里插入图片描述从上面的信息可以看出,该0~3字节是一个不知道是什么的数据,4~7字节是node的编号,8~15字节是应该是一个指针,0x6032d0结构体中的指针为0x006032e0,这个指针指向node2,同样node2中的指针指向node3 …最后一个node6中的指针指向地址0。该结构体是一个链表啊!

可以大致确定这个结构体为:

struct node{
int val;//这个暂不知道是什么数据 
int id;//id是编号 
node *next;	//next是指向的node的地址
}

再看这段的汇编代码,将第三部分忠实地逆向得:

while(1){
	cx=*(sp+si*1);  // cx=sp+0,sp+0x4,sp+0x8,sp+0xc,....
	if(cx<=1) dx=0x6032d0; //dx=表头地址
	else {
		ax=1;
		dx=0x6032d0;
		//遍历链表,使得dx=第sp[si/4]项的地址
		do{	
			dx=*(dx+0x8);	//dx=(dx.next)->val
			ax+=1;
		}while(ax!=cx);
	}	
	*(sp+si*2+0x20)=dx;//从sp+0x20起,每8字节记录一个链表项地址
	si+=4;
	if(si==0x18) break; //sp+0x18,即&sp[6]
}

简化为C代码为:

node *n[6];	//n = sp+0x20
for (int i=0;i<6;i++)
{	
	addr = 0x6032d0;
	for (int j=0;j<val[i];j++)
		addr = *(addr+0x8);
	n[i]=addr;
}

C代码就很清楚了,一开始的节点规定为0x6032d0,即node1的地址,然后是不断地找当前节点的next节点(next的次数为数组的值),找到后放在新分配的sp+0x20内存中。也就是相当于根据数组值,给6个node节点重新排列,并放于其实地址为sp+0x20指针数组n[6]中。
第三部分真是费劲,但是基本搞清这个node是什么了,接下来进入Part4部分。
四、Part4

  4011ab:	48 8b 5c 24 20     mov    0x20(%rsp),%rbx # rbx=n[0]
  4011b0:	48 8d 44 24 28     lea    0x28(%rsp),%rax # rax=&n[1]
  4011b5:	48 8d 74 24 50     lea    0x50(%rsp),%rsi # 边界 rsi=&n[6]
  4011ba:	48 89 d9           mov    %rbx,%rcx	# rcx=rbx
>>>>>>>>>>>>>>>>>>>>>>>>>>> 循环开始<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
// rax是数组n的下一个值的地址,rcx是数组n前一个值,记:rax=&n[i+1] rcx=n[i] rdx=n[i+1]
  4011bd:	48 8b 10           mov    (%rax),%rdx	# rdx=n[i+1]
  4011c0:	48 89 51 08        mov    %rdx,0x8(%rcx)	#n[i]->next=n[i+1]
  4011c4:	48 83 c0 08        add    $0x8,%rax 	# rax+=8
  4011c8:	48 39 f0           cmp    %rsi,%rax
  4011cb:	74 05              je     4011d2 <phase_6+0xde>
// if (rax==&n[6]),即 i->退出循环
  4011cd:	48 89 d1           mov    %rdx,%rcx
// else -> rcx=rdx , 回到循环开始循环
  4011d0:	eb eb              jmp    4011bd <phase_6+0xc9>
>>>>>>>>>>>>>>>>>>>>>>>>>>> 循环结束<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
  4011d2:	48 c7 42 08 00 00 00 	movq   $0x0,0x8(%rdx)
  4011d9:	00 

一开始i=0 rcx=n[0]rax=&n[1]rdx=n[1],使得rcx->next=rdx,即n[0]->next=n[1]
然后循环一次后变成 -> rcx=n[1]rax=&n[2]rdx=n[2],使得n[1]->next=n[2]
最后一次循环时,rcx=n[4]rax=&n[5]rdx=n[5],使得n[4]->next=n[5],再将当前的n[5]的next指向地址0,结束Part4的部分。

将其上述过程逆向得:

bx=n[0]
ax=&n[1]
si=&n[6]
cx=bx
while(1)
{	
   dx = *ax
   cx->next = dx
   ax+=0x8
   if(ax==si) {
// n[0]~n[4] 都已经分配了next,再给n[5]分配0地址为next
   		dx->next=0;
   		break;
   }	
}

这一部分就是把按照数组n的顺序去修改链表的next,使得该链表按数组的顺序串成顺链。
下面进入最后一部分的分析,答案近在咫尺!
五、Part5

  4011da: mov    $0x5,%ebp
  4011df: mov    0x8(%rbx),%rax # rax=rbx的下一个节点的指针
  4011e3: mov    (%rax),%eax # 结构体中val的值 保存在rax中
  4011e5: cmp    %eax,(%rbx) # 比较两个node的val值
  4011e7: jge    4011ee <phase_6+0xfa> # 如果靠前结点的val < 靠后结点的val
  4011e9: callq  40143a <explode_bomb> # 爆炸
  4011ee: mov    0x8(%rbx),%rbx # 移动指针
  4011f2: sub    $0x1,%ebp  
  4011f5: jne    4011df <phase_6+0xeb> # 循环
  4011f7: add    $0x50,%rsp
  4011fb: pop    %rbx
  4011fc: pop    %rbp
  4011fd: pop    %r12
  4011ff: pop    %r13
  401201: pop    %r14
  401203: retq

最后一段逻辑很清晰,即要求排序按val递减,否则爆炸。
上面分析了这么多,是时候全部整合起来并出结果了!
进入最后一部分,结果的呈现。

六、Part6 在这里插入图片描述

val:
node1=14c	node2=0a8	node3=39c
node4=2b3	node5=1dd   node6=1bb

根据val从大到小排序分别为:3 4 5 6 1 2,但由于之前进行了一次a[i]=7-[i]的线性变换,所以现在的3 4 5 6 1 2即为之前的4 3 2 1 6 5
结果:4 3 2 1 6 5

至此,lab1~lab6已全部完成,运行一下来看看结果吧
在这里插入图片描述这个实验花了好久,尤其是最后一个Phase,太难搞了不过也终于把bomb lab给完成了,看到弹出的 Congratulations!还是挺激动的。
下面附上六关全部的结果吧

Border relations with Canada have never been better.
1 2 4 8 16 32
0 207
7 0
YONUVW
4 3 2 1 6 5

除特别声明,本站所有文章均为原创,如需转载请以超级链接形式注明出处:SmartCat's Blog

上一篇: keil下的FreeRtos多任务程序

下一篇: PowerDesigner书签(02)导入SQL脚本生成ER图

精华推荐