初识win32堆

@lzeroyuee  October 24, 2020

win32堆管理

摘录于《0day安全》、《漏洞战争》

  • 堆块:堆区的内存按不同大小组织成块,以堆块位单位进行标识,其包含:

    1. 块首:堆块自身的信息
    2. 块身:数据部分
  • 堆表:一般位于堆区的起始位置,用于索引堆区中的堆块

1.png

在Windows中,占用态的堆块被使用它的程序索引,而空闲态的堆块才被堆表索引

其中,最重要的有两种堆表:

  1. 空表 FreeList
  2. 快表 Lookaside

空表 FreeList

空闲双向链表,有FreeList[0] ~ FreeList[127]128项,其中:

  1. FreeList[0]为所有大于等于1024字节且小于512KB的空闲堆块的链表,按照堆块大小升序排列
  2. i >= 1 && i <= 127时,FreeList[i]为所有大小为i * 8字节的空闲堆块的链表

2.png

快表 Lookaside

快速单向链表,为了加速堆块分配而设计出来的

其空闲块块首被设置为占用态而防止了堆块合并操作,同样拥有Lookaside[0] ~ Lookaside[127]128项

组织方式与空表类似,区别如下:

  1. 此表为单向链表
  2. 每项最多只有4个堆块节点
  3. 快表总是被初始化为空

3.png

堆块的分配和释放

在Windows中,堆块的分配算法并不固定,根据系统版本不同而分配策略也不经相同,大致如下:

分配释放
小块
Size < 1KB
首先进行快表分配
若快表分配失败,进行普通空表分配
若普通空表分配失败,使用堆缓存分配
若堆缓存分配失败,尝试从FreeList[0]中分配
FreeList[0]分配失败,进行内存紧缩后在尝试分配
若仍无法分配,返回NULL
优先链入快表(只能链入4个)
如果快表满,则将其链入相应的空表
大块
1KB < Size < 512KB
首先使用堆缓存进行分配
若堆缓存分配失败,使用FreeList[0]中的大块进行分配
优先将其放入堆缓存
若堆缓存满,将链入FreeList[0]
巨块
Size >= 512KB
一般来说巨块申请非常罕见,要用到虚分配方法(实际上并不是从堆区分配的)
这种类型的堆块在堆溢出利用中几乎不会遇到
直接释放,没有堆表操作

需要注意的:

  1. 快表中空闲块被设置为占用态,因此不会发送堆块合并,且只有精确匹配时才会分配
  2. 快表是单向链表,且每项只有4个堆块节点,在分配和释放时总是优先使用快表,失败时才用空表

Windows下堆分配体系概览

4.png

以下参考ReactOS的RtlAllocateHeap函数

PVOID NTAPI
RtlAllocateHeap(IN PVOID HeapPtr,
                IN ULONG Flags,
                IN SIZE_T Size)
{
    PHEAP Heap = (PHEAP)HeapPtr;
    PULONG FreeListsInUse;
    ULONG FreeListsInUseUlong;
    SIZE_T AllocationSize;
    SIZE_T Index, InUseIndex, i;
    PLIST_ENTRY FreeListHead;
    PHEAP_ENTRY InUseEntry;
    PHEAP_FREE_ENTRY FreeBlock;
    UCHAR FreeFlags, EntryFlags = HEAP_ENTRY_BUSY;
    EXCEPTION_RECORD ExceptionRecord;
    BOOLEAN HeapLocked = FALSE;
    PHEAP_VIRTUAL_ALLOC_ENTRY VirtualBlock = NULL;
    PHEAP_ENTRY_EXTRA Extra;
    NTSTATUS Status;

    /* Force flags */
    Flags |= Heap->ForceFlags;

    /* Call special heap */
    if (RtlpHeapIsSpecial(Flags))    // 判断是否是Debug模式,调用Debug分配算法
        return RtlDebugAllocateHeap(Heap, Flags, Size);

    /* Check for the maximum size */
    if (Size >= 0x80000000)        // 检查最大分配大小
    {
        RtlSetLastWin32ErrorAndNtStatusFromNtStatus(STATUS_NO_MEMORY);
        DPRINT1("HEAP: Allocation failed!\n");
        return NULL;
    }

    if (Flags & (HEAP_CREATE_ENABLE_TRACING))    // 跟踪信息
    {
        DPRINT1("HEAP: RtlAllocateHeap is called with unsupported flags %x, ignoring\n", Flags);
    }

    //DPRINT("RtlAllocateHeap(%p %x %x)\n", Heap, Flags, Size);

    /* Calculate allocation size and index */    // -> 计算分配大小
    if (Size)
        AllocationSize = Size;
    else
        AllocationSize = 1;
    AllocationSize = (AllocationSize + Heap->AlignRound) & Heap->AlignMask;

    /* Add extra flags in case of settable user value feature is requested,
       or there is a tag (small or normal) or there is a request to
       capture stack backtraces */
    if ((Flags & HEAP_EXTRA_FLAGS_MASK) ||
        Heap->PseudoTagEntries)
    {
        /* Add flag which means that the entry will have extra stuff attached */
        EntryFlags |= HEAP_ENTRY_EXTRA_PRESENT;

        /* Account for extra stuff size */
        AllocationSize += sizeof(HEAP_ENTRY_EXTRA);    // 检查是否有EXTRA标志,有则大小+sizeof(HEAP_ENTRY_EXTRA)
    }

    /* Add settable user flags, if any */
    EntryFlags |= (Flags & HEAP_SETTABLE_USER_FLAGS) >> 4;

    Index = AllocationSize >> HEAP_ENTRY_SHIFT;        // 获取链表索引

    /* Acquire the lock if necessary */
    if (!(Flags & HEAP_NO_SERIALIZE))    // 多线程环境获取锁
    {
        RtlEnterHeapLock(Heap->LockVariable, TRUE);
        HeapLocked = TRUE;
    }

    /* Depending on the size, the allocation is going to be done from dedicated,
       non-dedicated lists or a virtual block of memory */
    if (Index < HEAP_FREELISTS)
    {
        FreeListHead = &Heap->FreeLists[Index];

        if (!IsListEmpty(FreeListHead))        // 精确匹配
        {
            /* There is a free entry in this list */
            FreeBlock = CONTAINING_RECORD(FreeListHead->Blink,
                                          HEAP_FREE_ENTRY,
                                          FreeList);

            /* Save flags and remove the free entry */
            FreeFlags = FreeBlock->Flags;
            RtlpRemoveFreeBlock(Heap, FreeBlock, TRUE, FALSE);

            /* Update the total free size of the heap */
            Heap->TotalFreeSize -= Index;

            /* Initialize this block */
            InUseEntry = (PHEAP_ENTRY)FreeBlock;
            InUseEntry->Flags = EntryFlags | (FreeFlags & HEAP_ENTRY_LAST_ENTRY);
            InUseEntry->UnusedBytes = (UCHAR)(AllocationSize - Size);
            InUseEntry->SmallTagIndex = 0;
        }
        else    // 最小满分配
        {
            /* Find smallest free block which this request could fit in */
            InUseIndex = Index >> 5;
            FreeListsInUse = &Heap->u.FreeListsInUseUlong[InUseIndex];

            /* This bit magic disables all sizes which are less than the requested allocation size */
            FreeListsInUseUlong = *FreeListsInUse++ & ~((1 << ((ULONG)Index & 0x1f)) - 1);

            /* If size is definitily more than our lists - go directly to the non-dedicated one */
            if (InUseIndex > 3)
                return RtlpAllocateNonDedicated(Heap, Flags, Size, AllocationSize, Index, HeapLocked);

            /* Go through the list */
            for (i = InUseIndex; i < 4; i++)
            {
                if (FreeListsInUseUlong)
                {
                    FreeListHead = &Heap->FreeLists[i * 32];
                    break;
                }

                if (i < 3) FreeListsInUseUlong = *FreeListsInUse++;
            }

            /* Nothing found, search in the non-dedicated list */
            if (i == 4)
                return RtlpAllocateNonDedicated(Heap, Flags, Size, AllocationSize, Index, HeapLocked);

            /* That list is found, now calculate exact block  */
            FreeListHead += RtlpFindLeastSetBit(FreeListsInUseUlong);

            /* Take this entry and remove it from the list of free blocks */
            FreeBlock = CONTAINING_RECORD(FreeListHead->Blink,
                                          HEAP_FREE_ENTRY,
                                          FreeList);
            RtlpRemoveFreeBlock(Heap, FreeBlock, TRUE, FALSE);

            /* Split it */
            InUseEntry = RtlpSplitEntry(Heap, Flags, FreeBlock, AllocationSize, Index, Size);
        }

        /* Release the lock */
        if (HeapLocked) RtlLeaveHeapLock(Heap->LockVariable);    // 多线程环境解锁

        /* Zero memory if that was requested */
        if (Flags & HEAP_ZERO_MEMORY)
            RtlZeroMemory(InUseEntry + 1, Size);
        else if (Heap->Flags & HEAP_FREE_CHECKING_ENABLED)
        {
            /* Fill this block with a special pattern */
            RtlFillMemoryUlong(InUseEntry + 1, Size & ~0x3, ARENA_INUSE_FILLER);
        }

        /* Fill tail of the block with a special pattern too if requested */
        if (Heap->Flags & HEAP_TAIL_CHECKING_ENABLED)
        {
            RtlFillMemory((PCHAR)(InUseEntry + 1) + Size, sizeof(HEAP_ENTRY), HEAP_TAIL_FILL);
            InUseEntry->Flags |= HEAP_ENTRY_FILL_PATTERN;
        }

        /* Prepare extra if it's present */
        if (InUseEntry->Flags & HEAP_ENTRY_EXTRA_PRESENT)
        {
            Extra = RtlpGetExtraStuffPointer(InUseEntry);
            RtlZeroMemory(Extra, sizeof(HEAP_ENTRY_EXTRA));

            // TODO: Tagging
        }

        /* User data starts right after the entry's header */
        return InUseEntry + 1;
    }
    else if (Index <= Heap->VirtualMemoryThreshold)        // FreeList[0]分配
    {
        /* The block is too large for dedicated lists, but fine for a non-dedicated one */
        return RtlpAllocateNonDedicated(Heap, Flags, Size, AllocationSize, Index, HeapLocked);
    }
    else if (Heap->Flags & HEAP_GROWABLE)    // 虚分配
    {
        /* We've got a very big allocation request, satisfy it by directly allocating virtual memory */
        AllocationSize += sizeof(HEAP_VIRTUAL_ALLOC_ENTRY) - sizeof(HEAP_ENTRY);

        Status = ZwAllocateVirtualMemory(NtCurrentProcess(),
                                         (PVOID *)&VirtualBlock,
                                         0,
                                         &AllocationSize,
                                         MEM_COMMIT,
                                         PAGE_READWRITE);

        if (!NT_SUCCESS(Status))
        {
            // Set STATUS!
            /* Release the lock */
            if (HeapLocked) RtlLeaveHeapLock(Heap->LockVariable);
            DPRINT1("HEAP: Allocation failed!\n");
            return NULL;
        }

        /* Initialize the newly allocated block */
        VirtualBlock->BusyBlock.Size = (USHORT)(AllocationSize - Size);
        ASSERT(VirtualBlock->BusyBlock.Size >= sizeof(HEAP_VIRTUAL_ALLOC_ENTRY));
        VirtualBlock->BusyBlock.Flags = EntryFlags | HEAP_ENTRY_VIRTUAL_ALLOC | HEAP_ENTRY_EXTRA_PRESENT;
        VirtualBlock->CommitSize = AllocationSize;
        VirtualBlock->ReserveSize = AllocationSize;

        /* Insert it into the list of virtual allocations */
        InsertTailList(&Heap->VirtualAllocdBlocks, &VirtualBlock->Entry);

        /* Release the lock */
        if (HeapLocked) RtlLeaveHeapLock(Heap->LockVariable);

        /* Return pointer to user data */
        return VirtualBlock + 1;
    }

    /* Generate an exception */
    if (Flags & HEAP_GENERATE_EXCEPTIONS)
    {
        ExceptionRecord.ExceptionCode = STATUS_NO_MEMORY;
        ExceptionRecord.ExceptionRecord = NULL;
        ExceptionRecord.NumberParameters = 1;
        ExceptionRecord.ExceptionFlags = 0;
        ExceptionRecord.ExceptionInformation[0] = AllocationSize;

        RtlRaiseException(&ExceptionRecord);
    }

    RtlSetLastWin32ErrorAndNtStatusFromNtStatus(STATUS_BUFFER_TOO_SMALL);

    /* Release the lock */
    if (HeapLocked) RtlLeaveHeapLock(Heap->LockVariable);
    DPRINT1("HEAP: Allocation failed!\n");
    return NULL;
}

堆结构

堆块结构

堆块由块首与块身组成,块首记录着堆块本身的信息,而块身则是数据部分,故堆块的总大小总是比申请时所指定的大小多出一部分

由API申请到的堆内存所返回的地址则是指向块身的

  • 在32位下,块首为8字节,且块首+块身总大小8字节对齐
  • 在64位下,块首为16字节,且块首+块身总大小16字节对齐

5.png

在空闲态块中,块首后的Data区域前是一对双向链表的指针,在变为占用块后此空间将交还给Data

在占用态块中,块首是_HEAP_ENTRY结构

:以下结由Win10 2004版的PDB文件转换而来

_HEAP_ENTRY

          typedef struct _HEAP_ENTRY                          // 26 elements, 0x10 bytes (sizeof) 
          {                                                                                       
              union                                           // 5 elements, 0x10 bytes (sizeof)  
              {                                                                                   
/*0x000*/         struct _HEAP_UNPACKED_ENTRY UnpackedEntry;  // 10 elements, 0x10 bytes (sizeof) 
                  struct                                      // 2 elements, 0x10 bytes (sizeof)  
                  {                                                                               
/*0x000*/             VOID*        PreviousBlockPrivateData;                                      
                      union                                   // 3 elements, 0x8 bytes (sizeof)   
                      {                                                                           
                          struct                              // 3 elements, 0x8 bytes (sizeof)   
                          {                                                                       
/*0x008*/                     UINT16       Size;                                                  
/*0x00A*/                     UINT8        Flags;                                                 
/*0x00B*/                     UINT8        SmallTagIndex;                                         
/*0x00C*/                     UINT8        _PADDING0_[0x4];                                       
                          };                                                                      
                          struct                              // 4 elements, 0x8 bytes (sizeof)   
                          {                                                                       
/*0x008*/                     ULONG32      SubSegmentCode;                                        
/*0x00C*/                     UINT16       PreviousSize;                                          
                              union                           // 2 elements, 0x1 bytes (sizeof)   
                              {                                                                   
/*0x00E*/                         UINT8        SegmentOffset;                                     
/*0x00E*/                         UINT8        LFHFlags;                                          
                              };                                                                  
/*0x00F*/                     UINT8        UnusedBytes;                                           
                          };                                                                      
/*0x008*/                 UINT64       CompactHeader;                                             
                      };                                                                          
                  };                                                                              
/*0x000*/         struct _HEAP_EXTENDED_ENTRY ExtendedEntry;  // 7 elements, 0x10 bytes (sizeof)  
                  struct                                      // 5 elements, 0x10 bytes (sizeof)  
                  {                                                                               
/*0x000*/             VOID*        Reserved;                                                      
                      union                                   // 2 elements, 0x4 bytes (sizeof)   
                      {                                                                           
                          struct                              // 2 elements, 0x4 bytes (sizeof)   
                          {                                                                       
/*0x008*/                     UINT16       FunctionIndex;                                         
/*0x00A*/                     UINT16       ContextValue;                                          
                          };                                                                      
/*0x008*/                 ULONG32      InterceptorValue;                                          
                      };                                                                          
/*0x00C*/             UINT16       UnusedBytesLength;                                             
/*0x00E*/             UINT8        EntryOffset;                                                   
/*0x00F*/             UINT8        ExtendedBlockSignature;                                        
                  };                                                                              
                  struct                                      // 2 elements, 0x10 bytes (sizeof)  
                  {                                                                               
/*0x000*/             VOID*        ReservedForAlignment;                                          
                      union                                   // 2 elements, 0x8 bytes (sizeof)   
                      {                                                                           
                          struct                              // 2 elements, 0x8 bytes (sizeof)   
                          {                                                                       
/*0x008*/                     ULONG32      Code1;                                                 
                              union                           // 2 elements, 0x4 bytes (sizeof)   
                              {                                                                   
                                  struct                      // 3 elements, 0x4 bytes (sizeof)   
                                  {                                                               
/*0x00C*/                             UINT16       Code2;                                         
/*0x00E*/                             UINT8        Code3;                                         
/*0x00F*/                             UINT8        Code4;                                         
                                  };                                                              
/*0x00C*/                         ULONG32      Code234;                                           
                              };                                                                  
                          };                                                                      
/*0x008*/                 UINT64       AgregateCode;                                              
                      };                                                                          
                  };                                                                              
              };                                                                                  
          }HEAP_ENTRY, *PHEAP_ENTRY;    

_HEAP_SEGMENT

          typedef struct _HEAP_SEGMENT                     // 14 elements, 0x70 bytes (sizeof) 
          {                                                                                    
/*0x000*/     struct _HEAP_ENTRY Entry;                    // 26 elements, 0x10 bytes (sizeof) 
/*0x010*/     ULONG32      SegmentSignature;                                                   
/*0x014*/     ULONG32      SegmentFlags;                                                       
/*0x018*/     struct _LIST_ENTRY SegmentListEntry;         // 2 elements, 0x10 bytes (sizeof)  
/*0x028*/     struct _HEAP* Heap;                                                              
/*0x030*/     VOID*        BaseAddress;                                                        
/*0x038*/     ULONG32      NumberOfPages;                                                      
/*0x03C*/     UINT8        _PADDING0_[0x4];                                                    
/*0x040*/     struct _HEAP_ENTRY* FirstEntry;                                                  
/*0x048*/     struct _HEAP_ENTRY* LastValidEntry;                                              
/*0x050*/     ULONG32      NumberOfUnCommittedPages;                                           
/*0x054*/     ULONG32      NumberOfUnCommittedRanges;                                          
/*0x058*/     UINT16       SegmentAllocatorBackTraceIndex;                                     
/*0x05A*/     UINT16       Reserved;                                                           
/*0x05C*/     UINT8        _PADDING1_[0x4];                                                    
/*0x060*/     struct _LIST_ENTRY UCRSegmentList;           // 2 elements, 0x10 bytes (sizeof)  
          }HEAP_SEGMENT, *PHEAP_SEGMENT;  

_HEAP

          typedef struct _HEAP                                     // 59 elements, 0x2C0 bytes (sizeof) 
          {                                                                                             
              union                                                // 2 elements, 0x70 bytes (sizeof)   
              {                                                                                         
/*0x000*/         struct _HEAP_SEGMENT Segment;                    // 14 elements, 0x70 bytes (sizeof)  
                  struct                                           // 14 elements, 0x70 bytes (sizeof)  
                  {                                                                                     
/*0x000*/             struct _HEAP_ENTRY Entry;                    // 26 elements, 0x10 bytes (sizeof)  
/*0x010*/             ULONG32      SegmentSignature;                                                    
/*0x014*/             ULONG32      SegmentFlags;                                                        
/*0x018*/             struct _LIST_ENTRY SegmentListEntry;         // 2 elements, 0x10 bytes (sizeof)   
/*0x028*/             struct _HEAP* Heap;                                                               
/*0x030*/             VOID*        BaseAddress;                                                         
/*0x038*/             ULONG32      NumberOfPages;                                                       
/*0x03C*/             UINT8        _PADDING0_[0x4];                                                     
/*0x040*/             struct _HEAP_ENTRY* FirstEntry;                                                   
/*0x048*/             struct _HEAP_ENTRY* LastValidEntry;                                               
/*0x050*/             ULONG32      NumberOfUnCommittedPages;                                            
/*0x054*/             ULONG32      NumberOfUnCommittedRanges;                                           
/*0x058*/             UINT16       SegmentAllocatorBackTraceIndex;                                      
/*0x05A*/             UINT16       Reserved;                                                            
/*0x05C*/             UINT8        _PADDING1_[0x4];                                                     
/*0x060*/             struct _LIST_ENTRY UCRSegmentList;           // 2 elements, 0x10 bytes (sizeof)   
                  };                                                                                    
              };                                                                                        
/*0x070*/     ULONG32      Flags;    // 堆标志    
/*0x074*/     ULONG32      ForceFlags;                                                                  
/*0x078*/     ULONG32      CompatibilityFlags;                                                          
/*0x07C*/     ULONG32      EncodeFlagMask;                                                              
/*0x080*/     struct _HEAP_ENTRY Encoding;                         // 26 elements, 0x10 bytes (sizeof)  
/*0x090*/     ULONG32      Interceptor;                                                                 
/*0x094*/     ULONG32      VirtualMemoryThreshold;                                                      
/*0x098*/     ULONG32      Signature;                                                                   
/*0x09C*/     UINT8        _PADDING2_[0x4];                                                             
/*0x0A0*/     UINT64       SegmentReserve;                                                              
/*0x0A8*/     UINT64       SegmentCommit;                                                               
/*0x0B0*/     UINT64       DeCommitFreeBlockThreshold;                                                  
/*0x0B8*/     UINT64       DeCommitTotalFreeThreshold;                                                  
/*0x0C0*/     UINT64       TotalFreeSize;                                                               
/*0x0C8*/     UINT64       MaximumAllocationSize;                                                       
/*0x0D0*/     UINT16       ProcessHeapsListIndex;                                                       
/*0x0D2*/     UINT16       HeaderValidateLength;                                                        
/*0x0D4*/     UINT8        _PADDING3_[0x4];                                                             
/*0x0D8*/     VOID*        HeaderValidateCopy;                                                          
/*0x0E0*/     UINT16       NextAvailableTagIndex;                                                       
/*0x0E2*/     UINT16       MaximumTagIndex;                                                             
/*0x0E4*/     UINT8        _PADDING4_[0x4];                                                             
/*0x0E8*/     struct _HEAP_TAG_ENTRY* TagEntries;                                                       
/*0x0F0*/     struct _LIST_ENTRY UCRList;                          // 2 elements, 0x10 bytes (sizeof)   
/*0x100*/     UINT64       AlignRound;                                                                  
/*0x108*/     UINT64       AlignMask;                                                                   
/*0x110*/     struct _LIST_ENTRY VirtualAllocdBlocks;              // 2 elements, 0x10 bytes (sizeof)   
/*0x120*/     struct _LIST_ENTRY SegmentList;                      // 2 elements, 0x10 bytes (sizeof)   
/*0x130*/     UINT16       AllocatorBackTraceIndex;                                                     
/*0x132*/     UINT8        _PADDING5_[0x2];                                                             
/*0x134*/     ULONG32      NonDedicatedListLength;                                                      
/*0x138*/     VOID*        BlocksIndex;                                                                 
/*0x140*/     VOID*        UCRIndex;                                                                    
/*0x148*/     struct _HEAP_PSEUDO_TAG_ENTRY* PseudoTagEntries;                                          
/*0x150*/     struct _LIST_ENTRY FreeLists;                        // 2 elements, 0x10 bytes (sizeof)   
/*0x160*/     struct _HEAP_LOCK* LockVariable;                                                          
/*0x168*/     FUNCT_00A3_1B3D_CommitRoutine* CommitRoutine;                                             
/*0x170*/     union _RTL_RUN_ONCE StackTraceInitVar;               // 3 elements, 0x8 bytes (sizeof)    
/*0x178*/     struct _RTL_HEAP_MEMORY_LIMIT_DATA CommitLimitData;  // 4 elements, 0x20 bytes (sizeof)   
/*0x198*/     VOID*        FrontEndHeap;                                                                
/*0x1A0*/     UINT16       FrontHeapLockCount;                                                          
/*0x1A2*/     UINT8        FrontEndHeapType;                                                            
/*0x1A3*/     UINT8        RequestedFrontEndHeapType;                                                   
/*0x1A4*/     UINT8        _PADDING6_[0x4];                                                             
/*0x1A8*/     WCHAR*       FrontEndHeapUsageData;                                                       
/*0x1B0*/     UINT16       FrontEndHeapMaximumIndex;                                                    
/*0x1B2*/     UINT8        FrontEndHeapStatusBitmap[129];                                               
/*0x233*/     UINT8        _PADDING7_[0x5];                                                             
/*0x238*/     struct _HEAP_COUNTERS Counters;                      // 23 elements, 0x78 bytes (sizeof)  
/*0x2B0*/     struct _HEAP_TUNING_PARAMETERS TuningParameters;     // 2 elements, 0x10 bytes (sizeof)   
          }HEAP, *PHEAP;

示例

示例代码:

/************** GetPEB.asm **************/
GetPEB proc
    mov rax, gs:[60h]
    ret
GetPEB endp

/************** main.cpp **************/
int main(void)
{
    // 获取PEB
    ULONG64 peb = GetPEB();
    printf(OK_PRINT_FLAG("PEB: 0x%p\n"), peb);


    char hello[] = "Hello World!";
    char str[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    
    HANDLE h_default = GetProcessHeap();
    HANDLE h_heap = HeapCreate(HEAP_GENERATE_EXCEPTIONS, 0x1000, 0xffff);

    char *buf1 = (char *)HeapAlloc(h_default, 0, strlen(hello) + 1);
    char *buf2 = (char*)HeapAlloc(h_heap, 0, strlen(str) + 1);

    // 拷贝数据到堆内存上
    strcpy(buf1, hello);
    strcpy(buf2, str);

    printf(OK_PRINT_FLAG("Default Heap is %p\n    Heap buf address: 0x%p\n    Buffer: %s\n"),
        h_default, buf1, buf1);
    printf(OK_PRINT_FLAG("My Heap is %p\n    Heap buf address: 0x%p\n    Buffer: %s\n"),
        h_heap, buf2, buf2);
    
    printf(OK_PRINT_FLAG("等待调试附加,按任意键释放堆内存\n"));
    getchar();  // 暂停,让调试器附加

    // 释放资源
    HeapFree(h_default, 0, buf1);
    HeapFree(h_heap, 0, buf2);
    
    HeapDestroy(h_heap);

    system("pause");
    
    return 0;
}

常规

6.png

在PEB中,跟堆有关的成员如下:

kd> dt _PEB 277000
nt!_PEB
   ...
   +0x030 ProcessHeap      : 0x00000000`00480000 Void        // 当前进程的默认堆 _HEAP
   ...
   +0x0c8 HeapSegmentReserve : 0x100000        // 堆段保留大小
   +0x0d0 HeapSegmentCommit : 0x2000        // 堆段提交大小
   +0x0d8 HeapDeCommitTotalFreeThreshold : 0x10000    // 释放总空闲大小的阈值
   +0x0e0 HeapDeCommitFreeBlockThreshold : 0x1000    // 释放块大小的阈值
   +0x0e8 NumberOfHeaps    : 4            // 当前堆个数
   +0x0ec MaximumNumberOfHeaps : 0x10    // 最大堆个数
   +0x0f0 ProcessHeaps     : 0x00007ff8`502b8d40  -> 0x00000000`00480000 Void    // 当前进程的堆数组
   ...
   +0x378 HeapTracingEnabled : 0y0        // 堆追踪启用标志

查看当前进程所拥有的堆

kd> !heap -h
HEAPEXT: Unable to get address of ntdll!RtlpHeapInvalidBadAddress.
Index   Address  Name      Debugging options enabled
  1:   00480000 
    Segment at 0000000000480000 to 000000000057f000 (0001b000 bytes committed)        // 进程默认堆
  2:   00010000 
    Segment at 0000000000010000 to 0000000000020000 (00001000 bytes committed)
  3:   00810000 
    Segment at 0000000000810000 to 0000000000850000 (00002000 bytes committed)
  4:   00a00000 
    Segment at 0000000000a00000 to 0000000000a10000 (00002000 bytes committed)        // 创建的堆

// 查看PEB.ProcessHeaps数组,刚好是四个
kd> dq 0x00007ff8`502b8d40
00007ff8`502b8d40  00000000`00480000 00000000`00010000
00007ff8`502b8d50  00000000`00810000 00000000`00a00000
00007ff8`502b8d60  00000000`00000000 00000000`00000000
00007ff8`502b8d70  00000000`00000000 00000000`00000000
00007ff8`502b8d80  00000000`00000000 00000000`00000000
00007ff8`502b8d90  00000000`00000000 00000000`00000000
00007ff8`502b8da0  00000000`00000000 00000000`00000000
00007ff8`502b8db0  00000000`00000000 00000000`00000000

PEB.ProcessHeaps成员保存了当前进程所拥有的堆,每个元素即是相应的堆句柄,为_HEAP结构

在win10 2004中,_HEAP的前0x70个字节为一个“特殊”union结构,此结构也就是_HEAP_SEGMENT结构,也即0号堆段

_HEAP_SEGMENT结构之后的成员保存了这个堆的相关信息

kd> dt _HEAP 00000000`00480000
ntdll!_HEAP
   +0x000 Segment          : _HEAP_SEGMENT
   +0x000 Entry            : _HEAP_ENTRY    // 由于_HEAP_SEGMENT结构是在堆中,其本身也是个堆块,故第一个成员是一个_HEAP_ENTRY
   +0x010 SegmentSignature : 0xffeeffee        // 堆段签名0xffeeffee
   +0x014 SegmentFlags     : 2                // 堆段标志
   +0x018 SegmentListEntry : _LIST_ENTRY [ 0x00000000`00480120 - 0x00000000`00480120 ]    // 堆段链表
   +0x028 Heap             : 0x00000000`00480000 _HEAP    // 该堆段所隶属的堆
   +0x030 BaseAddress      : 0x00000000`00480000 Void    // 堆段基址
   +0x038 NumberOfPages    : 0xff    // 堆段内存页数,当分配的空间大小 > NumberOfPages * page_size时,会有一个新堆段链入链表中
   +0x040 FirstEntry       : 0x00000000`00480740 _HEAP_ENTRY    // 第一个堆块的指针
   +0x048 LastValidEntry   : 0x00000000`0057f000 _HEAP_ENTRY    // 最后一个有效堆块的指针(边界值)
   +0x050 NumberOfUnCommittedPages : 0xe4    // 未提交的内存页数
   +0x054 NumberOfUnCommittedRanges : 1        // UCRSegmentList节点个数
   +0x058 SegmentAllocatorBackTraceIndex : 0
   +0x05a Reserved         : 0
   +0x060 UCRSegmentList   : _LIST_ENTRY [ 0x00000000`0049afe0 - 0x00000000`0049afe0 ]
   +0x070 Flags            : 2        // 堆标志 HEAP_GROWABLE(0x2)为默认属性,私有堆(0x1000)
   +0x074 ForceFlags       : 0
   +0x078 CompatibilityFlags : 0
   +0x07c EncodeFlagMask   : 0x100000        // 编码标志掩码,检测后续是否对_HEAP_ENTRY进行编码
   +0x080 Encoding         : _HEAP_ENTRY    // 为后续解码_HEAP_ENTRY结构的key
   +0x090 Interceptor      : 0
   +0x094 VirtualMemoryThreshold : 0xff00    // 堆块阈值
   +0x098 Signature        : 0xeeffeeff        // 堆的签名0xeeffeeff
   +0x0a0 SegmentReserve   : 0x100000        // 堆保留
   +0x0a8 SegmentCommit    : 0x2000            // 堆提交
   +0x0b0 DeCommitFreeBlockThreshold : 0x400    // 释放总空闲大小的阈值
   +0x0b8 DeCommitTotalFreeThreshold : 0x1000    // 释放块大小的阈值
   +0x0c0 TotalFreeSize    : 0x183            // 空闲块总大小
   +0x0c8 MaximumAllocationSize : 0x00007fff`fffdefff    // 允许分配的最大空间
   +0x0d0 ProcessHeapsListIndex : 1            // 该堆在进程堆列表中的索引(从1开始)
   +0x0d2 HeaderValidateLength : 0x2c0        // _HEAP结构大小
   +0x0d8 HeaderValidateCopy : (null) 
   +0x0e0 NextAvailableTagIndex : 0            // 下一个可用堆块标记索引
   +0x0e2 MaximumTagIndex  : 0                // 最大堆块标记索引
   +0x0e8 TagEntries       : (null)         // 指向堆块标记结构的指针
   +0x0f0 UCRList          : _LIST_ENTRY [ 0x00000000`0049afd0 - 0x00000000`0049afd0 ]
   +0x100 AlignRound       : 0x1f
   +0x108 AlignMask        : 0xffffffff`fffffff0    // 对齐掩码
   +0x110 VirtualAllocdBlocks : _LIST_ENTRY [ 0x00000000`00480110 - 0x00000000`00480110 ]
   +0x120 SegmentList      : _LIST_ENTRY [ 0x00000000`00480018 - 0x00000000`00480018 ]    // 分别指向0号堆段和最后一个堆段
   +0x130 AllocatorBackTraceIndex : 0
   +0x134 NonDedicatedListLength : 0
   +0x138 BlocksIndex      : 0x00000000`004802e8 Void
   +0x140 UCRIndex         : (null) 
   +0x148 PseudoTagEntries : (null) 
   +0x150 FreeLists        : _LIST_ENTRY [ 0x00000000`00493ce0 - 0x00000000`0049a3b0 ]    // 空表
   +0x160 LockVariable     : 0x00000000`004802c0 _HEAP_LOCK
   +0x168 CommitRoutine    : 0x535a1b5a`ba712500     long  +535a1b5aba712500
   +0x170 StackTraceInitVar : _RTL_RUN_ONCE
   +0x178 CommitLimitData  : _RTL_HEAP_MEMORY_LIMIT_DATA
   +0x198 FrontEndHeap     : 0x00000000`00020000 Void        // 前端堆
   +0x1a0 FrontHeapLockCount : 0                            // 前端堆锁定计数
   +0x1a2 FrontEndHeapType : 0x2 ''                            // 前端堆类型
   +0x1a3 RequestedFrontEndHeapType : 0x2 ''
   +0x1a8 FrontEndHeapUsageData : 0x00000000`004891a0  ""
   +0x1b0 FrontEndHeapMaximumIndex : 0x402                    // 前端堆最大索引
   +0x1b2 FrontEndHeapStatusBitmap : [129]  "<"
   +0x238 Counters         : _HEAP_COUNTERS
   +0x2b0 TuningParameters : _HEAP_TUNING_PARAMETERS

7.png

然后寻找一下本例中的堆块,默认堆中0x48C930 - 0x10 = 0x48C920,私有堆中0xA00750 - 0x10 = 0xA00740,其中0x10_HEAP_ENTRY的大小,利用!heap xxx -a命令在堆中查找,会发现一个有意思的地方

在默认堆中,我们的堆数据落在被标记为Internal的地址内,而私有堆中却正好落在地址上

kd> !heap 00480000 -a
Index   Address  Name      Debugging options enabled
  1:   00480000 
    Segment at 0000000000480000 to 000000000057f000 (0001b000 bytes committed)
    ...
    Granularity:          16 bytes    // 内存对齐粒度
    ...
    ...

    Heap entries for Segment00 in Heap 0000000000480000
                 address: psize . size  flags   state (requested size)
        0000000000480000: 00000 . 00740 [101] - busy (73f)
        0000000000480740: 00740 . 00030 [101] - busy (1a)
        ...
        000000000048c840: 00040 . 00090 [101] - busy (80)
        000000000048c8d0: 00090 . 00810 [101] - busy (800) Internal     <--- 0x48C920刚好落在这个区间内
        000000000048d0e0: 00810 . 00070 [101] - busy (68)
        ...
        ...
        000000000049b000:      000e4000      - uncommitted bytes.
        
kd> !heap a00000 -a
Index   Address  Name      Debugging options enabled
  4:   00a00000 
    Segment at 0000000000a00000 to 0000000000a10000 (00002000 bytes committed)
    ...
    Granularity:          16 bytes
    ...
    ...

    Heap entries for Segment00 in Heap 0000000000a00000
                 address: psize . size  flags   state (requested size)
        0000000000a00000: 00000 . 00740 [101] - busy (73f)
        0000000000a00740: 00740 . 00030 [101] - busy (1b)                <--- 0xA00740恰好在这
        0000000000a00770: 00030 . 01850 [100]
        0000000000a01fc0: 01850 . 00040 [111] - busy (3d)
        0000000000a02000:      0000e000      - uncommitted bytes.

那么就先从简单的私有对开始,获取到私有堆的_HEAP.Encoding成员,以及目标堆块,由于_HEAP.EncodeFlagMask = 0x100000,故这里是启用了编码的,如下:

kd> dx -id 0,0,ffffc5876c855080 -r1 (*((ntdll!_HEAP_ENTRY *)0xa00080))
(*((ntdll!_HEAP_ENTRY *)0xa00080))                 [Type: _HEAP_ENTRY]
    [+0x000] UnpackedEntry    [Type: _HEAP_UNPACKED_ENTRY]
    [+0x000] PreviousBlockPrivateData : 0x0 [Type: void *]
    [+0x008] Size             : 0xee [Type: unsigned short]
    [+0x00a] Flags            : 0xf2 [Type: unsigned char]
    [+0x00b] SmallTagIndex    : 0xb1 [Type: unsigned char]
    [+0x008] SubSegmentCode   : 0xb1f200ee [Type: unsigned long]
    [+0x00c] PreviousSize     : 0x4661 [Type: unsigned short]
    [+0x00e] SegmentOffset    : 0x0 [Type: unsigned char]
    [+0x00e] LFHFlags         : 0x0 [Type: unsigned char]
    [+0x00f] UnusedBytes      : 0x0 [Type: unsigned char]
    [+0x008] CompactHeader    : 0x4661b1f200ee [Type: unsigned __int64]
    [+0x000] ExtendedEntry    [Type: _HEAP_EXTENDED_ENTRY]
    [+0x000] Reserved         : 0x0 [Type: void *]
    [+0x008] FunctionIndex    : 0xee [Type: unsigned short]
    [+0x00a] ContextValue     : 0xb1f2 [Type: unsigned short]
    [+0x008] InterceptorValue : 0xb1f200ee [Type: unsigned long]
    [+0x00c] UnusedBytesLength : 0x4661 [Type: unsigned short]
    [+0x00e] EntryOffset      : 0x0 [Type: unsigned char]
    [+0x00f] ExtendedBlockSignature : 0x0 [Type: unsigned char]
    [+0x000] ReservedForAlignment : 0x0 [Type: void *]
    [+0x008] Code1            : 0xb1f200ee [Type: unsigned long]
    [+0x00c] Code2            : 0x4661 [Type: unsigned short]
    [+0x00e] Code3            : 0x0 [Type: unsigned char]
    [+0x00f] Code4            : 0x0 [Type: unsigned char]
    [+0x00c] Code234          : 0x4661 [Type: unsigned long]
    [+0x008] AgregateCode     : 0x4661b1f200ee [Type: unsigned __int64]
    
kd> dt _HEAP_ENTRY 0a00740
ntdll!_HEAP_ENTRY
   +0x000 UnpackedEntry    : _HEAP_UNPACKED_ENTRY        // 0x0
   +0x000 PreviousBlockPrivateData : (null)             // 0x0
   +0x008 Size             : 0xed                        // 0x3
   +0x00a Flags            : 0xf3 ''                    // 0x1
   +0x00b SmallTagIndex    : 0xb3 ''                    // 0x2
   +0x008 SubSegmentCode   : 0xb3f300ed                    // 0x2010003
   +0x00c PreviousSize     : 0x4615                        // 0x74
   +0x00e SegmentOffset    : 0 ''                        // 0x0
   +0x00e LFHFlags         : 0 ''                        // 0x0
   +0x00f UnusedBytes      : 0x15 ''                    // 0x15
   +0x008 CompactHeader    : 0x15004615`b3f300ed        // 0x1500007402010003
   +0x000 ExtendedEntry    : _HEAP_EXTENDED_ENTRY        // 0x0
   +0x000 Reserved         : (null)                     // 0x0
   +0x008 FunctionIndex    : 0xed                        // 0x3
   +0x00a ContextValue     : 0xb3f3                        // 0x201
   +0x008 InterceptorValue : 0xb3f300ed                    // 0x2010003
   +0x00c UnusedBytesLength : 0x4615                    // 0x74
   +0x00e EntryOffset      : 0 ''                        // 0x0
   +0x00f ExtendedBlockSignature : 0x15 ''                // 0x15
   +0x000 ReservedForAlignment : (null)                 // 0x0
   +0x008 Code1            : 0xb3f300ed                    // 0x2010003
   +0x00c Code2            : 0x4615                        // 0x74
   +0x00e Code3            : 0 ''                        // 0x0
   +0x00f Code4            : 0x15 ''                    // 0x15
   +0x00c Code234          : 0x15004615                    // 0x15000074
   +0x008 AgregateCode     : 0x15004615`b3f300ed        // 0x1500007402010003

对这两个结构进行异或,例如对于_HEAP_ENTRY.Size = 0xed ^ 0xee = 0x3 ,由于对齐粒度为0x10,故大小为0x3 * 0x10 = 0x30

那么在来看一下默认堆

kd> dx -id 0,0,ffffc5876c855080 -r1 (*((ntdll!_HEAP_ENTRY *)0x480080))
(*((ntdll!_HEAP_ENTRY *)0x480080))                 [Type: _HEAP_ENTRY]
    [+0x000] UnpackedEntry    [Type: _HEAP_UNPACKED_ENTRY]
    [+0x000] PreviousBlockPrivateData : 0x0 [Type: void *]
    [+0x008] Size             : 0xf815 [Type: unsigned short]
    [+0x00a] Flags            : 0xf1 [Type: unsigned char]
    [+0x00b] SmallTagIndex    : 0xae [Type: unsigned char]
    [+0x008] SubSegmentCode   : 0xaef1f815 [Type: unsigned long]
    [+0x00c] PreviousSize     : 0x4de3 [Type: unsigned short]
    [+0x00e] SegmentOffset    : 0x0 [Type: unsigned char]
    [+0x00e] LFHFlags         : 0x0 [Type: unsigned char]
    [+0x00f] UnusedBytes      : 0x0 [Type: unsigned char]
    [+0x008] CompactHeader    : 0x4de3aef1f815 [Type: unsigned __int64]
    [+0x000] ExtendedEntry    [Type: _HEAP_EXTENDED_ENTRY]
    [+0x000] Reserved         : 0x0 [Type: void *]
    [+0x008] FunctionIndex    : 0xf815 [Type: unsigned short]
    [+0x00a] ContextValue     : 0xaef1 [Type: unsigned short]
    [+0x008] InterceptorValue : 0xaef1f815 [Type: unsigned long]
    [+0x00c] UnusedBytesLength : 0x4de3 [Type: unsigned short]
    [+0x00e] EntryOffset      : 0x0 [Type: unsigned char]
    [+0x00f] ExtendedBlockSignature : 0x0 [Type: unsigned char]
    [+0x000] ReservedForAlignment : 0x0 [Type: void *]
    [+0x008] Code1            : 0xaef1f815 [Type: unsigned long]
    [+0x00c] Code2            : 0x4de3 [Type: unsigned short]
    [+0x00e] Code3            : 0x0 [Type: unsigned char]
    [+0x00f] Code4            : 0x0 [Type: unsigned char]
    [+0x00c] Code234          : 0x4de3 [Type: unsigned long]
    [+0x008] AgregateCode     : 0x4de3aef1f815 [Type: unsigned __int64]

kd> dt _HEAP_ENTRY 48c8d0
ntdll!_HEAP_ENTRY
   +0x000 UnpackedEntry    : _HEAP_UNPACKED_ENTRY
   +0x000 PreviousBlockPrivateData : (null) 
   +0x008 Size             : 0xf894                // (0xf894 ^ 0xf815) * 0x10 = 0x810,大小刚好对上
   +0x00a Flags            : 0xf8 ''            // 0x9
   +0x00b SmallTagIndex    : 0x26 '&'
   +0x008 SubSegmentCode   : 0x26f8f894
   +0x00c PreviousSize     : 0x4dea
   +0x00e SegmentOffset    : 0 ''
   +0x00e LFHFlags         : 0 ''
   +0x00f UnusedBytes      : 0x10 ''
   +0x008 CompactHeader    : 0x10004dea`26f8f894
   +0x000 ExtendedEntry    : _HEAP_EXTENDED_ENTRY
   +0x000 Reserved         : (null) 
   +0x008 FunctionIndex    : 0xf894
   +0x00a ContextValue     : 0x26f8
   +0x008 InterceptorValue : 0x26f8f894
   +0x00c UnusedBytesLength : 0x4dea
   +0x00e EntryOffset      : 0 ''
   +0x00f ExtendedBlockSignature : 0x10 ''
   +0x000 ReservedForAlignment : (null) 
   +0x008 Code1            : 0x26f8f894
   +0x00c Code2            : 0x4dea
   +0x00e Code3            : 0 ''
   +0x00f Code4            : 0x10 ''
   +0x00c Code234          : 0x10004dea
   +0x008 AgregateCode     : 0x10004dea`26f8f894

计算发现,对于地址0x48C8D0这个堆块中0x48C8D0 ~ 0x48C8E0_HEAP_ENTRY结构),那么在_HEAP_ENTRY结构到堆内存0x48C930之间的是什么?

关于Internal标记,stackoverflow上的回答如下:

whats the meaning of internal in heap-h output in windbg

有关堆标志,发现0xf8 ^ 0xf1 = 0x9,包含HEAP_ENTRY_VIRTUAL_ALLOC,推测可能和虚分配有关

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

有关虚分配的推测

注:以下仅是推测

那么再参考下RecatOS和有关泄露出的源码,有关虚分配的结构为_HEAP_VIRTUAL_ALLOC_ENTRY

          typedef struct _HEAP_VIRTUAL_ALLOC_ENTRY // 5 elements, 0x40 bytes (sizeof)  
          {                                                                            
/*0x000*/     struct _LIST_ENTRY Entry;            // 2 elements, 0x10 bytes (sizeof)  
/*0x010*/     struct _HEAP_ENTRY_EXTRA ExtraStuff; // 5 elements, 0x10 bytes (sizeof)  
/*0x020*/     UINT64       CommitSize;                                                 
/*0x028*/     UINT64       ReserveSize;                                                
/*0x030*/     struct _HEAP_ENTRY BusyBlock;        // 26 elements, 0x10 bytes (sizeof) 
          }HEAP_VIRTUAL_ALLOC_ENTRY, *PHEAP_VIRTUAL_ALLOC_ENTRY;

在泄露出的源码中发现ReturnValue = (PHEAP_ENTRY)(VirtualAllocBlock + 1);,推测这种内存布局应该是_HEAP_VIRTUAL_ALLOC_ENTRY + _HEAP_ENTR 大小一共0x500x48C8E0 + 0x50 = 0x48C930刚好是缓冲区的首地址

但是,在调试中,发现又不太符合这两个结构,可能存在加密?这一部分还待进一步的学习

 else if (Heap->Flags & HEAP_GROWABLE) {
     PHEAP_VIRTUAL_ALLOC_ENTRY VirtualAllocBlock;

     VirtualAllocBlock = NULL;

     //
     //  Compute how much memory we will need for this allocation which
     //  will include the allocation size plus a header, and then go
     //  get the committed memory
     //

     AllocationSize += FIELD_OFFSET( HEAP_VIRTUAL_ALLOC_ENTRY, BusyBlock );

     Status = ZwAllocateVirtualMemory( NtCurrentProcess(),
                                      (PVOID *)&VirtualAllocBlock,
                                      0,
                                      &AllocationSize,
                                      MEM_COMMIT,
                                      HEAP_PROTECTION );

     if (NT_SUCCESS(Status)) {

         //
         //  Just committed, already zero.  Fill in the new block
         //  and insert it in the list of big allocation
         //

         VirtualAllocBlock->BusyBlock.Size = (USHORT)(AllocationSize - Size);
         VirtualAllocBlock->BusyBlock.Flags = HEAP_ENTRY_VIRTUAL_ALLOC | HEAP_ENTRY_EXTRA_PRESENT | HEAP_ENTRY_BUSY;
         VirtualAllocBlock->CommitSize = AllocationSize;
         VirtualAllocBlock->ReserveSize = AllocationSize;

         InsertTailList( &Heap->VirtualAllocdBlocks, (PLIST_ENTRY)VirtualAllocBlock );

         //
         //  Return the address of the user portion of the allocated block.
         //  This is the byte following the header.
         //

         ReturnValue = (PHEAP_ENTRY)(VirtualAllocBlock + 1);
         BlockSize = AllocationSize;

         leave;
     }
 }

添加新评论