Translated ['src/binary-exploitation/libc-heap/use-after-free/first-fit.

This commit is contained in:
Translator 2025-07-30 12:29:24 +00:00
parent 6cba4fe29f
commit d6a5cea54b

View File

@ -12,10 +12,10 @@ Kiedy zwalniasz kawałek pamięci, który nie jest szybkim kawałkiem, trafia on
Przykład:
- Alokujesz 300 bajtów (`a`), następnie 250 bajtów (`b`), zwalniasz `a` i ponownie żądasz 250 bajtów (`c`).
- Alokujesz 300 bajtów (`a`), potem 250 bajtów (`b`), następnie zwalniasz `a` i ponownie żądasz 250 bajtów (`c`).
- Kiedy zwalniasz `a`, trafia on do kosza niesortowanego.
- Jeśli następnie ponownie zażądzasz 250 bajtów, alokator znajduje `a` na ogonie i dzieli go, zwracając część, która pasuje do twojego żądania, a resztę pozostawiając w koszu.
- `c` będzie wskazywać na poprzednie `a` i będzie wypełnione danymi z `a`.
- `c` będzie wskazywać na poprzednie `a` i będzie wypełnione zawartością `a`.
```c
char *a = malloc(300);
char *b = malloc(250);
@ -26,11 +26,7 @@ char *c = malloc(250);
Fastbins są używane do małych kawałków pamięci. W przeciwieństwie do nieposortowanych binów, fastbins dodają nowe kawałki na początek, tworząc zachowanie last-in-first-out (LIFO). Jeśli poprosisz o mały kawałek pamięci, alokator pobierze z głowy fastbina.
Przykład:
- Alokujesz cztery kawałki po 20 bajtów każdy (`a`, `b`, `c`, `d`).
- Kiedy je zwolnisz w dowolnej kolejności, zwolnione kawałki są dodawane do głowy fastbina.
- Jeśli następnie poprosisz o kawałek 20-bajtowy, alokator zwróci najnowszy zwolniony kawałek z głowy fastbina.
Example:
```c
char *a = malloc(20);
char *b = malloc(20);
@ -45,21 +41,89 @@ b = malloc(20); // c
c = malloc(20); // b
d = malloc(20); // a
```
---
### 🔥 Nowoczesne rozważania dotyczące glibc (tcache ≥ 2.26)
Od glibc 2.26 każdy wątek przechowuje własny **tcache**, który jest sprawdzany *przed* nieposortowanym koszem. Dlatego scenariusz first-fit **zostanie osiągnięty tylko jeśli**:
1. Żądany rozmiar jest **większy niż `tcache_max`** (domyślnie 0x420 na 64-bit), *lub*
2. Odpowiedni kosz tcache jest **już pełny lub opróżniony ręcznie** (poprzez alokację 7 elementów i utrzymanie ich w użyciu).
W rzeczywistych exploitach zazwyczaj dodasz pomocniczą rutynę, taką jak:
```c
// Drain the tcache for a given size
for(int i = 0; i < 7; i++) pool[i] = malloc(0x100);
for(int i = 0; i < 7; i++) free(pool[i]);
```
Gdy tcache jest wyczerpane, kolejne zwolnienia trafiają do nieposortowanego kosza, a klasyczne zachowanie first-fit (przeszukiwanie od końca, wstawianie na początku) może być ponownie wywołane.
---
### 🚩 Tworzenie UAF z nakładającymi się kawałkami za pomocą first-fit
Fragment poniżej (testowany na glibc 2.38) pokazuje, jak splitter w nieposortowanym koszu może być nadużyty do stworzenia 2 **nakładających się wskaźników** potężnego prymitywu, który przekształca pojedyncze zwolnienie w zapis po zwolnieniu.
```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(){
setbuf(stdout, NULL);
/* 1. prepare 2 adjacent chunks and free the first one */
char *A = malloc(0x420); // big enough to bypass tcache
char *B = malloc(0x420);
strcpy(A, "AAAA\n");
free(A); // A → unsorted
/* 2. request a *smaller* size to force a split of A */
char *C = malloc(0x400); // returns lower half of former A
/* 3. The remainder of A is still in the unsorted bin.
Another 0x400-byte malloc will now return the *same*
region pointed to by B creating a UAF/overlap. */
char *C2 = malloc(0x400);
printf("B = %p\nC2 = %p (overlaps B)\n", B, C2);
// Arbitrary write in B is immediately visible via C2
memset(B, 'X', 0x10);
fwrite(C2, 1, 0x10, stdout); // prints Xs
}
```
Exploitation recipe (common in recent CTFs):
1. **Opróżnij** tcache dla docelowego rozmiaru.
2. **Zwolnij** kawałek, aby trafił do nieposortowanej skrzynki.
3. **Przydziel** nieco mniejszy rozmiar alokator dzieli nieposortowany kawałek.
4. **Przydziel** ponownie pozostała część nakłada się na istniejący używany kawałek → UAF.
5. Nadpisz wrażliwe pola (wskaźniki funkcji, vtable pliku itp.)
Praktyczne zastosowanie można znaleźć w wyzwaniu *Setjmp* z 2024 HITCON Quals, gdzie ten dokładny prymityw jest używany do przejścia z UAF do pełnej kontroli nad `__free_hook`.{{#ref}}
../../../../references/2024_setjmp_firstfit.md
{{#endref}}
---
### 🛡️ Mitigacje i wzmocnienia
* **Bezpieczne linkowanie (glibc ≥ 2.32)** chroni tylko pojedynczo połączone listy *tcache*/**fastbin**. Nieposortowane/małe/duże skrzynki nadal przechowują surowe wskaźniki, więc nakładki oparte na first-fit pozostają wykonalne, jeśli możesz uzyskać wyciek z heap.
* **Szyfrowanie wskaźników heap i MTE** (ARM64) nie wpływają jeszcze na x86-64 glibc, ale flagi wzmocnienia dystrybucji, takie jak `GLIBC_TUNABLES=glibc.malloc.check=3`, przerwą działanie przy niespójnych metadanych i mogą złamać naiwne PoC.
* **Wypełnianie tcache przy zwolnieniu** (proponowane w 2024 dla glibc 2.41) dodatkowo zmniejszyłoby użycie nieposortowanych; monitoruj przyszłe wydania podczas opracowywania ogólnych exploitów.
---
## Inne odniesienia i przykłady
- [**https://heap-exploitation.dhavalkapil.com/attacks/first_fit**](https://heap-exploitation.dhavalkapil.com/attacks/first_fit)
- [**https://8ksec.io/arm64-reversing-and-exploitation-part-2-use-after-free/**](https://8ksec.io/arm64-reversing-and-exploitation-part-2-use-after-free/)
- ARM64. Użycie po zwolnieniu: Wygeneruj obiekt użytkownika, zwolnij go, wygeneruj obiekt, który uzyskuje zwolniony kawałek i pozwól na zapis do niego, **nadpisując pozycję user->password** z poprzedniego. Ponownie użyj użytkownika, aby **obejść sprawdzanie hasła**
- ARM64. Użycie po zwolnieniu: Wygeneruj obiekt użytkownika, zwolnij go, wygeneruj obiekt, który uzyskuje zwolniony kawałek i pozwól na zapis do niego, **nadpisując pozycję user->password** z poprzedniego. Ponownie użyj użytkownika, aby **obejść sprawdzanie hasła**.
- [**https://ctf-wiki.mahaloz.re/pwn/linux/glibc-heap/use_after_free/#example**](https://ctf-wiki.mahaloz.re/pwn/linux/glibc-heap/use_after_free/#example)
- Program pozwala na tworzenie notatek. Notatka będzie miała informacje o notatce w malloc(8) (z wskaźnikiem do funkcji, która mogłaby być wywołana) oraz wskaźnik do innego malloc(\<size>) z treścią notatki.
- Atak polegałby na stworzeniu 2 notatek (note0 i note1) z większą zawartością malloc niż rozmiar informacji o notatce, a następnie ich zwolnieniu, aby trafiły do szybkiego koszyka (lub tcache).
- Następnie stwórz inną notatkę (note2) o rozmiarze treści 8. Zawartość będzie w note1, ponieważ kawałek będzie ponownie użyty, gdzie moglibyśmy zmodyfikować wskaźnik funkcji, aby wskazywał na funkcję wygranej, a następnie użyć Use-After-Free note1, aby wywołać nowy wskaźnik funkcji.
- Program pozwala na tworzenie notatek. Notatka będzie miała informacje o notatce w malloc(8) (z wskaźnikiem do funkcji, która mogłaby być wywołana) oraz wskaźnik do innego malloc(<size>) z treścią notatki.
- Atak polegałby na stworzeniu 2 notatek (note0 i note1) z większą zawartością malloc niż rozmiar informacji o notatce, a następnie ich zwolnieniu, aby trafiły do szybkiej skrzynki (lub tcache).
- Następnie stwórz inną notatkę (note2) o rozmiarze zawartości 8. Zawartość będzie w note1, ponieważ kawałek będzie ponownie użyty, gdzie moglibyśmy zmodyfikować wskaźnik funkcji, aby wskazywał na funkcję wygranej, a następnie użyć UAF notatki note1, aby wywołać nowy wskaźnik funkcji.
- [**https://guyinatuxedo.github.io/26-heap_grooming/pico_areyouroot/index.html**](https://guyinatuxedo.github.io/26-heap_grooming/pico_areyouroot/index.html)
- Możliwe jest przydzielenie pamięci, zapisanie pożądanej wartości, zwolnienie jej, ponowne przydzielenie i ponieważ poprzednie dane wciąż tam są, będą traktowane zgodnie z nową oczekiwaną strukturą w kawałku, co umożliwia ustawienie wartości lub uzyskanie flagi.
- Możliwe jest przydzielenie pamięci, zapisanie pożądanej wartości, zwolnienie jej, ponowne przydzielenie, a ponieważ poprzednie dane wciąż tam są, będą traktowane zgodnie z nową oczekiwaną strukturą w kawałku, co umożliwia ustawienie wartości, aby uzyskać flagę.
- [**https://guyinatuxedo.github.io/26-heap_grooming/swamp19_heapgolf/index.html**](https://guyinatuxedo.github.io/26-heap_grooming/swamp19_heapgolf/index.html)
- W tym przypadku konieczne jest zapisanie 4 wewnątrz konkretnego kawałka, który jest pierwszym przydzielonym (nawet po wymuszeniu zwolnienia wszystkich). Przy każdym nowym przydzielonym kawałku jego numer w indeksie tablicy jest przechowywany. Następnie przydziel 4 kawałki (+ początkowo przydzielony), ostatni będzie miał 4 wewnątrz, zwolnij je i wymuś ponowne przydzielenie pierwszego, które użyje ostatniego zwolnionego kawałka, który ma 4 wewnątrz.
- W tym przypadku trzeba zapisać 4 wewnątrz konkretnego kawałka, który jest pierwszym przydzielonym (nawet po wymuszeniu zwolnienia wszystkich). Przy każdym nowym przydzielonym kawałku jego numer w indeksie tablicy jest przechowywany. Następnie przydziel 4 kawałki (+ początkowo przydzielony), ostatni będzie miał 4 wewnątrz, zwolnij je i wymuś ponowne przydzielenie pierwszego, które użyje ostatniego zwolnionego kawałka, który ma 4 wewnątrz.
- 2024 HITCON Quals Setjmp write-up (Quarkslab) praktyczny atak first-fit / unsorted-split overlap: <https://ctftime.org/writeup/39355>
- Angstrom CTF 2024 *heapify* write-up nadużywanie dzielenia nieposortowanej skrzynki, aby wyciekować libc i uzyskać nakładkę: <https://hackmd.io/@aneii11/H1S2snV40>
{{#include ../../../banners/hacktricks-training.md}}