hacktricks/binary-exploitation/heap/bins-and-memory-allocations.md

628 lines
35 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Bins & 内存分配
<details>
<summary><strong>从零开始学习AWS黑客技术成为专家</strong> <a href="https://training.hacktricks.xyz/courses/arte"><strong>htARTEHackTricks AWS红队专家</strong></a><strong></strong></summary>
支持HackTricks的其他方式
- 如果您想看到您的**公司在HackTricks中做广告**或**下载PDF格式的HackTricks**,请查看[**订阅计划**](https://github.com/sponsors/carlospolop)!
- 获取[**官方PEASS & HackTricks周边产品**](https://peass.creator-spring.com)
- 探索[**PEASS家族**](https://opensea.io/collection/the-peass-family),我们的独家[NFT收藏品](https://opensea.io/collection/the-peass-family)
- **加入** 💬 [**Discord群**](https://discord.gg/hRep4RUj7f) 或 [**电报群**](https://t.me/peass) 或在**Twitter**上关注我们 🐦 [**@hacktricks\_live**](https://twitter.com/hacktricks\_live)**。**
- 通过向[**HackTricks**](https://github.com/carlospolop/hacktricks)和[**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) github仓库提交PR来分享您的黑客技巧。
</details>
## 基本信息
为了提高块存储的效率每个块不仅仅在一个链接列表中而是有几种类型。这些是bins有5种类型的bins[62](https://sourceware.org/git/gitweb.cgi?p=glibc.git;a=blob;f=malloc/malloc.c;h=6e766d11bc85b6480fa5c9f2a76559f8acf9deb5;hb=HEAD#l1407) small bins63 large bins1 unsorted bin10 fast bins和每个线程64个tcache bins。
每个未排序、小型和大型bins的初始地址都在同一个数组内。索引0未使用1是未排序binbins 2-64是小型binsbins 65-127是大型bins。
### Tcache每线程缓存Bins
尽管线程尝试拥有自己的堆(参见[Arenas](bins-and-memory-allocations.md#arenas)和[Subheaps](bins-and-memory-allocations.md#subheaps)但有可能一个具有大量线程的进程如Web服务器**最终会与其他线程共享堆**。在这种情况下,主要解决方案是使用**锁**,这可能会**显著减慢线程**。
因此tcache类似于每个线程的快速bin因为它是一个**不合并块的单链表**。每个线程有**64个单链表tcache bins**。每个bin最多可以有[7个相同大小的块](https://sourceware.org/git/?p=glibc.git;a=blob;f=malloc/malloc.c;h=2527e2504761744df2bdb1abdc02d936ff907ad2;hb=d5c3fafc4307c9b7a4c7d5cb381fcdbfad340bcc#l323),范围从[24到1032字节64位系统和12到516字节32位系统](https://sourceware.org/git/?p=glibc.git;a=blob;f=malloc/malloc.c;h=2527e2504761744df2bdb1abdc02d936ff907ad2;hb=d5c3fafc4307c9b7a4c7d5cb381fcdbfad340bcc#l315)。
当一个线程释放一个块时如果它不太大以至于无法在tcache中分配并且相应的tcache bin**未满**已有7个块**它将被分配到那里**。如果无法进入tcache它将需要等待堆锁以执行全局释放操作。
当**分配一个块**时,如果在**Tcache中有所需大小的空闲块**它将使用它否则它将需要等待堆锁以在全局bins中找到一个或创建一个新的。\
还有一个优化,在这种情况下,在持有堆锁的同时,线程**将用所请求大小的堆块7个填充其Tcache**因此如果需要更多它将在Tcache中找到它们。
<details>
<summary>添加一个tcache块示例</summary>
```c
#include <stdlib.h>
#include <stdio.h>
int main(void)
{
char *chunk;
chunk = malloc(24);
printf("Address of the chunk: %p\n", (void *)chunk);
gets(chunk);
free(chunk);
return 0;
}
```
编译并在主函数的ret操作码处设置断点进行调试。然后使用gef工具您可以看到正在使用的tcache bin
```bash
gef➤ heap bins
──────────────────────────────────────────────────────────────────────────────── Tcachebins for thread 1 ────────────────────────────────────────────────────────────────────────────────
Tcachebins[idx=0, size=0x20, count=1] ← Chunk(addr=0xaaaaaaac12a0, size=0x20, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
```
</details>
#### Tcache 结构和函数
在下面的代码中,可以看到**最大 bin**和**每个索引的块数**,创建的**`tcache_entry`**结构用于避免双重释放,以及**`tcache_perthread_struct`**,每个线程使用它来存储 bin 的每个索引的地址。
<details>
<summary><code>tcache_entry</code><code>tcache_perthread_struct</code></summary>
```c
// From https://github.com/bminor/glibc/blob/f942a732d37a96217ef828116ebe64a644db18d7/malloc/malloc.c
/* We want 64 entries. This is an arbitrary limit, which tunables can reduce. */
# define TCACHE_MAX_BINS 64
# define MAX_TCACHE_SIZE tidx2usize (TCACHE_MAX_BINS-1)
/* Only used to pre-fill the tunables. */
# define tidx2usize(idx) (((size_t) idx) * MALLOC_ALIGNMENT + MINSIZE - SIZE_SZ)
/* When "x" is from chunksize(). */
# define csize2tidx(x) (((x) - MINSIZE + MALLOC_ALIGNMENT - 1) / MALLOC_ALIGNMENT)
/* When "x" is a user-provided size. */
# define usize2tidx(x) csize2tidx (request2size (x))
/* With rounding and alignment, the bins are...
idx 0 bytes 0..24 (64-bit) or 0..12 (32-bit)
idx 1 bytes 25..40 or 13..20
idx 2 bytes 41..56 or 21..28
etc. */
/* This is another arbitrary limit, which tunables can change. Each
tcache bin will hold at most this number of chunks. */
# define TCACHE_FILL_COUNT 7
/* Maximum chunks in tcache bins for tunables. This value must fit the range
of tcache->counts[] entries, else they may overflow. */
# define MAX_TCACHE_COUNT UINT16_MAX
[...]
typedef struct tcache_entry
{
struct tcache_entry *next;
/* This field exists to detect double frees. */
uintptr_t key;
} tcache_entry;
/* There is one of these for each thread, which contains the
per-thread cache (hence "tcache_perthread_struct"). Keeping
overall size low is mildly important. Note that COUNTS and ENTRIES
are redundant (we could have just counted the linked list each
time), this is for performance reasons. */
typedef struct tcache_perthread_struct
{
uint16_t counts[TCACHE_MAX_BINS];
tcache_entry *entries[TCACHE_MAX_BINS];
} tcache_perthread_struct;
```
</details>
函数`__tcache_init`是创建并为`tcache_perthread_struct`对象分配空间的函数
<details>
<summary>tcache_init 代码</summary>
```c
// From https://github.com/bminor/glibc/blob/f942a732d37a96217ef828116ebe64a644db18d7/malloc/malloc.c#L3241C1-L3274C2
static void
tcache_init(void)
{
mstate ar_ptr;
void *victim = 0;
const size_t bytes = sizeof (tcache_perthread_struct);
if (tcache_shutting_down)
return;
arena_get (ar_ptr, bytes);
victim = _int_malloc (ar_ptr, bytes);
if (!victim && ar_ptr != NULL)
{
ar_ptr = arena_get_retry (ar_ptr, bytes);
victim = _int_malloc (ar_ptr, bytes);
}
if (ar_ptr != NULL)
__libc_lock_unlock (ar_ptr->mutex);
/* In a low memory situation, we may not be able to allocate memory
- in which case, we just keep trying later. However, we
typically do this very early, so either there is sufficient
memory, or there isn't enough memory to do non-trivial
allocations anyway. */
if (victim)
{
tcache = (tcache_perthread_struct *) victim;
memset (tcache, 0, sizeof (tcache_perthread_struct));
}
}
```
</details>
#### Tcache索引
Tcache有几个bin取决于大小和**每个索引的第一个chunk的初始指针以及每个索引的chunk数量都位于一个chunk内**。这意味着定位具有此信息的chunk通常是第一个chunk可以找到所有tcache的初始点和Tcache chunk的数量。
### 快速bins
快速bins旨在通过将最近释放的chunks保留在快速访问结构中**加快小块内存分配的速度**。这些bins使用后进先出LIFO的方法这意味着**最近释放的chunk是首先**在有新的分配请求时被重用的。这种行为对于速度是有利的因为与队列FIFO相比从栈的顶部LIFO插入和删除更快。
此外,**快速bins使用单链表**而不是双链表这进一步提高了速度。由于快速bins中的chunks不会与相邻的chunks合并所以不需要允许从中间删除的复杂结构。单链表对于这些操作来说更简单更快。
基本上在这里发生的情况是头部指向要检查的第一个chunk的指针始终指向该大小的最新释放的chunk。所以
- 当分配该大小的新chunk时头部指向一个可用的free chunk。由于此free chunk指向下一个要使用的chunk因此该地址存储在头部中以便下一个分配知道从哪里获取可用的chunk。
- 当释放一个chunk时free chunk将保存当前可用chunk的地址并且这个新释放的chunk的地址将放在头部中。
链表的最大大小为`0x80`,它们被组织成大小为`0x20-0x2f`的chunk将位于索引`0`,大小为`0x30-0x3f`的chunk将位于`idx` `1`...
{% hint style="danger" %}
快速bins中的chunks未设置为可用因此它们在一段时间内保持为快速bin chunks而不是能够与周围的其他空闲chunks合并。
{% endhint %}
```c
// From https://github.com/bminor/glibc/blob/a07e000e82cb71238259e674529c37c12dc7d423/malloc/malloc.c#L1711
/*
Fastbins
An array of lists holding recently freed small chunks. Fastbins
are not doubly linked. It is faster to single-link them, and
since chunks are never removed from the middles of these lists,
double linking is not necessary. Also, unlike regular bins, they
are not even processed in FIFO order (they use faster LIFO) since
ordering doesn't much matter in the transient contexts in which
fastbins are normally used.
Chunks in fastbins keep their inuse bit set, so they cannot
be consolidated with other free chunks. malloc_consolidate
releases all chunks in fastbins and consolidates them with
other free chunks.
*/
typedef struct malloc_chunk *mfastbinptr;
#define fastbin(ar_ptr, idx) ((ar_ptr)->fastbinsY[idx])
/* offset 2 to use otherwise unindexable first 2 bins */
#define fastbin_index(sz) \
((((unsigned int) (sz)) >> (SIZE_SZ == 8 ? 4 : 3)) - 2)
/* The maximum fastbin request size we support */
#define MAX_FAST_SIZE (80 * SIZE_SZ / 4)
#define NFASTBINS (fastbin_index (request2size (MAX_FAST_SIZE)) + 1)
```
<details>
<summary>添加一个fastbin块示例</summary>
```c
#include <stdlib.h>
#include <stdio.h>
int main(void)
{
char *chunks[8];
int i;
// Loop to allocate memory 8 times
for (i = 0; i < 8; i++) {
chunks[i] = malloc(24);
if (chunks[i] == NULL) { // Check if malloc failed
fprintf(stderr, "Memory allocation failed at iteration %d\n", i);
return 1;
}
printf("Address of chunk %d: %p\n", i, (void *)chunks[i]);
}
// Loop to free the allocated memory
for (i = 0; i < 8; i++) {
free(chunks[i]);
}
return 0;
}
```
注意我们如何分配和释放8个相同大小的块使它们填满tcache并且第八个存储在快速块中。
编译并在主函数的ret操作码处设置断点进行调试。然后使用gef您可以看到tcache bin填充和快速bin中的一个块
```bash
gef➤ heap bins
──────────────────────────────────────────────────────────────────────────────── Tcachebins for thread 1 ────────────────────────────────────────────────────────────────────────────────
Tcachebins[idx=0, size=0x20, count=7] ← Chunk(addr=0xaaaaaaac1770, size=0x20, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac1750, size=0x20, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac1730, size=0x20, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac1710, size=0x20, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac16f0, size=0x20, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac16d0, size=0x20, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac12a0, size=0x20, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
───────────────────────────────────────────────────────────────────────── Fastbins for arena at 0xfffff7f90b00 ─────────────────────────────────────────────────────────────────────────
Fastbins[idx=0, size=0x20] ← Chunk(addr=0xaaaaaaac1790, size=0x20, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
Fastbins[idx=1, size=0x30] 0x00
```
</details>
### 未排序 bin
未排序 bin 是堆管理器用来加快内存分配速度的缓存。它的工作原理如下:当程序释放一个块时,如果这个块不能在 tcache 或 fast bin 中分配,并且不与顶部块发生冲突,堆管理器不会立即将其放入特定的小型或大型 bin 中。相反,它首先尝试**与任何相邻的空闲块合并**,以创建一个更大的空闲内存块。然后,它将这个新块放入一个名为“未排序 bin”的通用 bin 中。
当程序**请求内存**时,堆管理器会**检查未排序 bin**,看看是否有足够大小的块。如果找到一个,它会立即使用。如果在未排序 bin 中找不到合适的块,它会将此列表中的所有块移动到它们相应的 bin 中,无论是小型还是大型,都基于它们的大小。
请注意,如果一个较大的块被分成两半,剩下的部分大于 MINSIZE它将被放回未排序 bin 中。
因此,未排序 bin 是一种通过快速重用最近释放的内存来加快内存分配速度的方法,从而减少对耗时搜索和合并的需求。
{% hint style="danger" %}
请注意,即使块属于不同的类别,如果一个可用块与另一个可用块发生冲突(即使它们最初属于不同的 bin它们将被合并。
{% endhint %}
<details>
<summary>添加一个未排序块示例</summary>
```c
#include <stdlib.h>
#include <stdio.h>
int main(void)
{
char *chunks[9];
int i;
// Loop to allocate memory 8 times
for (i = 0; i < 9; i++) {
chunks[i] = malloc(0x100);
if (chunks[i] == NULL) { // Check if malloc failed
fprintf(stderr, "Memory allocation failed at iteration %d\n", i);
return 1;
}
printf("Address of chunk %d: %p\n", i, (void *)chunks[i]);
}
// Loop to free the allocated memory
for (i = 0; i < 8; i++) {
free(chunks[i]);
}
return 0;
}
```
注意我们如何分配和释放9个相同大小的块使它们填满了tcache第八个块存储在未排序的bin中因为它对于fastbin来说太大了第九个块没有被释放所以第九个和第八个块不会与顶部块合并。
编译并在主函数的ret操作码处设置断点进行调试。然后使用gef可以看到tcache bin填充和未排序bin中的一个块
```bash
gef➤ heap bins
──────────────────────────────────────────────────────────────────────────────── Tcachebins for thread 1 ────────────────────────────────────────────────────────────────────────────────
Tcachebins[idx=15, size=0x110, count=7] ← Chunk(addr=0xaaaaaaac1d10, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac1c00, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac1af0, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac19e0, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac18d0, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac17c0, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac12a0, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
───────────────────────────────────────────────────────────────────────── Fastbins for arena at 0xfffff7f90b00 ─────────────────────────────────────────────────────────────────────────
Fastbins[idx=0, size=0x20] 0x00
Fastbins[idx=1, size=0x30] 0x00
Fastbins[idx=2, size=0x40] 0x00
Fastbins[idx=3, size=0x50] 0x00
Fastbins[idx=4, size=0x60] 0x00
Fastbins[idx=5, size=0x70] 0x00
Fastbins[idx=6, size=0x80] 0x00
─────────────────────────────────────────────────────────────────────── Unsorted Bin for arena at 0xfffff7f90b00 ───────────────────────────────────────────────────────────────────────
[+] unsorted_bins[0]: fw=0xaaaaaaac1e10, bk=0xaaaaaaac1e10
→ Chunk(addr=0xaaaaaaac1e20, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
[+] Found 1 chunks in unsorted bin.
```
</details>
### 小型 Bins
小型 bins 比大型 bins 快,但比快速 bins 慢。
62 个 bins 中的每个 bin 都将具有**相同大小的块**16、24、...(在 32 位系统中最大大小为 504 字节,在 64 位系统中为 1024 字节)。这有助于加快查找应分配空间的 bin、在这些列表上插入和移除条目的速度。
这是根据 bin 的索引计算小型 bin 大小的方法:
* 最小大小2\*4\*索引(例如,索引 5 -> 40
* 最大大小2\*8\*索引(例如,索引 5 -> 80
```c
// From https://github.com/bminor/glibc/blob/a07e000e82cb71238259e674529c37c12dc7d423/malloc/malloc.c#L1711
#define NSMALLBINS 64
#define SMALLBIN_WIDTH MALLOC_ALIGNMENT
#define SMALLBIN_CORRECTION (MALLOC_ALIGNMENT > CHUNK_HDR_SZ)
#define MIN_LARGE_SIZE ((NSMALLBINS - SMALLBIN_CORRECTION) * SMALLBIN_WIDTH)
#define in_smallbin_range(sz) \
((unsigned long) (sz) < (unsigned long) MIN_LARGE_SIZE)
#define smallbin_index(sz) \
((SMALLBIN_WIDTH == 16 ? (((unsigned) (sz)) >> 4) : (((unsigned) (sz)) >> 3))\
+ SMALLBIN_CORRECTION)
```
### Function to choose between small and large bins:
### 选择小块和大块之间的函数:
```c
#define bin_index(sz) \
((in_smallbin_range (sz)) ? smallbin_index (sz) : largebin_index (sz))
```
<details>
<summary>添加一个小块示例</summary>
```c
#include <stdlib.h>
#include <stdio.h>
int main(void)
{
char *chunks[10];
int i;
// Loop to allocate memory 8 times
for (i = 0; i < 9; i++) {
chunks[i] = malloc(0x100);
if (chunks[i] == NULL) { // Check if malloc failed
fprintf(stderr, "Memory allocation failed at iteration %d\n", i);
return 1;
}
printf("Address of chunk %d: %p\n", i, (void *)chunks[i]);
}
// Loop to free the allocated memory
for (i = 0; i < 8; i++) {
free(chunks[i]);
}
chunks[9] = malloc(0x110);
return 0;
}
```
注意我们如何分配和释放9个相同大小的块使它们**填满tcache**第八个块存储在未排序的bin中因为它**对于fastbin来说太大**,第九个块没有被释放,所以第九个和第八个**不会与顶部块合并**。然后我们分配一个更大的0x110块这使得**未排序bin中的块进入small bin**。
编译它并在main函数的ret操作码处设置断点进行调试。然后使用gef您可以看到tcache bin填充和small bin中的一个块
```bash
gef➤ heap bins
──────────────────────────────────────────────────────────────────────────────── Tcachebins for thread 1 ────────────────────────────────────────────────────────────────────────────────
Tcachebins[idx=15, size=0x110, count=7] ← Chunk(addr=0xaaaaaaac1d10, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac1c00, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac1af0, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac19e0, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac18d0, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac17c0, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac12a0, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
───────────────────────────────────────────────────────────────────────── Fastbins for arena at 0xfffff7f90b00 ─────────────────────────────────────────────────────────────────────────
Fastbins[idx=0, size=0x20] 0x00
Fastbins[idx=1, size=0x30] 0x00
Fastbins[idx=2, size=0x40] 0x00
Fastbins[idx=3, size=0x50] 0x00
Fastbins[idx=4, size=0x60] 0x00
Fastbins[idx=5, size=0x70] 0x00
Fastbins[idx=6, size=0x80] 0x00
─────────────────────────────────────────────────────────────────────── Unsorted Bin for arena at 0xfffff7f90b00 ───────────────────────────────────────────────────────────────────────
[+] Found 0 chunks in unsorted bin.
──────────────────────────────────────────────────────────────────────── Small Bins for arena at 0xfffff7f90b00 ────────────────────────────────────────────────────────────────────────
[+] small_bins[16]: fw=0xaaaaaaac1e10, bk=0xaaaaaaac1e10
→ Chunk(addr=0xaaaaaaac1e20, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
[+] Found 1 chunks in 1 small non-empty bins.
```
</details>
### 大型bins
与管理固定大小的块的小型bins不同**每个大型bin处理一系列块大小**。这样更加灵活允许系统在不需要为每个大小单独设置bin的情况下容纳**各种大小**。
在内存分配器中大型bins从小型bins结束的地方开始。大型bins的范围逐渐增大意味着第一个bin可能覆盖从512到576字节的块而下一个覆盖576到640字节的块。这种模式继续下去最大的bin包含所有大于1MB的块。
与小型bins相比大型bins的操作速度较慢因为它们必须**对各种块大小的列表进行排序和搜索,以找到最佳匹配**。当一个块被插入到大型bin中时它必须被排序当内存被分配时系统必须找到正确的块。这额外的工作使它们**更慢**,但由于大型分配比小型分配更少见,这是一个可以接受的权衡。
有:
* 32个64字节范围的bins与小型bins冲突
* 16个512字节范围的bins与小型bins冲突
* 8个4096字节范围的bins部分与小型bins冲突
* 4个32768字节范围的bins
* 2个262144字节范围的bins
* 1个用于剩余大小的bin
<details>
<summary>大型bin大小代码</summary>
```c
// From https://github.com/bminor/glibc/blob/a07e000e82cb71238259e674529c37c12dc7d423/malloc/malloc.c#L1711
#define largebin_index_32(sz) \
(((((unsigned long) (sz)) >> 6) <= 38) ? 56 + (((unsigned long) (sz)) >> 6) :\
((((unsigned long) (sz)) >> 9) <= 20) ? 91 + (((unsigned long) (sz)) >> 9) :\
((((unsigned long) (sz)) >> 12) <= 10) ? 110 + (((unsigned long) (sz)) >> 12) :\
((((unsigned long) (sz)) >> 15) <= 4) ? 119 + (((unsigned long) (sz)) >> 15) :\
((((unsigned long) (sz)) >> 18) <= 2) ? 124 + (((unsigned long) (sz)) >> 18) :\
126)
#define largebin_index_32_big(sz) \
(((((unsigned long) (sz)) >> 6) <= 45) ? 49 + (((unsigned long) (sz)) >> 6) :\
((((unsigned long) (sz)) >> 9) <= 20) ? 91 + (((unsigned long) (sz)) >> 9) :\
((((unsigned long) (sz)) >> 12) <= 10) ? 110 + (((unsigned long) (sz)) >> 12) :\
((((unsigned long) (sz)) >> 15) <= 4) ? 119 + (((unsigned long) (sz)) >> 15) :\
((((unsigned long) (sz)) >> 18) <= 2) ? 124 + (((unsigned long) (sz)) >> 18) :\
126)
// XXX It remains to be seen whether it is good to keep the widths of
// XXX the buckets the same or whether it should be scaled by a factor
// XXX of two as well.
#define largebin_index_64(sz) \
(((((unsigned long) (sz)) >> 6) <= 48) ? 48 + (((unsigned long) (sz)) >> 6) :\
((((unsigned long) (sz)) >> 9) <= 20) ? 91 + (((unsigned long) (sz)) >> 9) :\
((((unsigned long) (sz)) >> 12) <= 10) ? 110 + (((unsigned long) (sz)) >> 12) :\
((((unsigned long) (sz)) >> 15) <= 4) ? 119 + (((unsigned long) (sz)) >> 15) :\
((((unsigned long) (sz)) >> 18) <= 2) ? 124 + (((unsigned long) (sz)) >> 18) :\
126)
#define largebin_index(sz) \
(SIZE_SZ == 8 ? largebin_index_64 (sz) \
: MALLOC_ALIGNMENT == 16 ? largebin_index_32_big (sz) \
: largebin_index_32 (sz))
```
</details>
<details>
<summary>添加一个大块示例</summary>
```c
#include <stdlib.h>
#include <stdio.h>
int main(void)
{
char *chunks[2];
chunks[0] = malloc(0x1500);
chunks[1] = malloc(0x1500);
free(chunks[0]);
chunks[0] = malloc(0x2000);
return 0;
}
```
2个大内存分配被执行然后一个被释放将其放入未排序的bin中接着进行更大的内存分配将释放的内存从未排序的bin移动到大的bin中
编译并在主函数的ret操作码处设置断点进行调试。然后使用gef可以看到tcache bin的填充情况以及大的bin中的一个chunk
```bash
gef➤ heap bin
──────────────────────────────────────────────────────────────────────────────── Tcachebins for thread 1 ────────────────────────────────────────────────────────────────────────────────
All tcachebins are empty
───────────────────────────────────────────────────────────────────────── Fastbins for arena at 0xfffff7f90b00 ─────────────────────────────────────────────────────────────────────────
Fastbins[idx=0, size=0x20] 0x00
Fastbins[idx=1, size=0x30] 0x00
Fastbins[idx=2, size=0x40] 0x00
Fastbins[idx=3, size=0x50] 0x00
Fastbins[idx=4, size=0x60] 0x00
Fastbins[idx=5, size=0x70] 0x00
Fastbins[idx=6, size=0x80] 0x00
─────────────────────────────────────────────────────────────────────── Unsorted Bin for arena at 0xfffff7f90b00 ───────────────────────────────────────────────────────────────────────
[+] Found 0 chunks in unsorted bin.
──────────────────────────────────────────────────────────────────────── Small Bins for arena at 0xfffff7f90b00 ────────────────────────────────────────────────────────────────────────
[+] Found 0 chunks in 0 small non-empty bins.
──────────────────────────────────────────────────────────────────────── Large Bins for arena at 0xfffff7f90b00 ────────────────────────────────────────────────────────────────────────
[+] large_bins[100]: fw=0xaaaaaaac1290, bk=0xaaaaaaac1290
→ Chunk(addr=0xaaaaaaac12a0, size=0x1510, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
[+] Found 1 chunks in 1 large non-empty bins.
```
</details>
### 顶部块
```c
// From https://github.com/bminor/glibc/blob/a07e000e82cb71238259e674529c37c12dc7d423/malloc/malloc.c#L1711
/*
Top
The top-most available chunk (i.e., the one bordering the end of
available memory) is treated specially. It is never included in
any bin, is used only if no other chunk is available, and is
released back to the system if it is very large (see
M_TRIM_THRESHOLD). Because top initially
points to its own bin with initial zero size, thus forcing
extension on the first malloc request, we avoid having any special
code in malloc to check whether it even exists yet. But we still
need to do so when getting memory from system, so we make
initial_top treat the bin as a legal but unusable chunk during the
interval between initialization and the first call to
sysmalloc. (This is somewhat delicate, since it relies on
the 2 preceding words to be zero during this interval as well.)
*/
/* Conveniently, the unsorted bin can be used as dummy top on first call */
#define initial_top(M) (unsorted_chunks (M))
```
基本上这是一个包含所有当前可用堆的块。当执行malloc时如果没有可用的空闲块可用这个顶部块将减小其大小提供必要的空间。\
顶部块的指针存储在`malloc_state`结构中。
此外,在开始时,可以将未排序的块用作顶部块。
<details>
<summary>观察顶部块示例</summary>
```c
#include <stdlib.h>
#include <stdio.h>
int main(void)
{
char *chunk;
chunk = malloc(24);
printf("Address of the chunk: %p\n", (void *)chunk);
gets(chunk);
return 0;
}
```
在编译和调试后在主函数的ret操作码处设置断点我发现malloc返回了地址`0xaaaaaaac12a0`,这些是分块:
```bash
gef➤ heap chunks
Chunk(addr=0xaaaaaaac1010, size=0x290, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
[0x0000aaaaaaac1010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................]
Chunk(addr=0xaaaaaaac12a0, size=0x20, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
[0x0000aaaaaaac12a0 41 41 41 41 41 41 41 00 00 00 00 00 00 00 00 00 AAAAAAA.........]
Chunk(addr=0xaaaaaaac12c0, size=0x410, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
[0x0000aaaaaaac12c0 41 64 64 72 65 73 73 20 6f 66 20 74 68 65 20 63 Address of the c]
Chunk(addr=0xaaaaaaac16d0, size=0x410, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
[0x0000aaaaaaac16d0 41 41 41 41 41 41 41 0a 00 00 00 00 00 00 00 00 AAAAAAA.........]
Chunk(addr=0xaaaaaaac1ae0, size=0x20530, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← top chunk
```
在地址`0xaaaaaaac1ae0`处可以看到顶部块的存在。这并不奇怪,因为最新分配的块位于`0xaaaaaaac12a0`,大小为`0x410`,因此`0xaaaaaaac12a0 + 0x410 = 0xaaaaaaac1ae0`。\
还可以在块头中看到顶部块的长度:
```bash
gef➤ x/8wx 0xaaaaaaac1ae0 - 16
0xaaaaaaac1ad0: 0x00000000 0x00000000 0x00020531 0x00000000
0xaaaaaaac1ae0: 0x00000000 0x00000000 0x00000000 0x00000000
```
</details>
### 最后提醒
当使用malloc并且一个块被分割例如从未链接列表或从顶部块分割从剩余分割块创建的块称为最后提醒其指针存储在`malloc_state`结构中。
## 分配流程
查看:
{% content-ref url="heap-memory-functions/malloc-and-sysmalloc.md" %}
[malloc-and-sysmalloc.md](heap-memory-functions/malloc-and-sysmalloc.md)
{% endcontent-ref %}
## 释放流程
查看:
{% content-ref url="heap-memory-functions/free.md" %}
[free.md](heap-memory-functions/free.md)
{% endcontent-ref %}
## 堆函数安全检查
查看堆中常用函数执行的安全检查:
{% content-ref url="heap-memory-functions/heap-functions-security-checks.md" %}
[heap-functions-security-checks.md](heap-memory-functions/heap-functions-security-checks.md)
{% endcontent-ref %}
## 参考资料
* [https://azeria-labs.com/heap-exploitation-part-1-understanding-the-glibc-heap-implementation/](https://azeria-labs.com/heap-exploitation-part-1-understanding-the-glibc-heap-implementation/)
* [https://azeria-labs.com/heap-exploitation-part-2-glibc-heap-free-bins/](https://azeria-labs.com/heap-exploitation-part-2-glibc-heap-free-bins/)
* [https://heap-exploitation.dhavalkapil.com/diving\_into\_glibc\_heap/core\_functions](https://heap-exploitation.dhavalkapil.com/diving\_into\_glibc\_heap/core\_functions)
* [https://ctf-wiki.mahaloz.re/pwn/linux/glibc-heap/implementation/tcache/](https://ctf-wiki.mahaloz.re/pwn/linux/glibc-heap/implementation/tcache/)
<details>
<summary><strong>从零开始学习AWS黑客技术</strong> <a href="https://training.hacktricks.xyz/courses/arte"><strong>htARTEHackTricks AWS Red Team Expert</strong></a><strong></strong></summary>
支持HackTricks的其他方式
* 如果您想在HackTricks中看到您的**公司广告**或**下载PDF格式的HackTricks**,请查看[**订阅计划**](https://github.com/sponsors/carlospolop)!
* 获取[**官方PEASS & HackTricks周边产品**](https://peass.creator-spring.com)
* 发现我们的独家[NFTs](https://opensea.io/collection/the-peass-family)收藏品[**The PEASS Family**](https://opensea.io/collection/the-peass-family)
* **加入** 💬 [**Discord群**](https://discord.gg/hRep4RUj7f) 或 [**电报群**](https://t.me/peass) 或在**Twitter** 🐦 [**@hacktricks\_live**](https://twitter.com/hacktricks\_live)**上关注**我们。
* 通过向[**HackTricks**](https://github.com/carlospolop/hacktricks)和[**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) github仓库提交PR来分享您的黑客技巧。
</details>