前言
之前看到星盟Q群里面的消息,Freedom師傅在B站直播關於虛擬pwn入門的公開課,然后就去聽了一波,感覺受益匪淺。之前一直以為虛擬pwn是超級復雜的東西,今年打比賽也遇到了好幾次,一直無從下手。所以借着公開課學到的內容,復現了去年國賽虛擬pwn的那道題。這里寫一篇博客記錄下來,當作自己博客園的第一篇技術博文吧!主要是為了水周五的分享會
漏洞分析
就像Freedom師傅所說的,虛擬pwn的難點不是在於漏洞的利用,而是在於漏洞的分析,在於逆向分析能力。首先說明一點,這里我是直接在網上搜到了一個已經逆好的i64文件,自己跟着去逆向了一波。雖然感覺那位師傅逆的還是有點小瑕疵,但是u1s1,這么大的工程量,給我自己單獨從頭逆的話,真的逆不了這么清楚(留下了沒技術的淚水)。
先在虛擬機運行一下:

從這里可以大致了解程序的功能,就是類似一個虛擬機一樣執行我們輸入的指令。
然后,直接拉進ida,找到main函數,f5反編譯

可以看到,程序一開始便申請了三個段(利用堆申請出來的),一般虛擬pwn就是這三個段吧:代碼段,棧段(運行棧),數據段(緩沖區),所以這個程序也不例外。(代碼段主要用來放置我們的偽匯編指令,數據段放置我們一開始輸入的數據,運行棧段就是執行add等這些指令時的棧段)
但是作為一個段的話,肯定會需要例如索引,段里面元素個數等信息,所以根據初始化函數sub_4013B4,我們可以重構一個結構體(上圖是已經重構完的了)

然后就是一系列程序名,指令,數據等的輸入,這里不作詳細分析,直接查看run這個函數

發現這里的ida並不能反匯編得很清楚,所以只能肝匯編代碼。

可以發現,其實圖中左側部分的代碼塊就是典型的for語句,但是問題出現在左下角的代碼塊

我一開始看到這里的匯編,感到很奇怪,於是就去利用gdb動態調試了一波,感覺更加奇怪了,雖然隱約感覺得出來他是一個跳轉表,但是發現他是靠溢出實現得跳轉(之前了解到的條狀表是像switch那種具有一定規律的),然后那晚的話,教授剛好在工一,向他討教之后,發現其實這里就是簡單依靠目標地址減去數組本身地址得到的跳轉表(ORZ)。然后再觀察以下圖中右上角以及結合gdb動態調試,右上角應該就是我們輸入偽指令時的執行了。

這里給出了所有我們可以使用的偽匯編,除了load和save,其他的和我們80-86平台的無異,這里不一一分析,直接看save和load這兩個漏洞指令。

這里可以看到,load指令將我們運行棧棧頂的元素取出來,當作一個索引idx,然后再將data_addr->section_ptr + 8 * (data_addr->numb + idx 這個元素給放到棧頂,由於沒有對我們輸入的棧頂的元素給檢查,所以這里可以產生任意讀功能。

再來看看save函數,這里的save主要是將運行棧的棧頂取出來,作為index,然后將后來的棧頂元素取出,作為value,然后將對應將對應的位置賦值為value:*(8 *(data_addr->idx + index) + data_addr->section_info_ptr) = value
這樣的話,save便實現了任意寫。
漏洞利用
這里我主要時參考了知乎上認識的四川大學的一個pwn爺的思路
Step one
攻擊思路:
instruction='push push save'
data=str(0x0404088)+' '+str(-3)
查看一開始堆的分配

查看正常運行時,運行棧(靠堆實現)的情況(push push add pop || 1 1)

對比我們攻擊腳本時的運行棧的情況

可以看到,運行棧中實際放置我們輸入數據的地址已經被修改了
Step two
攻擊思路:
instruction='push push save push load'
data=str(0x0404088)+' '+str(-3)+' '+'-12'
先看看實現的效果

可以看出,我們的0x404088已經被覆蓋成我們的put@got了,這就是load實現的效果,因為我們的0x404088和我們的puts@got剛好相隔13(12+1)
Step three
既然已經修改0x404088為我們的put@got表,那么我們可以根據libc里面system函數和puts@got的偏移,再利用add這個指令去修改puts@got這個值為system函數
攻擊思路:
instruction=’push push save push load push add’
offset = -(libc.sym[‘puts’] - libc.sym[‘system’])
data=str(0x0404088)+’ ‘+str(-3)+’ ‘+’-12’+str(offset)
看看實現的效果

可以看到,這個地址已經被我們覆蓋為system函數了
Step four
前面已經做到把system函數寫到puts@got表附近了,再看看我們ida

可以看到,這里調用了puts函數去打印我們的程序名,我們第二步的時候實現了利用save指令去修改0x404088這個地址,反過來,我們也可以用save去修改我們的puts@got表為system函數,那只要我們把程序名設置為system函數的‘/bin/sh\x0’,然后程序最后打印時便會觸發這個函數,從而提權成功。
攻擊思路:
ins=’push push save push load push add push save’
data=[data_addr,-3,-12,offset,-12]
最后本地提權成功

總結
這是我本人第一次去做的虛擬pwn相關題目,這道題是十分基礎的,但是其實這么基礎的題,在比賽中如果真的給我遇到了,自己真的不一定可以找得到思路去解決。回頭看看思路,其實感覺就是利用了idx越界,然后去修改一些關系表的指針實現的,感覺不少題目都是這種思路,像今年國賽的那道nofree,亦或者說tcache heap的常用套路。
其實這篇博客因該在三天前就應該搞定的了,但是最近實在是有點忙,而且之前編輯一半的時候忘了保存了,昨天又是廣外的羊城杯,說起羊城杯,自己真的是拉跨,給一道簡單題卡了兩三個小時,后面發現只是一個版本數據問題(一度質疑服務器或者docker出問題了),然后去搞了一道REpwn最后一直沒有思路,逆向水平太差了,看匯編能力不夠,后來一點多的時候就睡覺了,起床后發現,俺們的師兄jb大佬居然肝到凌晨五點,肝出來了一道Java web,對比一下,自己實在慚愧。

最終隊里web大佬差一道web就把web全AK了,而二進制只是輸出了一道(慚愧)

最后說一下最近打算更的博文的,明后兩天打算學習Linux內核的進程線程基礎(看源碼,一些重要的數據結構,以及結合自己本身的教科書),然后去學一下iot安全入門(了解架構基礎吧),應該會總結兩篇博客出來。
