CSAPP二進位制炸彈實驗 bomb lab詳細解析

Eternitykc發表於2020-12-01

前段時間剛剛做完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

相關文章