Bins & Memory Allocations
Reading time: 21 minutes
tip
Učite i vežbajte AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Učite i vežbajte GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Podržite HackTricks
- Proverite planove pretplate!
- Pridružite se 💬 Discord grupi ili telegram grupi ili pratite nas na Twitteru 🐦 @hacktricks_live.
- Podelite hakerske trikove slanjem PR-ova na HackTricks i HackTricks Cloud github repozitorijume.
Osnovne Informacije
Da bi se poboljšala efikasnost načina na koji se delovi čuvaju, svaki deo nije samo u jednoj povezanoj listi, već postoji nekoliko tipova. To su binovi i postoji 5 tipova binova: 62 mali binovi, 63 veliki binovi, 1 nesortirani bin, 10 brzih binova i 64 tcache binova po niti.
Početna adresa za svaki nesortirani, mali i veliki bin je unutar istog niza. Indeks 0 se ne koristi, 1 je nesortirani bin, binovi 2-64 su mali binovi, a binovi 65-127 su veliki binovi.
Tcache (Per-Thread Cache) Bins
Iako niti pokušavaju da imaju svoj vlastiti heap (vidi Arenas i Subheaps), postoji mogućnost da proces sa puno niti (kao što je web server) će završiti deljenjem heapa sa drugim nitima. U ovom slučaju, glavno rešenje je korišćenje zaključavanja, što može značajno usporiti niti.
Stoga, tcache je sličan brzom binu po niti na način da je to jedna povezana lista koja ne spaja delove. Svaka nit ima 64 jednostruko povezane tcache binove. Svaki bin može imati maksimalno 7 delova iste veličine u rasponu od 24 do 1032B na 64-bitnim sistemima i 12 do 516B na 32-bitnim sistemima.
Kada nit oslobodi deo, ako nije prevelik da bi se alocirao u tcache i odgovarajući tcache bin nije pun (već 7 delova), biće alociran tamo. Ako ne može da ide u tcache, moraće da čeka na zaključavanje heapa da bi mogla da izvrši operaciju oslobađanja globalno.
Kada je deo alociran, ako postoji slobodan deo potrebne veličine u Tcache, koristiće ga, ako ne, moraće da čeka na zaključavanje heapa da bi mogla da pronađe jedan u globalnim binovima ili da kreira novi.
Takođe postoji optimizacija, u ovom slučaju, dok ima zaključavanje heapa, nit će napuniti svoj Tcache delovima heapa (7) tražene veličine, tako da u slučaju da mu zatreba više, naći će ih u Tcache.
Dodaj primer tcache dela
#include <stdlib.h>
#include <stdio.h>
int main(void)
{
char *chunk;
chunk = malloc(24);
printf("Address of the chunk: %p\n", (void *)chunk);
gets(chunk);
free(chunk);
return 0;
}
Kompajlirajte ga i debagujte sa tačkom prekida u ret opkodu iz main funkcije. Tada sa gef možete videti tcache bin u upotrebi:
gef➤ heap bins
──────────────────────────────────────────────────────────────────────────────── Tcachebins for thread 1 ────────────────────────────────────────────────────────────────────────────────
Tcachebins[idx=0, size=0x20, count=1] ← Chunk(addr=0xaaaaaaac12a0, size=0x20, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
Tcache strukture i funkcije
U sledećem kodu moguće je videti max bins i chunks per index, tcache_entry
strukturu kreiranu da izbegne duple oslobađanja i tcache_perthread_struct
, strukturu koju svaka nit koristi za čuvanje adresa za svaki indeks bin-a.
tcache_entry
i tcache_perthread_struct
// From https://github.com/bminor/glibc/blob/f942a732d37a96217ef828116ebe64a644db18d7/malloc/malloc.c
/* We want 64 entries. This is an arbitrary limit, which tunables can reduce. */
# define TCACHE_MAX_BINS 64
# define MAX_TCACHE_SIZE tidx2usize (TCACHE_MAX_BINS-1)
/* Only used to pre-fill the tunables. */
# define tidx2usize(idx) (((size_t) idx) * MALLOC_ALIGNMENT + MINSIZE - SIZE_SZ)
/* When "x" is from chunksize(). */
# define csize2tidx(x) (((x) - MINSIZE + MALLOC_ALIGNMENT - 1) / MALLOC_ALIGNMENT)
/* When "x" is a user-provided size. */
# define usize2tidx(x) csize2tidx (request2size (x))
/* With rounding and alignment, the bins are...
idx 0 bytes 0..24 (64-bit) or 0..12 (32-bit)
idx 1 bytes 25..40 or 13..20
idx 2 bytes 41..56 or 21..28
etc. */
/* This is another arbitrary limit, which tunables can change. Each
tcache bin will hold at most this number of chunks. */
# define TCACHE_FILL_COUNT 7
/* Maximum chunks in tcache bins for tunables. This value must fit the range
of tcache->counts[] entries, else they may overflow. */
# define MAX_TCACHE_COUNT UINT16_MAX
[...]
typedef struct tcache_entry
{
struct tcache_entry *next;
/* This field exists to detect double frees. */
uintptr_t key;
} tcache_entry;
/* There is one of these for each thread, which contains the
per-thread cache (hence "tcache_perthread_struct"). Keeping
overall size low is mildly important. Note that COUNTS and ENTRIES
are redundant (we could have just counted the linked list each
time), this is for performance reasons. */
typedef struct tcache_perthread_struct
{
uint16_t counts[TCACHE_MAX_BINS];
tcache_entry *entries[TCACHE_MAX_BINS];
} tcache_perthread_struct;
Funkcija __tcache_init
je funkcija koja kreira i alocira prostor za objekat tcache_perthread_struct
tcache_init kod
// From https://github.com/bminor/glibc/blob/f942a732d37a96217ef828116ebe64a644db18d7/malloc/malloc.c#L3241C1-L3274C2
static void
tcache_init(void)
{
mstate ar_ptr;
void *victim = 0;
const size_t bytes = sizeof (tcache_perthread_struct);
if (tcache_shutting_down)
return;
arena_get (ar_ptr, bytes);
victim = _int_malloc (ar_ptr, bytes);
if (!victim && ar_ptr != NULL)
{
ar_ptr = arena_get_retry (ar_ptr, bytes);
victim = _int_malloc (ar_ptr, bytes);
}
if (ar_ptr != NULL)
__libc_lock_unlock (ar_ptr->mutex);
/* In a low memory situation, we may not be able to allocate memory
- in which case, we just keep trying later. However, we
typically do this very early, so either there is sufficient
memory, or there isn't enough memory to do non-trivial
allocations anyway. */
if (victim)
{
tcache = (tcache_perthread_struct *) victim;
memset (tcache, 0, sizeof (tcache_perthread_struct));
}
}
Tcache indeksi
Tcache ima nekoliko binova u zavisnosti od veličine, a inicijalni pokazivači na prvi deo svakog indeksa i količina delova po indeksu nalaze se unutar dela. To znači da lociranje dela sa ovom informacijom (obično prvim) omogućava pronalaženje svih tcache inicijalnih tačaka i količine Tcache delova.
Brzi binovi
Brzi binovi su dizajnirani da ubrza alokaciju memorije za male delove čuvajući nedavno oslobođene delove u strukturi brzog pristupa. Ovi binovi koriste pristup Last-In, First-Out (LIFO), što znači da je najnovije oslobođeni deo prvi koji će se ponovo koristiti kada postoji nova zahtev za alokaciju. Ovo ponašanje je korisno za brzinu, jer je brže umetati i uklanjati sa vrha steka (LIFO) u poređenju sa redom (FIFO).
Pored toga, brzi binovi koriste jednostruko povezane liste, a ne dvostruko povezane, što dodatno poboljšava brzinu. Pošto se delovi u brzim binovima ne spajaju sa susedima, nema potrebe za složenom strukturom koja omogućava uklanjanje iz sredine. Jednostruko povezana lista je jednostavnija i brža za ove operacije.
U suštini, ono što se ovde dešava je da je zaglavlje (pokazivač na prvi deo koji treba proveriti) uvek usmereno na najnovije oslobođeni deo te veličine. Dakle:
- Kada se alocira novi deo te veličine, zaglavlje pokazuje na slobodan deo koji se može koristiti. Pošto ovaj slobodan deo pokazuje na sledeći koji se može koristiti, ova adresa se čuva u zaglavlju tako da sledeća alokacija zna gde da pronađe dostupni deo.
- Kada se deo oslobodi, slobodan deo će sačuvati adresu trenutnog dostupnog dela, a adresa ovog novog oslobođenog dela će biti stavljena u zaglavlje.
Maksimalna veličina povezane liste je 0x80
i organizovane su tako da će deo veličine 0x20
biti u indeksu 0
, deo veličine 0x30
biće u indeksu 1
...
oprez
Delovi u brzim binovima nisu postavljeni kao dostupni, tako da se čuvaju kao delovi brzih binova neko vreme umesto da se mogu spojiti sa drugim slobodnim delovima koji ih okružuju.
// From https://github.com/bminor/glibc/blob/a07e000e82cb71238259e674529c37c12dc7d423/malloc/malloc.c#L1711
/*
Fastbins
An array of lists holding recently freed small chunks. Fastbins
are not doubly linked. It is faster to single-link them, and
since chunks are never removed from the middles of these lists,
double linking is not necessary. Also, unlike regular bins, they
are not even processed in FIFO order (they use faster LIFO) since
ordering doesn't much matter in the transient contexts in which
fastbins are normally used.
Chunks in fastbins keep their inuse bit set, so they cannot
be consolidated with other free chunks. malloc_consolidate
releases all chunks in fastbins and consolidates them with
other free chunks.
*/
typedef struct malloc_chunk *mfastbinptr;
#define fastbin(ar_ptr, idx) ((ar_ptr)->fastbinsY[idx])
/* offset 2 to use otherwise unindexable first 2 bins */
#define fastbin_index(sz) \
((((unsigned int) (sz)) >> (SIZE_SZ == 8 ? 4 : 3)) - 2)
/* The maximum fastbin request size we support */
#define MAX_FAST_SIZE (80 * SIZE_SZ / 4)
#define NFASTBINS (fastbin_index (request2size (MAX_FAST_SIZE)) + 1)
Dodajte primer fastbin chunk-a
#include <stdlib.h>
#include <stdio.h>
int main(void)
{
char *chunks[8];
int i;
// Loop to allocate memory 8 times
for (i = 0; i < 8; i++) {
chunks[i] = malloc(24);
if (chunks[i] == NULL) { // Check if malloc failed
fprintf(stderr, "Memory allocation failed at iteration %d\n", i);
return 1;
}
printf("Address of chunk %d: %p\n", i, (void *)chunks[i]);
}
// Loop to free the allocated memory
for (i = 0; i < 8; i++) {
free(chunks[i]);
}
return 0;
}
Napomena kako alociramo i oslobađamo 8 delova iste veličine tako da popune tcache, a osmi se čuva u fast chunk.
Kompajlirajte to i debagujte sa breakpoint-om u ret
opcode-u iz main
funkcije. Tada sa gef
možete videti da je tcache bin pun i da je jedan chunk u fast bin:
gef➤ heap bins
──────────────────────────────────────────────────────────────────────────────── Tcachebins for thread 1 ────────────────────────────────────────────────────────────────────────────────
Tcachebins[idx=0, size=0x20, count=7] ← Chunk(addr=0xaaaaaaac1770, size=0x20, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac1750, size=0x20, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac1730, size=0x20, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac1710, size=0x20, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac16f0, size=0x20, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac16d0, size=0x20, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac12a0, size=0x20, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
───────────────────────────────────────────────────────────────────────── Fastbins for arena at 0xfffff7f90b00 ─────────────────────────────────────────────────────────────────────────
Fastbins[idx=0, size=0x20] ← Chunk(addr=0xaaaaaaac1790, size=0x20, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
Fastbins[idx=1, size=0x30] 0x00
Neuređeni kontejner
Neuređeni kontejner je keš koji koristi upravitelj heap-a kako bi ubrzao alokaciju memorije. Evo kako to funkcioniše: Kada program oslobodi deo memorije, i ako se taj deo ne može alocirati u tcache ili fast bin i ne sudara se sa vrhunskim delom, upravitelj heap-a ga odmah ne stavlja u određeni mali ili veliki kontejner. Umesto toga, prvo pokušava da spoji ga sa bilo kojim susednim slobodnim delovima kako bi stvorio veći blok slobodne memorije. Zatim, stavlja ovaj novi deo u opšti kontejner nazvan "neuređeni kontejner."
Kada program traži memoriju, upravitelj heap-a proverava neuređeni kontejner da vidi da li postoji deo dovoljne veličine. Ako ga pronađe, odmah ga koristi. Ako ne pronađe odgovarajući deo u neuređenom kontejneru, premestiće sve delove u ovoj listi u njihove odgovarajuće kontejnere, bilo male ili velike, na osnovu njihove veličine.
Napomena: ako se veći deo podeli na 2 polovine i ostatak je veći od MINSIZE, biće vraćen nazad u neuređeni kontejner.
Dakle, neuređeni kontejner je način da se ubrza alokacija memorije brzo ponovnim korišćenjem nedavno oslobođene memorije i smanji potreba za vremenski zahtevnim pretragama i spajanjima.
caution
Napomena: čak i ako su delovi različitih kategorija, ako se dostupan deo sudara sa drugim dostupnim delom (čak i ako prvobitno pripadaju različitim kontejnerima), biće spojeni.
Dodaj primer neuređenog dela
#include <stdlib.h>
#include <stdio.h>
int main(void)
{
char *chunks[9];
int i;
// Loop to allocate memory 8 times
for (i = 0; i < 9; i++) {
chunks[i] = malloc(0x100);
if (chunks[i] == NULL) { // Check if malloc failed
fprintf(stderr, "Memory allocation failed at iteration %d\n", i);
return 1;
}
printf("Address of chunk %d: %p\n", i, (void *)chunks[i]);
}
// Loop to free the allocated memory
for (i = 0; i < 8; i++) {
free(chunks[i]);
}
return 0;
}
Napomena kako alociramo i oslobađamo 9 delova iste veličine tako da popune tcache i osmi se čuva u nesortiranom binu jer je prevelik za fastbin, a deveti nije oslobođen, tako da se deveti i osmi ne spajaju sa vrhunskim delom.
Kompajlirajte to i debagujte sa tačkom prekida u ret
opkodu iz main
funkcije. Tada sa gef
možete videti da je tcache bin pun i jedan deo je u nesortiranom binu:
gef➤ heap bins
──────────────────────────────────────────────────────────────────────────────── Tcachebins for thread 1 ────────────────────────────────────────────────────────────────────────────────
Tcachebins[idx=15, size=0x110, count=7] ← Chunk(addr=0xaaaaaaac1d10, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac1c00, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac1af0, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac19e0, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac18d0, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac17c0, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac12a0, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
───────────────────────────────────────────────────────────────────────── Fastbins for arena at 0xfffff7f90b00 ─────────────────────────────────────────────────────────────────────────
Fastbins[idx=0, size=0x20] 0x00
Fastbins[idx=1, size=0x30] 0x00
Fastbins[idx=2, size=0x40] 0x00
Fastbins[idx=3, size=0x50] 0x00
Fastbins[idx=4, size=0x60] 0x00
Fastbins[idx=5, size=0x70] 0x00
Fastbins[idx=6, size=0x80] 0x00
─────────────────────────────────────────────────────────────────────── Unsorted Bin for arena at 0xfffff7f90b00 ───────────────────────────────────────────────────────────────────────
[+] unsorted_bins[0]: fw=0xaaaaaaac1e10, bk=0xaaaaaaac1e10
→ Chunk(addr=0xaaaaaaac1e20, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
[+] Found 1 chunks in unsorted bin.
Male Bine
Male bine su brže od velikih bina, ali sporije od brzih bina.
Svaki bin od 62 će imati delove iste veličine: 16, 24, ... (sa maksimalnom veličinom od 504 bajta u 32bita i 1024 u 64bita). Ovo pomaže u brzini pronalaženja bina gde bi prostor trebao biti dodeljen i umetanja i uklanjanja unosa na ovim listama.
Ovako se veličina malog bina izračunava prema indeksu bina:
- Najmanja veličina: 2*4*indeks (npr. indeks 5 -> 40)
- Najveća veličina: 2*8*indeks (npr. indeks 5 -> 80)
// From https://github.com/bminor/glibc/blob/a07e000e82cb71238259e674529c37c12dc7d423/malloc/malloc.c#L1711
#define NSMALLBINS 64
#define SMALLBIN_WIDTH MALLOC_ALIGNMENT
#define SMALLBIN_CORRECTION (MALLOC_ALIGNMENT > CHUNK_HDR_SZ)
#define MIN_LARGE_SIZE ((NSMALLBINS - SMALLBIN_CORRECTION) * SMALLBIN_WIDTH)
#define in_smallbin_range(sz) \
((unsigned long) (sz) < (unsigned long) MIN_LARGE_SIZE)
#define smallbin_index(sz) \
((SMALLBIN_WIDTH == 16 ? (((unsigned) (sz)) >> 4) : (((unsigned) (sz)) >> 3))\
+ SMALLBIN_CORRECTION)
Функција за избор између малих и великих контејнера:
#define bin_index(sz) \
((in_smallbin_range (sz)) ? smallbin_index (sz) : largebin_index (sz))
Dodajte mali primer
#include <stdlib.h>
#include <stdio.h>
int main(void)
{
char *chunks[10];
int i;
// Loop to allocate memory 8 times
for (i = 0; i < 9; i++) {
chunks[i] = malloc(0x100);
if (chunks[i] == NULL) { // Check if malloc failed
fprintf(stderr, "Memory allocation failed at iteration %d\n", i);
return 1;
}
printf("Address of chunk %d: %p\n", i, (void *)chunks[i]);
}
// Loop to free the allocated memory
for (i = 0; i < 8; i++) {
free(chunks[i]);
}
chunks[9] = malloc(0x110);
return 0;
}
Napomena kako alociramo i oslobađamo 9 delova iste veličine tako da popunimo tcache i osmi se čuva u nesortiranom binu jer je prevelik za fastbin, a deveti nije oslobođen, tako da se deveti i osmi ne spajaju sa vrhunskim delom. Zatim alociramo veći deo od 0x110 što čini da deo u nesortiranom binu ide u mali bin.
Kompajlirajte to i debagujte sa tačkom prekida u ret
opkodu iz main
funkcije. Tada sa gef
možete videti da je tcache bin pun i da je jedan deo u malom binu:
gef➤ heap bins
──────────────────────────────────────────────────────────────────────────────── Tcachebins for thread 1 ────────────────────────────────────────────────────────────────────────────────
Tcachebins[idx=15, size=0x110, count=7] ← Chunk(addr=0xaaaaaaac1d10, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac1c00, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac1af0, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac19e0, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac18d0, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac17c0, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac12a0, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
───────────────────────────────────────────────────────────────────────── Fastbins for arena at 0xfffff7f90b00 ─────────────────────────────────────────────────────────────────────────
Fastbins[idx=0, size=0x20] 0x00
Fastbins[idx=1, size=0x30] 0x00
Fastbins[idx=2, size=0x40] 0x00
Fastbins[idx=3, size=0x50] 0x00
Fastbins[idx=4, size=0x60] 0x00
Fastbins[idx=5, size=0x70] 0x00
Fastbins[idx=6, size=0x80] 0x00
─────────────────────────────────────────────────────────────────────── Unsorted Bin for arena at 0xfffff7f90b00 ───────────────────────────────────────────────────────────────────────
[+] Found 0 chunks in unsorted bin.
──────────────────────────────────────────────────────────────────────── Small Bins for arena at 0xfffff7f90b00 ────────────────────────────────────────────────────────────────────────
[+] small_bins[16]: fw=0xaaaaaaac1e10, bk=0xaaaaaaac1e10
→ Chunk(addr=0xaaaaaaac1e20, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
[+] Found 1 chunks in 1 small non-empty bins.
Veliki kontejneri
Za razliku od malih kontejnera, koji upravljaju delovima fiksnih veličina, svaki veliki kontejner upravlja opsegom veličina delova. Ovo je fleksibilnije, omogućavajući sistemu da prilagodi različite veličine bez potrebe za posebnim kontejnerom za svaku veličinu.
U alokatoru memorije, veliki kontejneri počinju gde mali kontejneri završavaju. Opsezi za velike kontejneri postaju progresivno veći, što znači da prvi kontejner može pokriti delove od 512 do 576 bajtova, dok sledeći pokriva od 576 do 640 bajtova. Ovaj obrazac se nastavlja, pri čemu najveći kontejner sadrži sve delove iznad 1MB.
Veliki kontejneri su sporiji za rad u poređenju sa malim kontejnerima jer moraju sortirati i pretraživati listu delova različitih veličina kako bi pronašli najbolju opciju za alokaciju. Kada se deo umetne u veliki kontejner, mora se sortirati, a kada se memorija alocira, sistem mora pronaći pravi deo. Ovaj dodatni rad ih čini sporijim, ali pošto su velike alokacije ređe od malih, to je prihvatljiva kompenzacija.
Postoji:
- 32 kontejnera opsega 64B (sukob sa malim kontejnerima)
- 16 kontejnera opsega 512B (sukob sa malim kontejnerima)
- 8 kontejnera opsega 4096B (delimično sukob sa malim kontejnerima)
- 4 kontejnera opsega 32768B
- 2 kontejnera opsega 262144B
- 1 kontejner za preostale veličine
Kod veličina velikih kontejnera
// From https://github.com/bminor/glibc/blob/a07e000e82cb71238259e674529c37c12dc7d423/malloc/malloc.c#L1711
#define largebin_index_32(sz) \
(((((unsigned long) (sz)) >> 6) <= 38) ? 56 + (((unsigned long) (sz)) >> 6) :\
((((unsigned long) (sz)) >> 9) <= 20) ? 91 + (((unsigned long) (sz)) >> 9) :\
((((unsigned long) (sz)) >> 12) <= 10) ? 110 + (((unsigned long) (sz)) >> 12) :\
((((unsigned long) (sz)) >> 15) <= 4) ? 119 + (((unsigned long) (sz)) >> 15) :\
((((unsigned long) (sz)) >> 18) <= 2) ? 124 + (((unsigned long) (sz)) >> 18) :\
126)
#define largebin_index_32_big(sz) \
(((((unsigned long) (sz)) >> 6) <= 45) ? 49 + (((unsigned long) (sz)) >> 6) :\
((((unsigned long) (sz)) >> 9) <= 20) ? 91 + (((unsigned long) (sz)) >> 9) :\
((((unsigned long) (sz)) >> 12) <= 10) ? 110 + (((unsigned long) (sz)) >> 12) :\
((((unsigned long) (sz)) >> 15) <= 4) ? 119 + (((unsigned long) (sz)) >> 15) :\
((((unsigned long) (sz)) >> 18) <= 2) ? 124 + (((unsigned long) (sz)) >> 18) :\
126)
// XXX It remains to be seen whether it is good to keep the widths of
// XXX the buckets the same or whether it should be scaled by a factor
// XXX of two as well.
#define largebin_index_64(sz) \
(((((unsigned long) (sz)) >> 6) <= 48) ? 48 + (((unsigned long) (sz)) >> 6) :\
((((unsigned long) (sz)) >> 9) <= 20) ? 91 + (((unsigned long) (sz)) >> 9) :\
((((unsigned long) (sz)) >> 12) <= 10) ? 110 + (((unsigned long) (sz)) >> 12) :\
((((unsigned long) (sz)) >> 15) <= 4) ? 119 + (((unsigned long) (sz)) >> 15) :\
((((unsigned long) (sz)) >> 18) <= 2) ? 124 + (((unsigned long) (sz)) >> 18) :\
126)
#define largebin_index(sz) \
(SIZE_SZ == 8 ? largebin_index_64 (sz) \
: MALLOC_ALIGNMENT == 16 ? largebin_index_32_big (sz) \
: largebin_index_32 (sz))
Dodajte veliki primer
#include <stdlib.h>
#include <stdio.h>
int main(void)
{
char *chunks[2];
chunks[0] = malloc(0x1500);
chunks[1] = malloc(0x1500);
free(chunks[0]);
chunks[0] = malloc(0x2000);
return 0;
}
2 velike alokacije se vrše, zatim se jedna oslobađa (stavljajući je u neusortiranu kantu) i vrši se veća alokacija (premještajući oslobođenu iz neusortirane kante u veliku kantu).
Kompajlirajte to i debagujte sa tačkom prekida u ret
opkodu iz main
funkcije. Tada sa gef
možete videti da je tcache kanta puna i da je jedan deo u velikoj kanti:
gef➤ heap bin
──────────────────────────────────────────────────────────────────────────────── Tcachebins for thread 1 ────────────────────────────────────────────────────────────────────────────────
All tcachebins are empty
───────────────────────────────────────────────────────────────────────── Fastbins for arena at 0xfffff7f90b00 ─────────────────────────────────────────────────────────────────────────
Fastbins[idx=0, size=0x20] 0x00
Fastbins[idx=1, size=0x30] 0x00
Fastbins[idx=2, size=0x40] 0x00
Fastbins[idx=3, size=0x50] 0x00
Fastbins[idx=4, size=0x60] 0x00
Fastbins[idx=5, size=0x70] 0x00
Fastbins[idx=6, size=0x80] 0x00
─────────────────────────────────────────────────────────────────────── Unsorted Bin for arena at 0xfffff7f90b00 ───────────────────────────────────────────────────────────────────────
[+] Found 0 chunks in unsorted bin.
──────────────────────────────────────────────────────────────────────── Small Bins for arena at 0xfffff7f90b00 ────────────────────────────────────────────────────────────────────────
[+] Found 0 chunks in 0 small non-empty bins.
──────────────────────────────────────────────────────────────────────── Large Bins for arena at 0xfffff7f90b00 ────────────────────────────────────────────────────────────────────────
[+] large_bins[100]: fw=0xaaaaaaac1290, bk=0xaaaaaaac1290
→ Chunk(addr=0xaaaaaaac12a0, size=0x1510, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
[+] Found 1 chunks in 1 large non-empty bins.
Gornji deo
// From https://github.com/bminor/glibc/blob/a07e000e82cb71238259e674529c37c12dc7d423/malloc/malloc.c#L1711
/*
Top
The top-most available chunk (i.e., the one bordering the end of
available memory) is treated specially. It is never included in
any bin, is used only if no other chunk is available, and is
released back to the system if it is very large (see
M_TRIM_THRESHOLD). Because top initially
points to its own bin with initial zero size, thus forcing
extension on the first malloc request, we avoid having any special
code in malloc to check whether it even exists yet. But we still
need to do so when getting memory from system, so we make
initial_top treat the bin as a legal but unusable chunk during the
interval between initialization and the first call to
sysmalloc. (This is somewhat delicate, since it relies on
the 2 preceding words to be zero during this interval as well.)
*/
/* Conveniently, the unsorted bin can be used as dummy top on first call */
#define initial_top(M) (unsorted_chunks (M))
U suštini, ovo je deo koji sadrži sve trenutno dostupne heap-ove. Kada se izvrši malloc, ako ne postoji dostupna slobodna jedinica za korišćenje, ova gornja jedinica će smanjiti svoju veličinu kako bi dala neophodan prostor.
Pokazivač na Gornju Jedinicu se čuva u malloc_state
strukturi.
Pored toga, na početku, moguće je koristiti nesortiranu jedinicu kao gornju jedinicu.
Posmatrajte primer Gornje Jedinice
#include <stdlib.h>
#include <stdio.h>
int main(void)
{
char *chunk;
chunk = malloc(24);
printf("Address of the chunk: %p\n", (void *)chunk);
gets(chunk);
return 0;
}
Nakon kompajliranja i debagovanja sa tačkom prekida u ret
opkodu main
, video sam da je malloc vratio adresu 0xaaaaaaac12a0
i ovo su delovi:
gef➤ heap chunks
Chunk(addr=0xaaaaaaac1010, size=0x290, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
[0x0000aaaaaaac1010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................]
Chunk(addr=0xaaaaaaac12a0, size=0x20, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
[0x0000aaaaaaac12a0 41 41 41 41 41 41 41 00 00 00 00 00 00 00 00 00 AAAAAAA.........]
Chunk(addr=0xaaaaaaac12c0, size=0x410, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
[0x0000aaaaaaac12c0 41 64 64 72 65 73 73 20 6f 66 20 74 68 65 20 63 Address of the c]
Chunk(addr=0xaaaaaaac16d0, size=0x410, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
[0x0000aaaaaaac16d0 41 41 41 41 41 41 41 0a 00 00 00 00 00 00 00 00 AAAAAAA.........]
Chunk(addr=0xaaaaaaac1ae0, size=0x20530, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← top chunk
Gde se može videti da je gornji deo na adresi 0xaaaaaaac1ae0
. To nije iznenađenje jer je poslednji alocirani deo bio na 0xaaaaaaac12a0
sa veličinom 0x410
i 0xaaaaaaac12a0 + 0x410 = 0xaaaaaaac1ae0
.
Takođe je moguće videti dužinu gornjeg dela na njegovom zaglavlju dela:
gef➤ x/8wx 0xaaaaaaac1ae0 - 16
0xaaaaaaac1ad0: 0x00000000 0x00000000 0x00020531 0x00000000
0xaaaaaaac1ae0: 0x00000000 0x00000000 0x00000000 0x00000000
Poslednji Ostatak
Kada se koristi malloc i deo se deli (na primer, iz nesortiranog bin-a ili iz gornjeg dela), deo koji se stvara od ostatka podeljenog dela se naziva Poslednji Ostatak i njegov pokazivač se čuva u malloc_state
strukturi.
Tok Alokacije
Pogledajte:
Tok Oslobađanja
Pogledajte:
Provere Bezbednosti Funkcija na Heap-u
Proverite provere bezbednosti koje obavljaju često korišćene funkcije na heap-u u:
Heap Functions Security Checks
Reference
- https://azeria-labs.com/heap-exploitation-part-1-understanding-the-glibc-heap-implementation/
- https://azeria-labs.com/heap-exploitation-part-2-glibc-heap-free-bins/
- https://heap-exploitation.dhavalkapil.com/diving_into_glibc_heap/core_functions
- https://ctf-wiki.mahaloz.re/pwn/linux/glibc-heap/implementation/tcache/
tip
Učite i vežbajte AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Učite i vežbajte GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Podržite HackTricks
- Proverite planove pretplate!
- Pridružite se 💬 Discord grupi ili telegram grupi ili pratite nas na Twitteru 🐦 @hacktricks_live.
- Podelite hakerske trikove slanjem PR-ova na HackTricks i HackTricks Cloud github repozitorijume.