Double Free

Support HackTricks

Podstawowe informacje

Jeśli zwolnisz blok pamięci więcej niż raz, może to zepsuć dane alokatora i otworzyć drzwi do ataków. Oto jak to się dzieje: gdy zwalniasz blok pamięci, wraca on do listy wolnych kawałków (np. "szybki bin"). Jeśli zwolnisz ten sam blok dwa razy z rzędu, alokator to wykrywa i zgłasza błąd. Ale jeśli zwolnisz inny kawałek pomiędzy, sprawdzenie podwójnego zwolnienia jest omijane, co powoduje uszkodzenie.

Teraz, gdy poprosisz o nową pamięć (używając malloc), alokator może dać ci blok, który został zwolniony dwa razy. Może to prowadzić do dwóch różnych wskaźników wskazujących na to samo miejsce w pamięci. Jeśli atakujący kontroluje jeden z tych wskaźników, może zmienić zawartość tej pamięci, co może powodować problemy z bezpieczeństwem lub nawet pozwolić im na wykonanie kodu.

Przykład:

#include <stdio.h>
#include <stdlib.h>

int main() {
// Allocate memory for three chunks
char *a = (char *)malloc(10);
char *b = (char *)malloc(10);
char *c = (char *)malloc(10);
char *d = (char *)malloc(10);
char *e = (char *)malloc(10);
char *f = (char *)malloc(10);
char *g = (char *)malloc(10);
char *h = (char *)malloc(10);
char *i = (char *)malloc(10);

// Print initial memory addresses
printf("Initial allocations:\n");
printf("a: %p\n", (void *)a);
printf("b: %p\n", (void *)b);
printf("c: %p\n", (void *)c);
printf("d: %p\n", (void *)d);
printf("e: %p\n", (void *)e);
printf("f: %p\n", (void *)f);
printf("g: %p\n", (void *)g);
printf("h: %p\n", (void *)h);
printf("i: %p\n", (void *)i);

// Fill tcache
free(a);
free(b);
free(c);
free(d);
free(e);
free(f);
free(g);

// Introduce double-free vulnerability in fast bin
free(h);
free(i);
free(h);


// Reallocate memory and print the addresses
char *a1 = (char *)malloc(10);
char *b1 = (char *)malloc(10);
char *c1 = (char *)malloc(10);
char *d1 = (char *)malloc(10);
char *e1 = (char *)malloc(10);
char *f1 = (char *)malloc(10);
char *g1 = (char *)malloc(10);
char *h1 = (char *)malloc(10);
char *i1 = (char *)malloc(10);
char *i2 = (char *)malloc(10);

// Print initial memory addresses
printf("After reallocations:\n");
printf("a1: %p\n", (void *)a1);
printf("b1: %p\n", (void *)b1);
printf("c1: %p\n", (void *)c1);
printf("d1: %p\n", (void *)d1);
printf("e1: %p\n", (void *)e1);
printf("f1: %p\n", (void *)f1);
printf("g1: %p\n", (void *)g1);
printf("h1: %p\n", (void *)h1);
printf("i1: %p\n", (void *)i1);
printf("i2: %p\n", (void *)i2);

return 0;
}

W tym przykładzie, po wypełnieniu tcache kilkoma zwolnionymi kawałkami (7), kod zwalnia kawałek h, następnie kawałek i, a potem h ponownie, co powoduje podwójne zwolnienie (znane również jako Fast Bin dup). Otwiera to możliwość uzyskania nakładających się adresów pamięci podczas ponownej alokacji, co oznacza, że dwa lub więcej wskaźników może wskazywać na tę samą lokalizację pamięci. Manipulowanie danymi przez jeden wskaźnik może następnie wpłynąć na drugi, co stwarza krytyczne ryzyko bezpieczeństwa i potencjał do wykorzystania.

Wykonując to, zauważ, jak i1 i i2 uzyskały ten sam adres:

Initial allocations:
a: 0xaaab0f0c22a0
b: 0xaaab0f0c22c0
c: 0xaaab0f0c22e0
d: 0xaaab0f0c2300
e: 0xaaab0f0c2320
f: 0xaaab0f0c2340
g: 0xaaab0f0c2360
h: 0xaaab0f0c2380
i: 0xaaab0f0c23a0
After reallocations:
a1: 0xaaab0f0c2360
b1: 0xaaab0f0c2340
c1: 0xaaab0f0c2320
d1: 0xaaab0f0c2300
e1: 0xaaab0f0c22e0
f1: 0xaaab0f0c22c0
g1: 0xaaab0f0c22a0
h1: 0xaaab0f0c2380
i1: 0xaaab0f0c23a0
i2: 0xaaab0f0c23a0

Przykłady

  • Możemy alokować tylko kawałki o rozmiarze Fast-Bin, z wyjątkiem rozmiaru 0x70, co uniemożliwia zwykłe nadpisanie __malloc_hook.

  • Zamiast tego używamy adresów PIE, które zaczynają się od 0x56 jako celu dla Fast Bin dup (1/2 szansy).

  • Jednym z miejsc, w których przechowywane są adresy PIE, jest main_arena, który znajduje się w Glibc i blisko __malloc_hook.

  • Celujemy w konkretny offset main_arena, aby alokować kawałek tam i kontynuować alokację kawałków, aż dotrzemy do __malloc_hook, aby uzyskać wykonanie kodu.

  • Używając pojemników Tcache i przepełnienia null-byte, możemy osiągnąć sytuację podwójnego zwolnienia:

  • Alokujemy trzy kawałki o rozmiarze 0x110 (A, B, C)

  • Zwalniamy B

  • Zwalniamy A i alokujemy ponownie, aby wykorzystać przepełnienie null-byte

  • Teraz pole rozmiaru B wynosi 0x100, zamiast 0x111, więc możemy je zwolnić ponownie

  • Mamy jeden pojemnik Tcache o rozmiarze 0x110 i jeden o rozmiarze 0x100, które wskazują na ten sam adres. Mamy więc podwójne zwolnienie.

  • Wykorzystujemy podwójne zwolnienie, używając Tcache poisoning

Odniesienia

Wsparcie dla HackTricks

Last updated