Translated ['src/binary-exploitation/libc-heap/unsorted-bin-attack.md']

This commit is contained in:
Translator 2025-08-28 17:10:37 +00:00
parent fea0cb7885
commit f00687fc5a

View File

@ -4,53 +4,109 @@
## 基本情報
未整理ビンについての詳細はこのページを確認してください:
For more information about what is an unsorted bin check this page:
{{#ref}}
bins-and-memory-allocations.md
{{#endref}}
未整理リストは、チャンクの `bk` アドレスに `unsorted_chunks (av)` のアドレスを書き込むことができます。したがって、攻撃者が未整理ビン内のチャンクの `bk` ポインタのアドレスを**変更できれば**、**そのアドレスを任意のアドレスに書き込むことができ**、Glibc アドレスを漏洩させたり、一部の防御を回避したりするのに役立ちます。
unsorted リストはチャンクの `bk` アドレスに `unsorted_chunks (av)` のアドレスを書き込むことができます。したがって、攻撃者がunsorted bin内のチャンクの `bk` ポインタのアドレスを**変更できる**のであれば、任意のアドレスにそのアドレスを**書き込む**ことができ、Glibc のアドレスを leak したり防御を回避したりするのに役立ちます。
基本的に、この攻撃は**任意のアドレスに大きな数を設定する**ことを可能にします。この大きな数はアドレスであり、ヒープアドレスまたはGlibcアドレスである可能性があります。典型的なターゲットは**`global_max_fast`**であり、これによりより大きなサイズのファストビンを作成できるようになります(未整理ビン攻撃からファストビン攻撃に移行することができます)。
基本的に、この攻撃では任意のアドレスに**大きな数値を設定**できます。 この大きな数値はアドレスであり、heap アドレスや Glibc アドレスになり得ます。伝統的なターゲットは `global_max_fast` で、fast bin を大きなサイズで作成できるようにしunsorted bin attack から fast bin attack へ移行するため)でした。
- Modern note (glibc ≥ 2.39): `global_max_fast` became an 8bit global. Blindly writing a pointer there via an unsorted-bin write will clobber adjacent libc data and will not reliably raise the fastbin limit anymore. Prefer other targets or other primitives when running against glibc 2.39+. See "Modern constraints" below and consider combining with other techniques like a [large bin attack](large-bin-attack.md) or a [fast bin attack](fast-bin-attack.md) once you have a stable primitive.
> [!TIP]
> 提供された例を見て、[https://ctf-wiki.mahaloz.re/pwn/linux/glibc-heap/unsorted_bin_attack/#principle](https://ctf-wiki.mahaloz.re/pwn/linux/glibc-heap/unsorted_bin_attack/#principle) で 0x4000 と 0x5000 をチャンクサイズとして使用するとTcacheを避けるため、**現在**エラー **`malloc(): unsorted double linked list corrupted`** がトリガーされることがわかります。
> T> この例([https://ctf-wiki.mahaloz.re/pwn/linux/glibc-heap/unsorted_bin_attack/#principle](https://ctf-wiki.mahaloz.re/pwn/linux/glibc-heap/unsorted_bin_attack/#principle))を見て、チャンクサイズに 0x4000 と 0x5000 をTcache を避けるために0x400 と 0x500 の代わりに使用すると、**現在では** エラー **`malloc(): unsorted double linked list corrupted`** が発生するのが確認できます。
>
> したがって、この unsorted bin attack は現在(他のチェックに加えて)ダブルリンクリストを整合させる必要があり、`victim->bk->fd == victim` または `victim->fd == av (arena)` を回避する必要があります。つまり、書き込み先のアドレスはその `fd` フィールドに偽チャンクのアドレスを持ち、偽チャンクの `fd` が arena を指している必要があります。
>
> したがって、この未整理ビン攻撃は、(他のチェックの中で)二重リンクリストを修正できる必要があるため、`victim->bk->fd == victim` または `victim->fd == av (arena)` でないことが要求されます。これは、書き込みたいアドレスがその `fd` ポジションにフェイクチャンクのアドレスを持ち、フェイクチャンクの `fd` がアリーナを指している必要があることを意味します。
> [!CAUTION]
> この攻撃は未整理ビンを破損させることに注意してください(したがって小さなものと大きなものも)。したがって、**現在はファストビンからの割り当てのみを使用できます**(より複雑なプログラムは他の割り当てを行い、クラッシュする可能性があります)、これをトリガーするには、**同じサイズを割り当てる必要があります。さもなければプログラムはクラッシュします。**
> この攻撃は unsorted bin を破損させます(したがって small や large も影響を受けます)。そのため、現在は **fast bin からの割り当てのみを使用** できます(より複雑なプログラムは他の割り当てを行いクラッシュする可能性があります)。また、これを引き起こすためには **同じサイズを割り当てる必要があり、さもないとプログラムがクラッシュします**
>
> **`global_max_fast`** を上書きすることは、この場合に役立つかもしれません。ファストビンが他のすべての割り当てを処理できると信頼して、エクスプロイトが完了するまで
> `global_max_fast` を上書きすることで、fast bin が他の割り当てを処理してくれると信頼してこの問題を回避できる場合があります
[**guyinatuxedo**](https://guyinatuxedo.github.io/31-unsortedbin_attack/unsorted_explanation/index.html) のコードは非常によく説明していますが、mallocを修正して十分な大きさのメモリを割り当て、Tcacheに終わらないようにすると、前述のエラーが発生し、この技術を妨げることがわかります**`malloc(): unsorted double linked list corrupted`**
The code from [**guyinatuxedo**](https://guyinatuxedo.github.io/31-unsortedbin_attack/unsorted_explanation/index.html) explains it very well, although if you modify the mallocs to allocate memory big enough so don't end in a Tcache you can see that the previously mentioned error appears preventing this technique: **`malloc(): unsorted double linked list corrupted`**
## 未整理ビン情報漏洩攻撃
### 書き込みが実際に行われる仕組み
これは実際には非常に基本的な概念です。未整理ビン内のチャンクにはポインタが含まれます。未整理ビンの最初のチャンクは、実際には**`fd`** と **`bk`** リンクが**メインアリーナGlibcの一部を指しています**。\
したがって、**未整理ビン内にチャンクを置いてそれを読み取る**use after freeか、**ポインタの少なくとも1つを上書きせずに再度割り当ててから**それを**読み取る**ことができれば、**Glibc情報漏洩**を得ることができます。
- The unsorted-bin write is triggered on `free` when the freed chunk is inserted at the head of the unsorted list.
- During insertion, the allocator performs `bck = unsorted_chunks(av); fwd = bck->fd; victim->bk = bck; victim->fd = fwd; fwd->bk = victim; bck->fd = victim;`
- If you can set `victim->bk` to `(mchunkptr)(TARGET - 0x10)` before calling `free(victim)`, the final statement will perform the write: `*(TARGET) = victim`.
- Later, when the allocator processes the unsorted bin, integrity checks will verify (among other things) that `bck->fd == victim` and `victim->fd == unsorted_chunks(av)` before unlinking. Because the insertion already wrote `victim` into `bck->fd` (our `TARGET`), these checks can be satisfied if the write succeeded.
この書き込みで使用された類似の[**攻撃**](https://guyinatuxedo.github.io/33-custom_misc_heap/csaw18_alienVSsamurai/index.html)は、4つのチャンク構造A、B、C、D - Dはトップチャンクとの統合を防ぐためだけに存在しますを悪用するもので、Bでのヌルバイトオーバーフローを使用してCがBを未使用として示すようにしました。また、Bでは `prev_size` データが変更され、サイズがBのサイズではなくA+Bになりました。\
その後、Cが解放され、A+Bと統合されましたただしBはまだ使用中でした。サイズAの新しいチャンクが割り当てられ、その後、libcから漏洩したアドレスがBに書き込まれ、そこから漏洩しました。
## 現代の制約 (glibc ≥ 2.33)
## 参考文献と他の例
To use unsortedbin writes reliably on current glibc:
- Tcache の干渉: サイズが tcache に入る場合、free はそちらに流され unsorted bin に触れません。対策としては次のいずれか:
- MAX_TCACHE_SIZE より大きいサイズのリクエストを行う64bit のデフォルトでは ≥ 0x410、または
- 該当する tcache bin を満たして7 エントリ)追加の free が global bins に到達するようにする、または
- 環境を制御可能であれば tcache を無効化する(例: GLIBC_TUNABLES glibc.malloc.tcache_count=0
- unsorted リストの整合性チェック: unsorted bin を調べる次の allocation パスで、glibc は(簡略化して)次をチェックします:
- `bck->fd == victim` and `victim->fd == unsorted_chunks(av)`; otherwise it aborts with `malloc(): unsorted double linked list corrupted`.
- つまり、ターゲットとするアドレスは2回の書き込みを受け入れられなければなりません最初は free 時に `*(TARGET) = victim`、後はチャンクが取り除かれる際に `*(TARGET) = unsorted_chunks(av)`allocator が `bck->fd` をビンヘッドに書き戻す)となります。単純に大きな非ゼロ値を設定するだけで役に立つターゲットを選んでください。
- 現代のエクスプロイトでの典型的で安定したターゲット
- アプリケーションやグローバル状態で「large」な値をフラグや制限として扱うもの。
- 間接的なプリミティブ(例:後続の [fast bin attack]({{#ref}}fast-bin-attack.md{{#endref}}) の準備や後の write-what-where をピボットするための設定)。
- 新しい glibc では `__malloc_hook`/`__free_hook` は 2.34 で削除されているため避けること。`global_max_fast` は ≥ 2.39 では避けてください(次の注を参照)。
- 最近の glibc における `global_max_fast` について
- glibc 2.39+ では `global_max_fast` は 8 ビットのグローバルです。ヒープポインタを書き込んで fastbins を拡張するという古典的手口はもはやきれいに動作せず、隣接するアロケータ状態を破壊する可能性があります。別の戦略を推奨します。
## 最小限のエクスプロイト手順(現代の glibc
Goal: achieve a single arbitrary write of a heap pointer to an arbitrary address using the unsortedbin insertion primitive, without crashing.
- レイアウト/準備
- A, B, C を tcache を回避するのに十分なサイズで割り当てする(例: 0x5000。C は top chunk と合体するのを防ぎます。
- 破壊Corruption
- A から B のチャンクヘッダへ overflow して `B->bk = (mchunkptr)(TARGET - 0x10)` を設定する。
- トリガー
- `free(B)`。挿入時に allocator は `bck->fd = B` を実行し、したがって `*(TARGET) = B` となります。
- 続行
- 割り当てを続ける予定でプログラムが unsorted bin を使用する場合、後で allocator が `*(TARGET) = unsorted_chunks(av)` を設定することを想定してください。両方の値は通常大きく、単に「big」かどうかだけをチェックするターゲットではサイズ/制限の意味を変えるのに十分な場合があります。
Pseudocode skeleton:
```c
// 64-bit glibc 2.352.38 style layout (tcache bypass via large sizes)
void *A = malloc(0x5000);
void *B = malloc(0x5000);
void *C = malloc(0x5000); // guard
// overflow from A into Bs metadata (prev_size/size/.../bk). You must control B->bk.
*(size_t *)((char*)B - 0x8) = (size_t)(TARGET - 0x10); // write fake bk
free(B); // triggers *(TARGET) = B (unsorted-bin insertion write)
```
> [!NOTE]
> • If you cannot bypass tcache with size, fill the tcache bin for the chosen size (7 frees) before freeing the corrupted chunk so the free goes to unsorted.
> • If the program immediately aborts on the next allocation due to unsorted-bin checks, reexamine that `victim->fd` still equals the bin head and that your `TARGET` holds the exact `victim` pointer after the first write.
## Unsorted Bin Infoleak Attack
これは非常に基本的な概念です。unsorted bin 内のチャンクはポインタを持ちます。unsorted bin の最初のチャンクは実際に **`fd`** と **`bk`** のリンクが **main arena (Glibc) の一部を指している** ことになります。\
したがって、チャンクを unsorted bin に入れて読み取れるuse after freeか、少なくとも1つのポインタを上書きしないまま再度割り当ててから読み取ることができれば、**Glibc info leak** を得ることができます。
A similar [**attack used in this writeup**](https://guyinatuxedo.github.io/33-custom_misc_heap/csaw18_alienVSsamurai/index.html)、は4チャンク構成A, B, C, D - Dはtop chunkとのconsolidationを防ぐためだけを悪用するもので、BのnullバイトオーバーフローによってCがBを未使用と示すようにし、さらにBの`prev_size`を変更してサイズがBのサイズではなくA+Bとなるようにしました。\
その後CをfreeしてA+Bと統合されただしBはまだ使用中、サイズAの新しいチャンクを割り当てると、libcのleakedアドレスがBに書き込まれ、そこからleakされました。
## 参考と他の例
- [**https://ctf-wiki.mahaloz.re/pwn/linux/glibc-heap/unsorted_bin_attack/#hitcon-training-lab14-magic-heap**](https://ctf-wiki.mahaloz.re/pwn/linux/glibc-heap/unsorted_bin_attack/#hitcon-training-lab14-magic-heap)
- 目標は、4869より大きな値でグローバル変数を上書きすることで、フラグを取得できるようにすることです。PIEは有効になっていません。
- 任意のサイズのチャンクを生成することができ、希望するサイズのヒープオーバーフローがあります。
- 攻撃は3つのチャンクを作成することから始まりますチャンク0はオーバーフローを悪用し、チャンク1はオーバーフローされ、チャンク2はトップチャンクが前のものと統合しないようにします。
- 次に、チャンク1が解放され、チャンク0がチャンク1の `bk` ポインタを指すようにオーバーフローされます:`bk = magic - 0x10`
- 次に、チャンク1と同じサイズのチャンク3が割り当てられ、これが未整理ビン攻撃をトリガーし、グローバル変数の値を変更し、フラグを取得できるようにします。
- 目的はグローバル変数を4869より大きい値で上書きしてflagを取得することで、PIEは有効ではない
- 任意のサイズのチャンクを生成でき、目的のサイズでヒープオーバーフローが存在する
- 攻撃は3つのチャンクを作成するところから始まる: chunk0オーバーフローを悪用するため、chunk1オーバーフローされる対象、chunk2top chunkが前のものとconsolidateしないようにするため
- その後chunk1をfreeし、chunk0をオーバーフローさせてchunk1の`bk`ポインタが指す先を `bk = magic - 0x10` にする。
- 続いてchunk1と同じサイズでchunk3を割り当てるとunsorted bin attackが発動し、グローバル変数の値を変更してflagを得られるようにする
- [**https://guyinatuxedo.github.io/31-unsortedbin_attack/0ctf16_zerostorage/index.html**](https://guyinatuxedo.github.io/31-unsortedbin_attack/0ctf16_zerostorage/index.html)
- マージ関数は脆弱であり、渡された両方のインデックスが同じであれば、それを再割り当てし、その後解放しますが、解放された領域へのポインタを返します。
- したがって、**2つのチャンクが作成されます****チャンク0**は自分自身とマージされ、チャンク1はトップチャンクとの統合を防ぎます。次に、**マージ関数がチャンク0**に対して2回呼び出され、使用後の解放が発生します。
- 次に、**`view`** 関数がインデックス2使用後の解放チャンクのインデックスで呼び出され、**libcアドレスが漏洩します**。
- バイナリには**`global_max_fast`** より大きなサイズのみをmallocする保護があるため、ファストビンは使用されず、未整理ビン攻撃が使用されてグローバル変数 `global_max_fast` を上書きします。
- 次に、インデックス2使用後の解放ポインタで編集関数を呼び出し、`bk` ポインタを `p64(global_max_fast-0x10)` を指すように上書きします。次に、新しいチャンクを作成すると、以前に妥協された解放アドレス0x20が使用され、**未整理ビン攻撃**がトリガーされ、非常に大きな値の `global_max_fast` を上書きし、ファストビンでチャンクを作成できるようになります。
- その後、**ファストビン攻撃**が実行されます:
- まず、**`__free_hook`** の場所でサイズ200のファストチャンクを操作できることが発見されます
- merge関数は、同じインデックスが2回渡された場合にそれをreallocしてからfreeし、解放された領域のポインタを返してしまう脆弱性があるため脆弱である
- したがって、**2つのチャンクが作成される**: **chunk0**自身とマージされると、top chunkとのconsolidationを防ぐためのchunk1。次に、**merge関数をchunk0で**2回呼ぶとuse after freeが発生する
- その後、インデックス2use after freeチャンクのインデックスで**`view`**関数を呼ぶと、**libcアドレスがleak**される
- バイナリは `global_max_fast` より大きいサイズのみmallocを許可する保護があるためfastbinは使われず、unsorted bin attackを用いてグローバル変数 `global_max_fast` を上書きす
- 続けて、インデックス2use after freeポインタでedit関数を呼び、`bk`ポインタを `p64(global_max_fast-0x10)` を指すように上書きできる。そうして新しいチャンクを作ると、以前に改竄されたfreeアドレス(0x20) が使われ、unsorted bin attack が発動して `global_max_fast` を上書きする。これにより `global_max_fast` が非常に大きな値になり、fast bin のチャンクを作成できるようになる
- ここから **fast bin attack** が行われる:
- まず、`__free_hook` の場所でサイズ200のfastチャンクを扱えることが発見される:
- <pre class="language-c"><code class="lang-c">gef➤ p &__free_hook
$1 = (void (**)(void *, const void *)) 0x7ff1e9e607a8 <__free_hook>
gef➤ x/60gx 0x7ff1e9e607a8 - 0x59
@ -59,16 +115,20 @@ gef➤ x/60gx 0x7ff1e9e607a8 - 0x59
0x7ff1e9e6076f <list_all_lock+15>: 0x0000000000000000 0x0000000000000000
0x7ff1e9e6077f <_IO_stdfile_2_lock+15>: 0x0000000000000000 0x0000000000000000
</code></pre>
- この場所でサイズ0x200のファストチャンクを取得できれば、実行される関数ポインタを上書きすることが可能になります
- そのために、サイズ `0xfc` の新しいチャンクを作成し、そのポインタでマージ関数を2回呼び出すことで、サイズ `0xfc*2 = 0x1f8` の解放されたチャンクへのポインタを取得します
- 次に、このチャンクの編集関数を呼び出して、このファストビンの**`fd`** アドレスを前の**`__free_hook`** 関数を指すように変更します
- 次に、サイズ `0x1f8` のチャンクを作成して、ファストビンから以前の無駄なチャンクを取得し、別のサイズ `0x1f8` のチャンクを作成して**`__free_hook`** でファストビンチャンクを取得し、**`system`** 関数のアドレスで上書きします
- 最後に、文字列 `/bin/sh\x00` を含むチャンクを解放し、削除関数を呼び出して、**`__free_hook`** 関数をトリガーし、`system``/bin/sh\x00` をパラメータとして指すようにします
- この場所にサイズ0x200のfastチャンクを配置できれば、実行される関数ポインタを上書きすることが可能になる
- そのために、サイズ `0xfc` の新しいチャンクを作成し、そのポインタでmerge関数を2回呼ぶことで、fast binにサイズ `0xfc*2 = 0x1f8` の解放済みチャンクへのポインタを得る
- そのチャンクでedit関数を呼んで、このfast binの **`fd`** を前の **`__free_hook`** を指すように変更する
- 続いてサイズ `0x1f8` のチャンクを作ってfast binから前の無用なチャンクを取り出し、さらにもう一つサイズ `0x1f8` のチャンクを作ることで `__free_hook` の位置にあるfast binチャンクを取得し、これを **`system`** のアドレスで上書きする
- 最後に `/bin/sh\x00` を含むチャンクをdelete関数でfreeすると、`__free_hook` が system を指しているため `/bin/sh\x00` を引数に system が実行される
- **CTF** [**https://guyinatuxedo.github.io/33-custom_misc_heap/csaw19_traveller/index.html**](https://guyinatuxedo.github.io/33-custom_misc_heap/csaw19_traveller/index.html)
- 1Bオーバーフローを悪用して未整理ビン内のチャンクを統合し、libc情報漏洩を取得し、その後ファストビン攻撃を実行してmallocフックをワンガジェットアドレスで上書きする別の例。
- 1バイトオーバーフローを悪用してチャンクをunsorted binで統合しlibcのinfoleakを得た後、fast bin attackでmalloc hookをone gadgetアドレスで上書きする別の例。
- [**Robot Factory. BlackHat MEA CTF 2022**](https://7rocky.github.io/en/ctf/other/blackhat-ctf/robot-factory/)
- サイズが `0x100` より大きいチャンクのみを割り当てることができます
- 未整理ビン攻撃を使用して `global_max_fast` を上書きしますASLRのために1/16回機能します。12ビットを変更する必要がありますが、16ビットを変更する必要があります)。
- グローバルチャンク配列を変更するためのファストビン攻撃。これにより、任意の読み取り/書き込みプリミティブが得られ、GOTを変更し、いくつかの関数を `system` を指すように設定できます
- チャンクは `0x100` より大きいサイズしか割り当てられない
- Unsorted Bin attack を使って `global_max_fast` を上書きするASLR のため成功確率は1/16、12ビットを変更する必要があるが16ビットを変更しなければならないため)。
- Fast Bin attack によりグローバルなチャンク配列を改変する。これにより任意の read/write プリミティブを得られ、GOT を改変してある関数を `system` を指すようにできる
## 参考
- Glibc malloc unsorted-bin integrity checks2.33 のソースの例): https://elixir.bootlin.com/glibc/glibc-2.33/source/malloc/malloc.c
- `global_max_fast` と関連定義modern glibc 2.39: https://elixir.bootlin.com/glibc/glibc-2.39/source/malloc/malloc.c
{{#include ../../banners/hacktricks-training.md}}