mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
601 lines
38 KiB
Markdown
601 lines
38 KiB
Markdown
# Bins & Memory Allocations
|
||
|
||
{{#include ../../banners/hacktricks-training.md}}
|
||
|
||
## 基本情報
|
||
|
||
チャンクの保存効率を向上させるために、各チャンクは単一のリンクリストにだけ存在するのではなく、いくつかのタイプがあります。これらはビンと呼ばれ、5種類のビンがあります: [62](https://sourceware.org/git/gitweb.cgi?p=glibc.git;a=blob;f=malloc/malloc.c;h=6e766d11bc85b6480fa5c9f2a76559f8acf9deb5;hb=HEAD#l1407) 小ビン、63 大ビン、1 未整理ビン、10 ファストビン、64 tcacheビン(スレッドごと)。
|
||
|
||
未整理、小ビン、大ビンの各ビンへの初期アドレスは同じ配列内にあります。インデックス0は未使用、1は未整理ビン、ビン2-64は小ビン、ビン65-127は大ビンです。
|
||
|
||
### Tcache(スレッドごとのキャッシュ)ビン
|
||
|
||
スレッドはそれぞれ独自のヒープを持とうとしますが([アリーナ](bins-and-memory-allocations.md#arenas)および[サブヒープ](bins-and-memory-allocations.md#subheaps)を参照)、多くのスレッドを持つプロセス(ウェブサーバーなど)が**他のスレッドとヒープを共有する可能性があります**。この場合、主な解決策は**ロッカー**の使用であり、これにより**スレッドが大幅に遅くなる可能性があります**。
|
||
|
||
したがって、tcacheはスレッドごとのファストビンに似ており、**チャンクをマージしない単一のリンクリスト**です。各スレッドには**64の単一リンクtcacheビン**があります。各ビンは、[64ビットシステムで24から1032B、32ビットシステムで12から516Bの範囲の](https://sourceware.org/git/?p=glibc.git;a=blob;f=malloc/malloc.c;h=2527e2504761744df2bdb1abdc02d936ff907ad2;hb=d5c3fafc4307c9b7a4c7d5cb381fcdbfad340bcc#l315) [同サイズのチャンクを最大7つ](https://sourceware.org/git/?p=glibc.git;a=blob;f=malloc/malloc.c;h=2527e2504761744df2bdb1abdc02d936ff907ad2;hb=d5c3fafc4307c9b7a4c7d5cb381fcdbfad340bcc#l323)持つことができます。
|
||
|
||
**スレッドがチャンクを解放するとき**、**tcacheに割り当てるには大きすぎない場合**、かつ該当するtcacheビンが**満杯でない**(すでに7つのチャンクがある)場合、**そこに割り当てられます**。tcacheに行けない場合は、グローバルに解放操作を実行するためにヒープロックを待つ必要があります。
|
||
|
||
**チャンクが割り当てられるとき**、必要なサイズのフリーなチャンクが**Tcacheにあればそれを使用し**、なければ、グローバルビンで見つけるか新しいものを作成するためにヒープロックを待つ必要があります。\
|
||
また、最適化もあり、この場合、ヒープロックを保持している間、スレッドは**要求されたサイズのヒープチャンク(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;
|
||
}
|
||
```
|
||
コンパイルして、main関数の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 構造体と関数
|
||
|
||
以下のコードでは、**max bins** と **chunks per index**、ダブルフリーを避けるために作成された **`tcache_entry`** 構造体、各スレッドがビンの各インデックスへのアドレスを格納するために使用する構造体 **`tcache_perthread_struct`** を見ることができます。
|
||
|
||
<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 には、サイズに応じていくつかのビンがあり、**各インデックスの最初のチャンクへのポインタとインデックスごとのチャンクの量はチャンク内にあります**。これは、この情報(通常は最初のもの)を持つチャンクを特定することで、すべての tcache 初期ポイントと Tcache チャンクの量を見つけることができることを意味します。
|
||
|
||
### ファストビン
|
||
|
||
ファストビンは、**小さなチャンクのメモリ割り当てを高速化するために設計されています**。最近解放されたチャンクを迅速にアクセスできる構造に保持します。これらのビンは、後入れ先出し(LIFO)アプローチを使用しており、**最も最近解放されたチャンクが最初**に再利用されます。これは、スタックの上部から挿入および削除する方が、キュー(FIFO)よりも速いため、速度にとって有利です。
|
||
|
||
さらに、**ファストビンは単方向リンクリストを使用**しており、双方向リンクではないため、速度がさらに向上します。ファストビンのチャンクは隣接するチャンクとマージされないため、中間から削除を可能にする複雑な構造は必要ありません。単方向リンクリストは、これらの操作に対してよりシンプルで迅速です。
|
||
|
||
基本的に、ここで起こることは、ヘッダー(最初のチャンクをチェックするためのポインタ)が常にそのサイズの最新の解放されたチャンクを指しているということです。したがって:
|
||
|
||
- そのサイズの新しいチャンクが割り当てられると、ヘッダーは使用するための空きチャンクを指します。この空きチャンクが次に使用するチャンクを指しているため、このアドレスはヘッダーに保存され、次の割り当てが利用可能なチャンクを取得する場所を知ることができます。
|
||
- チャンクが解放されると、空きチャンクは現在の利用可能なチャンクへのアドレスを保存し、この新しく解放されたチャンクへのアドレスがヘッダーに置かれます。
|
||
|
||
リンクリストの最大サイズは `0x80` であり、サイズ `0x20` のチャンクはインデックス `0` に、サイズ `0x30` のチャンクはインデックス `1` に配置されます...
|
||
|
||
> [!CAUTION]
|
||
> ファストビンのチャンクは利用可能として設定されていないため、周囲の他の空きチャンクとマージできる代わりに、しばらくの間ファストビンチャンクとして保持されます。
|
||
```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>ファストビンチャンクの例を追加</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が満たされ、8つ目のチャンクがファストチャンクに格納されます。
|
||
|
||
コンパイルして、`main`関数の`ret`オペコードにブレークポイントを設定してデバッグします。次に、`gef`を使用すると、tcacheビンが満杯で、1つのチャンクがファストビンにあることがわかります。
|
||
```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>
|
||
|
||
### 未整理ビン
|
||
|
||
未整理ビンは、ヒープマネージャーによってメモリ割り当てを迅速に行うために使用される**キャッシュ**です。動作は次のようになります:プログラムがチャンクを解放すると、そのチャンクがtcacheやファストビンに割り当てられず、トップチャンクと衝突しない場合、ヒープマネージャーはすぐに特定の小さなビンや大きなビンに入れません。代わりに、最初に**隣接する空きチャンクとマージしようとします**。これにより、より大きな空きメモリブロックが作成されます。その後、この新しいチャンクは「未整理ビン」と呼ばれる一般的なビンに配置されます。
|
||
|
||
プログラムが**メモリを要求すると**、ヒープマネージャーは**未整理ビンをチェック**して、十分なサイズのチャンクがあるかどうかを確認します。見つかれば、すぐに使用します。未整理ビンに適切なチャンクが見つからない場合は、このリスト内のすべてのチャンクをサイズに基づいて対応するビン(小または大)に移動します。
|
||
|
||
注意すべきは、より大きなチャンクが2つの半分に分割され、残りがMINSIZEより大きい場合、それは未整理ビンに戻されるということです。
|
||
|
||
したがって、未整理ビンは、最近解放されたメモリを迅速に再利用することでメモリ割り当てを加速し、時間のかかる検索やマージの必要性を減らす方法です。
|
||
|
||
> [!CAUTION]
|
||
> チャンクが異なるカテゴリであっても、利用可能なチャンクが別の利用可能なチャンクと衝突している場合(元々異なるビンに属していても)、それらはマージされることに注意してください。
|
||
|
||
<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が満たされ**、8つ目は**fastbinには大きすぎる**ため、未ソートのビンに格納されます。そして9つ目は解放されていないため、8つ目と9つ目は**トップチャンクとマージされません**。
|
||
|
||
それをコンパイルし、`main`関数の`ret`オペコードにブレークポイントを設定してデバッグします。次に、`gef`を使用すると、tcacheビンが満杯で、1つのチャンクが未ソートのビンにあることがわかります:
|
||
```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>
|
||
|
||
### スモールビン
|
||
|
||
スモールビンはラージビンよりも速いですが、ファストビンよりは遅いです。
|
||
|
||
62の各ビンは**同じサイズのチャンク**を持ちます:16、24、...(32ビットでの最大サイズは504バイト、64ビットでの最大サイズは1024バイト)。これは、スペースを割り当てるべきビンを見つける速度や、これらのリストへのエントリの挿入と削除を助けます。
|
||
|
||
スモールビンのサイズはビンのインデックスに応じて次のように計算されます:
|
||
|
||
- 最小サイズ:2\*4\*index(例:インデックス5 -> 40)
|
||
- 最大サイズ:2\*8\*index(例:インデックス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)
|
||
```
|
||
小さなビンと大きなビンを選択するための関数:
|
||
```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が満たされ**、8つ目は**fastbinには大きすぎる**ため、未ソートのビンに格納されます。そして9つ目は解放されていないため、9つ目と8つ目は**トップチャンクとマージされません**。次に、0x110のより大きなチャンクを割り当てると、**未ソートのビンのチャンクが小さなビンに移動します**。
|
||
|
||
それをコンパイルし、`main`関数の`ret`オペコードにブレークポイントを設定してデバッグします。次に、`gef`を使用すると、tcacheビンが満杯で、1つのチャンクが小さなビンにあることがわかります。
|
||
```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>
|
||
|
||
### 大きなビン
|
||
|
||
小さなビンが固定サイズのチャンクを管理するのに対し、各**大きなビンはチャンクサイズの範囲を扱います**。これはより柔軟で、システムが**さまざまなサイズ**を別々のビンなしで収容できるようにします。
|
||
|
||
メモリアロケータでは、大きなビンは小さなビンが終了するところから始まります。大きなビンの範囲は徐々に大きくなり、最初のビンは512バイトから576バイトのチャンクをカバーし、次のビンは576バイトから640バイトをカバーします。このパターンは続き、最大のビンは1MBを超えるすべてのチャンクを含みます。
|
||
|
||
大きなビンは小さなビンに比べて操作が遅くなります。なぜなら、**最適なフィットを見つけるためにさまざまなチャンクサイズのリストをソートして検索しなければならないからです**。チャンクが大きなビンに挿入されると、それはソートされ、メモリが割り当てられるときにシステムは適切なチャンクを見つけなければなりません。この追加の作業により、**遅くなります**が、大きな割り当ては小さな割り当てよりも一般的ではないため、許容できるトレードオフです。
|
||
|
||
以下があります:
|
||
|
||
- 64B範囲のビンが32個(小さなビンと衝突)
|
||
- 512B範囲のビンが16個(小さなビンと衝突)
|
||
- 4096B範囲のビンが8個(小さなビンと部分的に衝突)
|
||
- 32768B範囲のビンが4個
|
||
- 262144B範囲のビンが2個
|
||
- 残りのサイズ用のビンが1個
|
||
|
||
<details>
|
||
|
||
<summary>大きなビンサイズのコード</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つの大きなアロケーションが行われ、その後1つが解放され(未ソートビンに入る)、より大きなアロケーションが行われます(解放されたものが未ソートビンから大きなビンに移動します)。
|
||
|
||
それをコンパイルし、`main`関数の`ret`オペコードにブレークポイントを設定してデバッグします。次に、`gef`を使用すると、tcacheビンが満杯であり、1つのチャンクが大きなビンにあることがわかります。
|
||
```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;
|
||
}
|
||
```
|
||
`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` だからです。\
|
||
トップチャンクの長さは、そのチャンクヘッダーでも確認できます:
|
||
```bash
|
||
gef➤ x/8wx 0xaaaaaaac1ae0 - 16
|
||
0xaaaaaaac1ad0: 0x00000000 0x00000000 0x00020531 0x00000000
|
||
0xaaaaaaac1ae0: 0x00000000 0x00000000 0x00000000 0x00000000
|
||
```
|
||
</details>
|
||
|
||
### 最後の残り
|
||
|
||
mallocが使用され、チャンクが分割されると(例えば、未整列ビンまたはトップチャンクから)、分割されたチャンクの残りから作成されたチャンクは「最後の残り」と呼ばれ、そのポインタは`malloc_state`構造体に格納されます。
|
||
|
||
## 割り当てフロー
|
||
|
||
チェックアウト:
|
||
|
||
{{#ref}}
|
||
heap-memory-functions/malloc-and-sysmalloc.md
|
||
{{#endref}}
|
||
|
||
## 解放フロー
|
||
|
||
チェックアウト:
|
||
|
||
{{#ref}}
|
||
heap-memory-functions/free.md
|
||
{{#endref}}
|
||
|
||
## ヒープ関数のセキュリティチェック
|
||
|
||
ヒープで広く使用される関数によって実行されるセキュリティチェックを確認するには:
|
||
|
||
{{#ref}}
|
||
heap-memory-functions/heap-functions-security-checks.md
|
||
{{#endref}}
|
||
|
||
## 参考文献
|
||
|
||
- [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/)
|
||
|
||
{{#include ../../banners/hacktricks-training.md}}
|