How can I calculate heap chunk size from raw bytes read from memory. I tried below thing.
0:001> !heap
Index Address Name Debugging options enable
Form Vista and later, the heap entries are scrambled so it’s a hard task to do any calculations. Check this link read about randomization.
The DT command are therefore unable do display any sensible information at all. Take a look at the offsets:
0:001> dt _HEAP_ENTRY
+0x000 Size
+0x000 FunctionIndex
+0x000 InterceptorValue
+0x000 AgregateCode
A lot of elements with same offset, hence same memory.
Also observe your
+0x004 PreviousSize : 0x1849
Does not correspond with the psize of 0000 from !heap –a.
On Win XP and earlier your technique was possible, but here the
_HEAP_ENTRY-> Size
was number of heap blocks, usually of 8 bytes.
Edit: I’m not aware of any manual method to decode the heap entry, but I guess it’s possible. I have used the !heap –i command to do it for me. First:
!heap –i <heap> , in your case !heap –i 00500000
Then
!heap –I <heap entry> , in your case !heap –I 00500588 (for second entry)
Sample:
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
See also : this link
Summary: heap entries are now encoded, the key is in the heap itself.
Let's say I have a heap at 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.
There's a busy block at 0x00d60480: its allocated size is 0x118 (the size of the previous block is 0x480).
If we dump this block we can see it's encoded:
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
Back to the heap, pay a particular attention to the field named "Encoding " (at offset 0x50):
0:000> dt _heap encoding
ntdll!_HEAP
+0x050 Encoding : _HEAP_ENTRY
Dumping the whole _HEAP structure:
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
Dumping the encoding field as two DWORDs:
0:000> dd 00d60000 + 0x50 L2
00d60050 40f273a4 0000b9cd
Now dumping the heap entry as two DWORDs:
0:000> dd 00d60480 L2
00d60480 64f57387 1800b95d
Let's XOR them:
0:000> ? 40f273a4 ^ 64f57387
Evaluate expression: 604438563 = 24070023
0:000> ? 0000b9cd ^ 1800b95d
Evaluate expression: 402653328 = 18000090
Now just writing a fake _HEAP_ENTRY so we can 'dt' it:
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
Size field is 0x23, granularity is 8 bytes (as reported by the !heap -a
command output). The real size of the block is the Size
field value multiplied by the granularity, so:
0:000> ? 23 * 8
Evaluate expression: 280 = 00000118
It also works for the size of the previous block (reported to be 0x480):
0:000> ? 0x90 * 8
Evaluate expression: 11552 = 00000480
We found the same sizes.
Granularity
Granularity (as given by the !heap -a
command output) is not indicated by a specific field, it's just the size of a HEAP_ENTRY
structure:
8 bytes on x86 systems (or WOW64):
0:000> ?? sizeof(_HEAP_ENTRY)
unsigned int64 8
16 bytes on x64 systems:
0:000> ?? sizeof(_HEAP_ENTRY)
unsigned int64 0x10