Unsorted Bin Attack

Reading time: 11 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

Podstawowe informacje

Aby uzyskać więcej informacji o tym, czym jest unsorted bin sprawdź tę stronę:

Bins & Memory Allocations

Unsorted lists mogą zapisać adres unsorted_chunks (av) w polu bk chunku. Dlatego, jeśli atakujący może zmodyfikować adres wskaźnika bk w chunku znajdującym się w unsorted bin, może być w stanie zapisać ten adres pod dowolnym adresem, co może pomóc w leak adresów Glibc lub obejściu niektórych zabezpieczeń.

Zasadniczo ten atak pozwala ustawić dużą liczbę pod dowolnym adresem. Ta duża liczba to adres, który może być adresem na heapie lub adresem w Glibc. Tradycyjnym celem było global_max_fast, aby umożliwić tworzenie fast binów o większych rozmiarach (i przejście z unsorted bin attack do fast bin attack).

  • Modern note (glibc ≥ 2.39): global_max_fast stał się globalem 8‑bitowym. Ślepe zapisanie wskaźnika tam przez unsorted‑bin write nadpisze sąsiednie dane libc i nie podniesie już niezawodnie limitu fastbin. Preferuj inne cele lub inne prymitywy przy pracy przeciwko glibc 2.39+. Zobacz "Modern constraints" poniżej i rozważ łączenie z innymi technikami, takimi jak large bin attack lub fast bin attack, gdy masz stabilny prymityw.

tip

Spoglądając na przykład podany w https://ctf-wiki.mahaloz.re/pwn/linux/glibc-heap/unsorted_bin_attack/#principle i używając 0x4000 oraz 0x5000 zamiast 0x400 i 0x500 jako rozmiarów chunków (aby uniknąć Tcache) można zobaczyć, że współcześnie pojawia się błąd malloc(): unsorted double linked list corrupted.

W związku z tym ten unsorted bin attack teraz (między innymi sprawdzeniami) wymaga również możliwości naprawienia listy dwukierunkowej, tak aby sprawdzenia victim->bk->fd == victim oraz victim->fd == av (arena) nie powodowały abortu; oznacza to, że adres, pod który chcemy zapisać, musi zawierać adres fałszywego chunku na pozycji fd, a pole fd fałszywego chunku musi wskazywać na arena.

[!CAUTION] Zauważ, że ten atak korumpuje unsorted bin (a zatem również small i large). Dlatego teraz możemy korzystać tylko z alokacji z fast binów (bardziej złożony program może wykonać inne alokacje i się zawiesić), i aby to wywołać musimy alokować tego samego rozmiaru, inaczej program się zawiesi.

Zastanów się nad nadpisaniem global_max_fast, co w tym przypadku może pomóc z założeniem, że fast bin poradzi sobie ze wszystkimi innymi alokacjami do czasu zakończenia exploitu.

Kod od guyinatuxedo bardzo dobrze to wyjaśnia, chociaż jeśli zmodyfikujesz mallocy tak, aby alokować pamięć wystarczająco dużą, by nie trafiać do Tcache, możesz zobaczyć wcześniej wspomniany błąd uniemożliwiający tę technikę: malloc(): unsorted double linked list corrupted

How the write actually happens

  • 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.

Modern constraints (glibc ≥ 2.33)

Aby używać unsorted‑bin writes niezawodnie na aktualnym glibc:

  • Tcache interference: dla rozmiarów trafiających do tcache, frees są przekierowywane tam i nie dotykają unsorted bin. Możesz:
    • robić żądania o rozmiarach > MAX_TCACHE_SIZE (≥ 0x410 na 64‑bit domyślnie), lub
    • zapełnić odpowiadającą tcache binę (7 wpisów), tak aby kolejne free trafiały do globalnych binów, lub
    • jeśli środowisko jest kontrolowalne, wyłączyć tcache (np. GLIBC_TUNABLES glibc.malloc.tcache_count=0).
  • Integrity checks on the unsorted list: na ścieżce alokacji, która bada unsorted bin, glibc sprawdza (uproszczone):
    • bck->fd == victim oraz victim->fd == unsorted_chunks(av); w przeciwnym wypadku abortuje z malloc(): unsorted double linked list corrupted.
  • To oznacza, że adres, który atakujesz, musi tolerować dwa zapisy: najpierw *(TARGET) = victim w czasie free; później, gdy chunk jest usuwany, *(TARGET) = unsorted_chunks(av) (allocator nadpisuje bck->fd z powrotem adresem nagłówka binka). Wybierz cele, gdzie wymuszenie dużej, nie‑zerowej wartości ma sens.
  • Typowe stabilne cele we współczesnych exploitach:
    • stan aplikacji lub globalny, który traktuje "duże" wartości jako flagi/limity,
    • prymitywy pośrednie (np. przygotowanie do późniejszego [fast bin attack](Fast Bin Attack) lub do pivotu późniejszego write‑what‑where).
  • Unikaj __malloc_hook/__free_hook w nowych glibc: zostały usunięte w 2.34. Unikaj global_max_fast na ≥ 2.39 (zobacz wcześniejszą uwagę).

Minimal exploitation recipe (modern glibc)

Cel: osiągnąć pojedynczy arbitralny zapis wskaźnika heapowego pod dowolny adres przy użyciu prymitywu wstawiania do unsorted‑bin, bez crasha.

  • Layout/grooming
    • Alokuj A, B, C o rozmiarach wystarczająco dużych, by ominąć tcache (np. 0x5000). C zapobiega konsolidacji z top chunk.
  • Corruption
    • Overflow z A do nagłówka chunku B, aby ustawić B->bk = (mchunkptr)(TARGET - 0x10).
  • Trigger
    • free(B). W czasie wstawiania allocator wykonuje bck->fd = B, więc *(TARGET) = B.
  • Continuation
    • Jeśli planujesz dalsze alokacje i program używa unsorted bin, spodziewaj się, że allocator później ustawi *(TARGET) = unsorted_chunks(av). Obie wartości są zwykle duże i mogą wystarczyć do zmiany semantyki rozmiaru/limitu w celach, które tylko sprawdzają "duże".

Pseudocode skeleton:

c
// 64-bit glibc 2.35–2.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 B’s 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

• Jeśli nie możesz obejść tcache przez rozmiar, zapełnij tcache bin dla wybranego rozmiaru (7 frees) przed zwolnieniem sfałszowanego chunku, tak aby free trafił do unsorted. • Jeśli program natychmiast przerywa wykonanie przy następnej alokacji z powodu unsorted-bin checks, ponownie sprawdź, że victim->fd wciąż równa się bin head i że Twój TARGET zawiera dokładny wskaźnik victim po pierwszym zapisie.

Unsorted Bin Infoleak Attack

To w gruncie rzeczy bardzo podstawowa koncepcja. Chunki w unsorted bin zawierają wskaźniki. Pierwszy chunk w unsorted bin będzie miał faktycznie linki fd i bk wskazujące na część main arena (Glibc).
W związku z tym, jeśli potrafisz umieścić chunk w unsorted bin i odczytać go (use after free) lub zaalokować go ponownie bez nadpisywania przynajmniej jednego ze wskaźników, aby potem go odczytać, możesz uzyskać Glibc info leak.

Podobny attack used in this writeup polegał na nadużyciu struktury z 4 chunkami (A, B, C i D — D służy tylko do zapobieżenia konsolidacji z top chunk), więc null byte overflow w B został użyty, aby C wskazywało, że B jest nieużywane. Również w B dane prev_size zostały zmodyfikowane tak, że rozmiar zamiast być rozmiarem B był A+B.
Następnie C zostało zwolnione i skonsolidowane z A+B (ale B nadal było w użyciu). Nowy chunk o rozmiarze A został zaalokowany, a następnie libc leaked addresses zostały zapisane do B, skąd zostały wycieknięte.

Odnośniki i inne przykłady

  • https://ctf-wiki.mahaloz.re/pwn/linux/glibc-heap/unsorted_bin_attack/#hitcon-training-lab14-magic-heap
  • Celem jest nadpisanie globalnej zmiennej wartością większą niż 4869, aby możliwe było zdobycie flagi i PIE nie jest włączone.
  • Możliwe jest wygenerowanie chunków o dowolnych rozmiarach i istnieje heap overflow o wymaganym rozmiarze.
  • Atak zaczyna się od stworzenia 3 chunków: chunk0 do wykorzystania overflowu, chunk1 który ma zostać przepełniony oraz chunk2, żeby top chunk nie skonsolidował poprzednich.
  • Następnie chunk1 jest zwalniany, a chunk0 jest przepełniany tak, że wskaźnik bk chunk1 wskazuje na: bk = magic - 0x10
  • Potem chunk3 jest zaalokowany o tym samym rozmiarze co chunk1, co wywoła unsorted bin attack i zmodyfikuje wartość globalnej zmiennej, umożliwiając zdobycie flagi.
  • https://guyinatuxedo.github.io/31-unsortedbin_attack/0ctf16_zerostorage/index.html
  • Funkcja merge jest podatna, ponieważ jeśli oba przekazane indeksy są identyczne, wykona realloc na nim, a potem free, ale zwróci wskaźnik do tej zwolnionej pamięci, który można potem wykorzystać.
  • W związku z tym tworzone są 2 chunki: chunk0, który zostanie scalony sam ze sobą, oraz chunk1, aby zapobiec konsolidacji z top chunk. Następnie merge jest wywołane z chunk0 dwukrotnie, co spowoduje use after free.
  • Potem funkcja view jest wywoływana z indeksem 2 (który jest indeksem chunku z use after free), co spowoduje leak a libc address.
  • Ponieważ binarka ma zabezpieczenie żeby mallocować tylko rozmiary większe niż global_max_fast (więc fastbin nie jest używany), zostanie użyty unsorted bin attack do nadpisania globalnej zmiennej global_max_fast.
  • Następnie możliwe jest wywołanie funkcji edit z indeksem 2 (wskaźnik use after free) i nadpisanie wskaźnika bk, tak by wskazywał na p64(global_max_fast-0x10). Potem utworzenie nowego chunku wykorzysta wcześniej skompromitowany address z free (0x20) i wywoła unsorted bin attack, nadpisując global_max_fast na bardzo dużą wartość, co pozwoli teraz tworzyć chunki w fast bins.
  • Teraz przeprowadzany jest fast bin attack:
  • Po pierwsze odkryto, że możliwe jest operowanie na fast 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 się umieścić fast chunk o rozmiarze 0x200 w tej lokalizacji, będzie możliwe nadpisanie wskaźnika do funkcji, która zostanie wykonana.
  • W tym celu tworzy się nowy chunk o rozmiarze 0xfc i merge jest wywoływane z tym wskaźnikiem dwukrotnie; w ten sposób otrzymujemy wskaźnik do zwolnionego chunku o rozmiarze 0xfc*2 = 0x1f8 w fast bin.
  • Następnie funkcja edit jest wywoływana na tym chunku, by zmodyfikować adres fd tego fast bin, tak aby wskazywał na wcześniej wspomniany __free_hook.
  • Potem tworzy się chunk o rozmiarze 0x1f8, aby pobrać z fast bin poprzedni bezużyteczny chunk; kolejny chunk o rozmiarze 0x1f8 zostaje utworzony, aby uzyskać fast bin chunk w lokalizacji __free_hook, która zostaje nadpisana adresem funkcji system.
  • I na koniec chunk zawierający string /bin/sh\x00 jest zwalniany przez wywołanie funkcji delete, co wywołuje funkcję __free_hook, która wskazuje na system z /bin/sh\x00 jako parametrem.
  • CTF https://guyinatuxedo.github.io/33-custom_misc_heap/csaw19_traveller/index.html
  • Inny przykład nadużycia 1B overflow do skonsolidowania chunków w unsorted bin i uzyskania libc infoleak, a następnie przeprowadzenia fast bin attack w celu nadpisania malloc hook adresem one gadget
  • Robot Factory. BlackHat MEA CTF 2022
  • Możemy alokować tylko chunki o rozmiarze większym niż 0x100.
  • Nadpisz global_max_fast przy użyciu Unsorted Bin attack (działa 1/16 razy z powodu ASLR, ponieważ trzeba zmodyfikować 12 bitów, ale musimy zmodyfikować 16 bitów).
  • Fast Bin attack, aby zmodyfikować globalną tablicę chunków. To daje arbitralny read/write primitive, który pozwala modyfikować GOT i ustawić jakąś funkcję, aby wskazywała na system.

Referencje

  • Glibc malloc unsorted-bin integrity checks (example in 2.33 source): https://elixir.bootlin.com/glibc/glibc-2.33/source/malloc/malloc.c
  • global_max_fast and related definitions in modern glibc (2.39): https://elixir.bootlin.com/glibc/glibc-2.39/source/malloc/malloc.c

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