windows_debug - thawk/wiki GitHub Wiki

Windows调试相关知识

1. 调试工具

1.1. windbg

1.2. 使用windbg自带的symchk下载pdb文件

"c:\Program Files\Windows Kits\10\Debuggers\x86\symchk" /r ntdll.dll /s SRV*d:\symbols*https://msdl.microsoft.com/download/symbols

2. Magic Number

  • 0xABABABAB : Used by Microsoft’s HeapAlloc() to mark "no man’s land" guard bytes after allocated heap memory

  • 0xABADCAFE : A startup to this value to initialize all free memory to catch errant pointers

  • 0xBAADF00D : Used by Microsoft’s LocalAlloc(LMEM_FIXED) to mark uninitialised allocated heap memory

  • 0xBADCAB1E : Error Code returned to the Microsoft eVC debugger when connection is severed to the debugger

  • 0xBEEFCACE : Used by Microsoft .NET as a magic number in resource files

  • 0xCCCCCCCC : Used by Microsoft’s C++ debugging runtime library to mark uninitialised stack memory

  • 0xCDCDCDCD : Used by Microsoft’s C++ debugging runtime library to mark uninitialised heap memory

  • 0xDEADDEAD : A Microsoft Windows STOP Error code used when the user manually initiates the crash.

  • 0xFDFDFDFD : Used by Microsoft’s C++ debugging heap to mark "no man’s land" guard bytes before and after allocated heap memory

  • 0xFEEEFEEE : Used by Microsoft’s HeapFree() to mark freed heap memory

  • 0xFFEEFFEE : _HEAP.SegmentSignature (+0x8)

  • 0xF0E0D0C0 : _HEAP_USERDATA_HEADER.Signature (+0xC)

  • 0xFFEEDDCC : _DPH_HEAP_ROOT.Signature (+0x0)

  • 0xEEEEEEEE : PageHeap中,内存块的开头4字节。紧接着4个字节指向verifier!_DPH_HEAP_BLOCK。 可以通过`_DPH_BLOCK_INFORMATION地址+sizeof(_DPH_BLOCK_INFORMATION)+_DPH_BLOCK_INFORMATION.RequestedSize-_DPH_BLOCK_INFORMATION.ActualSize`得到。

3. TEB

即`TIB`,存放在fs:0中。其中,`fs:18h`包含本结构的线性地址。

dd @fs:0

对应结构为`ntdll!_NT_TIB`

4. PEB

TEB+30h

5. 堆(_HEAP)

5.1. 堆总体结构

  • 每个进程有一个`进程堆`,但更多是使用`CRT堆`,还可以通过`HeapCreate()`创建额外的堆

  • 每个堆包括前端分配器和后端分配器

    • 有两种前端分配器:

      • 旁视列表(Look Aside List,LAL)前端分配器

      • 低碎片(Low Fragmenttation,LF)前端分配器

    • 旧版本Windows默认使用LAL前端分配器。Vista以后默认使用LF前端分配器

5.1.1. LAL前端分配器

  • LAL前端分配器的旁视列表是一张表,包含128项,每项是一个单向链表,每个链表包含一组固定大小的空闲堆块

  • 空闲堆块从16字节(包含一个8字节的堆块元数据)大小开始依次递增(每级比上一组多8个字节),因此可用字节大小从8开始

  • 索引0不用,索引1对应16字节(8字节用户可用,8字节堆块元数据)。

  • 最后一个索引127包含大小为1024字节(1016可用)。

5.1.2. LFH前端分配器

The LFH claims sub-segments from the backend allocator. Each subsegment starts with a _HEAP_USERDATA_HEADER and it is followed by an array of allocations of the same size. Each such allocation has a _HEAP_ENTRY at the start. To the backend allocator the subsegments simply look like largish opaque allocations (and are therefore also contained in a backend _HEAP_ENTRY ).

The LFH reuses the _HEAP_ENTRY struct (again encoded with the heap’s key) to describe each allocation, but since all entries in a subsegments are the same size, there is no need to use Size andPreviousSize to track them.

The _HEAP_ENTRY.UnusedBytes member describes how many bytes are unused in the allocation (e.g. if the allocation is 20 bytes but the user only wanted 18 bytes there are 2 bytes unused), and also contains flags to indicate if the entry is BUSY or FREE.

5.1.3. 后端分配器

  • 包含一个空闲列表(Free Lists),表中每一项是一个空闲链表(Free List),记录在堆中的所有空闲堆块

  • 空闲列表包含128个空闲链表,每个链表中包含固定大小的空闲堆块

  • 每个空闲堆块也有8个字节元数据,因此空闲列表1(对应8字节)不使用。空闲列表2对应16字节,其中8字节可用

  • 空闲列表127包含可用大小为1016字节的空闲堆块

  • 堆管理器中有一个128 bit的空闲列表位图,每一位表示一个空闲列表中是否有空闲堆块,以便提高查找效率

  • 如果无法找到一个等于所请求大小的空闲堆块,将找一个更大的空闲堆块,将其对半分割以满足分配请求

  • 大于1016字节而不大于虚拟内存分配限值(0x7FFF0,524272字节)的空闲堆块按大小升序存放到空闲列表0中

  • 大于0x7FFF0的内存分配请求将转发到`虚拟分配链表`(Virtual Allocation List)中。当请求一个很大的内存分配时,堆管理器将向虚拟内存管理器发出请求,并且将相应的分配信息保留在`虚拟分配链表`中

5.2. 堆结构

5.2.1. 各函数使用的结构

正常堆
CreateHeap

创建_HEAP

AllocHeap

创建_HEAP_ENTRY

启用页堆(gflags.exe /i +hpa)
CreateHeap

_DPH_HEAP_ROOT (+ _HEAP + 2x _HEAP_ENTRY)

With page heap enabled there will still be a _HEAP with two constant _HEAP_ENTRY’s for every CreateHeap call.

AllocHeap

_DPH_HEAP_BLOCK

Table 1. 相关术语
Term Description Heap type

HeapHandle

= value returned by HeapCreate or GetProcessHeap For normal heap: HeapHandle == HeapStartAddr

Normal & page

HeapAddr

= startAddr = NormalHeap

Normal & page

UserAddr, UserPtr

= value in the range [HeapAlloc…​HeapAlloc+AllocSize] For normal heap this range is further within Heap[startAddr-endAddr]

Normal & page

UserSize

= AllocSize (value passed to HeapAlloc)

Normal & page

_HEAP

= HeapHandle = HeapStartAddr For every HeapCreate a _HEAP struct is created. You can use "!heap -p -all" to get these addresses.

Normal heap

_HEAP_ENTRY

For every HeapAlloc a _HEAP_ENTRY is created. You can use "!heap -p -all" to get these addresses.

Normal heap

_DPH_HEAP_ROOT

= usually HeapHandle + 0x1000 For every HeapCreate a _DPH_HEAP_ROOT is created. You can use "!heap -p -all" to get these addresses.

Page heap

_DPH_HEAP_BLOCK

For every HeapAlloc a _DPH_HEAP_BLOCK is created. You can use "!heap -p -all" to get these addresses.

Page heap

5.2.2. _HEAP_SEGMENT结构

_HEAP_ENTRY(8 bytes)

_HEAP_SEGMENT

_HEAP_ENTRY(8 bytes)

new或malloc分配的空间

固定填充空间

堆申请内存的大小是以段为单位的,当新建一个堆的时候,系统会默认为这个堆分配一个段叫0号段,通过刚开始的new和malloc分配的空间都是在这个段上分配的,当这个段用完的时候,如果当初创建堆的时候指明了HEAP_GROWABLE这个标志,那么系统会为这个堆在再分配一个段,这个时候新分配的段就称为1号段了,以下以此类推。每个段的开始初便是_HEAP_SEGMENT结构的首地址,由于这个结构也是申请的一块内存,所以它前面也会有个_HEAP_ENTRY结构。

_HEAP_SEGMENT结构会记录段的一些基本信息,该段申请的大小,已经提交内存的大小,第一个_HEAP_ENTRY结构的入口点。

Note
0号段很特别,这个段的起始地址就是堆句柄指针指向的值,也就是说,HeapCreate返回的堆句柄总是指向0号段,为什么呢?因为_HEAP结构是_HEAP_ENTRY,_HEAP_SEGMENT的合体加长版。

5.2.3. _HEAP结构(HeapBase)

基本信息

_HEAP句柄是HeapCreate()的返回值,也是HeapFree()等函数的第一个参数。

_HEAP结构记录了这个堆的信息,这个结构可以找到_HEAP_SEGMENT链表入口,空闲内存链表的入口,内存分配粒度等等信息。_HEAP的首地址便是堆句柄的值,但是堆句柄的值又是0号段的首地址也是堆句柄,何解?其实很简单,0号段的_HEAP_SEGMENT就在_HEAP结构里面,_HEAP结构类定义如这样

_HEAP_ENTRY(8 bytes)

_HEAP_SEGMENT

_HEAP

更确切的说,_HEAP结构中本身就包含了_HEAP_ENTRY和_HEAP_SEGMENT,_HEAP_ENTRY结构是_HEAP的第一个数据成员,_HEAP_SEGMENT是它第二个数据成员。而对于_HEAP_SEGMENT,它的第一个数据成员便是_HEAP_ENTRY。这里为了方便理解,才在内存组织结构中把它们拆开展示。

!heap _HEAP句柄
!heap 460000

dt _HEAP _HEAP句柄
dt _HEAP 460000
ForceFlags的取值
//
// Public Heap Flags
//
#if !defined(NTOS_MODE_USER) && !defined(_NTIFS_)
#define HEAP_NO_SERIALIZE             0x00000001
#define HEAP_GROWABLE                 0x00000002
#define HEAP_GENERATE_EXCEPTIONS      0x00000004
#define HEAP_ZERO_MEMORY              0x00000008
#define HEAP_REALLOC_IN_PLACE_ONLY    0x00000010
#define HEAP_TAIL_CHECKING_ENABLED    0x00000020
#define HEAP_FREE_CHECKING_ENABLED    0x00000040
#define HEAP_DISABLE_COALESCE_ON_FREE 0x00000080
#define HEAP_CREATE_ALIGN_16          0x00010000
#define HEAP_CREATE_ENABLE_TRACING    0x00020000
#define HEAP_CREATE_ENABLE_EXECUTE    0x00040000
#endif

//
// User-Defined Heap Flags and Classes
//
#define HEAP_SETTABLE_USER_VALUE 0x00000100
#define HEAP_SETTABLE_USER_FLAG1 0x00000200
#define HEAP_SETTABLE_USER_FLAG2 0x00000400
#define HEAP_SETTABLE_USER_FLAG3 0x00000800
#define HEAP_SETTABLE_USER_FLAGS 0x00000E00
#define HEAP_CLASS_0             0x00000000
#define HEAP_CLASS_1             0x00001000
#define HEAP_CLASS_2             0x00002000
#define HEAP_CLASS_3             0x00003000
#define HEAP_CLASS_4             0x00004000
#define HEAP_CLASS_5             0x00005000
#define HEAP_CLASS_6             0x00006000
#define HEAP_CLASS_7             0x00007000
#define HEAP_CLASS_8             0x00008000
#define HEAP_CLASS_MASK          0x0000F000

//
// Internal _HEAP Structure Flags
//
#define HEAP_FLAG_PAGE_ALLOCS            0x01000000
#define HEAP_PROTECTION_ENABLED          0x02000000
#define HEAP_BREAK_WHEN_OUT_OF_VM        0x04000000
#define HEAP_NO_ALIGNMENT                0x08000000
#define HEAP_CAPTURE_STACK_BACKTRACES    0x08000000
#define HEAP_SKIP_VALIDATION_CHECKS      0x10000000
#define HEAP_VALIDATE_ALL_ENABLED        0x20000000
#define HEAP_VALIDATE_PARAMETERS_ENABLED 0x40000000

#define HEAP_LOCK_USER_ALLOCATED         0x80000000
LFH标志
FrontEndHeapType

缺省为`0x0`,表示backend only。取值为`0x2`代表使用LFH。

FrontEndHeap

指向front-end heap指针。其值为NULL或指向_LFH_HEAP结构的指针。

其它字段
BlocksIndex

_HEAP_LIST_LOOKUP指针

5.2.4. _HEAP_LIST_LOOKUP (HeapBase→BlocksIndex)

通常情况下,第一个`_HEAP_LIST_LOOKUP`结构由`RtlCreateHeap()创建,位于`HeapBase+0x150处。

ArraySize

The highest block size that this structure will track, otherwise storing it in a special ListHint. The only two sizes that Windows 7 currently uses are 0x80 and 0x800.

BaseIndex

Used to find the relative offset into the ListHints array, since each _HEAP_LIST_LOOKUP is designated for a certain size. For example, the BaseIndex for 1st BlocksIndex would be 0x0 because it manages lists for chunks from 0x0 – 0x80, while the 2nd BlocksIndex would have a BaseIndex of 0x80.

OutOfRangeItems

This 4-byte value counts the number of items in the FreeList[0]-like structure. Each _HEAP_LIST_LOOKUP tracks free chunks larger than ArraySize-1 in ListHint[ArraySize-BaseIndex-1].

ListHead

This points to the same location as HeapBase→FreeLists, which is a linked list of all the free chunks available to a heap.

ListsInUseUlong

Formally known as the FreeListInUseBitmap, this 4-byte integer is an optimization used to determine which ListHints have available chunks.

ListHints

Also known as FreeLists, these linked lists provide pointers to free chunks of memory, while also serving another purpose. If the LFH is enabled for a given Bucket size, then the blink of a specifically sized ListHint/FreeList will contain the address of a _HEAP_BUCKET + 1. ==== _LFH_HEAP (HeapBase→FrontEndHeap)

The Low Fragmentation heap is managed by this data structure.

5.2.5. _LFH_BLOCK_ZONE (HeapBase→FrontEndHeap→LocalData→CrtZone)

This data structure is used to keep track of locations in memory that are used to service allocation requests.

5.2.6. _HEAP_LOCAL_DATA (HeapBase→FrontEndHeap→LocalData)

A key structure that provides _HEAP_LOCAL_SEGMENT_INFO instances to the Low Fragmentation heap.

5.2.7. _HEAP_LOCAL_SEGMENT_INFO (HeapBase→FrontEndHeap→LocalData→SegmentInfo[])

This structure holds information that the heap algorithms use when determining the most efficient way to allocate and free memory.

5.2.8. _HEAP_SUBSEGMENT (HeapBase→FrontEndHeap→LocalData→SegmentInfo[]→Hint,ActiveSubsegment,CachedItems)

0:000:x86> dt ntdll_777a0000!_HEAP_SUBSEGMENT
   +0x000 LocalInfo        : Ptr32 _HEAP_LOCAL_SEGMENT_INFO
   +0x004 UserBlocks       : Ptr32 _HEAP_USERDATA_HEADER
   +0x008 AggregateExchg   : _INTERLOCK_SEQ
   +0x010 BlockSize        : Uint2B
   +0x012 Flags            : Uint2B
   +0x014 BlockCount       : Uint2B
   +0x016 SizeIndex        : UChar
   +0x017 AffinityIndex    : UChar
   +0x010 Alignment        : [2] Uint4B
   +0x018 SFreeListEntry   : _SINGLE_LIST_ENTRY
   +0x01c Lock             : Uint4B

5.2.9. _HEAP_USERDATA_HEADER (HeapBase→FrontEndHeap→LocalData→SegmentInfo[]→Hint,ActiveSubsegment,CachedItems→UserBlocks)

5.2.10. _INTERLOCK_SEQ (HeapBase→FrontEndHeap→LocalData→SegmentInfo[]→Hint,ActiveSubsegment,CachedItems→AggregateExchg)

5.2.11. _HEAP_ENTRY (Chunk Header)

0:001> dt _HEAP_ENTRY ntdll!_HEAP_ENTRY
  +0x000 Size             : Uint2B
  +0x002 Flags            : UChar
  +0x003 SmallTagIndex    : Uchar
  +0x000 SubSegmentCode   : Ptr32 Void
  +0x004 PreviousSize     : Uint2B
  +0x006 SegmentOffset    : Uchar
  +0x006 LFHFlags         : Uchar
  +0x007 UnusedBytes      : Uchar
  +0x000 FunctionIndex    : Uint2B
  +0x002 ContextValue     : Uint2B
  +0x000 InterceptorValue : Uint4B
  +0x004 UnusedBytesLength : Uint2B
  +0x006 EntryOffset      : Uchar
  +0x007 ExtendedBlockSignature : Uchar
Size

The size, in blocks, of the chunk. This includes the _HEAP_ENTRY itself.

Flags

Flags denoting the state of this heap chunk. Some examples are FREE or BUSY.

SmallTagIndex

This value will hold the XOR’ed checksum of the first three bytes of the _HEAP_ENTRY.

UnusedBytes/ExtendedBlockSignature

A value used to hold the unused bytes or a byte indicating the state of the chunk being managed by the LFH. LFH中,UnusedBytes-0x88=未用的字节数,UnusedBytes & 0x38确定是busy/free

The _HEAP_ENTRY, also known as the heap chunk header, is an 8-byte value stored before every chunk of memory in the heap (even the chunks inside the UserBlocks).

AllocHeap()返回_HEAP_ENTRY指针。

_HEAP_ENTRY(8 bytes)

new或malloc分配的空间

固定填充空间

Note
new或者malloc的地址减去8就指向该结构。 NOTE: 第三部分的固定填充空间是为了内存对齐而生成的,这部分空间还有一部分是用来额外记录这块内存的其它信息。
0:022:x86> !heap 460000 -h
...
    Heap entries for Segment00 in Heap 0000000000460000
                 address: psize . size  flags   state (requested size)
        0000000000523f60: 01000 . 00200 [101] - busy (1f8) Internal
        0000000000524160: 00200 . 00068 [100]
...
  • psize是前一个_HEAP_ENTRY的大小

  • size是本内存块的大小(应用申请的内存大小 + _HEAP_ENTRY 8字节)

  • [101]是这块内存的标志位,最右边一位为1表示该内存块被占用

  • busy(1f8) Internal就是解释说这块内存是被占用的(非空闲的)

    0x01 - HEAP_ENTRY_BUSY
    0x02 - HEAP_ENTRY_EXTRA_PRESENT
    0x04 - HEAP_ENTRY_FILL_PATTERN
    0x08 - HEAP_ENTRY_VIRTUAL_ALLOC
    0x10 - HEAP_ENTRY_LAST_ENTRY

5.2.12. 计算HeapEntry

read_input() {
    ret=$2
    if [ -z "$ret" ]; then
        read -p "$1: " ret
    fi
    echo "${ret}"
}

calc_heap_entry_code() {
    rtlpLFHKey="0x$1"
    heap="0x$2"
    heapEntryAddress="0x$3"
    heapEntrySize="0x$4"

    echo -n "Subsegment: "
    echo "obase=16; ibase=10; $((((($heapEntryAddress)/8) ^ $rtlpLFHKey ^ $heap ^ $heapEntrySize) + 4))" | bc
}

heap_entry_code() {
    if [ $# -le 7 ]
    then
        rtlpLFHKey=$(read_input "rtlpLFHKey" "$1")
        heap=$(read_input "heap" "$2")
        heapEntryAddress=$(read_input "heapEntryAddress" "$3")
        heapEntrySize=$(read_input "heapEntrySize/8" "$4")

        calc_heap_entry_code $rtlpLFHKey $heap $heapEntrySize $heapEntryAddress
    else
        # 输入rtlpLFHKey、HEAP、_HEAP_ENTRY地址、_HEAP_ENTRY开始8个byte的内容
        calc_heap_entry_code $1 $2 $3 $7$6$5$4
    fi
}
? ((0BD2F538-8)/8) ^ 58fb66e6 ^ 003D0000 ^ 0000000b + 4
? ((<地址>-8)/8) ^ <rtlpLFHKey> ^ <Heap> ^ <地址-8指向的内容> + 4

5.2.13. _DPH_HEAP_ROOT

0:013:x86> dt ntdll_77200000!_DPH_HEAP_ROOT
   +0x000 Signature        : Uint4B
   +0x004 HeapFlags        : Uint4B
   +0x008 HeapCritSect     : Ptr32 _RTL_CRITICAL_SECTION
   +0x00c nRemoteLockAcquired : Uint4B
   +0x010 pVirtualStorageListHead : Ptr32 _DPH_HEAP_BLOCK
   +0x014 pVirtualStorageListTail : Ptr32 _DPH_HEAP_BLOCK
   +0x018 nVirtualStorageRanges : Uint4B
   +0x01c nVirtualStorageBytes : Uint4B
   +0x020 BusyNodesTable   : _RTL_AVL_TABLE
   +0x058 NodeToAllocate   : Ptr32 _DPH_HEAP_BLOCK
   +0x05c nBusyAllocations : Uint4B
   +0x060 nBusyAllocationBytesCommitted : Uint4B
   +0x064 pFreeAllocationListHead : Ptr32 _DPH_HEAP_BLOCK
   +0x068 pFreeAllocationListTail : Ptr32 _DPH_HEAP_BLOCK
   +0x06c nFreeAllocations : Uint4B
   +0x070 nFreeAllocationBytesCommitted : Uint4B
   +0x074 AvailableAllocationHead : _LIST_ENTRY
   +0x07c nAvailableAllocations : Uint4B
   +0x080 nAvailableAllocationBytesCommitted : Uint4B
   +0x084 pUnusedNodeListHead : Ptr32 _DPH_HEAP_BLOCK
   +0x088 pUnusedNodeListTail : Ptr32 _DPH_HEAP_BLOCK
   +0x08c nUnusedNodes     : Uint4B
   +0x090 nBusyAllocationBytesAccessible : Uint4B
   +0x094 pNodePoolListHead : Ptr32 _DPH_HEAP_BLOCK
   +0x098 pNodePoolListTail : Ptr32 _DPH_HEAP_BLOCK
   +0x09c nNodePools       : Uint4B
   +0x0a0 nNodePoolBytes   : Uint4B
   +0x0a4 NextHeap         : _LIST_ENTRY
   +0x0ac ExtraFlags       : Uint4B
   +0x0b0 Seed             : Uint4B
   +0x0b4 NormalHeap       : Ptr32 Void
   +0x0b8 CreateStackTrace : Ptr32 _RTL_TRACE_BLOCK
   +0x0bc FirstThread      : Ptr32 Void

Signature是ffeeddcc。

5.2.14. _DPH_BLOCK_INFORMATION

打开 pageheap 之后,在分配的堆前有一个`_DPH_BLOCK_INFORMATION`结构。

0:001> dt _DPH_BLOCK_INFORMATION 0282ffe0-0x20
ntdll!_DPH_BLOCK_INFORMATION
   +0x000 StartStamp       : 0xabcdbbbb
   +0x004 Heap             : 0x00171000
   +0x008 RequestedSize    : 0x20
   +0x00c ActualSize       : 0x1000
   +0x010 FreeQueue        : _LIST_ENTRY [ 0x359 - 0x0 ]
   +0x010 TraceIndex       : 0x359
   +0x018 StackTrace       : 0x028c271c
   +0x01c EndStamp         : 0xdcbabbbb
  1. There is a data structure _DPH_BLOCK_INFORMATION in front of the allocated heap block. _DPH_BLOCK_INFORMATIO is composed with useful information for debugging, for example StartStamp, EndStamp, StackTrace…etc. Below is

    Alloc/Free Page Mode Fill Pattern (4) PageHeap metadata (24) Fill Pattern (4) Accessible area Suffix Pattern

    Alloc

    Normal

    ABCDAAAA

    DCBAAAAA

    E0…

    A0A0A0A0…

    Free

    Normal

    ABCDAAA9

    DCBAAAA9

    F0…

    A0A0A0A0…

    Alloc

    Full

    ABCDBBBB

    DCBABBBB

    C0

    D0D0D0D0…

    Free

    Full

    ABCDBBBA

    DCBABBBA

    F0

    D0D0D0D0…

    占用堆块 空闲堆块

    页堆

    准页堆

    页堆

    准页堆

    头结构起始签名

    ABCDBBBB

    ABCDAAAA

    ABCDAAA9

    ABCDBBBA

    头结构结束签名

    DCBABBBB

    DCBAAAAA

    DCBAAAA9

    DCBABBBA

    用户区

    C0

    E0

    F0

    F0

    栅栏字节

    N/A

    A0

    N/A

    N/A

    补齐字节

    D0

    00

    N/A

    N/A

  2. The fill patterns for allocate and free are different.

  3. Suffix Pattern, because the allocated size is not always exactly the same as request ((requested bytes) + 8) / 8 * 8), the extra heap is filled with Suffix Pattern.

  4. There is inaccessible page if pageheap is enabled under full mode to protect underruns and overruns, the application causes an access violation when under/overrun the allocated heap.

    1. Behind Suffix Pattern block

    2. Inform of Accessible area.

Full Page Heap的签名(ABCDBBBB/DCBABBBB)只在`verifier.dll!AVrfDebugPageHeapAllocate()→ `verifier.dll!AVrfpDphWritePageHeapBlockInformation() 中写入。

Normal Page Heap的签名(ABCDAAAA/DCBAAAAA)verifier.dll!AVrfpDphNormalHeapAllocate()verifier.dll!AVrfpDphWriteNormalHeapBlockInformation() 中写入。

5.2.15. _DPH_HEAP_BLOCK

0:014:x86> dt verifier_73240000!_DPH_HEAP_BLOCK -b
   +0x000 NextFullPageHeapDelayedNode : Ptr32
   +0x004 DelayQueueEntry  : _DPH_DELAY_FREE_QUEUE_ENTRY
      +0x000 Flags            : _DPH_DELAY_FREE_FLAGS
         +0x000 PageHeapBlock    : Pos 0, 1 Bit
         +0x000 NormalHeapBlock  : Pos 1, 1 Bit
         +0x000 Lookaside        : Pos 2, 1 Bit
         +0x000 All              : Uint4B
      +0x004 NextEntry        : Ptr32
   +0x000 LookasideEntry   : _LIST_ENTRY
      +0x000 Flink            : Ptr32
      +0x004 Blink            : Ptr32
   +0x000 UnusedListEntry  : _LIST_ENTRY
      +0x000 Flink            : Ptr32
      +0x004 Blink            : Ptr32
   +0x000 VirtualListEntry : _LIST_ENTRY
      +0x000 Flink            : Ptr32
      +0x004 Blink            : Ptr32
   +0x000 FreeListEntry    : _LIST_ENTRY
      +0x000 Flink            : Ptr32
      +0x004 Blink            : Ptr32
   +0x000 TableLinks       : _RTL_BALANCED_LINKS
      +0x000 Parent           : Ptr32
      +0x004 LeftChild        : Ptr32
      +0x008 RightChild       : Ptr32
      +0x00c Balance          : Char
      +0x00d Reserved         : UChar
   +0x010 pUserAllocation  : Ptr32
   +0x014 pVirtualBlock    : Ptr32
   +0x018 nVirtualBlockSize : Uint4B
   +0x01c Flags            : _DPH_HEAP_BLOCK_FLAGS
      +0x000 Virtual          : Pos 0, 1 Bit
      +0x000 UnusedNode       : Pos 1, 1 Bit
      +0x000 Delay            : Pos 2, 1 Bit
      +0x000 Lookaside        : Pos 3, 1 Bit
      +0x000 Free             : Pos 4, 1 Bit
      +0x000 Busy             : Pos 5, 1 Bit
      +0x000 All              : Uint4B
   +0x020 nUserRequestedSize : Uint4B
   +0x024 AdjacencyEntry   : _LIST_ENTRY
      +0x000 Flink            : Ptr32
      +0x004 Blink            : Ptr32
   +0x02c ThreadId         : Uint4B
   +0x030 StackTrace       : Ptr32

5.3. 各结构间的关系

  • _HEAP.FrontEndHeap`指向`_LFH_HEAP

  • _LFH_HEAP.LocalData`指向`_HEAP_LOCAL_DATA

  • _HEAP_LOCAL_DATA.SegmentInfo`指向`_HEAP_LOCAL_SEGMENT_INFO

  • _HEAP_LOCAL_SEGMENT_INFO.Hint/ActiveSubsegment/CachedItems`指向`_HEAP_SUBSEGMENT

  • _HEAP_SUBSEGMENT.UserBlocks`指向`_HEAP_USERDATA_HEADER

5.4. 查看进程堆

一个新的进程创建的时候,在用户态初始化阶段,会调用RtlCreateHeap 函数为新的进程创建第一个堆,成为进程的默认堆(进程堆)。

  1. 使用`!peb`命令得到PEB地址

  2. `dt PEB _PEB地址`命令查看PEB内容

    1. ProcessHeap 字段保存进程堆的地址

    2. NumberOfHeaps 记录着进程的堆的数量,`ProcessHeaps`记录的是一个数组,数组的内容是进程每一个堆的句柄。

      • 可通过`dd ProcessHeaps字段值 LNumberOfHeaps`命令查看所有堆的句柄

      • 可以通过`!heap -h`命令查看堆列表

5.5. 内存释放

delete/free`最终调用`ntdll!RtlFreeHeap

释放了从堆上分配的内存,不代表堆管理器会把申请到的内存归还给系统的内存管理器,这样是为了减少与内存管理器的交互次数。只有满足下面两个条件时才归还内存:

  1. 本次释放的堆块大小超过了堆参数中的DeCommitFreeBlockThreshold代表的阈值

  2. 累积的总空闲空间大于DeCommitTotalFreeThreshold代表的阈值

上述的两个字段的值是根据PEB中的相应字段来初始化的。

   +0x080 HeapDeCommitTotalFreeThreshold : 0x10000
   +0x084 HeapDeCommitFreeBlockThreshold : 0x1000

如果释放的堆块大小如果没有满足上述的条件,堆管理器会把这个块加到空闲列表中,并且更新堆管理器的总空闲空间值。

堆管理内部是以分配粒度为单位来表示上述的两个阀值和计算堆块大小的。

使用`!heap -v 堆句柄`命令可以查看堆的分配粒度和解除提交阀值

0:000> !heap -v 011d0000
...
    DeCommit Block Thres: 00000200
    DeCommit Total Thres: 00002000
...

5.6. 部分系统调用的伪代码

5.6.1. AVrfpDphCheckNormalHeapBlock(verifier.dll)进行的检查

方法有三个参数:

  1. lpHeapRoot:: 指向`_DPH_HEAP_ROOT`结构,一般是heap handle+1000h的位置

  2. lpMem:: 指向要释放的内存

  3. 参数3:: 固定为4?

  4. 参数4:: 固定为1?

主要流程:

  1. lpMem-20h,得到指向`_DPH_BLOCK_INFORMATION`的`lpBlockInfo`

  2. 验证`lpBlockInfo→StartStamp`是`ABCDAAAA`

    • 如果不正确,则检查`lpBlockInfo→StartStamp`是否`ABCDAAA9`,是则`错误标志`为`101`,否则为'1'

  3. 验证`lpBlockInfo→EndStamp`是`DCBAAAAA`

    • 如果不正确,则`错误标志`为`2`

  4. 验证`lpBlockInfo→Heap` xor `80000000h`后,等于lpHeapRoot

    • 如果不正确,则`错误标志`为`4`

  5. 如果`参数4`为0,则正确返回

  6. 如果`lpHeapRoot→ExtraFlags`与`20000h`非0,则成功返回

  7. 以参数`内存块尾下一地址`、8A0h`为参数调用`_DphPatternCheck,结果为0则成功返回

    • 如果结果非0,则`错误标志`为`10`

  8. 如果有错误,则以`lpHeapRoot`、参数3lpMem错误标志`作为参数调用`_AVrfpDphReportCorruptedBlock

5.6.3. AVrfDebugPageHeapFree流程

bool AVrfDebugPageHeapFree(
    void* hHeap,    // 堆句柄
    heapFlags,      // 堆标志
    void* lpMem)    // 要释放的内存
{
  bool ret = false;
  _DPH_HEAP_ROOT* lpDphHeapRoot = AVrfpDphPointerFromHandle(hHeap);  // 一般是hHeap+0x1000处
  heapFlags = heapFlags | _DPH_HEAP_ROOT.HeapFlags;

  AVrfpDphEnterHeapPath(lpDphHeapRoot, heapFlags);

  lpDphHeapBlock = AVrfpDphFindBusyMemoryAndRemoveFromBusyList(lpDphHeapRoot, lpMem);
  if (lpDphHeapBlock != NULL)
  {
    lpDphHeapBlock->DelayQueueEntry.Flags = 0;
    lpDphHeapBlock->DelayQueueEntry.Flags |= 1;  // PageHeapBlock
    AVrfpDphPlaceOnDelayFree(lpDphHeapRoot, &lpDphHeapBlock->DelayQueueEntry);
    ret = true;
  } else
  {
    ret = AVrfpDphNormalHeapFree(lpDphHeapRoot, lpDphHeapRoot->NormalHeap, heapFlags, lpMem);
  }

  AVrfpDphExitHeapPath(lpDphHeapRoot, heapFlags);

  if (!ret)
  {
    if (heapFlags & HEAP_GENERATE_EXCEPTIONS)
    {
      AVrfpDphRaiseException(0xC0000005);
    }
  }

  return ret;
}

5.6.4. AVrfpDphFindBusyMemoryAndRemoveFromBusyList

bool AVrfpDphFindBusyMemoryAndRemoveFromBusyList(
    _DPH_HEAP_ROOT* lpDphHeapRoot,
    void* lpMem)
{
  AVrfpDphEnterCriticalSection(lpDphHeapRoot, 0);

  lpDphHeapBlock = AVrfpDphFindBusyMemory(lpDphHeapRoot, lpMem);

  if (lpDphHeapBlock != NULL)
  {
    var_8 = AVrfpDphGetBusyBlockData(lpDphHeapRoot, lpDphHeapBlock);
    *var_8--;

    lpDphHeapBlock->Flags &= 0xFFFFFFDF;    // ~HEAP_GROWABLE

    RtlDeleteElementGenericTableAvl(
        &lpDphHeapBlock->pUserAllocation,
        &lpDphHeapRoot->BusyNodesTable);

    lpDphHeapRoot->nBusyAllocations--;
    lpDphHeapRoot->nBusyAllocationBytesCommitted -= lpDphHeapBlock->nVirtualBlockSize;

    lpDphHeapRoot->nBusyAllocationBytesAccessible -=
      AVrfpDphGetActualSizeForAllocation(
          lpDphHeapRoot, lpDphHeapBlock->nUserRequestedSize);
  }

  AVrfpDphPostProcessing(lpDphHeapRoot);

  return lpDphHeapBlock;
}

5.6.5. AVrfpDphFindBusyMemory

_DPH_HEAP_BLOCK* AVrfpDphFindBusyMemory(
    _DPH_HEAP_ROOT* lpDphHeapRoot,
    void* lpMem)
{
}

5.6.6. AVrfpDphFindBusyMemoryNoCheck

_DPH_HEAP_BLOCK* AVrfpDphFindBusyMemoryNoCheck(
    _DPH_HEAP_ROOT* lpDphHeapRoot,
    void* lpMem)
{
  if (lpMem == NULL)
  {
    return NULL;
  }

  if (DphDelayFreeLookup(lpHeapRoot, lpMem) == lpMem)
  {
    AVrfpDphReportCorruptedBlock(); //  ReasonCode==0x100
  }

  if (lpDphHeapRoot->ExtraFlags & HEAP_REALLOC_IN_PLACE_ONLY)
  {
    void* ppUserAllocation = RtlLookupElementGenericTableAvl(&lpDphHeapRoot->BusyNodesTable, lpMem);

    if (ppUserAllocation != NULL)
    {
      lpDphHeapBlock = ppUserAllocation - offset(_DPH_HEAP_BLOCK.pUserAllocation);
    }
    else
    {
      lpDphHeapBlock = NULL;
    }
  }
  else
  {
    _DPH_BLOCK_INFORMATION* lpDphBlockInfo = (_DPH_BLOCK_INFORMATION*)(lpMem - 0x20);

    //  & 0xFFFFF000的目的是取得本地址对应的4K页的开始地址。
    // Windows 10中是lpMem - 0x28,
    // 即sizeof(_DPH_BLOCK_INFORMATION) + sizeof(0xEEEEEEEE) +  sizeof(_DPH_HEAP_BLOCK*)
    // 即只要本页还可以存放块信息+8个字节,就保存在本页,否则使用前一内存页。
    // 此处可能少减了4个字节。对于大小最后三位为FD9~FDC的内存,如果设置了unaligned选项,
    // 将开始于027~024的内存位置,应该是不足以存放信息的,却误判为足以存放,
    // 从而使用了错误的页块地址,造成完整性验证失败!
    DWORD* lpBlock = (lpMem - 0x24) & 0xFFFFF000;

    lpDphHeapBlock = NULL;

    if (lpDphBlockInfo->StartStamp == 0xABCDBBBB)
    {   // Full Page Heap
      if (lpDphBlockInfo->EndStamp == 0xDCBABBBB)
      {
        if (*lpBlock == 0xEEEEEEEE)
        {
          if ((_DPH_HEAP_BLOCK*)(lpBlock + 4)->pUserAllocation == lpMem)
          {
            lpDphHeapBlock = (_DPH_HEAP_BLOCK*)(lpBlock + 4);
          }
        }
      }
    }
  }

  return lpDphHeapBlock;
}

5.6.7. DphDelayFreeLookup

返回lpDphHeapRoot→DelayFreeListLookup中,以内存地址 % 0x101为下标的项目下标。

void* DphDelayFreeLookup(
    _DPH_HEAP_ROOT* lpDphHeapRoot,
    void* lpMem)
{
  return &lpDphHeapRoot->DelayFreeListLookup[
  lpMem % (sizeof(lpDphHeapRoot->DelayFreeListLookup) / sizeof(lpDphHeapRoot->DelayFreeListLookup[0])];
}

5.6.8. RtlDebugFreeHeap流程

  • 参考ReactOS代码

    1. lib/rtl/heapdbg.c:267 RtlDebugFreeHeap

          if (Heap->ForceFlags & HEAP_FLAG_PAGE_ALLOCS)
              return RtlpPageHeapFree(HeapPtr, Flags, Ptr);

5.6.9. RtlpLowFragHeapFree

RtlpLowFragHeapFree(
  _HEAP*       lpHeap,      // ecx
  _HEAP_ENTRY* lpHeapEntry) // edx
{
  (lpHeapEntry >> 3)
}

5.7. 部分函数的作用

_AVrfpDphEnterHeapPath()

验证指定的堆没有被多线程同时访问

_AVrfpDphPointerFromHandle()

hHeap → lpDphHeapRoot

RtlpDphReportCorruptedBlock(void * Heap, unsigned long Context, void * Block, struct _DPH_VALIDATION_INFORMATION * ValidationInformation)

PageHeap验证失败

RtlpDphNormalHeapFree(struct _DPH_HEAP_ROOT * Heap, void * NtHeap, unsigned long Flags, void * Block = 0x78d54690)

释放Normal PageHeap

RtlpDebugPageHeapFree(void * HeapHandle, unsigned long Flags, void * Address = 0x78d54690)

释放PageHeap

RtlDebugFreeHeap(void * HeapHandle, unsigned long Flags, void * BaseAddress = 0x78d54690)

释放PageHeap

RtlFreeHeapSlowly(void * HeapHandle, unsigned long Flags, void * BaseAddress = 0x78d54690)

释放PageHeap

RtlFreeHeap(void * HeapHandle, unsigned long Flags, void * BaseAddress = 0x78d54690)

释放PageHeap

_DphPatternCheck(void * lpUserMemEnd, ?, ?)

?

dt _DPH_VALIDATION_INFORMATION
   +0x000 ReasonCode
   +0x004 ExceptionCode
   +0x008 CorruptionLocation

5.7.1. AVrfDebugPageHeapAllocate()

6. boost::atomic的大小

Windows下,小于4字节的atomic都会扩展到4字节。

可参见 boost/atomic/detail/ops_windows.hpp。

7. 其它

7.1. _getptd()

返回 _tiddata

//_tiddata的定义. CRT SRC\mtdll.h
struct _tiddata {
        unsigned long   _tid;       /* thread ID */
        unsigned long   _thandle;   /* thread handle */
        int     _terrno;            /* errno value */
        unsigned long   _tdoserrno; /* _doserrno value */
        unsigned int    _fpds;      /* Floating Point data segment */
        unsigned long   _holdrand;  /* rand() seed value */
        char *      _token;         /* ptr to strtok() token */
#ifdef _WIN32
        wchar_t *   _wtoken;        /* ptr to wcstok() token */
#endif  /* _WIN32 */
        unsigned char * _mtoken;    /* ptr to _mbstok() token */
        /* following pointers get malloc'd at runtime */
        char *      _errmsg;        /* ptr to strerror()/_strerror() buff */
        char *      _namebuf0;      /* ptr to tmpnam() buffer */
#ifdef _WIN32
        wchar_t *   _wnamebuf0;     /* ptr to _wtmpnam() buffer */
#endif  /* _WIN32 */
        char *      _namebuf1;      /* ptr to tmpfile() buffer */
#ifdef _WIN32
        wchar_t *   _wnamebuf1;     /* ptr to _wtmpfile() buffer */
#endif  /* _WIN32 */
        char *      _asctimebuf;    /* ptr to asctime() buffer */
#ifdef _WIN32
        wchar_t *   _wasctimebuf;   /* ptr to _wasctime() buffer */
#endif  /* _WIN32 */
        void *      _gmtimebuf;     /* ptr to gmtime() structure */
        char *      _cvtbuf;        /* ptr to ecvt()/fcvt buffer */
        /* following fields are needed by _beginthread code */
        void *      _initaddr;      /* initial user thread address */
        void *      _initarg;       /* initial user thread argument */
        /* following three fields are needed to support signal handling and
         * runtime errors */
        void *      _pxcptacttab;   /* ptr to exception-action table */
        void *      _tpxcptinfoptrs; /* ptr to exception info pointers */
        int         _tfpecode;      /* float point exception code */
        /* following field is needed by NLG routines */
        unsigned long   _NLG_dwCode;
        /*
         * Per-Thread data needed by C++ Exception Handling
         */
        void *      _terminate;     /* terminate() routine */
        void *      _unexpected;    /* unexpected() routine */
        void *      _translator;    /* S.E. translator */
        void *      _curexception;  /* current exception */
        void *      _curcontext;    /* current exception context */
#if defined (_M_MRX000)
        void *      _pFrameInfoChain;
        void *      _pUnwindContext;
        void *      _pExitContext;
        int         _MipsPtdDelta;
        int         _MipsPtdEpsilon;
#elif defined (_M_PPC)
        void *      _pExitContext;
        void *      _pUnwindContext;
        void *      _pFrameInfoChain;
        int         _FrameInfo[6];
#endif  /* defined (_M_PPC) */
        };
typedef struct _tiddata * _ptiddata;
⚠️ **GitHub.com Fallback** ⚠️