.. | ||
use-after-free | ||
bins-and-memory-allocations.md | ||
double-free.md | ||
fast-bin-attack.md | ||
heap-functions-security-checks.md | ||
heap-overflow.md | ||
house-of-einherjar.md | ||
house-of-force.md | ||
house-of-lore.md | ||
house-of-spirit.md | ||
large-bin-attack.md | ||
off-by-one-overflow.md | ||
overwriting-a-freed-chunk.md | ||
README.md | ||
tcache-bin-attack.md | ||
unlink-attack.md | ||
unsorted-bin-attack.md | ||
use-after-free.md |
ヒープ
ヒープの基礎
ヒープは、プログラムが**malloc
、calloc
などの関数を呼び出してデータを要求するときにデータを格納できる場所です。さらに、このメモリが不要になった場合は、free
** 関数を呼び出すことで利用可能になります。
バイナリがメモリにロードされる直後にあることが示されています([heap]
セクションを確認):
ベーシック チャンクの割り当て
ヒープにデータを格納するように要求されると、ヒープの一部がそれに割り当てられます。このスペースはビンに属し、要求されたデータ + ビンヘッダのスペース + 最小ビンサイズオフセットだけがチャンクのために予約されます。目標は、各チャンクの場所を見つけるのが複雑にならないように、可能な限り最小限のメモリを予約することです。そのために、メタデータチャンク情報が使用され、どこに使用中/空きのチャンクがあるかを知るために使用されます。
使用されるビンによって主に異なる方法でスペースを予約する方法がありますが、一般的な方法論は次のとおりです:
- プログラムは一定量のメモリを要求して開始します。
- チャンクのリストに要求を満たすのに十分な大きさのチャンクがあれば、それが使用されます。
- これは、利用可能なチャンクの一部がこの要求に使用され、残りがチャンクリストに追加されることさえ意味するかもしれません。
- リストに利用可能なチャンクがない場合でも、割り当てられたヒープメモリにはまだスペースがある場合、ヒープマネージャは新しいチャンクを作成します。
- 新しいチャンクを割り当てるためのヒープスペースが十分でない場合、ヒープマネージャはカーネルにヒープに割り当てられたメモリを拡張するように要求し、そのメモリを使用して新しいチャンクを生成します。
- すべてが失敗した場合、
malloc
は null を返します。
要求されたメモリがしきい値を超える場合は、mmap
が要求されたメモリをマップするために使用されます。
アリーナ
マルチスレッド アプリケーションでは、ヒープマネージャはクラッシュにつながる可能性のある 競合状態 を防がなければなりません。最初は、グローバルミューテックス を使用して、一度に1つのスレッドだけがヒープにアクセスできるようにすることでこれを行っていましたが、これにより パフォーマンスの問題 が発生しました。
これを解決するために、ptmalloc2 ヒープアロケータは "アリーナ" を導入しました。ここでは、各アリーナ が 独自のデータ構造 と ミューテックス を持つ 別々のヒープ として機能し、異なるアリーナを使用する限り、複数のスレッドが互いに干渉せずにヒープ操作を実行できます。
デフォルトの "main" アリーナは、単一スレッドアプリケーションのヒープ操作を処理します。新しいスレッド が追加されると、ヒープマネージャは セカンダリアリーナ を割り当てて競合を減らします。まず、各新しいスレッドを未使用のアリーナにアタッチしようとし、必要に応じて新しいアリーナを作成し、32ビットシステムでは CPU コア数の2倍、64ビットシステムでは8倍の制限まで増やします。制限に達すると、スレッドはアリーナを共有する必要があり、競合が発生する可能性があります。
メインアリーナが brk
システムコールを使用して拡張するのに対し、セカンダリアリーナは mmap
と mprotect
を使用して "サブヒープ" を作成し、ヒープの動作をシミュレートしてメモリをマルチスレッド操作のために柔軟に管理します。
サブヒープ
サブヒープは、マルチスレッドアプリケーションのセカンダリアリーナにとってのメモリリザーブであり、メインヒープとは異なるヒープ領域を成長させ、管理することを可能にします。サブヒープが最初のヒープとどのように異なり、どのように動作するかを以下に示します:
- 最初のヒープとサブヒープの比較:
- 最初のヒープはプログラムのバイナリの直後にあり、
sbrk
システムコールを使用して拡張されます。 - セカンダリアリーナが使用するサブヒープは、指定されたメモリ領域をマップする
mmap
を介して作成されます。
mmap
を使用したメモリ予約:
- ヒープマネージャがサブヒープを作成すると、
mmap
を介して大きなメモリブロックを予約します。この予約はメモリを直ちに割り当てるのではなく、他のシステムプロセスや割り当てが使用しない領域を指定します。 - デフォルトでは、32ビットプロセスのサブヒープの予約サイズは1 MB、64ビットプロセスの場合は64 MB です。
mprotect
を使用した段階的な拡張:
- 予約されたメモリ領域は最初は
PROT_NONE
としてマークされ、カーネルはこのスペースに物理メモリを割り当てる必要がないことを示します。 - サブヒープを「成長」させるために、ヒープマネージャは
mprotect
を使用してページの権限をPROT_NONE
からPROT_READ | PROT_WRITE
に変更し、カーネルに以前に予約されたアドレスに物理メモリを割り当てるように促します。この段階的なアプローチにより、サブヒープは必要に応じて拡張されます。 - サブヒープ全体が使い切られると、ヒープマネージャは新しいサブヒープを作成して割り当てを続行します。
malloc_state
各ヒープ(メインアリーナまたは他のスレッドのアリーナ)には malloc_state
構造体 があります。
重要なのは、メインアリーナの malloc_state
構造体が libc のグローバル変数 であることに注意することです(したがって、libc メモリ空間に配置されています)。
スレッドのヒープのヒープの malloc_state
構造体の場合、それらは 独自のスレッド "ヒープ" の内部 に配置されています。
この構造体から注目すべき興味深い点がいくつかあります(以下のCコードを参照):
mchunkptr bins[NBINS * 2 - 2];
には、小さな、大きな、未整列の ビン の最初と最後のチャンクへの ポインタ が含まれています(-2 はインデックス 0 が使用されていないためです)。- したがって、これらのビンの最初のチャンクには、この構造体への 逆ポインタ があり、これらのビンの最後のチャンクには、この構造体への 前方ポインタ があります。つまり、メインアリーナでこれらのアドレスを リーク できれば、 libc の構造体へのポインタが得られます。
- 構造体
struct malloc_state *next;
とstruct malloc_state *next_free;
はアリーナのリンクリストです。 top
チャンクは最後の "チャンク" であり、基本的に ヒープの残りのスペース全体 です。top
チャンクが "空" になると、ヒープは完全に使用され、さらにスペースを要求する必要があります。last reminder
チャンクは、正確なサイズのチャンクが利用できない場合や、より大きなチャンクが分割された場合に残りの部分が配置されるケースから来ます。
// From https://heap-exploitation.dhavalkapil.com/diving_into_glibc_heap/malloc_state
struct malloc_state
{
/* Serialize access. */
__libc_lock_define (, mutex);
/* Flags (formerly in max_fast). */
int flags;
/* Fastbins */
mfastbinptr fastbinsY[NFASTBINS];
/* Base of the topmost chunk -- not otherwise kept in a bin */
mchunkptr top;
/* The remainder from the most recent split of a small request */
mchunkptr last_remainder;
/* Normal bins packed as described above */
mchunkptr bins[NBINS * 2 - 2];
/* Bitmap of bins */
unsigned int binmap[BINMAPSIZE];
/* Linked list */
struct malloc_state *next;
/* Linked list for free arenas. Access to this field is serialized
by free_list_lock in arena.c. */
struct malloc_state *next_free;
/* Number of threads attached to this arena. 0 if the arena is on
the free list. Access to this field is serialized by
free_list_lock in arena.c. */
INTERNAL_SIZE_T attached_threads;
/* Memory allocated from the system in this arena. */
INTERNAL_SIZE_T system_mem;
INTERNAL_SIZE_T max_system_mem;
};
typedef struct malloc_state *mstate;
malloc_chunk
この構造体は、特定のメモリチャンクを表します。さまざまなフィールドは、割り当てられたチャンクと未割り当てのチャンクで異なる意味を持ちます。
// From https://heap-exploitation.dhavalkapil.com/diving_into_glibc_heap/malloc_chunk
struct malloc_chunk {
INTERNAL_SIZE_T mchunk_prev_size; /* Size of previous chunk, if it is free. */
INTERNAL_SIZE_T mchunk_size; /* Size in bytes, including overhead. */
struct malloc_chunk* fd; /* double links -- used only if this chunk is free. */
struct malloc_chunk* bk;
/* Only used for large blocks: pointer to next larger size. */
struct malloc_chunk* fd_nextsize; /* double links -- used only if this chunk is free. */
struct malloc_chunk* bk_nextsize;
};
typedef struct malloc_chunk* mchunkptr;
以前にコメントされたように、これらのチャンクにはメタデータも含まれており、この画像で非常によく表現されています:
メタデータは通常、現在のチャンクサイズを示す0x08Bであり、最後の3ビットを使用して次のように示されます:
A
: 1の場合、サブヒープから来ており、0の場合はメインアリーナにあるM
: 1の場合、このチャンクはmmapで割り当てられたスペースの一部であり、ヒープの一部ではないP
: 1の場合、前のチャンクが使用中である
その後、ユーザーデータのスペースがあり、最後に、チャンクが利用可能な場合(または割り当てられている場合)に前のチャンクサイズを示すために0x08Bがあります。
さらに、利用可能な場合、ユーザーデータはいくつかのデータも含んでいます:
- 次のチャンクへのポインタ
- 前のチャンクへのポインタ
- リスト内の次のチャンクのサイズ
- リスト内の前のチャンクのサイズ
{% hint style="info" %} リストをこのようにリンクさせることで、すべてのチャンクが登録されている配列を持つ必要がなくなります。 {% endhint %}
クイックヒープの例
https://guyinatuxedo.github.io/25-heap/index.html からのクイックヒープの例(arm64で):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void main(void)
{
char *ptr;
ptr = malloc(0x10);
strcpy(ptr, "panda");
}
main関数の最後にブレークポイントを設定し、情報が格納されている場所を見つけましょう:
0xaaaaaaac12a0
に文字列pandaが格納されていることがわかります(これはx0
内のmallocによって返されたアドレスでした)。その前の0x10バイトをチェックすると、0x0
が前のチャンクが使用されていないことを示し、このチャンクの長さが0x21
であることがわかります。
余分に確保されたスペース(0x21-0x10=0x11)は、追加されたヘッダー(0x10)から来ており、0x1が0x21Bが予約されたことを意味するのではなく、現在のヘッダーの長さの最後の3ビットに特別な意味があることを示しています。長さは常に16バイトに整列されるため(64ビットマシンでは)、これらのビットは実際には長さ番号によって使用されることはありません。
0x1: Previous in Use - Specifies that the chunk before it in memory is in use
0x2: Is MMAPPED - Specifies that the chunk was obtained with mmap()
0x4: Non Main Arena - Specifies that the chunk was obtained from outside of the main arena
ビンとメモリの割り当て/解放
以下のリンク先で、ビンが何であり、どのように構成されているか、そしてメモリがどのように割り当てられ、解放されているかを確認してください:
{% content-ref url="bins-and-memory-allocations.md" %} bins-and-memory-allocations.md {% endcontent-ref %}
ヒープ関数のセキュリティチェック
ヒープに関わる関数は、そのアクションを実行する前に特定のチェックを実行し、ヒープが破損していないことを確認しようとします:
{% content-ref url="heap-functions-security-checks.md" %} heap-functions-security-checks.md {% endcontent-ref %}