虛擬PWN初探

T1e9u發表於2020-09-11

前言

之前看到星盟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

前面已經做到把ystem函式寫到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安全入門(瞭解架構基礎吧),應該會總結兩篇部落格出來。

相關文章