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

40 KiB
Raw Blame History

ビンとメモリ割り当て

{% hint style="success" %} AWSハッキングの学習と練習HackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングの学習と練習HackTricks Training GCP Red Team Expert (GRTE)

HackTricksをサポートする
{% endhint %}

基本情報

チャンクの格納効率を向上させるために、各チャンクは1つのリンクリストにだけではなく、複数のタイプが存在します。これらはビンであり、5種類のビンがあります62 small bins、63 large bins、1 unsorted bin、10 fast bins、およびスレッドごとに64個のtcache binsがあります。

各unsorted、small、large binsの初期アドレスは同じ配列内にあります。インデックス0は未使用で、1はunsorted bin、2〜64はsmall bins、65〜127はlarge binsです。

Tcacheスレッドごとのキャッシュビン

スレッドは独自のヒープを持とうとしますが(ArenasおよびSubheapsを参照、多くのスレッドを持つプロセスWebサーバーなどは別のスレッドとヒープを共有する可能性があります。この場合、主な解決策はロッカーの使用であり、これはスレッドを著しく遅くする可能性があります

したがって、tcacheは、チャンクをマージしない単方向リンクリストであり、各スレッドには64個の単方向リンクtcache binsがあります。各ビンには、64ビットシステムでは24〜1032B、32ビットシステムでは12〜516B同じサイズのチャンクを最大7つ持つことができます。

スレッドがチャンクを解放するとき、それがtcacheに割り当てられるには大きすぎないことと、対応するtcache binが満杯でないすでに7つのチャンクがあることが必要です。tcacheに移動できない場合は、ヒープロックを待ってグローバルな解放操作を行うことができるようにする必要があります。

チャンクが割り当てられるとき、Tcacheに必要なサイズの空きチャンクがあれば使用されます。そうでない場合は、グローバルビンから見つけるか新しいものを作成するためにヒープロックを待つ必要があります。
また、この場合の最適化もあり、ヒープロックを取得する間に、スレッドは要求されたサイズのヒープチャンク7つでTcacheを埋めるため、必要な場合はTcacheでそれらを見つけることができます。

tcacheチャンクの例を追加 ```c #include #include

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)

Tcacheの構造体と関数

以下のコードでは、max binschunks per index、**tcache_entry構造体が作成されて二重解放を避けるために使用されており、また、各スレッドがビンの各インデックスのアドレスを格納するために使用するtcache_perthread_struct**という構造体が見られます。

tcache_entrytcache_perthread_struct ```c // From f942a732d3/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));
}

}

Tcache インデックス

Tcache には、サイズに応じて複数のビンがあり、各インデックスの最初のチャンクへのポインタと、各インデックスごとのチャンクの数がチャンク内に配置されています。つまり、この情報を持つチャンク(通常は最初のチャンク)を見つけると、すべての tcache の最初のポイントと Tcache チャンクの数を見つけることができます。

ファストビン

ファストビンは、小さなチャンクのメモリ割り当てを高速化するために、最近解放されたチャンクをクイックアクセス構造に保持するように設計されています。これらのビンは、最後に解放されたチャンクが再利用されるため、最新の解放されたチャンクが最初になる Last-In, First-OutLIFOアプローチを使用します。この動作は、スタックLIFOの先頭に挿入および削除する速度が、キューFIFOよりも速いため、速度向上に有利です。

さらに、ファストビンは単方向リンクリストを使用しており、ダブルリンクリストではないため、速度が向上します。ファストビンのチャンクは隣接するチャンクとマージされないため、中間からの削除を許可する複雑な構造は不要です。単方向リンクリストは、これらの操作に対してより単純で迅速です。

基本的に、ここで起こることは、ヘッダー(チェックする最初のチャンクへのポインタ)が常にそのサイズの最新の解放されたチャンクを指しているということです。したがって:

  • そのサイズの新しいチャンクが割り当てられると、ヘッダーは使用する空きチャンクを指しています。この空きチャンクが次に使用するチャンクを指しているため、このアドレスはヘッダーに保存され、次の割り当てがどこから利用可能なチャンクを取得するかを知っています。
  • チャンクが解放されると、空きチャンクは現在の利用可能なチャンクへのアドレスを保存し、この新しく解放されたチャンクへのアドレスがヘッダーに配置されます。

リンクリストの最大サイズは 0x80 であり、サイズ 0x20 のチャンクはインデックス 0 に、サイズ 0x30 のチャンクはインデックス 1 に配置されます。

{% hint style="danger" %} ファストビンのチャンクは利用可能として設定されていないため、周囲の他の空きチャンクとマージできる代わりに、一定時間ファストビンのチャンクとして保持されます。 {% endhint %}

// 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)
高速ビンチャンクの例を追加 ```c #include #include

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がいっぱいになり、8番目のチャンクがfastチャンクに格納されます。

それをコンパイルして、`main`関数の`ret`オペコードでブレークポイントを設定してデバッグします。その後、`gef`を使用して、tcache binがいっぱいで1つのチャンクがfast 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

Unsorted bin

未整列のビンは、ヒープマネージャーがメモリ割り当てを迅速に行うために使用するキャッシュです。動作方法は次のとおりです。プログラムがチャンクを解放すると、このチャンクがtcacheやfast binに割り当てられず、かつトップチャンクと衝突していない場合、ヒープマネージャーはそれをすぐに特定の小さなビンや大きなビンに配置しません。代わりに、隣接する空きチャンクとマージしてより大きな空きメモリブロックを作成しようとします。その後、この新しいチャンクを「未整列のビン」と呼ばれる一般的なビンに配置します。

プログラムがメモリを要求すると、ヒープマネージャーは未整列のビンをチェックして十分なサイズのチャンクがあるかどうかを確認します。見つかればすぐに使用します。未整列のビンに適切なチャンクが見つからない場合、このリスト内のすべてのチャンクを、サイズに応じてそれぞれのビン(小さなビンまたは大きなビン)に移動します。

大きなチャンクが2つに分割され、残りがMINSIZEより大きい場合、それは未整列のビンに戻されます。

したがって、未整列のビンは、最近解放されたメモリを迅速に再利用し、時間のかかる検索とマージの必要性を減らすことでメモリ割り当てを高速化する方法です。

{% hint style="danger" %} 異なるカテゴリのチャンクであっても、利用可能なチャンクが他の利用可能なチャンクと衝突している場合(元々異なるビンに属していても)、それらはマージされます。 {% endhint %}

未整列のチャンクの例を追加 ```c #include #include

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が埋まり**、8番目のチャンクは**fastbinには大きすぎるためアンソートされたbinに格納**され、9番目のチャンクは解放されないため、9番目と8番目は**トップチャンクとマージされない**。

それをコンパイルして、`main`関数の`ret`オペコードでブレークポイントを設定してデバッグします。その後、`gef`を使用して、tcache binがいっぱいで、1つのチャンクがアンソートされた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.

Small Bins

スモールビンはラージビンよりも速く、ファストビンよりも遅いです。

62個のビンのそれぞれには、同じサイズのチャンクがあります: 16、24、...32ビットでは最大504バイト、64ビットでは1024バイト。これにより、空間を割り当てるべきビンを見つける速度が向上し、これらのリストにエントリを挿入および削除する際の速度も向上します。

スモールビンのサイズは、ビンのインデックスに応じて次のように計算されます:

  • 最小サイズ: 2*4*index例: インデックス5 -> 40
  • 最大サイズ: 2*8*index例: インデックス5 -> 80
// 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:

小さなビンと大きなビンの間を選択するための関数:

#define bin_index(sz) \
((in_smallbin_range (sz)) ? smallbin_index (sz) : largebin_index (sz))
小さなチャンクの例を追加 ```c #include #include

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が埋まり**、8番目のチャンクは**fastbinには大きすぎるためアンソートされたbinに格納**され、9番目のチャンクは解放されないため、9番目と8番目は**トップチャンクとマージされない**。その後、0x110の大きなチャンクを割り当てることで、**アンソートされたbinのチャンクがsmall binに移動**します。

それをコンパイルして、`main`関数の`ret`オペコードでブレークポイントを設定してデバッグします。その後、`gef`を使用して、tcache binがいっぱいで、1つのチャンクが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.

ラージビン

小さなビンが固定サイズのチャンクを管理するのに対し、各ラージビンはチャンクサイズの範囲を扱います。これにより、システムはさまざまなサイズを別々のビンを必要とせずに収容できるようになります。

メモリアロケーターにおいて、ラージビンは小さなビンの終わりから始まります。ラージビンの範囲は徐々に大きくなり、最初のビンは512から576バイトのチャンクをカバーし、次のビンは576から640バイトをカバーします。このパターンが続き、最大のビンには1MBを超えるすべてのチャンクが含まれます。

ラージビンは、割り当てに最適なチャンクを見つけるためにさまざまなチャンクサイズのリストをソートして検索する必要があるため、小さなビンよりも操作が遅くなります。チャンクがラージビンに挿入されると、ソートされなければならず、メモリが割り当てられるときにシステムは適切なチャンクを見つけなければなりません。この追加作業により、ラージビンは遅くなりますが、大きな割り当てが小さな割り当てよりも少ないため、これは許容できるトレードオフです。

以下のようなものがあります:

  • 64B範囲の32ビン小さなビンと衝突する
  • 512B範囲の16ビン小さなビンと衝突する
  • 4096B範囲の8ビン一部が小さなビンと衝突する
  • 32768B範囲の4ビン
  • 262144B範囲の2ビン
  • 残りのサイズ用の1ビン
ラージビンサイズのコード ```c // From a07e000e82/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つの大きな割り当てが行われ、そのうち1つが解放されますこれにより、unsorted binに配置されます、そしてより大きな割り当てが行われます解放されたものがunsorted binからlarge binに移動します

それをコンパイルし、main関数のretオペコードにブレークポイントを設定してデバッグします。その後、gefを使用して、tcache binがいっぱいであり、1つのチャンクがlarge binにあることがわかります。

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.

トップチャンク

// 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が実行されると、使用可能なフリーチャンクがない場合、このトップチャンクは必要なスペースを提供するためにサイズを減らします。
Top Chunkへのポインタはmalloc_state構造体に格納されています。

さらに、最初に、未整列のチャンクをTop Chunkとして使用することができます。

Top Chunkの例を観察 ```c #include #include

int main(void) { char *chunk; chunk = malloc(24); printf("Address of the chunk: %p\n", (void *)chunk); gets(chunk); return 0; }

コンパイルしてデバッグを行い、`main`の`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という結果になります。
また、トップチャンクの長さをそのチャンクヘッダーで見ることも可能です。

gef➤  x/8wx 0xaaaaaaac1ae0 - 16
0xaaaaaaac1ad0:	0x00000000	0x00000000	0x00020531	0x00000000
0xaaaaaaac1ae0:	0x00000000	0x00000000	0x00000000	0x00000000

最後の残り物

mallocが使用され、チャンクが分割されると未整列のビンからまたはトップチャンクからなど、分割されたチャンクの残りから作成されたチャンクは最後の残り物と呼ばれ、そのポインタはmalloc_state構造体に格納されます。

割り当てフロー

次をチェックしてください:

{% content-ref url="heap-memory-functions/malloc-and-sysmalloc.md" %} malloc-and-sysmalloc.md {% endcontent-ref %}

解放フロー

次をチェックしてください:

{% content-ref url="heap-memory-functions/free.md" %} free.md {% endcontent-ref %}

ヒープ関数のセキュリティチェック

ヒープ内で頻繁に使用される関数によって実行されるセキュリティチェックを確認してください:

{% content-ref url="heap-memory-functions/heap-functions-security-checks.md" %} heap-functions-security-checks.md {% endcontent-ref %}

参考文献

{% hint style="success" %} AWSハッキングを学び、実践するHackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングを学び、実践するHackTricks Training GCP Red Team Expert (GRTE)

HackTricksをサポート
{% endhint %}