Windows 堆块头解析和大小计算
Posted
技术标签:
【中文标题】Windows 堆块头解析和大小计算【英文标题】:Windows Heap Chunk Header Parsing and Size Calculation 【发布时间】:2015-04-13 13:02:00 【问题描述】:如何根据从内存中读取的原始字节计算堆块大小。 我试过下面的东西。
0:001> !heap
Index Address Name Debugging options enabled
1: 00500000
2: 00280000
3: 008f0000
4: 00ab0000
5: 00cc0000
0:001> !heap -a 00500000
..
..
Heap entries for Segment00 in Heap 00500000
address: psize . size flags state (requested size)
00500000: 00000 . 00588 [101] - busy (587)
00500588: 00588 . 00240 [101] - busy (23f)
005007c8: 00240 . 00020 [101] - busy (18)
005007e8: 00020 . 00ca0 [101] - busy (c94)
..
..
!heap -a 00500000 shows that size of first chunk is 588 bytes.
如果我们使用 dt _HEAP_ENTRY 转储块头,它会以某种方式显示大小为 0x3822
0:001> dt _HEAP_ENTRY 00500000
ntdll!_HEAP_ENTRY
+0x000 Size : 0x3822
+0x002 Flags : 0xfc ''
+0x003 SmallTagIndex : 0xbb ''
+0x000 SubSegmentCode : 0xbbfc3822 Void
+0x004 PreviousSize : 0x1849
+0x006 SegmentOffset : 0 ''
+0x006 LFHFlags : 0 ''
+0x007 UnusedBytes : 0x1 ''
+0x000 FunctionIndex : 0x3822
+0x002 ContextValue : 0xbbfc
+0x000 InterceptorValue : 0xbbfc3822
+0x004 UnusedBytesLength : 0x1849
+0x006 EntryOffset : 0 ''
+0x007 ExtendedBlockSignature : 0x1 ''
+0x000 Code1 : 0xbbfc3822
+0x004 Code2 : 0x1849
+0x006 Code3 : 0 ''
+0x007 Code4 : 0x1 ''
+0x000 AgregateCode : 0x01001849`bbfc3822
当我转储地址 0x00500000 时,我发现前两个字节是 22 和 38。
00500000 22 38 fc bb 49 18 00 01 ee ff ee ff 00 00 00 00 a8 00 "8..I.............
00500012 50 00 a8 00 50 00 00 00 50 00 00 00 50 00 00 01 00 00 P...P...P...P.....
00500024 88 05 50 00 00 00 60 00 cf 00 00 00 01 00 00 00 00 00 ..P...`...........
00500036 00 00 f0 0f 53 00 f0 0f 53 00 02 00 00 00 00 00 00 00 ....S...S.........
00500048 00 00 00 00 00 00 10 00 93 38 fd 0b 49 18 00 00 17 ff .........8..I.....
0050005a bb 44 00 00 00 00 00 fe 00 00 ff ee ff ee 00 00 10 00 .D................
0050006c 00 20 00 00 00 08 00 00 00 20 00 00 2e 04 00 00 ff ef . ....... ........
0050007e fd 7f 01 00 38 01 00 00 00 00 00 00 00 00 00 00 00 00 ....8.............
00500090 e8 0f 53 00 e8 0f 53 00 0f 00 00 00 f8 ff ff ff a0 00 ..S...S...........
005000a2 50 00 a0 00 50 00 10 00 50 00 10 00 50 00 00 00 00 00 P...P...P...P.....
我的问题是 22 和 38(或 0x3822)如何变成 0x588
【问题讨论】:
其中“588”的列似乎是十六进制的(“00ca0”在同一列中)。不过,我在操作系统内存调试和堆方面的经验为零。 是的!因为 0x00500000 + 0x588 将变为 0x00500588。这是下一个堆块的开始。 【参考方案1】:总结:堆条目现在已编码,密钥在堆本身中。
假设我在 0x00d60000 有一个堆:
0:000> !heap -a 00d60000
Index Address Name Debugging options enabled
2: 00d60000
Segment at 00d60000 to 00d70000 (00001000 bytes committed)
Flags: 40000061
ForceFlags: 40000061
Granularity: 8 bytes
Segment Reserve: 00100000
Segment Commit: 00002000
DeCommit Block Thres: 00000200
DeCommit Total Thres: 00002000
Total Free Size: 00000149
Max. Allocation Size: 7ffdefff
Lock Variable at: 00000000
Next TagIndex: 0000
Maximum TagIndex: 0000
Tag Entries: 00000000
PsuedoTag Entries: 00000000
Virtual Alloc List: 00d6009c
Uncommitted ranges: 00d6008c
00d61000: 0000f000 (61440 bytes)
FreeList[ 00 ] at 00d600c0: 00d605a0 . 00d605a0
00d60598: 00118 . 00a48 [104] - free
Segment00 at 00d60000:
Flags: 00000000
Base: 00d60000
First Entry: 00d60480
Last Entry: 00d70000
Total Pages: 00000010
Total UnCommit: 0000000f
Largest UnCommit:00000000
UnCommitted Ranges: (1)
Heap entries for Segment00 in Heap 00d60000
address: psize . size flags state (requested size)
00d60000: 00000 . 00480 [101] - busy (47f)
00d60480: 00480 . 00118 [107] - busy (100), tail fill
00d60598: 00118 . 00a48 [104] free fill
00d60fe0: 00a48 . 00020 [111] - busy (1d)
00d61000: 0000f000 - uncommitted bytes.
0x00d60480 处有一个忙块:它的分配大小是 0x118(前一个块的大小是 0x480)。
如果我们转储这个块,我们可以看到它是编码的:
0:000> dt _heap_entry 00d60480
ntdll!_HEAP_ENTRY
+0x000 Size : 0x7387
+0x002 Flags : 0xf5 ''
+0x003 SmallTagIndex : 0x64 'd'
+0x000 SubSegmentCode : 0x64f57387
+0x004 PreviousSize : 0xb95d
+0x006 SegmentOffset : 0 ''
+0x006 LFHFlags : 0 ''
+0x007 UnusedBytes : 0x18 ''
+0x000 FunctionIndex : 0x7387
+0x002 ContextValue : 0x64f5
+0x000 InterceptorValue : 0x64f57387
+0x004 UnusedBytesLength : 0xb95d
+0x006 EntryOffset : 0 ''
+0x007 ExtendedBlockSignature : 0x18 ''
+0x000 Code1 : 0x64f57387
+0x004 Code2 : 0xb95d
+0x006 Code3 : 0 ''
+0x007 Code4 : 0x18 ''
+0x004 Code234 : 0x1800b95d
+0x000 AgregateCode : 0x1800b95d`64f57387
回到堆,特别注意名为“Encoding”的字段(偏移量0x50):
0:000> dt _heap encoding
ntdll!_HEAP
+0x050 Encoding : _HEAP_ENTRY
转储整个 _HEAP 结构:
0:000> dt _heap 00d60000
ntdll!_HEAP
+0x000 Entry : _HEAP_ENTRY
+0x008 SegmentSignature : 0xffeeffee
+0x00c SegmentFlags : 0
+0x010 SegmentListEntry : _LIST_ENTRY [ 0xd600a4 - 0xd600a4 ]
+0x018 Heap : 0x00d60000 _HEAP
+0x01c BaseAddress : 0x00d60000 Void
+0x020 NumberOfPages : 0x10
+0x024 FirstEntry : 0x00d60480 _HEAP_ENTRY
+0x028 LastValidEntry : 0x00d70000 _HEAP_ENTRY
+0x02c NumberOfUnCommittedPages : 0xf
+0x030 NumberOfUnCommittedRanges : 1
+0x034 SegmentAllocatorBackTraceIndex : 0
+0x036 Reserved : 0
+0x038 UCRSegmentList : _LIST_ENTRY [ 0xd60ff0 - 0xd60ff0 ]
+0x040 Flags : 0x40000061
+0x044 ForceFlags : 0x40000061
+0x048 CompatibilityFlags : 0
+0x04c EncodeFlagMask : 0x100000
+0x050 Encoding : _HEAP_ENTRY
+0x058 Interceptor : 0
+0x05c VirtualMemoryThreshold : 0xfe00
+0x060 Signature : 0xeeffeeff
+0x064 SegmentReserve : 0x100000
+0x068 SegmentCommit : 0x2000
+0x06c DeCommitFreeBlockThreshold : 0x200
+0x070 DeCommitTotalFreeThreshold : 0x2000
+0x074 TotalFreeSize : 0x149
+0x078 MaximumAllocationSize : 0x7ffdefff
+0x07c ProcessHeapsListIndex : 2
+0x07e HeaderValidateLength : 0x248
+0x080 HeaderValidateCopy : (null)
+0x084 NextAvailableTagIndex : 0
+0x086 MaximumTagIndex : 0
+0x088 TagEntries : (null)
+0x08c UCRList : _LIST_ENTRY [ 0xd60fe8 - 0xd60fe8 ]
+0x094 AlignRound : 0x17
+0x098 AlignMask : 0xfffffff8
+0x09c VirtualAllocdBlocks : _LIST_ENTRY [ 0xd6009c - 0xd6009c ]
+0x0a4 SegmentList : _LIST_ENTRY [ 0xd60010 - 0xd60010 ]
+0x0ac AllocatorBackTraceIndex : 0
+0x0b0 NonDedicatedListLength : 0
+0x0b4 BlocksIndex : 0x00d60248 Void
+0x0b8 UCRIndex : (null)
+0x0bc PseudoTagEntries : (null)
+0x0c0 FreeLists : _LIST_ENTRY [ 0xd605a0 - 0xd605a0 ]
+0x0c8 LockVariable : (null)
+0x0cc CommitRoutine : 0x7944d754 long +7944d754
+0x0d0 FrontEndHeap : (null)
+0x0d4 FrontHeapLockCount : 0
+0x0d6 FrontEndHeapType : 0 ''
+0x0d7 RequestedFrontEndHeapType : 0 ''
+0x0d8 FrontEndHeapUsageData : (null)
+0x0dc FrontEndHeapMaximumIndex : 0
+0x0de FrontEndHeapStatusBitmap : [257] ""
+0x1e0 Counters : _HEAP_COUNTERS
+0x23c TuningParameters : _HEAP_TUNING_PARAMETERS
将编码字段转储为两个 DWORD:
0:000> dd 00d60000 + 0x50 L2
00d60050 40f273a4 0000b9cd
现在将堆条目转储为两个 DWORD:
0:000> dd 00d60480 L2
00d60480 64f57387 1800b95d
让我们对它们进行异或:
0:000> ? 40f273a4 ^ 64f57387
Evaluate expression: 604438563 = 24070023
0:000> ? 0000b9cd ^ 1800b95d
Evaluate expression: 402653328 = 18000090
现在只需编写一个假的 _HEAP_ENTRY 以便我们可以“修改”它:
0:000> ed 00d604b0
00d604b0 00000000 24070023
24070023
00d604b4 00000000 18000090
18000090
00d604b8 00000000
0:000> dt _HEAP_ENTRY 00d604b0
ntdll!_HEAP_ENTRY
+0x000 Size : 0x23
+0x002 Flags : 0x7 ''
+0x003 SmallTagIndex : 0x24 '$'
+0x000 SubSegmentCode : 0x24070023
+0x004 PreviousSize : 0x90
+0x006 SegmentOffset : 0 ''
+0x006 LFHFlags : 0 ''
+0x007 UnusedBytes : 0x18 ''
+0x000 FunctionIndex : 0x23
+0x002 ContextValue : 0x2407
+0x000 InterceptorValue : 0x24070023
+0x004 UnusedBytesLength : 0x90
+0x006 EntryOffset : 0 ''
+0x007 ExtendedBlockSignature : 0x18 ''
+0x000 Code1 : 0x24070023
+0x004 Code2 : 0x90
+0x006 Code3 : 0 ''
+0x007 Code4 : 0x18 ''
+0x004 Code234 : 0x18000090
+0x000 AgregateCode : 0x18000090`24070023
大小字段为 0x23,粒度为 8 个字节(由!heap -a
命令输出报告)。块的真实大小是Size
字段值乘以粒度,所以:
0:000> ? 23 * 8
Evaluate expression: 280 = 00000118
它也适用于前一个块的大小(报告为0x480):
0:000> ? 0x90 * 8
Evaluate expression: 11552 = 00000480
我们找到了相同的尺寸。
粒度
粒度(由!heap -a
命令输出给出)不是由特定字段指示的,它只是HEAP_ENTRY
结构的大小:
x86 系统(或 WOW64)上 8 个字节:
0:000> ?? sizeof(_HEAP_ENTRY)
unsigned int64 8
x64 系统上 16 字节:
0:000> ?? sizeof(_HEAP_ENTRY)
unsigned int64 0x10
【讨论】:
太好了,我承认我的“艰巨任务”这句话被夸大了:-) 非常感谢@Neitsa :) 我应该更仔细地阅读 Chris Valasek 的 LFH 论文 :) 那么“请求的大小”和“状态”呢? windbg 是如何找到的? @MarcinK。我在上面的帖子上添加了一个编辑;基本上粒度不是由字段给出的,它只是HEAP_ENTRY
结构的大小。【参考方案2】:
在 Vista 及更高版本中,堆条目被打乱,因此这是一项艰巨的任务 做任何计算。 检查此link 阅读有关随机化的信息。
因此,DT 命令根本无法显示任何有意义的信息。 看看偏移量:
0:001> dt _HEAP_ENTRY
+0x000 Size
+0x000 FunctionIndex
+0x000 InterceptorValue
+0x000 AgregateCode
许多元素具有相同的偏移量,因此具有相同的内存。
还要观察你的
+0x004 PreviousSize : 0x1849
与 !heap –a 中的 0000 的 psize 不对应。
在 Win XP 和更早的版本中,您的技术是可行的,但在这里
_HEAP_ENTRY-> Size
是堆块的数量,通常为 8 个字节。
编辑: 我不知道有任何手动方法来解码堆条目,但我想这是可能的。 我已经使用 !heap –i 命令为我做这件事。 第一:
!heap –i <heap> , in your case !heap –i 00500000
然后
!heap –I <heap entry> , in your case !heap –I 00500588 (for second entry)
示例:
address: psize . size flags state (requested size)
00240000: 00000 . 00588 [101] - busy (587)
00240588: 00588 . 00240 [101] - busy (23f)
....
0:000> !heap -i 00240000
Heap context set to the heap 0x00240000
0:000> !heap -i 00240588
Detailed information for block entry 00240588
Assumed heap : 0x00240000 (Use !heap -i NewHeapHandle to change)
Header content : 0x32343AD9 0x0100B0F1 (decoded : 0x49010048 0x010000B1)
Owning segment : 0x00240000 (offset 0)
Block flags : 0x1 (busy )
Total block size : 0x48 units (0x240 bytes)
Requested size : 0x23f bytes (unused 0x1 bytes)
Previous block size: 0xb1 units (0x588 bytes)
Block CRC : OK - 0x49
Previous block : 0x00240000
Next block : 0x002407c8
另请参阅:this link
【讨论】:
谢谢,那么 "!heap -a 00500000" 如何确定大小?我们有什么办法可以使用进程内存中存在的任何其他数据结构来做到这一点?? 我知道 HeapWalk() api 也可以正确返回大小。但就我而言,我将允许访问调试器。因此,如果我能够使用调试器解析堆头,将解决我的问题。以上是关于Windows 堆块头解析和大小计算的主要内容,如果未能解决你的问题,请参考以下文章
我在哪里可以在 Windows pc 上永久设置 java 堆大小?
如何增加 sbt 在 Windows 下运行的 JVM 堆大小?