记一次内核堆溢出的调试

moshui Lv3

记一次内核堆溢出的调试

在WFP内核抓包驱动开发的时候遇到了不定期蓝屏,也就是跑着跑着才会蓝屏。且蓝屏代码不一致,这个是我当时联想到堆溢出的错误代码,其中我们可以发现栈回溯看不出来任何我们模块中的信息。

image.png
image.png

完整的文本信息点击展开
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
---------

我们首先去微软官网看信息,直接按照参数来看一下。
image.png
我们分析一下参数就可以发现,其中像是直接构造了一个错误的堆块
image.png
我们针对相关代码下断点进行调试即可发现明显的溢出。
image.png
image.png
我们修改相关代码让其正确即可。

修改的代码点击展开
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;
//offset的计算
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中。
image.png
其中可以发现选择使用哪个分配函数是由ExpPoolFlagsToPoolType函数决定的,我们深入分析一下。

我们想分析明白这个函数需要观察一下PageType,这个则是用户设置并传入ExpPoolFlagsToPoolType函数的第一个参数。
image.png

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; // er10
__int64 v7; // rax
int v8; // edx
int v9; // er9
int v10; // edx

v6 = 0;
*outPageType = 0;
*outSelectAllocFunc = 0;
*a5 = 0;
if ( (PageType & 0xFFFFF800) != 0 || (PageType & 0x10) != 0 && !zero )
return 0xC000000Di64;
v7 = PageType & 0x1C0; // PageType & (POOL_FLAG_NON_PAGED | POOL_FLAG_NON_PAGED_EXECUTE | POOL_FLAG_PAGED)
if ( v7 == 0x40 )
{
v6 = 0x200; // when set POOL_FLAG_NON_PAGED, v6 = 0x200
}
else if ( v7 != 0x80 )
{
if ( v7 != 0x100 )
return 0xC000000Di64;
v6 = 0x80000001; // when set ( POOL_FLAG_PAGED | POOL_FLAG_RESERVED1 ), v6 = 0x80000001
if ( (PageType & 0x10) == 0 )
v6 = 1; // when set POOL_FLAG_PAGED, v6 = 1
}
v8 = v6 | 0x20; // when set POOL_FLAG_SESSION, v8 = v6 | 0x20
if ( (PageType & 4) == 0 )
v8 = v6; // when not set POOL_FLAG_SESSION, v8 = v6
v9 = v8 | 0x400; // when not set POOL_FLAG_UNINITIALIZED, v9 = v8 | 0x400
if ( (PageType & 2) != 0 )
v9 = v8; // when set POOL_FLAG_UNINITIALIZED, v9 = v8
if ( (PageType & 0x100000629i64) != 0 ) // PageType & (POOL_FLAG_USE_QUOTA | POOL_FLAG_CACHE_ALIGNED | POOL_FLAG_RAISE_ON_FAILURE | POOL_FLAG_RESERVED2 | POOL_FLAG_RESERVED3 | POOL_FLAG_SPECIAL_POOL)
{
v10 = v9 | 4;
if ( (PageType & 8) == 0 )
v10 = v9;
v9 = v10;
if ( _bittest64(&PageType, 9u) )
v9 = v10 | 0x80;
if ( _bittest64(&PageType, 0xAu) )
v9 |= 0x40u;
if ( (PageType & 1) != 0 )
{
*outSelectAllocFunc = 1;
if ( (PageType & 0x20) == 0 )
v9 |= 8u;
}
else if ( (PageType & 0x20) != 0 )
{
v9 |= 0x10u;
}
if ( (PageType & 0x100000000i64) != 0 )
*a5 = 1;
}
*outPageType = v9;
return 0i64;
}

经过简单分析我们可以发现,在不使用POOL_FLAG_USE_QUOTAPOOL_FLAG_CACHE_ALIGNEDPOOL_FLAG_RAISE_ON_FAILURE POOL_FLAG_RESERVED2POOL_FLAG_RESERVED3POOL_FLAG_SPECIAL_POOL时,都是用ExpAllocatePoolWithTagFromNode函数进行分配内存。

使用基础的 POOL_FLAG_NON_PAGEDPOOL_FLAG_NON_PAGED_EXECUTEPOOL_FLAG_PAGED,对应的v6(同v8)则是0x20001v9则是这些值与0x400做与运算,也就是0x6000x4000x401,这些值将被传入ExpAllocatePoolWithTagFromNode函数**作为PoolType**。

通过调试,同样也可以发现其中使用的是ExpAllocatePoolWithTagFromNode函数进行分配内存。
image.png

转至ExpAllocatePoolWithTagFromNode函数进行分析结构。

堆结构

进入ExpAllocatePoolWithTagFromNode函数后可以发现,其做了一些操作,不过核心分配函数还是ExAllocateHeapPool
image.png
点进去后会发现其超级长,我们从tag入手来摸清堆的结构。
image.png
通过交叉引用可以发现一个关键位置。
image.png
关于堆头大小,虽然这里的ETW指示了堆头长度为16,我们可以继续翻找,跟着v36往下看,就可以发现16确实是堆头的大小了,因为程序拿到的堆是从堆内容开始的,自然的v36就是整个堆块结构。
image.png
此时可以大致看出来一个结构:

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的变体。
image.png
其计算过程如下:

1
2
3
4
5
v10 = PoolType; //上文中所述计算出来的PoolType 
if ( (PoolType & 0x44) == 0x44 )
v10 = PoolType & 0xFFFFFFFB; //基本上没有用这个的
v13 = v10 | 0x200;
v38 = v13 & 0x6D | 2;

举个例子,比如POOL_FLAG_NON_PAGED,在上文中已经计算出其PoolType也就是0x600v38则为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
image.png
我们转去内存查看。
image.png
此时整个堆的结构是如此的简洁明了。

  • 标题: 记一次内核堆溢出的调试
  • 作者: moshui
  • 创建于 : 2024-11-17 09:35:55
  • 更新于 : 2024-11-19 01:54:06
  • 链接: https://www.moshui.eu.org/2024/11/17/kernel-dbg-heap/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论