记一次内核堆溢出的调试 在WFP内核抓包驱动开发的时候遇到了不定期蓝屏 ,也就是跑着跑着才会蓝屏。且蓝屏代码不一致 ,这个是我当时联想到堆溢出的错误代码,其中我们可以发现栈回溯看不出来 任何我们模块中的信息。
完整的文本信息点击展开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 BAD_POOL_CALLER (c2) The current thread is making a bad pool request. Typically this is at a bad IRQL level or double freeing the same allocation, etc. Arguments: Arg1: 000000000000000d, Attempt to release quota on a corrupted pool allocation. Arg2: ffffca8567403350, Address of pool Arg3: 000000006c6b6a69, Pool allocation's tag Arg4: 6ff54e1309b56bd5, Quota process pointer (bad). Debugging Details: ------------------ PEB address is NULL ! KEY_VALUES_STRING: 1 Key : Analysis.CPU.mSec Value: 625 Key : Analysis.Elapsed.mSec Value: 1969 Key : Analysis.IO.Other.Mb Value: 0 Key : Analysis.IO.Read.Mb Value: 11 Key : Analysis.IO.Write.Mb Value: 14 Key : Analysis.Init.CPU.mSec Value: 3359 Key : Analysis.Init.Elapsed.mSec Value: 4965562 Key : Analysis.Memory.CommitPeak.Mb Value: 212 Key : Analysis.Version.DbgEng Value: 10.0.27725.1000 Key : Analysis.Version.Description Value: 10.2408.27.01 amd64fre Key : Analysis.Version.Ext Value: 1.2408.27.1 Key : Bugcheck.Code.KiBugCheckData Value: 0xc2 Key : Bugcheck.Code.LegacyAPI Value: 0xc2 Key : Bugcheck.Code.TargetModel Value: 0xc2 Key : Failure.Bucket Value: 0xc2_d_nt!SMKM_STORE_MGR_SM_TRAITS_::SmWorkItemFree Key : Failure.Hash Value: {5cf54755-bb91-6fee-0812-cb7f2797587d} Key : Hypervisor.Enlightenments.Value Value: 12576 Key : Hypervisor.Enlightenments.ValueHex Value: 3120 Key : Hypervisor.Flags.AnyHypervisorPresent Value: 1 Key : Hypervisor.Flags.ApicEnlightened Value: 0 Key : Hypervisor.Flags.ApicVirtualizationAvailable Value: 0 Key : Hypervisor.Flags.AsyncMemoryHint Value: 0 Key : Hypervisor.Flags.CoreSchedulerRequested Value: 0 Key : Hypervisor.Flags.CpuManager Value: 0 Key : Hypervisor.Flags.DeprecateAutoEoi Value: 1 Key : Hypervisor.Flags.DynamicCpuDisabled Value: 0 Key : Hypervisor.Flags.Epf Value: 0 Key : Hypervisor.Flags.ExtendedProcessorMasks Value: 0 Key : Hypervisor.Flags.HardwareMbecAvailable Value: 0 Key : Hypervisor.Flags.MaxBankNumber Value: 0 Key : Hypervisor.Flags.MemoryZeroingControl Value: 0 Key : Hypervisor.Flags.NoExtendedRangeFlush Value: 1 Key : Hypervisor.Flags.NoNonArchCoreSharing Value: 0 Key : Hypervisor.Flags.Phase0InitDone Value: 1 Key : Hypervisor.Flags.PowerSchedulerQos Value: 0 Key : Hypervisor.Flags.RootScheduler Value: 0 Key : Hypervisor.Flags.SynicAvailable Value: 1 Key : Hypervisor.Flags.UseQpcBias Value: 0 Key : Hypervisor.Flags.Value Value: 536632 Key : Hypervisor.Flags.ValueHex Value: 83038 Key : Hypervisor.Flags.VpAssistPage Value: 1 Key : Hypervisor.Flags.VsmAvailable Value: 0 Key : Hypervisor.RootFlags.AccessStats Value: 0 Key : Hypervisor.RootFlags.CrashdumpEnlightened Value: 0 Key : Hypervisor.RootFlags.CreateVirtualProcessor Value: 0 Key : Hypervisor.RootFlags.DisableHyperthreading Value: 0 Key : Hypervisor.RootFlags.HostTimelineSync Value: 0 Key : Hypervisor.RootFlags.HypervisorDebuggingEnabled Value: 0 Key : Hypervisor.RootFlags.IsHyperV Value: 0 Key : Hypervisor.RootFlags.LivedumpEnlightened Value: 0 Key : Hypervisor.RootFlags.MapDeviceInterrupt Value: 0 Key : Hypervisor.RootFlags.MceEnlightened Value: 0 Key : Hypervisor.RootFlags.Nested Value: 0 Key : Hypervisor.RootFlags.StartLogicalProcessor Value: 0 Key : Hypervisor.RootFlags.Value Value: 0 Key : Hypervisor.RootFlags.ValueHex Value: 0 Key : SecureKernel.HalpHvciEnabled Value: 0 Key : WER.OS.Branch Value: vb_release Key : WER.OS.Version Value: 10.0.19041.1 BUGCHECK_CODE: c2 BUGCHECK_P1: d BUGCHECK_P2: ffffca8567403350 BUGCHECK_P3: 6c6b6a69 BUGCHECK_P4: 6ff54e1309b56bd5 FAULTING_THREAD: ffffca8563b4f080 PROCESS_NAME: MemCompression STACK_TEXT: ffff8d8f`809d4b98 fffff807`0851b2f2 : ffff8d8f`809d4d00 fffff807`08384010 00000000`00000000 00000000`00000000 : nt!DbgBreakPointWithStatus ffff8d8f`809d4ba0 fffff807`0851a8d6 : 00000000`00000003 ffff8d8f`809d4d00 fffff807`08418040 00000000`000000c2 : nt!KiBugCheckDebugBreak+0x12 ffff8d8f`809d4c00 fffff807`08400da7 : ffffca85`5f602100 fffff807`0821ca42 ffffca85`5f602000 00000008`000000ff : nt!KeBugCheck2+0x946 ffff8d8f`809d5310 fffff807`08427612 : 00000000`000000c2 00000000`0000000d ffffca85`67403350 00000000`6c6b6a69 : nt!KeBugCheckEx+0x107 ffff8d8f`809d5350 fffff807`089bc0b9 : ffffca85`63805050 00000000`00000003 ffff8d8f`809d5490 01000000`00100000 : nt!ExFreeHeapPool+0x20af32 ffff8d8f`809d5430 fffff807`0825b8e6 : 00000000`00000001 00000000`00000000 ffffca85`63805000 ffffca85`63805050 : nt!ExFreePool+0x9 ffff8d8f`809d5460 fffff807`0825c523 : ffffca85`63805000 ffff8d8f`809d5558 00000000`00000001 ffffca85`63b4f080 : nt!SMKM_STORE_MGR<SM_TRAITS>::SmWorkItemFree+0x176 ffff8d8f`809d54e0 fffff807`08335a91 : ff004a5a`00000000 ffffca85`00000000 ff004859`00000000 00000000`00000000 : nt!SMKM_STORE<SM_TRAITS>::SmStWorker+0x2f7 ffff8d8f`809d55a0 fffff807`0832b5f5 : ffffca85`63805000 fffff807`08335a80 ffff8d8f`80406d68 001fa067`b8bbbdff : nt!SMKM_STORE<SM_TRAITS>::SmStWorkerThread+0x11 ffff8d8f`809d55d0 fffff807`084098d8 : ffffa781`44dea180 ffffca85`63b4f080 fffff807`0832b5a0 ff00394b`ff003b4d : nt!PspSystemThreadStartup+0x55 ffff8d8f`809d5620 00000000`00000000 : ffff8d8f`809d6000 ffff8d8f`809cf000 00000000`00000000 00000000`00000000 : nt!KiStartSystemThread+0x28 SYMBOL_NAME: nt!SMKM_STORE_MGR<SM_TRAITS>::SmWorkItemFree+176 MODULE_NAME: nt IMAGE_NAME: ntkrnlmp.exe STACK_COMMAND: .process /r /p 0xffffca855fccf040; .thread 0xffffca8563b4f080 ; kb BUCKET_ID_FUNC_OFFSET: 176 FAILURE_BUCKET_ID: 0xc2_d_nt!SMKM_STORE_MGR_SM_TRAITS_::SmWorkItemFree OS_VERSION: 10.0.19041.1 BUILDLAB_STR: vb_release OSPLATFORM_TYPE: x64 OSNAME: Windows 10 FAILURE_ID_HASH: {5cf54755-bb91-6fee-0812-cb7f2797587d} Followup: MachineOwner ---------
我们首先去微软官网看信息,直接按照参数来看一下。 我们分析一下参数就可以发现,其中像是直接构造了一个错误的堆块 。 我们针对相关代码下断点进行调试即可发现明显的溢出。 我们修改相关代码让其正确即可。
修改的代码点击展开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 static auto GetNetStreamRawData (PNET_BUFFER_LIST pIoPacket) { struct Ret { Ke::mtd::auto_ptr<CHAR> pData; SIZE_T stDataSize; }; PNET_BUFFER_LIST pCurrentNbl = pIoPacket; PNET_BUFFER pCurrentBuff = 0 ; ULONG ulOffset = 0 ; PMDL pMdl = 0 ; SIZE_T stDataLen = 0 ; Ke::mtd::auto_ptr<CHAR> pData = 0 ; SIZE_T stLastDataCount = 0 ; while (pCurrentNbl) { pCurrentBuff = NET_BUFFER_LIST_FIRST_NB (pCurrentNbl); while (pCurrentBuff) { ulOffset = NET_BUFFER_DATA_OFFSET (pCurrentBuff); pMdl = NET_BUFFER_FIRST_MDL (pCurrentBuff); stDataLen = NET_BUFFER_DATA_LENGTH (pCurrentBuff); if (pMdl == NULL || stDataLen == 0 ) { continue ; } pData = Ke::mtd::auto_ptr<CHAR>{ stDataLen }; while (pMdl) { SIZE_T stCopyCount = 0 ; while (pMdl->ByteCount <= ulOffset) { LONGLONG llMdlCount = (LONGLONG)pMdl->ByteCount - (LONGLONG)ulOffset; if (llMdlCount < 0 ) { ulOffset -= pMdl->ByteCount; } else { break ; } pMdl = pMdl->Next; } stCopyCount = pMdl->ByteCount - ulOffset; PCHAR pBuff = (PCHAR)MmGetSystemAddressForMdlSafe (pMdl, NormalPagePriority) + ulOffset; RtlCopyMemory (pData.ptr () + stLastDataCount, pBuff, stCopyCount); stLastDataCount += stCopyCount; pMdl = pMdl->Next; ulOffset = 0 ; if (stLastDataCount > stDataLen) { DbgBreakPoint (); } } stLastDataCount = 0 ; pCurrentBuff = NET_BUFFER_NEXT_NB (pCurrentBuff); } pCurrentNbl = NET_BUFFER_LIST_NEXT_NBL (pCurrentNbl); } return Ret{ pData, stDataLen }; }
内核堆块浅析 首先我们使用IDA看一下分配函数,其中可以发现具体的分配函数在ExpAllocatePoolWithTagFromNode
或者ExAllocatePoolWithQuotaTag
中。 其中可以发现选择使用哪个分配函数是由ExpPoolFlagsToPoolType
函数决定的,我们深入分析一下。
我们想分析明白这个函数需要观察一下PageType,这个则是用户设置并传入ExpPoolFlagsToPoolType
函数的第一个参数。
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 _int64 __fastcall ExpPoolFlagsToPoolType (__int64 PageType, int zero, int *outPageType, _BYTE *outSelectAllocFunc, _BYTE *a5) { unsigned int v6; __int64 v7; int v8; int v9; int v10; v6 = 0 ; *outPageType = 0 ; *outSelectAllocFunc = 0 ; *a5 = 0 ; if ( (PageType & 0xFFFFF800 ) != 0 || (PageType & 0x10 ) != 0 && !zero ) return 0xC000000D i64; v7 = PageType & 0x1C0 ; if ( v7 == 0x40 ) { v6 = 0x200 ; } else if ( v7 != 0x80 ) { if ( v7 != 0x100 ) return 0xC000000D i64; v6 = 0x80000001 ; if ( (PageType & 0x10 ) == 0 ) v6 = 1 ; } v8 = v6 | 0x20 ; if ( (PageType & 4 ) == 0 ) v8 = v6; v9 = v8 | 0x400 ; if ( (PageType & 2 ) != 0 ) v9 = v8; if ( (PageType & 0x100000629 i64) != 0 ) { v10 = v9 | 4 ; if ( (PageType & 8 ) == 0 ) v10 = v9; v9 = v10; if ( _bittest64(&PageType, 9u ) ) v9 = v10 | 0x80 ; if ( _bittest64(&PageType, 0xA u) ) v9 |= 0x40 u; if ( (PageType & 1 ) != 0 ) { *outSelectAllocFunc = 1 ; if ( (PageType & 0x20 ) == 0 ) v9 |= 8u ; } else if ( (PageType & 0x20 ) != 0 ) { v9 |= 0x10 u; } if ( (PageType & 0x100000000 i64) != 0 ) *a5 = 1 ; } *outPageType = v9; return 0 i64; }
经过简单分析我们可以发现,在不使用POOL_FLAG_USE_QUOTA
、POOL_FLAG_CACHE_ALIGNED
、POOL_FLAG_RAISE_ON_FAILURE
POOL_FLAG_RESERVED2
、POOL_FLAG_RESERVED3
、 POOL_FLAG_SPECIAL_POOL
时,都是用ExpAllocatePoolWithTagFromNode
函数进行分配内存。
在使用基础的 POOL_FLAG_NON_PAGED
、POOL_FLAG_NON_PAGED_EXECUTE
、POOL_FLAG_PAGED
时 ,对应的v6
(同v8
)则是0x200
、0
、1
,v9
则是这些值与0x400
做与运算,也就是0x600
、0x400
、0x401
,这些值将被传入 ExpAllocatePoolWithTagFromNode
函数**作为PoolType
**。
通过调试,同样也可以发现其中使用的是ExpAllocatePoolWithTagFromNode
函数进行分配内存。
转至ExpAllocatePoolWithTagFromNode
函数进行分析结构。
堆结构 进入ExpAllocatePoolWithTagFromNode
函数后可以发现,其做了一些操作,不过核心分配函数还是ExAllocateHeapPool
点进去后会发现其超级长,我们从tag
入手来摸清堆的结构。 通过交叉引用可以发现一个关键位置。 关于堆头大小,虽然这里的ETW指示了堆头长度为16,我们可以继续翻找,跟着v36
往下看,就可以发现16确实是堆头的大小了,因为程序拿到的堆是从堆内容开始的,自然的v36
就是整个堆块结构。 此时可以大致看出来一个结构:
1 2 3 4 5 6 7 8 struct Heap { short unknow_1; unsigned char length; unsigned char v38; ULONG tag; unsigned char [8 ] unknow_2; }
继续的,我们关注一下v38
是什么,我们直接往上翻一下就可以发现其中就是PageType的变体。 其计算过程如下:
1 2 3 4 5 v10 = PoolType; if ( (PoolType & 0x44 ) == 0x44 ) v10 = PoolType & 0xFFFFFFFB ; v13 = v10 | 0x200 ; v38 = v13 & 0x6D | 2 ;
举个例子,比如POOL_FLAG_NON_PAGED
,在上文中已经计算出其PoolType
也就是0x600
,v38
则为0x600 | 0x200 & 0x6D | 2
的结果,其值为2
。
至此堆结构应为如下内容:
1 2 3 4 5 6 7 8 struct Heap { short unknow_1; unsigned char length; unsigned char poolType; ULONG tag; unsigned char [8 ] unknow_2; }
实例分析 我们选取一个分配的内存,其中是分配池为POOL_FLAG_NON_PAGED
,分配大小为0x28
,内存池标记为mtd
。 我们转去内存查看。 此时整个堆的结构是如此的简洁明了。