First Fit

Wesprzyj HackTricks

First Fit

Kiedy zwalniasz pamięć w programie korzystającym z glibc, różne "kosze" są używane do zarządzania kawałkami pamięci. Oto uproszczone wyjaśnienie dwóch częstych scenariuszy: kosze nieuporządkowane i szybkie kosze.

Kosze Nieuporządkowane

Kiedy zwalniasz kawałek pamięci, który nie jest szybkim kawałkiem, trafia on do kosza nieuporządkowanego. Ten kosz działa jak lista, do której nowe zwolnione kawałki są dodawane z przodu („głowa”). Gdy żądasz nowego kawałka pamięci, alokator patrzy na kosz nieuporządkowany od tyłu („ogon”), aby znaleźć kawałek wystarczająco duży. Jeśli kawałek z kosza nieuporządkowanego jest większy niż to, czego potrzebujesz, zostaje on podzielony, zwracając część pasującą do twojego żądania i pozostawiając resztę w koszu.

Przykład:

  • Alokujesz 300 bajtów (a), następnie 250 bajtów (b), zwolnisz a i ponownie zażądasz 250 bajtów (c).

  • Gdy zwalniasz a, trafia on do kosza nieuporządkowanego.

  • Jeśli następnie ponownie zażądasz 250 bajtów, alokator znajduje a na końcu i dzieli go, zwracając część pasującą do twojego żądania i zachowując resztę w koszu.

  • c będzie wskazywać na poprzednie a i wypełnione będzie zawartością a.

char *a = malloc(300);
char *b = malloc(250);
free(a);
char *c = malloc(250);

Fastbins

Fastbins są używane do małych kawałków pamięci. W przeciwieństwie do kubełków nieuporządkowanych, fastbins dodają nowe kawałki na początek, tworząc zachowanie Last-In-First-Out (LIFO). Jeśli żądasz małego kawałka pamięci, alokator pobierze go z głowy fastbin.

Przykład:

  • Alokujesz cztery kawałki po 20 bajtów każdy (a, b, c, d).

  • Gdy je zwolnisz w dowolnej kolejności, zwolnione kawałki są dodawane na początek fastbin.

  • Jeśli następnie poprosisz o kawałek 20 bajtów, alokator zwróci najbardziej niedawno zwolniony kawałek z głowy fastbin.

char *a = malloc(20);
char *b = malloc(20);
char *c = malloc(20);
char *d = malloc(20);
free(a);
free(b);
free(c);
free(d);
a = malloc(20);   // d
b = malloc(20);   // c
c = malloc(20);   // b
d = malloc(20);   // a

Inne odnośniki i przykłady

  • ARM64. Użycie po zwolnieniu: Generowanie obiektu użytkownika, zwolnienie go, generowanie obiektu, który otrzymuje zwolniony fragment i pozwala na zapis do niego, nadpisanie pozycji user->password z poprzedniego. Ponowne użycie użytkownika do obejścia sprawdzania hasła

  • Program pozwala tworzyć notatki. Notatka będzie miała informacje o notatce w malloc(8) (z wskaźnikiem do funkcji, która może być wywołana) oraz wskaźnik do innego malloc(<size>) z treścią notatki.

  • Atak polega na stworzeniu 2 notatek (note0 i note1) z większymi treściami malloc niż rozmiar informacji o notatce, a następnie zwolnieniu ich, aby trafiły do fast bin (lub tcache).

  • Następnie utwórz inną notatkę (note2) o rozmiarze treści 8. Treść znajdzie się w note1, ponieważ fragment zostanie ponownie użyty, gdzie można zmodyfikować wskaźnik funkcji, aby wskazywał na funkcję win, a następnie Użycie po Zwolnieniu note1, aby wywołać nowy wskaźnik funkcji.

  • Jest możliwe zarezerwowanie pewnej pamięci, zapisanie pożądanej wartości, zwolnienie jej, ponowne zarezerwowanie i ponieważ poprzednie dane są wciąż dostępne, będą traktowane zgodnie z nową oczekiwaną strukturą w fragmencie, co umożliwia ustawienie wartości lub uzyskanie flagi.

  • W tym przypadku konieczne jest wpisanie 4 w określonym fragmencie, który jest pierwszy przydzielony (nawet po wymuszeniu zwolnienia wszystkich). Przy każdym nowo przydzielonym fragmencie przechowywany jest jego numer indeksu w tablicy. Następnie przydziel 4 fragmenty (+ początkowo przydzielony), ostatni będzie zawierał w sobie 4, zwolnij je i wymuś ponowne przydzielenie pierwszego, który użyje ostatniego zwolnionego fragmentu, który zawiera 4 w sobie.

Last updated