六、特殊案例
譯者:飛龍
日期:2001.9.1
版本:v1.2
有一些可以利用的特定場(chǎng)景吧碾,不需要了解所有偏移烟阐,或者你可以使利用更加簡(jiǎn)單,直接拧晕,最重要的是:可靠隙姿。這里我列出了一些利用格式化字符串漏洞的常見(jiàn)方法。
6.1 替代目標(biāo)
受基于棧的緩沖區(qū)溢出的較長(zhǎng)歷史的影響厂捞,很多人認(rèn)為输玷,覆蓋棧上的返回地址是控制進(jìn)程的唯一方式。但是如果我們利用格式化字符串漏洞靡馁,我們不能準(zhǔn)確知道我們的緩沖區(qū)在哪里欲鹏,并且我們可以覆蓋另外一些東西。常見(jiàn)的基于棧的緩沖區(qū)溢出只能覆蓋返回地址臭墨,因?yàn)樗鼈円泊鎯?chǔ)在棧上赔嚎。但是使用格式化函數(shù),我們可以覆蓋內(nèi)存中的任意地址裙犹,讓我們能夠修改整個(gè)可寫(xiě)入的進(jìn)程空間尽狠。
因此,檢驗(yàn)其它部分或完全控制被利用程序的方式叶圃,就很有意思了袄膏。在特定場(chǎng)景下,這可以產(chǎn)生一種更簡(jiǎn)單的利用方式 -- 我們之前看到 -- 或者可以用于繞過(guò)特定的保護(hù)掺冠。
我會(huì)在這里簡(jiǎn)單討論一下替代的地址沉馆,并給出更深入的文章的引用。
6.1.1 GOT 覆蓋
任何 ELF 二進(jìn)制 [12] 的進(jìn)程空間都包含一個(gè)特殊區(qū)段德崭,叫做“全局偏移表”(GOT)斥黑。每個(gè)程序使用的庫(kù)函數(shù)都在這里擁有一個(gè)條目,它包含一個(gè)真實(shí)函數(shù)的地址眉厨。這樣是為了允許庫(kù)在進(jìn)程內(nèi)存中簡(jiǎn)單地重定向锌奴,而不是使用硬編碼的地址。在程序首次使用函數(shù)之前憾股,條目包含運(yùn)行時(shí)鏈接器(RTL)的地址鹿蜀。如果函數(shù)被程序調(diào)用,控制流就傳遞給了 RTL服球,并且函數(shù)的真實(shí)地址被解析并插入到 GOT茴恰。該函數(shù)的每個(gè)調(diào)用都將控制流直接傳遞給它自己,RTL 不再為該函數(shù)調(diào)用了斩熊。對(duì)于 GOT 利用的更加全面的概覽往枣,請(qǐng)參考 Lam3rZ 兄弟的不錯(cuò)的文章 [19]。
通過(guò)覆蓋程序隨后使用的函數(shù)的 GOT 條目,我們就可以利用格式化字符串漏洞分冈,獲取控制權(quán)圾另,并跳到任何可執(zhí)行的地址。不幸的是丈秩,這意味著任何基于棧的保護(hù)都會(huì)失效盯捌,它們檢查了返回地址。
我們從覆蓋 GOT 條目中獲得的巨大優(yōu)勢(shì)蘑秽,就是它獨(dú)立于環(huán)境變量(例如棧),以及動(dòng)態(tài)內(nèi)存分配(堆)箫攀。GOT 條目的地址在每個(gè)二進(jìn)制中是固定的肠牲,所以如果兩個(gè)系統(tǒng)運(yùn)行了相同的二進(jìn)制,GOT 條目始終是同一地址靴跛。
你可以通過(guò)執(zhí)行這個(gè)命令缀雳,看到 GOT 條目位于函數(shù)的哪里:
objdump --dynamic-reloc binary
真實(shí)函數(shù)(或者 RTL 鏈接函數(shù))的地址直接就是打印出的地址。
另一個(gè)非常重要的因素梢睛,為什么使用 GOT 條目來(lái)獲取控制權(quán)肥印,而不是返回地址,是代碼的形式(在一些“安全”指紋守護(hù)程序中發(fā)現(xiàn)):
syslog (LOG_NOTICE, user);
exit (EXIT_FAILURE);
這里你不能通過(guò)覆蓋返回地址绝葡,來(lái)可靠地獲取控制權(quán)深碱。你可以嘗試覆蓋syslog
自己的返回地址,但是更加可靠的方式就是覆蓋exit
函數(shù)的 GOT 條目藏畅,它會(huì)將執(zhí)行流傳遞給你指定的地址敷硅,只要exit
被調(diào)用。
譯者注:動(dòng)態(tài)鏈接時(shí)愉阎,程序會(huì)調(diào)用
libc
中的系統(tǒng)調(diào)用的封裝绞蹦。其它系統(tǒng)調(diào)用同理。
但是 GOT 技巧的最實(shí)用的優(yōu)點(diǎn)榜旦,就是它易于使用幽七,你只需要運(yùn)行objdump
,就能得到要覆蓋的地址(retloc
)溅呢。黑客們都懶得打字(除了粗心)澡屡。
6.1.2 DTORS
實(shí)用 GCC 編譯的二進(jìn)制包含一個(gè)特殊的析構(gòu)器表區(qū)段,叫做DTORS
藕届。在真實(shí)的exit
系統(tǒng)調(diào)用觸發(fā)之前挪蹭,在所有的常見(jiàn)清理操作完成之后,這里列出的析構(gòu)器會(huì)調(diào)用休偶。DTORS
區(qū)段為以下格式:
DTORS: 0xffffffff 0x00000000 ...
其中第一項(xiàng)是一個(gè)計(jì)數(shù)器梁厉,它保存了下面函數(shù)指針的數(shù)量,如果列表為空則為負(fù)一(就像這里)。在所有 DTORS 區(qū)段的實(shí)現(xiàn)中词顾,這個(gè)字段都是被忽略的八秃。之后,在相對(duì)偏移+4
的位置肉盹,就是清理函數(shù)的地址昔驱,以 NULL 地址終止。你可以僅僅將這個(gè) NULL 指針覆蓋為你的 shellcode 指針上忍,并且你的 shellcode 就會(huì)在程序退出時(shí)執(zhí)行骤肛。這一技巧更加復(fù)雜的介紹可以在 [17] 找到。
6.1.3 C 標(biāo)準(zhǔn)庫(kù)的鉤子
幾個(gè)月之前窍蓝,Splar Designer 介紹了一種新的技巧來(lái)利用malloc
分配的內(nèi)存中基于堆的溢出腋颠。它提倡覆蓋 GNU C 庫(kù)以及其他庫(kù)中的鉤子。通常吓笙,這個(gè)鉤子有內(nèi)存調(diào)試和性能工具使用淑玫,在應(yīng)用使用malloc
接口分配或釋放內(nèi)存時(shí)獲取通知。有一些鉤子面睛,但是最常見(jiàn)的是__malloc_hook
絮蒿、__realloc_hook
和__free_hook
。通常它們?cè)O(shè)為 NULL叁鉴,但是只要你使用指向你代碼的指針覆蓋了它土涝,你的代碼就會(huì)在malloc
、realloc
和free
執(zhí)行時(shí)調(diào)用亲茅。由于鉤子通常用作調(diào)試工具回铛,它們?cè)谡鎸?shí)函數(shù)執(zhí)行之前調(diào)用。
關(guān)于malloc
覆蓋技巧的討論在 Solar Designer 關(guān)于 Netspace JPEG 解碼器漏洞的報(bào)告中提供克锣。
6.1.4 __atexit
結(jié)構(gòu)
幾個(gè)月之前茵肃,Kalou 介紹了一種利用 Linux 下靜態(tài)鏈接二進(jìn)制的方式,它利用了叫做__atexit
的通用處理器袭祟,只要你的程序調(diào)用了exit
验残,它就會(huì)執(zhí)行。這允許程序建立很多處理器巾乳,它們會(huì)在退出時(shí)調(diào)用來(lái)釋放資源您没。__atexit
結(jié)構(gòu)上的攻擊的詳細(xì)討論,可以在 Pascal Bouchareines 的文章 [16] 中找到胆绊。
6.1.5 函數(shù)指針
如果漏洞應(yīng)用使用了函數(shù)指針氨鹏,我們就有機(jī)會(huì)覆蓋它們。為了充分利用它們压状,你需要覆蓋它并且之后觸發(fā)它們仆抵。一些守護(hù)程序使用函數(shù)指針表來(lái)處理命令跟继,例如 QPOP。同時(shí)镣丑,函數(shù)指針也通常用于模擬類似__atexit
的處理器舔糖,例如 SSHD。
6.1.6 jmpbuf
首先莺匠,jmpbuf
覆蓋技巧用于堆緩沖區(qū)的利用金吗。使用格式化字符串,jmpbuf
的行為就像函數(shù)指針趣竣,因?yàn)槲覀兛梢愿采w內(nèi)存的任意地方摇庙,不僅限于jmpbuf
到我們的緩沖區(qū)的相對(duì)位置。深入討論可以在 Shok 關(guān)于堆溢出的文章中發(fā)現(xiàn)遥缕。
6.2 Return-to-libc
你可以使用常見(jiàn)的 Return-to-libc 技巧跟匆,同樣是由 Solar Designer 提出的 [14]。但是有時(shí)會(huì)有捷徑通砍,它會(huì)產(chǎn)生更簡(jiǎn)單的利用。
FILE * f;
char foobuf[512];
snprintf (foobuf, sizeof (foobuf), user);
foobuf[sizeof (foobuf) - 1] = ’\0’;
f = fopen (foobuf, "r");
你可以將fopen
的 GOT 地址替換為system
的函數(shù)地址烤蜕。之后使用這樣的格式化字符串:
"cd /tmp;cp /bin/sh .;chmod 4777 sh;exit;" "addresses|stackpop|write"
其中addresses
封孙、stackpop
和write
是常見(jiàn)的格式化字符串利用?。它們用于架構(gòu)fopen
GOT 條目覆蓋為system
的地址讽营。fopen
調(diào)用的時(shí)候虎忌,字符串轉(zhuǎn)遞給了system
函數(shù)〕髋簦或者你可以使用常用的舊方法膜蠢,向上面描述的那樣。
6.3 多重打印
如果你可以在相同進(jìn)程中多次觸發(fā)格式化字符串漏洞(就像 wu-ftpd 那樣)莉兰,你就可以不僅僅覆蓋返回地址挑围。例如,你可以將整個(gè) shellcode 儲(chǔ)存在堆上來(lái)繞過(guò)任何不可執(zhí)行的棧保護(hù)糖荒。和其它這里解釋的技巧一起使用杉辙,你就可以繞過(guò)下面的保護(hù)措施(顯然是不完全的):
- StackGuard
- StackShield
- Openwall 內(nèi)核補(bǔ)丁(由 Solar Designer)
- libsafe
在 2000 年十月中旬捶朵,一群人發(fā)布了一系列 Linux 內(nèi)核補(bǔ)丁蜘矢,叫做 PaX [11],能夠高效實(shí)現(xiàn)可讀可寫(xiě)但不可執(zhí)行的頁(yè)面综看。由于它不燜好后在 x86 CPU 系列上這么做品腹,這個(gè)補(bǔ)丁用了一些技巧,它們被 Plex CPU 模擬器項(xiàng)目所發(fā)明红碑。在運(yùn)行這個(gè)補(bǔ)丁的系統(tǒng)中舞吭,幾乎不可能執(zhí)行引入該進(jìn)程的任意 shellcode。但是多數(shù)情況下,在進(jìn)程空間中已經(jīng)有了實(shí)用的代碼镣典。我們可以執(zhí)行這個(gè)代碼來(lái)做通常在 shellcode 中所做的事情兔毙。
使用通常的 Return-to-libc 技巧 [14],你可以繞過(guò)這個(gè)保護(hù)兄春。最簡(jiǎn)單的案例就是返回到system
庫(kù)函數(shù)澎剥,使用格式化字符串作為參數(shù)。
通過(guò)稍微優(yōu)化字符串赶舆,你可以將需要了解的強(qiáng)制性偏移減為一個(gè):system
函數(shù)地址哑姚。為了調(diào)用程序,你可以在格式化字符串的尾部使用這個(gè)序列:
";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;id > /tmp/owned;exit;"
任何指向;
字符的地址芜茵,傳遞給system
函數(shù)時(shí)叙量,都會(huì)執(zhí)行該命令,因?yàn)?code>;字符在 shell 的命令中是 NOP九串。
6.4 堆中的格式化字符串
到現(xiàn)在為止绞佩,我們假設(shè)格式化字符串始終在棧上。但是猪钮,有些情況下品山,它儲(chǔ)存在堆上。如果棧上有另一個(gè)我們可以影響的緩沖區(qū)烤低,我們就可以使用它來(lái)提供要寫(xiě)入的地址肘交,但是如果沒(méi)有這種緩沖區(qū),我們有幾種替代方案扑馁。
如果目標(biāo)緩沖區(qū)在棧上涯呻,我們首先可以打印它,之后使用那里的地址腻要,來(lái)使用%n
參數(shù)寫(xiě)入:
void func (char *user_at_heap) {
char outbuf[512];
snprintf (outbut, sizeof (outbuf), user_at_heap);
outbuf[sizeof (outbuf) - 1] = ’\0’;
return;
}
這里我們使用了一個(gè)格式化字符串复罐,它包含我們想要寫(xiě)入的地址,像通常一樣闯第。但是它特別的是市栗,我們不能從格式化字符串本身來(lái)訪問(wèn)這些地址,而是通過(guò)目標(biāo)緩沖區(qū)咳短。為此我們首先需要在棧上儲(chǔ)存地址填帽,通過(guò)簡(jiǎn)單打印它們。因此寫(xiě)入的序列需要在格式化字符串的地址后面咙好。
如果兩個(gè)緩沖區(qū)都不在棧上篡腌,問(wèn)題就來(lái)了:
void func (char *user_at_heap) {
char * outbuf = calloc (1, 512);
snprintf (outbut, 512, user_at_heap);
outbuf[511] = ’\0’;
return;
}
現(xiàn)在它取決于我們是否可能在棧上提供數(shù)據(jù)。例如勾效,一些 wu-ftpd 的利用使用密碼字段來(lái)儲(chǔ)存數(shù)據(jù)(shellcode嘹悼,并不是地址 -- 這些利用程序不能利用非匿名的賬戶)叛甫。
每個(gè)漏洞和利用都是不同的,在說(shuō)它不可利用之前杨伙,你應(yīng)該花費(fèi)幾個(gè)小時(shí)來(lái)學(xué)習(xí)漏洞其监,并且你有可能是錯(cuò)的,因?yàn)檫@里展示的不僅僅是格式化字符串漏洞的歷史限匣。(你好抖苦,OpenBSD 團(tuán)隊(duì)!)
6.5 特殊的考慮
除了利用自身米死,也有一些需要考慮的東西锌历。如果格式化字符串含有 shellcode,它不能包含\x25
(%
)或者空字節(jié)峦筒。但是由于沒(méi)有重要的操作碼是0x25
或者0x00
究西,你在構(gòu)造 shellcode 時(shí)不會(huì)有什么麻煩。如果地址儲(chǔ)存在格式化字符串中物喷,是一樣的卤材。如果你想要寫(xiě)入的地址包含空字符,你可以將其替換為某個(gè)奇數(shù)地址的短整形寫(xiě)入峦失,它位于你想要寫(xiě)入的地址下方商膊。雖然它在所有架構(gòu)上都是不可能的。同樣宠进,你也可以使用兩個(gè)單獨(dú)的格式化字符串。第一個(gè)在內(nèi)存中藐翎,整個(gè)字符串的后面創(chuàng)建你打算寫(xiě)入的地址材蹬。第二個(gè)使用這個(gè)地址來(lái)寫(xiě)入它。
這可能變得有些復(fù)雜吝镣,但是可以可靠地利用堤器,并且有時(shí)值得花費(fèi)精力。