前言
Hello, 歡迎來到windows kernel explot第五篇. 在這一部分我們會講述從windows 7到windows的各主流版本的利用技巧(GDI 對象濫用). 一共有兩篇, 這是上篇.
[+] 從windows 7到windows 10 1607(RS1)的利用
[+] windows 10 1703(RS2)和windows 1709(rs3)的利用.
這篇文章的起源來源于我在當時做第三篇博客的時候, 卡在了分析的點, 然后當時開始陷入自我懷疑, 分析和利用究竟哪一個重要一點. 于是, 我把第三篇博客的那個洞就先放了下, 在github上面更新了一個項目. 保證我在windows的主流平臺上都能掌握至少一種方法去利用.
學習的過程采用的主要思路是閱讀peper和參考別人的代碼實現, 調試來驗證觀點. 所以你能看到項目的代碼是十分丑陋的, 所以請不要fork… 會有一點點愧疚 :)
項目目前更新到了RS3, 所以接下來準備的工作是:
[+] 完成RS4和RS5的學習(有相關的的思路, 還未來得及驗證)
[+] 完成注釋和丑陋代碼的修繕
希望能夠對您有一點小小的幫助 :)
緩解措施的繞過
如果你有閱讀過我的系列第二篇的話, 你應該能夠知道write-what-where在內核當中的妙用, rootkits老師在他的個人博客里面有給出一個相當詳細的介紹. 所以在這里我就不再贅述.
我們來回顧一下第二篇的內容:
[+] 利用漏洞構造write-what-where
[+] 使用write-what-where替換掉nt!haldispatchTable+8
[+] 替換的時候不直接替換成Shellcode. 替換使程序執行ROP流, 繞過SMEP
[+] 執行shellcode.
==> 找到SYSTEM TOKEN VALUE
==> 找到user process TOKEN addr
==> mov [user process TOKEN addr], VALUE
[+] 提權驗證.
可以看到, 我們的思路是在內核當中執行我們的shellcode, 實現提權. 然后想在內核當中執行shellcode, 就需要繞過各種緩解措施. 那么, 如果我們能夠在用戶層次實現shellcode的功能, 是不是就不需要繞過那么多的緩解措施呢. 答案是肯定的(說來這是我剛剛開始做內核就有的一個猜想…)
我們一開始的假設是我們有任意的寫能力(write-what-where), 在項目當中我使用了HEVD模擬. 所以我們解決了:
mov [user process TOKEN addr], SYSTEM TOKEN VALUE
這條指令, 現在的關鍵點還差下面得兩個語句.
[+] 找到`SYSTEM TOKEN` VALUE
[+] 找到`USER PROCESS TOKEN`
找到SYSTEM TOKEN VALUE
我們知道我們要找到一個東西的值, 首先需要找到其地址. 所以讓我們先來找找SYSTEM TOEKN的地址. 我參考了這里給出的解釋, 但是不慌, 讓我們先一步一步的來, 先不管三七二十一, 最后再來驗證他.
第一步: 找到Ntoskrnl.exe基地址
先來看微軟給的一個API函數.

這個函數可以幫我們檢索內核當中的驅動的地址. 于是我們依葫蘆畫瓢. 創建下面的代碼.

需要注意的是, 上面的代碼的測試環境我是在windows 10 1803版本做的, 針對windows 1803以下的版本是同樣成立的(當然包括本小節的windows 7- window8.1). 你知道的, 我是一個懶得截圖的人 :)
接著做點小小的修改. 會得到下面的結果:

我們可以看到我們找到了ntoskrnl.exe的基地址. 那么ntoskrnl.exe是啥呢.
[+] Ntoskrnl.exe is a kernel image file that is a fundamental system component.
我們可以看到Ntoskrnl.exe包含是一個內核程序, 期間包含一些有趣的信息.
比如 :)
找到PsInitialSystemProcess

我們可以看到PsInitialSystemProcess存放一個指針, 其指向EPROCESS LIST的第一個項(也就是我們的SYSTEM EPROCESS). 我們可以利用我們在第二篇當中獲取nt!HalDispatchTable的思路來獲取它.
代碼如下:
調試器的驗證結果

現在問題就來了, 我們成功的找到了存放SYSTEM EPROCESS的地址放在那里, 但是我們卻沒有辦法去讀取他(xxx區域屬于內核區域). 我們對內核只有寫的權限, 那么我們怎么通過寫的權限去獲取到讀的權限呢. 于是我們的bitmap閃亮登場 :)
BITMAP 的基本利用
我們來講一下bitmap的利用思路之前, 先回顧我們在上一篇的內容當中, 已經成功的get到了如何泄露bitmap地址的能力(你看我安排的多么機智). 所以讓我們借助上一篇的代碼泄露一個bitmap觀察一一下它的數據.


上面那張圖看著有點蒙? 沒事的, 讓我們先來看看再內核當中bitmap對應的結構體.
typedef struct{
ULONG64 dhsurf; // 8bytes
ULONG64 hsurf; // 8bytes
ULONG64 dhpdev; // 8bytes
ULONG64 hdev; // 8bytes
SIZEL sizlBitmap; // 8bytes
// cx and cy
ULONG64 cjBits; // 8bytes
ULONG64 pvBits; // 8bytes
ULONG64 pvScan0; // 8bytes
ULONG32 lDelta; // 4bytes
ULONG32 iUniq; // 4bytes
ULONG32 iBitmapFormat; // 4bytes
USHORT iType; // 2bytes
USHORT fjBitmap; // 2bytes
} SURFOBJ64
你可以對照著我給的截圖當中的彩色部分, 是我們的關鍵數據. 藍色的第一個對應pvBits第二個對應pvScan0. 那么, pvScan0有什么用呢.

pvScan0的作用在bitmap的利用當中尤其重要, 所以我用笨蛋的方法來說明它.
[+] pVscan0指向我們的pixel data. 在CreateBitmap參數當中通過第五個參數傳入.
[+] pvScan0如果是fffff901`41ffdb88. 那么pixel data(上面的代碼樣例是"aaaa")就放在fffff901`41ffdb88.
驗證

而微軟的另外兩個API在本例當中也相當重要, 我們來看一下.


他們的作用是.
[+] SetBitMapBits(GetBitmaps)向pvScan0指向的地址寫(讀)cb byte大小的數據.
所以如果我們假設能夠篡改某個(術語 worker bitmap)bitmap的pvScan0的值為任意的值的話, 我們就能獲取向任意地址讀和寫的權限.
如果你能懂上面那一句話就太好了, 不懂的話讓我們通過一個實驗來一步一步理解它.
第一步
第一步讓我們先來看一份代碼, 代碼已經更新到我的github上, 你可以在這里找到它同步實驗.

看不懂沒有關系的, 沒有什么比調試器更能幫我們理解代碼了. 先來看在運行了這份代碼之后, 發生了寫什么神奇的事.

第二步.


在這里我們通過上一篇當中的bitmap地址泄露找到了manager的pvScan0的地址.和worker的pvScan0的地址. 聰明的你一定能根據前面的結構體明白0x60指的是pvScan0的地址 :)
第三步.

第三步我做了兩件事, 第一個單步運行到write_what_where函數里面. 你可以里面發現什么都沒有. 于是我借助于調試器模擬了一次write_what_where.
[+] 替換manager的pvSca0的內容為worker的pvScan0的地址.
接著我們就可以進行任意讀寫了. 運行之后就得到了上面的提權. 是不是感覺有點飄. 讓我們來分析一下(我比較建議您單步進入WriteOOB函數和ReadOOB觀察數據變化, 我比較懶…)
第四步: 任意讀
在上面的替換之后(第三步). 我們可以得到我們的manager.pvScan0指向worker.pvScan0. 由上面的截圖我們知道.
[+] manager.pvScan0 ==> fffff901`407491e0
[+] worker.pvScan0 ==> fffff901`40667cf0
實現了這個之后讓我來看看實現任意讀呢, 讓我們來看看我們的源碼:

我們假設我們要將0x4000的內容讀取出來, 那么readOOB會進行下面的操作.
[+] SetBitmapBits的時候, 向manager.Pvscan0存放的地址(A)出寫入0x4000
[+] 地址(A)即為worker.pvScan0的地址.
[+] 現在worker.pvScan0存放的地址為0x4000
[+] 調用GitBitmapBits能獲取0x4000處的內容
第五步: 任意寫

寫的操作和讀的內容是差不多的, 所以我原封不動的COPY了一份上面的內容, 稍微做了點修改.
我們假設我們要將0x4000的內容寫入值1, 那么readOOB會進行下面的操作.
[+] SetBitmapBits的時候, 向manager.Pvscan0存放的地址(A)出寫入0x4000
[+] 地址(A)即為worker.pvScan0的地址.
[+] 現在worker.pvScan0存放的地址為0x4000
[+] 調用SetbitmapBits能對0x4000處的內容進行寫操作.
韓國的有位師傅對流程做了一個流程圖. very beautiful!!!

第六步: 替換Token
第六步我們發現其實和我們系列一的內容是極其相似的. 只是利用在用戶層(我們已經有了任意讀寫的能力)用c++實現了匯編的功能. 在這里就不再贅述. 你可以閱讀我的系列第一篇獲取相關的信息.
總結
在這一篇當中我們講述了在windows 7. 8 . 8.1 1503 1511下利用bitmap實現任意讀寫的主體思路, 而在接下來的下半部分的文章當中. 我們會講述在windows 10后期的不同版本當中GDI的濫用. 也會介紹為什么我們在本篇的方法會什么會在windows 10后期的版本為什么會失效的原因. 敬請期待 :)
相關鏈接
- sakura師父的博客: http://eternalsakura13.com/
- 小刀師傅的博客: https://xiaodaozhi.com/
- SUFOBJECT64: http://gflow.co.kr/window-kernel-exploit-gdi-bitmap-abuse/
- 我參考的所有資料的PDF: https://redogwu.github.io/
- 我的個人博客: https://redogwu.github.io/
- 我的github上面的那個項目: https://github.com/redogwu/windows_kernel_exploit
- 做實驗所需要的代碼: https://github.com/redogwu/windows_kernel_exploit/tree/master/windows_8/blog_test_win8.1
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.jmbmsq.com/876/
暫無評論