一步一步學(xué)ROP之linux_x86篇

最近在學(xué)蒸米的《一步一步學(xué)ROP之linux_x86篇》渗常,內(nèi)容寫的很詳細(xì),個(gè)人學(xué)到了很多屡限,但同時(shí)學(xué)的過(guò)程中也有很多疑惑兼耀,然后文章有些地方也有些小錯(cuò)誤压昼,所以在這里自己再補(bǔ)充并修正一些方便自己日后回顧學(xué)習(xí)。如果有冒犯到博主在這里說(shuō)聲抱歉瘤运。(如侵權(quán)刪)

原文章地址:https://yq.aliyun.com/articles/58699


一窍霞、序

ROP的全稱為Return-oriented programming(返回導(dǎo)向編程),這是一種高級(jí)的內(nèi)存攻擊技術(shù)可以用來(lái)繞過(guò)現(xiàn)代操作系統(tǒng)的各種通用防御(比如內(nèi)存不可執(zhí)行和代碼簽名等)拯坟。雖然現(xiàn)在大家都在用64位的操作系統(tǒng)但金,但是想要扎實(shí)的學(xué)好ROP還是得從基礎(chǔ)的x86系統(tǒng)開始,但看官請(qǐng)不要著急郁季,在隨后的教程中我們還會(huì)帶來(lái)linux_x64以及android (arm)方面的ROP利用方法冷溃,歡迎大家繼續(xù)學(xué)習(xí)。

小編備注:文中涉及代碼可在文章最后的github鏈接找到梦裂。


二似枕、Control Flow Hijack 程序流劫持

比較常見(jiàn)的程序流劫持就是棧溢出贮聂,格式化字符串攻擊和堆溢出了槽卫。通過(guò)程序流劫持,攻擊者可以控制PC指針從而執(zhí)行目標(biāo)代碼拦键。為了應(yīng)對(duì)這種攻擊,系統(tǒng)防御者也提出了各種防御方法毅往,最常見(jiàn)的方法有DEP,NX(堆棧不可執(zhí)行)牵咙,ASLR(內(nèi)存地址隨機(jī)化),Stack Protector(棧保護(hù))等攀唯〗嘧溃可以在linux下使用命令

checksec + 目標(biāo)文件

查看文件所使用的防御

沒(méi)有開任何防御

但是如果上來(lái)就部署全部的防御,初學(xué)者可能會(huì)覺(jué)得無(wú)從下手侯嘀,所以我們先從最簡(jiǎn)單的沒(méi)有任何保護(hù)的程序開始另凌,隨后再一步步增加各種防御措施,接著再學(xué)習(xí)繞過(guò)的方法戒幔,循序漸進(jìn)吠谢。

首先來(lái)看這個(gè)有明顯緩沖區(qū)溢出的程序:

這里我們用

gcc -fno-stack-protector -z execstack -o level1 level1.c

這個(gè)命令編譯程序。-fno-stack-protector和-z execstack這兩個(gè)參數(shù)會(huì)分別關(guān)掉Stack Protector和NX(linux下開啟了NX的以及windows下開啟了DEP的程序堆棧是不可執(zhí)行的诗茎,linux下的程序默認(rèn)編譯時(shí)是開啟了NX的)工坊。同時(shí)我們?cè)趕hell中執(zhí)行:

這幾個(gè)指令。執(zhí)行完后我們就關(guān)掉整個(gè)linux系統(tǒng)的ASLR保護(hù)敢订。

接下來(lái)我們開始對(duì)目標(biāo)程序進(jìn)行分析王污。首先我們先來(lái)確定溢出點(diǎn)的位置,這里我推薦使用pattern.py這個(gè)腳本來(lái)進(jìn)行計(jì)算楚午。我們使用如下命令:

來(lái)生成一串測(cè)試用的150個(gè)字節(jié)的字符串:

隨后我們使用gdb ./level1調(diào)試程序昭齐。

我們可以得到內(nèi)存出錯(cuò)的地址為0x37654136。隨后我們使用命令:

就可以非常容易的計(jì)算出PC返回值的覆蓋點(diǎn)為140個(gè)字節(jié)矾柜。我們只要構(gòu)造一個(gè)”A”*140+ret字符串阱驾,就可以讓pc執(zhí)行ret地址上的代碼了。

接下來(lái)我們需要一段shellcode把沼,可以用msf生成啊易,或者自己反編譯一下吁伺。

這里我們使用一段最簡(jiǎn)單的執(zhí)行execve ("/bin/sh")命令的語(yǔ)句作為shellcode饮睬。

溢出點(diǎn)有了,shellcode有了篮奄,下一步就是控制PC跳轉(zhuǎn)到shellcode的地址上:

[shellcode][“AAAAAAAAAAAAAA”….][ret]
^------------------------------------------------|

對(duì)初學(xué)者來(lái)說(shuō)這個(gè)shellcode地址的位置其實(shí)是一個(gè)坑捆愁。因?yàn)檎5乃季S是使用gdb調(diào)試目標(biāo)程序,然后查看內(nèi)存來(lái)確定shellcode的位置窟却。但當(dāng)你真的執(zhí)行exp的時(shí)候你會(huì)發(fā)現(xiàn)shellcode壓根就不在這個(gè)地址上昼丑!這是為什么呢?原因是gdb的調(diào)試環(huán)境會(huì)影響buf在內(nèi)存中的位置夸赫,雖然我們關(guān)閉了ASLR菩帝,但這只能保證buf的地址在gdb的調(diào)試環(huán)境中不變,但當(dāng)我們直接執(zhí)行./level1的時(shí)候,buf的位置會(huì)固定在別的地址上呼奢。怎么解決這個(gè)問(wèn)題呢宜雀?

最簡(jiǎn)單的方法就是開啟core dump這個(gè)功能。

開啟之后握础,當(dāng)出現(xiàn)內(nèi)存錯(cuò)誤的時(shí)候辐董,系統(tǒng)會(huì)生成一個(gè)core dump文件在tmp目錄下。然后我們?cè)儆胓db查看這個(gè)core文件就可以獲取到buf真正的地址了禀综。

因?yàn)橐绯鳇c(diǎn)是140個(gè)字節(jié)简烘,再加上4個(gè)字節(jié)的ret地址,我們可以計(jì)算出buffer的地址為$esp-144定枷。通過(guò)gdb的命令 “x/10s $esp-144”孤澎,我們可以得到buf的地址為0xbffff290。

OK欠窒,現(xiàn)在溢出點(diǎn)亥至,shellcode和返回值地址都有了,可以開始寫exp了贱迟。寫exp的話姐扮,我強(qiáng)烈推薦pwntools這個(gè)工具,因?yàn)樗梢苑浅7奖愕淖龅奖镜卣{(diào)試和遠(yuǎn)程攻擊的轉(zhuǎn)換衣吠。本地測(cè)試成功后只需要簡(jiǎn)單的修改一條語(yǔ)句就可以馬上進(jìn)行遠(yuǎn)程攻擊茶敏。

最終本地測(cè)試代碼如下:

執(zhí)行exp:

接下來(lái)我們把這個(gè)目標(biāo)程序作為一個(gè)服務(wù)綁定到服務(wù)器的某個(gè)端口上,這里我們可以使用socat這個(gè)工具來(lái)完成缚俏,命令如下:

隨后這個(gè)程序的IO就被重定向到10001這個(gè)端口上了(ctrl+c關(guān)閉)惊搏,并且可以使用 nc 127.0.0.1 10001來(lái)訪問(wèn)我們的目標(biāo)程序服務(wù)了。

因?yàn)楝F(xiàn)在目標(biāo)程序是跑在socat的環(huán)境中忧换,exp腳本除了要把p = process('./level1')換成p = remote('127.0.0.1',10001) 之外恬惯,ret的地址還會(huì)發(fā)生改變。解決方法還是采用生成core dump的方案亚茬,然后用gdb調(diào)試core文件獲取返回地址酪耳。然后我們就可以使用exp進(jìn)行遠(yuǎn)程溢出啦!


三刹缝、Ret2libc – Bypass NX 通過(guò)ret2libc繞過(guò)NX防護(hù)

現(xiàn)在我們把NX打開碗暗,依然關(guān)閉stack protector和ASLR。編譯方法如下:

gcc -fno-stack-protector  -o level1 level1.c

這時(shí)候我們?nèi)绻褂胠evel1的exp來(lái)進(jìn)行測(cè)試的話梢夯,系統(tǒng)會(huì)拒絕執(zhí)行我們的shellcode言疗。如果你通過(guò)sudo cat /proc/[pid]/maps查看,你會(huì)發(fā)現(xiàn)level1的stack是rwx的颂砸,但是level2的stack卻是rw的噪奄。

level1: bffdf000-c0000000 rw-p 00000000 00:00 0 [stack]
level2: bffdf000-c0000000 rwxp 00000000 00:00 0 [stack]

那么如何執(zhí)行shellcode呢死姚?我們知道level2調(diào)用了libc.so,并且libc.so里保存了大量可利用的函數(shù)勤篮,我們?nèi)绻梢宰尦绦驁?zhí)行system(“/bin/sh”)的話知允,也可以獲取到shell。既然思路有了叙谨,那么接下來(lái)的問(wèn)題就是如何得到system()這個(gè)函數(shù)的地址以及”/bin/sh”這個(gè)字符串的地址温鸽。

如果關(guān)掉了ASLR的話,system()函數(shù)在內(nèi)存中的地址是不會(huì)變化的手负,并且libc.so中也包含”/bin/sh”這個(gè)字符串涤垫,并且這個(gè)字符串的地址也是固定的。那么接下來(lái)我們就來(lái)找一下這個(gè)函數(shù)的地址竟终。這時(shí)候我們可以使用gdb進(jìn)行調(diào)試蝠猬。然后通過(guò)print和find命令來(lái)查找system和”/bin/sh”字符串(查找/bin/sh的時(shí)候直接用find "/bin/sh"命令)的地址。

我們首先在main函數(shù)上下一個(gè)斷點(diǎn)统捶,然后執(zhí)行程序榆芦,這樣的話程序會(huì)加載libc.so到內(nèi)存中,然后我們就可以通過(guò)”print system”這個(gè)命令來(lái)獲取system函數(shù)在內(nèi)存中的位置喘鸟,隨后我們可以通過(guò)” print __libc_start_main”這個(gè)命令來(lái)獲取libc.so在內(nèi)存中的起始位置匆绣,接下來(lái)我們可以通過(guò)find命令來(lái)查找”/bin/sh”這個(gè)字符串。這樣我們就得到了system的地址0xb7e5f460以及"/bin/sh"的地址0xb7f81ff8什黑。下面我們開始寫exp:

要注意的是system()后面跟的是執(zhí)行完system函數(shù)后要返回地址崎淳,接下來(lái)才是”/bin/sh”字符串的地址。因?yàn)槲覀儓?zhí)行完后也不打算干別的什么事愕把,所以我們就隨便寫了一個(gè)0xdeadbeef作為返回地址拣凹。下面我們測(cè)試一下exp:

OK。測(cè)試成功恨豁。


四嚣镜、ROP– Bypass NX and ASLR 通過(guò)ROP繞過(guò)NX和ASLR防護(hù)

接下來(lái)我們打開ASLR保護(hù)。

sudo -s
echo 2 > /proc/sys/kernel/randomize_va_space
exit

現(xiàn)在我們?cè)倩仡^測(cè)試一下level2的exp橘蜜,發(fā)現(xiàn)已經(jīng)不好用了菊匿。

如果你通過(guò)sudo cat /proc/[pid]/maps或者ldd查看,你會(huì)發(fā)現(xiàn)level2的libc.so地址每次都是變化的扮匠。

那么如何解決地址隨機(jī)化的問(wèn)題呢捧请?思路是:我們需要先泄漏出libc.so某些函數(shù)在內(nèi)存中的地址,然后再利用泄漏出的函數(shù)地址根據(jù)偏移量計(jì)算出system()函數(shù)和/bin/sh字符串在內(nèi)存中的地址棒搜,然后再執(zhí)行我們的ret2libc的shellcode。既然棧活箕,libc力麸,heap的地址都是隨機(jī)的。我們?cè)趺床拍苄孤冻鰈ibc.so的地址呢?方法還是有的克蚂,因?yàn)槌绦虮旧碓趦?nèi)存中的地址并不是隨機(jī)的闺鲸,如圖所示:

Linux內(nèi)存隨機(jī)化分布圖

所以我們只要把返回值設(shè)置到程序本身就可執(zhí)行我們期望的指令了。首先我們利用objdump來(lái)查看可以利用的plt函數(shù)和函數(shù)對(duì)應(yīng)的got表:

我們發(fā)現(xiàn)除了程序本身的實(shí)現(xiàn)的函數(shù)之外埃叭,我們還可以使用read@plt()和write@plt()函數(shù)摸恍。但因?yàn)槌绦虮旧聿](méi)有調(diào)用system()函數(shù),所以我們并不能直接調(diào)用system()來(lái)獲取shell赤屋。但其實(shí)我們有write@plt()函數(shù)就夠了立镶,因?yàn)槲覀兛梢酝ㄟ^(guò)write@plt ()函數(shù)把write()函數(shù)在內(nèi)存中的地址也就是write.got給打印出來(lái)。既然write()函數(shù)實(shí)現(xiàn)是在libc.so當(dāng)中类早,那我們調(diào)用的write@plt()函數(shù)為什么也能實(shí)現(xiàn)write()功能呢? 這是因?yàn)閘inux采用了延時(shí)綁定技術(shù)媚媒,當(dāng)我們調(diào)用write@plit()的時(shí)候,系統(tǒng)會(huì)將真正的write()函數(shù)地址link到got表的write.got中涩僻,然后write@plit()會(huì)根據(jù)write.got 跳轉(zhuǎn)到真正的write()函數(shù)上去缭召。(如果還是搞不清楚的話,推薦閱讀《程序員的自我修養(yǎng) - 鏈接逆日、裝載與庫(kù)》這本書)

因?yàn)閟ystem()函數(shù)和write()在libc.so中的offset(相對(duì)地址)是不變的嵌巷,所以如果我們得到了write()的地址并且擁有目標(biāo)服務(wù)器上的libc.so就可以計(jì)算出system()在內(nèi)存中的地址了。然后我們?cè)賹c指針return回vulnerable_function()函數(shù)室抽,就可以進(jìn)行ret2libc溢出攻擊晴竞,并且這一次我們知道了system()在內(nèi)存中的地址,就可以調(diào)用system()函數(shù)來(lái)獲取我們的shell了狠半。

使用ldd命令可以查看目標(biāo)程序調(diào)用的so庫(kù)噩死。隨后我們把libc.so拷貝到當(dāng)前目錄,因?yàn)槲覀兊膃xp需要這個(gè)so文件來(lái)計(jì)算相對(duì)地址:

最后exp如下:

這里對(duì)payload1解釋一下

payload1 = 'a'*140 + p32(plt_write) + p32(vulfun_addr) + p32(1) + p32(got_write) + p32(4)

#p32(1) + p32(got_write) + p32(4)其實(shí)在調(diào)用write函數(shù)

#write用法:ssize_t write(int fd, const void *buf, size_t nbyte);
#fd:[文件描述符] fd=0時(shí)為輸入,fd=1時(shí)為輸出,fd=2為報(bào)錯(cuò)
#buf:指定的緩沖區(qū)神年,即[指針]已维,指向一段內(nèi)存單元;
#nbyte:要寫入文件指定的字節(jié)數(shù)已日;返回值:寫入文檔的字節(jié)數(shù)(成功)垛耳;-1(出錯(cuò))
#p32(1) + p32(got_write) + p32(4)相當(dāng)于傳進(jìn)去fd=1(輸出),buf= p32(got_write) 飘千,nbyte=4 最終就是為了打印出write()的地址

接著我們使用socat把level2綁定到10003端口:

最后執(zhí)行我們的exp:


五堂鲜、小結(jié)

本章簡(jiǎn)單介紹了ROP攻擊的基本原理,由于篇幅原因护奈,我們會(huì)在隨后的文章中會(huì)介紹更多的攻擊技巧:如何利用工具尋找gadgets缔莲,如何在不知道對(duì)方libc.so版本的情況下計(jì)算offset;如何繞過(guò)Stack Protector等霉旗。歡迎大家到時(shí)繼續(xù)學(xué)習(xí)痴奏。另外本文提到的所有源代碼和工具都可以從作者的github下載:https://github.com/zhengmin1989/ROP_STEP_BY_STEP


六蛀骇、參考文獻(xiàn)

The geometry of innocent flesh on the bone: return-into-libc without function calls (on the x86)
picoCTF 2013: https://github.com/picoCTF/2013-Problems
Smashing The Stack For Fun And Profit: http://phrack.org/issues/49/14.html
程序員的自我修養(yǎng)
ROP輕松談

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市读拆,隨后出現(xiàn)的幾起案子擅憔,更是在濱河造成了極大的恐慌,老刑警劉巖檐晕,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件暑诸,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡辟灰,警方通過(guò)查閱死者的電腦和手機(jī)个榕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)伞矩,“玉大人笛洛,你說(shuō)我怎么就攤上這事∧死ぃ” “怎么了苛让?”我有些...
    開封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)湿诊。 經(jīng)常有香客問(wèn)我狱杰,道長(zhǎng),這世上最難降的妖魔是什么厅须? 我笑而不...
    開封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任仿畸,我火速辦了婚禮,結(jié)果婚禮上朗和,老公的妹妹穿的比我還像新娘错沽。我一直安慰自己,他們只是感情好眶拉,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開白布千埃。 她就那樣靜靜地躺著,像睡著了一般忆植。 火紅的嫁衣襯著肌膚如雪放可。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天朝刊,我揣著相機(jī)與錄音耀里,去河邊找鬼。 笑死拾氓,一個(gè)胖子當(dāng)著我的面吹牛冯挎,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播痪枫,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼织堂,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼叠艳!你這毒婦竟也來(lái)了奶陈?” 一聲冷哼從身側(cè)響起易阳,我...
    開封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎吃粒,沒(méi)想到半個(gè)月后潦俺,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡徐勃,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年事示,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片僻肖。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡肖爵,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出臀脏,到底是詐尸還是另有隱情劝堪,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布揉稚,位于F島的核電站秒啦,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏搀玖。R本人自食惡果不足惜余境,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望灌诅。 院中可真熱鬧芳来,春花似錦、人聲如沸猜拾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)关带。三九已至侥涵,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間宋雏,已是汗流浹背芜飘。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留磨总,地道東北人嗦明。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像蚪燕,于是被迫代替她去往敵國(guó)和親娶牌。 傳聞我的和親對(duì)象是個(gè)殘疾皇子奔浅,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容