Windows Heap Chunk Header Parsing and Size Calculation

匆匆过客 提交于 2019-12-01 05:46:51

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
Kjell Gunnar

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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!