win32堆管理
摘录于《0day安全》、《漏洞战争》
堆块:堆区的内存按不同大小组织成块,以堆块位单位进行标识,其包含:
- 块首:堆块自身的信息
- 块身:数据部分
- 堆表:一般位于堆区的起始位置,用于索引堆区中的堆块
在Windows中,占用态的堆块被使用它的程序索引,而空闲态的堆块才被堆表索引
其中,最重要的有两种堆表:
- 空表 FreeList
- 快表 Lookaside
空表 FreeList
空闲双向链表,有FreeList[0] ~ FreeList[127]
128项,其中:
FreeList[0]
为所有大于等于1024字节且小于512KB
的空闲堆块的链表,按照堆块大小升序排列- 当
i >= 1 && i <= 127
时,FreeList[i]
为所有大小为i * 8
字节的空闲堆块的链表
快表 Lookaside
快速单向链表,为了加速堆块分配而设计出来的
其空闲块块首被设置为占用态而防止了堆块合并操作,同样拥有Lookaside[0] ~ Lookaside[127]
128项
组织方式与空表类似,区别如下:
- 此表为单向链表
- 每项最多只有4个堆块节点
- 快表总是被初始化为空
堆块的分配和释放
在Windows中,堆块的分配算法并不固定,根据系统版本不同而分配策略也不经相同,大致如下:
分配 | 释放 | |
---|---|---|
小块 Size < 1KB | 首先进行快表分配 若快表分配失败,进行普通空表分配 若普通空表分配失败,使用堆缓存分配 若堆缓存分配失败,尝试从 FreeList[0] 中分配若 FreeList[0] 分配失败,进行内存紧缩后在尝试分配若仍无法分配,返回 NULL | 优先链入快表(只能链入4个) 如果快表满,则将其链入相应的空表 |
大块 1KB < Size < 512KB | 首先使用堆缓存进行分配 若堆缓存分配失败,使用 FreeList[0] 中的大块进行分配 | 优先将其放入堆缓存 若堆缓存满,将链入 FreeList[0] |
巨块 Size >= 512KB | 一般来说巨块申请非常罕见,要用到虚分配方法(实际上并不是从堆区分配的) 这种类型的堆块在堆溢出利用中几乎不会遇到 | 直接释放,没有堆表操作 |
需要注意的:
- 快表中空闲块被设置为占用态,因此不会发送堆块合并,且只有精确匹配时才会分配
- 快表是单向链表,且每项只有4个堆块节点,在分配和释放时总是优先使用快表,失败时才用空表
Windows下堆分配体系概览
以下参考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字节对齐
在空闲态块中,块首后的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;
}
常规
在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
然后寻找一下本例中的堆块,默认堆中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
大小一共0x50
,0x48C8E0 + 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;
}
}