翻譯:shan66
預估稿費:300RMB
投稿方式:發送郵件至linwei#360.cn,或登陸網頁版在線投稿
前言
本文將為讀者詳細介紹QuickZip v4.60緩衝區溢出漏洞方面的知識。由於漏洞在2010年就出現了,所以它的設計僅適用於32位Windows XP。所以,我決定嘗試在64位Windows 7上重現該漏洞,這將是一個(有趣的)挑戰!
PoC
為此,我從exploit-db中抓取了QuickZip v4.60 Windows XP漏洞,並將用它創建了一個簡單的PoC來觸發崩潰。

上述代碼創建了一個壓縮文件,其中只包含一個名為4064A的文件,它的擴展名為「.txt」。 Header_1,header_2和header_3是ZIP文件結構所需的標題。 我不會詳細介紹,但您可以在這裡閱讀更多。
如果您在QuickZip中打開剛創建的ZIP文件,並嘗試提取其內容(或只需雙擊文件名),那麼QuickZip就會崩潰。
了解崩潰詳情
好的,我們來運行PoC,看看到底發生了什麼。
使用上面的Python腳本創建ZIP文件,使用QuickZip打開它,啟動ImmunityDebugger,附加到QuickZip進程,並在QuickZip中雙擊文件名以觸發崩潰。 注意:我們將不斷重複這個過程!

很好,崩潰如期而至。 另外,這裡出現了一個異常,屏幕底部可以看到「Access violation when writing to [00190000]」。 這意味着我們試圖寫入一個無效的內存地址,從而觸發了一個異常。
下面,我們來研究一下SEH鏈。

很好,看來我們能夠控制nSEH指針!下面,我們嘗試算出偏移量。
偏移量
一如既往,我要藉助mona(
https://github.com/corelan/mona )來完成許多工作。
首先,我們生成一個4064個獨特字符的模版,並將其放在PoC漏洞利用代碼的有效載荷中:

再次觸發崩潰,看看會發生什麼情況。

呃,崩潰看起來有點不同。 這裡的問題是LEAVE指令嘗試從堆棧跳回到0EEDFADE地址,不過這裡是該程序的無效內存地址。
此外,似乎我們無法控制SEH了。

但是,請注意,我們實際上是在內核模塊中(請看Immunity窗口的名稱:「CPU – main thread, module KERNELBA」)。 使用SHIFT + F9將執行權傳回給程序,看看是否觸發另一個異常,但是是在QuickZip模塊中。


真棒,看起來成功了!
使用以下命令讓mona計算所有偏移量:


在這裡,我們最感興趣的偏移是nSEH field: offset 292。
讓我們用偏移信息更新PoC,並嘗試再次觸發崩潰。


太好了,我們控制了SEH!讓我們將異常傳給程序(SHIFT + F9),並進一步調查發生了什麼。

當然,另外一個異常也被觸發,因為43434343是這個程序的無效內存地址,但是讓我們看看堆棧上到底發生了什麼——通常是SEH溢出,我們需要調用一組POP-POP-RET指令來返回到緩衝區。
找到這樣的指令是很容易的,但首先,我們必須知道允許使用哪些字符。這就是我們需要關注的下一個問題。
壞字符
總的來說,大部分是這樣的。為什麼?因為我們的溢出是針對filename參數的,而文件名用到的字符類型是相當有限的: 通常只有ASCII可打印的字符。
如果使用手動方式的話,那麼使用mona通過遍歷方法找到所有壞的字符將需要太長的時間,所以這裡簡單假設除了0x00、0x0a和0x0d(分別代表NULL、換行和回車)之外,我可以使用ASCII表中所有的字符(最高值為0x7F的字符)。
這個假設可能會比事情比實際情況要更困難(因為我需要避免使用實際可以使用的字符)一些,或者可能會導致更多的問題,如果我的假設範圍內的某些字符其實是錯誤的話。
我不喜歡這樣做假設,但為了進行這個練習,這裡例外一次。
我只需要記住,要格外小心,如果有情況,則需要再次檢查壞的字符。這有點冒險,但很好玩,繼續!
POP-POP-RET
讓我們通過mona來尋找一個易於使用的POP-POP-RET指令:


這裡找到很多結果(7909!),但突出顯示的結果看起來最有希望——全部由字母數字字符組成,位於QuickZip.exe二進制文件本身中,有望使其更具跨平台特性,因為我們不希望依賴特定的操作系統DLL。
這裡唯一的問題是0x00位元組,但是由於程序的地址空間的原因,每個地址都以0x00開頭,所以我們來嘗試一下,看看是否會影響我們的漏洞利用代碼。
更新PoC漏洞利用代碼,用 x33 x28 x42 x00替換目前代表SEH的CCCC,再次觸發崩潰並考察SEH鏈。

好的,看起來我們的地址沒有亂碼,跟我們的預期相符。 設置斷點(F2),然後按SHIFT + F9將控制權傳遞給程序。

如您所見,我們將重定向到POP-POP-RET指令,讓我們用F8進行操作,並在RETN 4指令之後停止。

真棒,我們已經進入有效載荷…但有一個問題:因為NULL位元組的緣故,SEH鏈之後的所有東西都被切斷了,所以沒有給我們太多的空間做任何事情。
shellcode去哪裡了?
好的,我們分析一下,看看我們進展情況。
我們設法讓它崩潰了,並且能控制SEH,這非常好! 問題是我們的有效載荷受制於一個非常有限的字符集,並且因為我們必須使用NULL位元組的地址來調用POP-POP-RET指令,我們的有效載荷被切斷了,並且留給shellcode的空間也不是很大。
那麼它究竟有多大呢? 別忘了,為了獲得SEH,我們還在有效負載開始部分進行了填充:

那麼我們有多少空間呢? 共計292個位元組。 不幸的是,這些是不夠的。
不過,這個問題好像可以用egghunter來解決!
Egghunter只是一堆指令,在程序的內存空間中查找一個特定的、已知的位元組序列(「egg」),一旦找到,將重定向到該區域。
這樣我們就不用擔心我們的shellcode在哪裡結束了,我們可以調用eghtunter例程,它會為我們找到它們!
聽起來不錯,但下一個問題是,有效載荷的「截止」部分真的位於在內存中嗎? 我們來看看吧。
讓我們生成3764個單字符的模版(在NULL位元組之後填寫我們的有效負載),並用它替換現有的A。

我們觸發崩潰,當我們得到我們的第一個異常時,不要將異常傳遞給程序,而是調用以下命令來在內存中搜索以前生成的模版:


太棒了! 有效載荷的整個「截斷」部分仍然在內存中,所以我們應該能夠成功地使用egghunter來獲取我們的shellcode。
Egghunter
現在我們能夠使用egghunter來獲取我們的shellcode,但是我們只有292個位元組可供使用。實際上,我們可以用292位元組空間做許多事情,但是別忘了,我們只能使用非常有限的字符集。
我們試着用metasploit的x86 / alpha_mixed編碼器對egghunter進行編碼,看看在這之後還剩下多少空間。
首先,讓我們生成egghunter有效載荷。 請記住,我們正在使用64位操作系統,因此還需要使用相應的egghunter例程(有關更多詳細信息,請訪問
https://www.corelan.be/index.php/2011/11/18/WOW64-egghunter/ ):

注意:我已經使用bufferedregister = eax選項,這是因為編碼器需要找到它在內存中的位置,以便能夠對有效載荷進行解碼。 最初,負責該項工作的例程不在ASCII可打印的字符集中,因此會破壞我們的有效載荷。
指定bufferregister選項基本上就是告訴編碼器不用擔心如何在內存中找到自己的位置,我們會事先做好這件事情,我們將其地址放在EAX寄存器中。 這樣,我們的編碼後的egghunter就是純ASCII字符(更多關於生成字母數字shellcode的信息可以在這裡找到)。
我們更新我們的PoC漏洞利用代碼,以反映我們迄今為止所做的工作的成效。

讓我們觸發崩潰,將控制權傳遞給該程序並執行POP-POP-RET指令。 之後,在CPU窗口中向上滾動,尋找egghunter有效載荷和一組EC ECX指令(代表字符A)的結束位置。

好的,看起來像是在那裡,它似乎也是正確的:沒有使用不符合要求的字符!
跳轉回來
現在我們還有更多的事情需要考慮——這裡最重要的一點是,我們需要把egghunter的地址放在EAX中,然後跳轉到那裡。
我們如何在空間有限的情況下做到這一點? 首先,我們有多少空間? 簡單計算一下就知道是146位元組(nseh偏移減去egghunter的大小)。
146位元組可以做什麼? 我們只需要寫幾個指令,但是它們必須屬於允許使用的有限的字符集。 在這種情況下,我們不能使用已經用於egghunter的通用編碼器,因為我們根本沒有足夠的空間來滿足它。
所以,我們需要創建自己的編碼器! 這聽起來很讓人頭疼,但實際上比看起來要簡單得多。
首先,我們來看看目前在程序中的位置。

我們只有4個位元組,可由我們支配用來跳回有效載荷並開始寫定製的編碼器。同時,這4個位元組最好是字母數字。 幸運的是,有多個指令可供使用,特別是在那些情況下!
在這方面,可以參考TheColonial分享的相關技巧:
http://buffered.io/posts/jumping-with-bad-chars/。
簡而言之,我們可以簡單地使用JO和JNO指令來調用近轉移指令到我們的有效載荷。 但我們能跳多遠? 通過用一些允許的字符的包裹後,我發現一些壞的字符會被轉換為A2,它轉換成十進制就是92,這應該能給我們提供足夠的空間,以創建我們的自定義編碼器。


令人驚奇的是,這次跳轉到了我們的有效載荷! 現在,創建自定義編碼器,編寫指令,跳轉到egg hunting例程。
定製編碼器
為了跳到eghunter,我們需要寫許多條指令,因為不使用「壞」字符的話,就沒有直接的方法。
要解決這個問題,我們需要執行以下操作:
找出我們想要寫的指令的操作代碼
使用簡單的數學指令(即ADD和SUB),通過允許的字符將來自上述步驟的操作碼的值放入我們選擇的寄存器(例如EAX)中
我們將這個寄存器的值寫入堆棧,從而將我們想要的指令寫入ESP指向的內存區域
聽起來很複雜? 但實際上並不是那麼糟糕。
首先,我們需要調整堆棧才能寫入我們控制的內存區域。 通過觀察ESP的值和我們目前的位置(上面的截圖),可以發現,我們需要將ESP偏移0x62C(0x0018FB58(EIP的值)減去0x0018F528(ESP的值)再減去0x4(用於填充的空位元組))。


太棒了,我們的有效載荷完全與堆棧可以完美搭配了,下面開始編寫我們的編碼器。
注意:由於pop esp指令( x5c)的緣故,ZIP文件的內容看起來會有點不同。 x5c表示一個反斜杠,由QuickZip解釋為一個文件夾…這可能在以後有一些影響,但現在沒什麼。

現在,我們需要做的最後一件事是寫一組指令,將egghunter的起始地址放入EAX並跳轉到它。
為了避免「壞」字符,我們將在EAX寄存器中設置我們需要的操作碼的值,並將其壓入我們調整的堆棧上。這樣,我們需要的指令將寫到我們控制的內存區域中。
下面用一個例子來解釋。

我們來更新漏洞利用代碼並運行它。

太棒了,我們已經在EAX中成功設定了我們需要的值,並把它壓入堆棧上,實際上寫的是我們需要的指令!
讓我們對所有剩餘的位元組做同樣的處理。
完成上述處理後,新的PoC應該如下所示:


執行之後:

太棒了,我們已經成功地利用有效字符編寫出了想要的代碼! 現在只需跳回到該區域來執行就好了。 我們還需要將我們寫入的臨時0xDEADBEEF地址更改為實際的偏移量,前提是我們知道它是什麼…但現在為時過早。
跳轉
不幸的是,我們沒有太多的空間可用於跳轉:在我們的編碼器代碼之後只有5個位元組,編碼器代碼之前是4個位元組。所以,我們需要找到相應的指令,讓我們跳轉到剛寫的代碼。
事實證明,由於字符限制,實際上我們無法做太多的事情。 任何短的向後跳轉指令都包含無效的字符,無法跳轉至恰當的地方。所以,應該考慮是否重用之前用過的跳轉。
下面來看看我們目前擁有的有效載荷。

我們需要發揮創造性。讓我們重用SEH中的JNO跳轉,以便再次回到我們控制的內存區域。我們可以在當前編碼器有效載荷的開頭部分添加一些NOP,然後通過自定義編碼器用其他跳轉指令將其覆蓋,以將我們跳轉到剛編寫的代碼之前。
哎,這樣行得通嗎?讓我解釋一下。
我們需要使用的跳轉指令本來可以是簡單的JMP $ -16( xeb xee),不幸的是它包含了無效的字符,因此不適用於我們…。但是,任何帶有有效的字符的跳轉指令都會讓我們離的太遠。
然而!我們可以使用自定義的編碼器來處理它們,就像我們將egghunter的地址放置到EAX一樣,只需要調整偏移量並修改代碼即可。
首先,添加我們的JMP指令。然後,修改我們的原始堆棧,使SEH跳轉能夠準確到達我們的初始位置。最後,在編碼器的開頭部分添加一些NOP,它們之後將被所覆蓋。下面我們具體介紹其工作原理。
這裡,讓我們先從自定義的編碼器前面的NOP開始。 由於我們要求使用有效的字符集,因此可以使用 x41 x41(INC ECX)作為NOP。
接下來,進行堆棧調整。 從目前的狀態來看,我們需要進一步偏移6個位元組,以便寫入到要覆蓋的區域。為此,我們可以進行相應的調整。
最後,我們需要用編碼器寫入JNZ $ -16( x75 xee)指令。 讓我們用新的指令來替換最後兩個 x90(記住這裡使用的是little – endianness,所以我們需要反過來寫入)。
最後,代碼將變成這樣:

一旦執行,會發生以下情況:
崩潰被觸發
POP-POP-RET指令被調用
獲得JNO $ -92的跳轉地址
從頭開始執行自定義編碼器
代碼最終將到達第3步中跳轉的JNO指令
再次取得JNO的跳轉地址,但這次,我們登陸的第一條指令是剛剛寫入的16個位元組的跳轉指令
獲取跳轉指令的跳轉地址
使用自定義編碼器寫入要執行的指令
我們來看看到底發生了什麼。
執行自定義的編碼器後:

取得JMP的跳轉地址

在寫入指令之前登陸,準備執行

真棒,正是我們期待的! 現在我們只需要弄清楚用什麼值替代0xDEADBEEF就可以了!
讓我們來計算一下——ESP的當前值是0x0018FB4E,而egghunter代碼從0x0018FA90開始,這意味着我們需要將EAX減去0xBE,讓EAX指向我們的目的地。
我們開始修改漏洞利用代碼,這裡不是從EAX中減去0xDEADBEEF,而是減去0xBE。 PoC應進行以下修改:


有一個警告指出在文件末尾有一些垃圾,但沒關係,因為仍然可以成功打開該文件。
讓我們觸發崩潰,看看這一次我們是否可以在內存中找到這個模版。

我的天哪,它就在那裡!!!
Shellcode
現在,我們只需安裝常規流程來處理一下shellcode就行了——我們需要找出壞字符,然後在shellcode之前插入一個「egg」(w00tw00t)並對齊堆棧。
我不會詳細介紹尋找壞字符的細枝末節,因為我已經在這裡詳細介紹過了。 幸運的是,對於我們來說,這部分有效負載中僅有的壞字符是 x00, x0a和 x0d。
我們還需要在shellcode的開頭插入w00tw00t字符,以確保egghunter可以定位它,並將執行權重定向到「egg」之後的第一個指令。
最後,我們需要對齊堆棧,以確保ESP指向一個16位元組倍數的地址。 這樣做的原因是有一些「SIMD」(單指令,多數據)指令可以並行處理內存中的多個字,所以要求這些字的起始地址是16位元組的倍數。
如果我們沒有正確對齊堆棧,那麼shellcode根本不起作用。 我們可以輕鬆地利用單個指令AND esp,0xFFFFFFF0來對齊堆棧,也就是讓它正好在w00tw00t「蛋」之後,在實際shellcode之前。
對於這個概念驗證來說,我們將使用msfvenom生成一個簡單的、彈出計算器的shellcode,具體如下所示:



當我們打開生成的cst.zip文件時,我們的漏洞利用代碼就會運行,幾秒鐘(因為egghunter通過應用程序的內存找到「蛋」)後,我們應該看到計算器被打開。

成功了!!
小結
在本文中,我們已經成功地重新創建了QuickZip漏洞利用代碼的64位版本,它已經可以在Windows 7上運行了!
總而言之,我們通過使用非常有限的、被允許的字符集(幾乎可以ASCII打印)創建了一個egghunter漏洞利用代碼,編寫了我們自己的編碼器,並通過在內存中的跳轉,到達egghunter代碼,最終到達shellcode。
需要注意的是:
找出允許使用的字符,並在發生錯誤時記住這些字符
如果緩衝區大小不夠,不要氣餒——發揮你的創造性!
確保您使用正確的egghunter代碼(32位與64位),具體取決於您正在開發漏洞的平台
編寫自己的編碼器不是那麼難,但需要大量的練習和耐心
確保在執行shellcode之前對齊堆棧
原創文章,作者:投稿專員,如若轉載,請註明出處:https://www.506064.com/zh-hk/n/231179.html