前言
DDCTF的學習在我github上面更新完了那個庫, 由于我之前做過整數溢出的學習, 加上做第二題的時候已經知道這是一個整數溢出的漏洞, and windows7的保護措施少的可憐, 所以我當時和師父說, 我覺得蠻簡單的樣子, 然后師父說, 是么, 那么把你看過的東西全忘了再做一遍. 于是... 對不起, 打擾了.
這篇文章的過程我會把自己的分析思路給貼出來. 離寫出利用到寫這篇博客已經過了很久了, 在這個過程當中對內核學習也有了新的看法. 現在的我覺得, 分析是最難的部分. 所以這篇文章會冗長一點. 希望您不要介意.
這篇文章主要分為一下幾個部分
[+] poc分析 --> 定位漏洞點
[+] 內核代碼分析 --> 定位可以利用的數據
[+] fengshui: 構造可利用的數據
[+] run shellcode: 進行提權
Let's Go
POC 分析
環境準備
下載的文件打開截圖如下:

查閱資料得知UUENCODE加密, 改后綴名為uu, 之后采用winrar解壓即可.
逆向POC文件
說是逆向. 其實就是一個F5的過程. 如下:

于是我整理了一下源碼(循環不方便調試, 所以我把循環去掉了. 之后發現還是可行的):
#include <Windows.h>
#include <iostream>
/*
* triggerVul:
* [+] 觸發此漏洞
*/
VOID triggerTheVul()
{
HDC hDc; // edi@1
HDC hdcCall; // esi@1
HICON hIcon; // ebx@1
HBITMAP hBitMap; // eax@1
HBRUSH i; // esi@1
hDc = GetWindowDC(0);
hdcCall = CreateCompatibleDC(hDc);
hIcon = LoadIconW(0, (LPCWSTR)0x7F02);
hBitMap = CreateDiscardableBitmap(hdcCall, 0xDEAD000, 0x1); // 這個地方分配的大小得改為0x18
i = CreatePatternBrush(hBitMap);
__debugbreak();
DrawIconEx(hDc, 0, 0x11223344, hIcon, 0x5566, 0x7788, 0x12345678u, i, 8u);
}
int main()
{
std::cout << "[+] Trigger The vul!!!" << std::endl;
triggerTheVul();
return 0;
}
需要注意的是, 我在源碼當中加了一句__debugbreak(). 相當于一個斷點. 程序運行到此的時候會停下來, 這個語句十分方便內核的exp編寫調試.
編譯運行程序的驗證過程如下:

記錄關鍵信息
采用!analyze -v指令.
崩潰指令

需要注意的是, 由于我們是擁有vmware的, 所以可以有效的利用虛擬機鏡像繞過KASLR, 也就是這些指令的地址是固定的. 這樣的話我們可以記錄一下關鍵的信息方便調試.
有用的調試斷點記錄(一開始記錄的地址多謝, 這兩個是我后面寫博客的用到的):
[+] 9803d6aa -- 賦值操作起始的地方
[+] 9803d730 -- 漏洞相關的pool分配的地方
[+] 980651e0 -- 漏洞崩潰處
留意:
[+] eax的值
==> 為F820 0000, 值很大(請留意這個結論)
==> ???? 代表不可訪問
==> 向不能訪問的地方進行了寫操作
[+] 發生在vSrcCopyS1D32當中
崩潰原因

查閱windows的文檔:

留意:
[+] 崩潰的原因是由于對非法的內存進行了寫操作
崩潰堆棧

堆棧信息的每一項的格式如下
ebp 函數返回地址 第一個參數 第二個參數 ...
由上面的思路我們可以記錄如下信息
98065145 win32k!vSrcCopyS1D32+0xa5
9b20f840 win32k!EngCopyBits+0x604
9b20f900 win32k!EngRealizeBrush+0x462
9b20f998 win32k!bGetRealizedBrush+0x70c
9b20f9b0 win32k!pvGetEngRbrush+0x1f
9b20fa14 win32k!EngBitBlt+0x2bf
9b20fa78 win32k!GrePatBltLockedDC+0x22b
9b20fb24 win32k!GrePolyPatBltInternal+0x176
9b20fb60 win32k!GrePolyPatBlt+0x45
9b20fba8 win32k!_DrawIconEx+0x153
9b20fc00 win32k!NtUserDrawIconEx+0xcb
9b20fc00 nt!KiFastCallEntry+0x12a
001af930 ntdll!KiFastSystemCallRet
001af934 USER32!NtUserDrawIconEx+0xc
001af990 USER32!DrawIconEx+0x260
推測漏洞類型
分析
首先, 在IDA當中獲取崩潰指令的位置:

c代碼對應如下:

這里我要吹一個叫做source insight的閱讀源碼的工具. 極大的方便了我閱讀源碼的過程. 此處在windows NT4對應的源碼如下:

是不是感覺有點慌, 什么都看不懂了, 這些變量名又是干啥的呢. 沒關系的, 我們來整合一下資源.
[+] 變量名猜測這應該是做類似于由jSrc(源)向pulDst(目的地址)的賦值操作.
[+] 由源碼可以推出v17類似于pjSrc的長度
[+] 復雜的部分可以通過調試器來驗證它.
調試器部分的分析
首先, 采用IDA逆向一下關鍵函數vSrcCopyS1D32:

可以得到參數只有一個, 且是一個指針. 接著我們在vSrcCopyS1D32函數起始的地方設下斷點. 運行到此, 打印出指針的值以及其對應的結構體內容.

運行到崩潰指令處.

我們看下windows nt源碼里面的各項定義:

可以看到eax的值是和pjDst聯系起來的. 有前面的源碼我們推測出v17類似于字符串長度. 不如來驗證他.
使用IDA找到v17對應的匯編指令.

在windbg當中運行到此打印出其值:

由于不小心失誤多操作了一次, 所以我們的值多減了一次一, 在后面我把它加回來了. 1bd5a000就是我們推測的長度. 對應BLTINFO結構體成員變量lDeltaSrc, 在其中我發現了一句有趣的注釋:

所以驗證了我的長度推測. 只是它復制的不是字符串. 而是其他的東西.
如果有點繞的話讓我們來總結一下目前獲得的信息:
[+] pulDst指向目標地址
[+] jSrc指向了目標字符串.
[+] 由jSrc向pulDst運送v17長度的東西A
接下來開始我們的推測之旅.
第一步
如果是我們自己寫字符串操作的話. 大概如下:
int len = 20;
char * Dst = memset(len);
for(int i = 0; i < 20; i++) // 注意這里
Dst[i] = Src[i];
什么時候會崩潰呢. 比如:
int len = 20
char * Dst = memset(len);
for(int i = 0; i < 20+0x1000; i++) // 注意這里
Dst[i] = Src[i];
由前面的分析我們知道了他是一個寫操作. 所以應該是目的字符串(代指)與其長度不匹配照成的. 為什么會不匹配. 由于這個地址的字符串長度比較大, 所以我猜測它應該是一個pool, 而不是堆棧. 于是我運行了如下命令.

其中需要留意的信息有:
[+] Gebr為其tag.
[+] 0x17ab5000與其復制的length是十分不一致的. 當然也有可能是由于其有特殊的運算規則.
[+] 漏洞相關的pool存在paged session pool
所以在這里我就已經可以開始推測其為整數溢出了. 理由如下
[+] 正常邏輯下size與其實際大小應該是一一對應的.
[+] 此處記錄的size遠大于其實際大小
[+] 推測:
==> size被記錄
==> 某種特殊的規則導致分配實際大小溢出變小
第二步
于是借助于tag(tag真的很有用)與前面的邏輯分析. 我定位到了分配此內存的點. 在EngRealizeBrush函數當中.

代碼的修復你依舊可以參照可愛的windows nt的源碼來完成. 與ulSizeTotal相關的賦值語句在這里.

可能你會比較疑惑的是各個變量的意義是什么, 與前面的思路一樣, 我采用了動態調試驗證.(定位到函數流, 跟著走一遍, 觀察相應變量的值). 給出結論之前先看我們POC代碼中的CreateDiscardableBitmap函數.

動態調試得到的結論是:
[+] cjsCanpat = (cx * 0x20 >> 3);
[+] v49 = cjScanpat * cy
[+] ulSizeTotal = cjsPanat * cyInput * 0x44
[+] 整理擴展一下pool整體分配的公式:
poolsize = ((cx * 0x20) >> 3) * cy + 0x44 + 0x40 + 0x8(x86下的pool header)
我們原來POC傳入的大小是:
hBitMap = CreateDiscardableBitmap(hdcCall, 0xDEAD000, 0x1);
借助于windbg, 讓我們來算一下我們的結論是否正確:

我們可以看到和我們前面的size(0x17ab5000)完全扯不上關系, 這是由于windbg實現的是無溢出的結果. 需要留意的是我們的1bd5a出現了
而另外一個方面我編寫了如下的c程序來看看程序的輸出.

bingo, 所以錯誤出現了, 我們看到了溢出的曙光.
這里的溢出一共有三個位置:
[+] cx * 0x20
[+] ((cx * 0x20) >> 3) * cy + 44
[+] poolsize = ((cx * 0x20) >> 3) * cy + 0x44 + 0x40
利用思路
在后面的故事當中, 我查閱了的這篇文章. 獲取了利用思路.
查閱了小刀師傅給的一個關鍵數據.

我們選取的大小為0x10(后面我會解釋0x10的分配為什么合適), 漏洞分配的pool整體大小是`0x10+8(header)
驗證:

之后的利用思路如下:
[+] 風水布局布置成0xf80(bitmap)+0x18(空閑)地址
[+] 觸發漏洞使其填充0x18
[+] 利用后面的代碼覆蓋相連的bitmapA的一個關鍵變量擴充其讀寫能力
[+] 利用bitmapA擴充之后的讀寫能力去覆蓋相連的bitmapB的pvScan0
[+] bitmapB成為manager bitmap
任意選取一個worker bitmap
[+] 由worker bitmap來實現任意讀寫
聽著有點亂? 讓我們一步一步的來.
pool fengshui
我們最后的布局效果如下
0xfe8(bitmap alloc) + 0x18(free)
首先來解釋為什么需要是這個布局(細節可以在這里找到):
[+] fe8大小的內存塊會放在頁的開頭. 釋放相連堆塊的時候不會進行`pool header`有無被破壞的檢測
[+] 0x18會被內核漏洞點分配的pool填充
[+] 漏洞的結構體是paged session pool --> 這個信息對pool fengshui十分有用.
實現.
fengshui布局這里我不會講的太細, 因為在后面的一篇爬坑指南當中我會去詳細的解釋fengshui布局的相關爬坑.
首先我們要選取合適的數據. 也就是剛剛可以分配0xfe8大小的pool, 以及此分配的pool應該為non page pool. 于是我想到了我在github上面維護的那個項目.
使用CreateBitmap是一個很好的選擇.
在調試之后(具體的調試技術在我的另外一篇爬坑文章). 再保持其余參數不變的情況下. 隨著width的關系如下:
// size = ((width-100) * 0x2) + 0x360
for (int i = 0; i < 0x1000; i++)
{
hBitmap[i] = CreateBitmap(0x744, 2, 1, 8, NULL);
}
驗證:

而另外一個0x18的分配我采用了k0shi師傅的Class進行了分配, 這幾天發現了lpszMenuName也適合(請期待我的fengshui布局的文章). 代碼如下.

最后的結果驗證:

當前讀寫能力.
bitmap對應的在內核當中的結構體如下(來源: 小刀師傅的博客)
typedef struct tagSIZEL {
LONG cx;
LONG cy;
} SIZEL, *PSIZEL;
typedef struct _SURFOBJ {
DHSURF dhsurf; //<[00,04] 04
HSURF hsurf; //<[04,04] 05
DHPDEV dhpdev; //<[08,04] 06
HDEV hdev; //<[0C,04] 07
SIZEL sizlBitmap; //<[10,08] 08 09
ULONG cjBits; //<[18,04] 0A
PVOID pvBits; //<[1C,04] 0B
PVOID pvScan0; //<[20,04] 0C
LONG lDelta; //<[24,04] 0D
ULONG iUniq; //<[28,04] 0E
ULONG iBitmapFormat; //<[2C,04] 0F
USHORT iType; //<[30,02] 10
USHORT fjBitmap; //<[32,02] xx
} SURFOBJ;
微軟有兩個API函數:

其中pvBits參數存放一些字符串. 會放到pvScan0指向的地方. 而能夠讀寫的能力是由SIZEL sizlBitmap決定的.
其能夠讀寫的size = cx *cy. 所以當其中的數據如果原來的cx = 1, cy = 2. 原有的讀寫能力應該是2 * 2 = 2. 如果能覆蓋其關鍵變量cx = f, cy = f. 現有的讀寫能力就為f * f = e1. 利用擴充的讀寫能力. 我們就可以對此bitmap相連的另外一個bitmap實現讀寫. 修改器pvScan0的值. 從而實現任意讀寫(bitmap濫用會在我的另外一篇博客里面).
現在, 讓我們來觀察一下污染前后的數據對比.

可以看到我們的讀寫能力發生了天大的變化:

而在IDA對應的污染數據的代碼如下:

這里可以看到我們的污染最多只能可控到0x3c處, 這就是我們的size為什么要分配0x10的理由.
bitmap濫用獲取任意讀寫權限.
在今天或者明天, 我會更新bitmap濫用的細節性分析(從windows7到windows10). 所以在這里就不再贅述.
我這里講一些其他的操作. 根據前面的讀寫能力的改變. 我們能夠得到哪一個bitmap被覆蓋.

fix header
我們可以通過前面的數據對比獲取哪些成員變量被破壞了, 依賴于我們的bitmap獲取了任意讀寫. 能夠很輕松的實現修復. 其中修復handle那里. 在我上面的截圖紫色的部分. 發現其殘留了一個handle的備份. 實現了恢復. 也借助于此. 我獲取了hmanager的句柄值. 相關的代碼如下:

提權.
提權與我的第二篇博客類似, 都是替換nt!haldispatchtable的指針. 你可以去看一下我的第二篇博客
代碼:

驗證:

后記
DDCTF困擾我的點主要在分析那里, 我花了較長的時間分析其中怎么控制內核中的數據. 做的不太好的地方是構造數據那里. 實在無法了才借用了小刀師傅的數據.學到了很多的東西. 也改變了我對做內核的一些看法. anyway, 希望這一篇文章能夠對你有一點點小小的幫助.
最后, wjllz是人間笨蛋.
相關鏈接:
- sakura師父博客: http://eternalsakura13.com/
- 小刀師傅博客: https://xiaodaozhi.com/
- 整數溢出利用思路: https://sensepost.com/blog/2017/exploiting-ms16-098-rgnobj-integer-overflow-on-windows-8.1-x64-bit-by-abusing-gdi-objects/
- 本文exp: https://github.com/redogwu/cve-study-write/tree/master/cve-2017-0401
- k0shi師傅的exp: https://github.com/k0keoyo/DDCTF-KERNEL-PWN550
- 我的個人博客: https://redogwu.github.io
- 我的github地址: https://github.com/redogwu
- 第一篇: https://redogwu.github.io/2018/11/02/windows-kernel-exploit-part-1/
- 第二篇: https://redogwu.github.io/2018/11/02/windows-kernel-exploit-part-2/
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.jmbmsq.com/874/
暫無評論