free
Reading time: 12 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)
Učite i vežbajte Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
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.
Free Sažetak redosleda
(U ovom sažetku nisu objašnjene provere i neki slučajevi su izostavljeni radi sažetka)
- Ako je adresa null ne radi ništa
- Ako je chunk mmaped, pozovi munmap i završi
- Pozovi
_int_free: - Ako je moguće, dodaj chunk u tcache
- Ako je moguće, dodaj chunk u fast bin
- Pozovi
_int_free_merge_chunkda konsoliduje chunk ako je potrebno i dodaj ga u unsorted list
Napomena: Počevši od glibc 2.42, tcache korak može takođe prihvatiti chunkove do mnogo veće veličine (vidi “Recent glibc changes” ispod). Ovo menja kada free završi u tcache naspram unsorted/small/large bins.
__libc_free
Free poziva __libc_free.
- Ako je prosleđena adresa Null (0), ne radi ništa.
- Proveri tag pokazivača
- Ako je chunk
mmaped, pozovimunmapi to je sve - Ako ne, dodaj color i pozovi
_int_freenad njim
__lib_free code
void
__libc_free (void *mem)
{
mstate ar_ptr;
mchunkptr p; /* chunk corresponding to mem */
if (mem == 0) /* free(0) has no effect */
return;
/* Quickly check that the freed pointer matches the tag for the memory.
This gives a useful double-free detection. */
if (__glibc_unlikely (mtag_enabled))
*(volatile char *)mem;
int err = errno;
p = mem2chunk (mem);
if (chunk_is_mmapped (p)) /* release mmapped memory. */
{
/* See if the dynamic brk/mmap threshold needs adjusting.
Dumped fake mmapped chunks do not affect the threshold. */
if (!mp_.no_dyn_threshold
&& chunksize_nomask (p) > mp_.mmap_threshold
&& chunksize_nomask (p) <= DEFAULT_MMAP_THRESHOLD_MAX)
{
mp_.mmap_threshold = chunksize (p);
mp_.trim_threshold = 2 * mp_.mmap_threshold;
LIBC_PROBE (memory_mallopt_free_dyn_thresholds, 2,
mp_.mmap_threshold, mp_.trim_threshold);
}
munmap_chunk (p);
}
else
{
MAYBE_INIT_TCACHE ();
/* Mark the chunk as belonging to the library again. */
(void)tag_region (chunk2mem (p), memsize (p));
ar_ptr = arena_for_chunk (p);
_int_free (ar_ptr, p, 0);
}
__set_errno (err);
}
libc_hidden_def (__libc_free)
_int_free
_int_free start
Počinje sa nekoliko provera koje osiguravaju:
- da je pointer aligned, inače se javlja greška
free(): invalid pointer - da size nije manji od minimuma i da je size takođe aligned, inače se javlja greška:
free(): invalid size
_int_free start
// From https://github.com/bminor/glibc/blob/f942a732d37a96217ef828116ebe64a644db18d7/malloc/malloc.c#L4493C1-L4513C28
#define aligned_OK(m) (((unsigned long) (m) &MALLOC_ALIGN_MASK) == 0)
static void
_int_free (mstate av, mchunkptr p, int have_lock)
{
INTERNAL_SIZE_T size; /* its size */
mfastbinptr *fb; /* associated fastbin */
size = chunksize (p);
/* Little security check which won't hurt performance: the
allocator never wraps around at the end of the address space.
Therefore we can exclude some size values which might appear
here by accident or by "design" from some intruder. */
if (__builtin_expect ((uintptr_t) p > (uintptr_t) -size, 0)
|| __builtin_expect (misaligned_chunk (p), 0))
malloc_printerr ("free(): invalid pointer");
/* We know that each chunk is at least MINSIZE bytes in size or a
multiple of MALLOC_ALIGNMENT. */
if (__glibc_unlikely (size < MINSIZE || !aligned_OK (size)))
malloc_printerr ("free(): invalid size");
check_inuse_chunk(av, p);
_int_free tcache
Prvo će pokušati da alocira ovaj chunk u odgovarajućem tcache-u. Međutim, pre toga se izvode neke provere. Proći će kroz sve chunke u tcache-u na istom indexu kao oslobođeni chunk i:
- If there are more entries than
mp_.tcache_count:free(): too many chunks detected in tcache - If the entry is not aligned: free():
unaligned chunk detected in tcache 2 - if the freed chunk was already freed and is present as chunk in the tcache:
free(): double free detected in tcache 2
If all goes well, the chunk is added to the tcache and the functions returns.
_int_free tcache
// From https://github.com/bminor/glibc/blob/f942a732d37a96217ef828116ebe64a644db18d7/malloc/malloc.c#L4515C1-L4554C7
#if USE_TCACHE
{
size_t tc_idx = csize2tidx (size);
if (tcache != NULL && tc_idx < mp_.tcache_bins)
{
/* Check to see if it's already in the tcache. */
tcache_entry *e = (tcache_entry *) chunk2mem (p);
/* This test succeeds on double free. However, we don't 100%
trust it (it also matches random payload data at a 1 in
2^<size_t> chance), so verify it's not an unlikely
coincidence before aborting. */
if (__glibc_unlikely (e->key == tcache_key))
{
tcache_entry *tmp;
size_t cnt = 0;
LIBC_PROBE (memory_tcache_double_free, 2, e, tc_idx);
for (tmp = tcache->entries[tc_idx];
tmp;
tmp = REVEAL_PTR (tmp->next), ++cnt)
{
if (cnt >= mp_.tcache_count)
malloc_printerr ("free(): too many chunks detected in tcache");
if (__glibc_unlikely (!aligned_OK (tmp)))
malloc_printerr ("free(): unaligned chunk detected in tcache 2");
if (tmp == e)
malloc_printerr ("free(): double free detected in tcache 2");
/* If we get here, it was a coincidence. We've wasted a
few cycles, but don't abort. */
}
}
if (tcache->counts[tc_idx] < mp_.tcache_count)
{
tcache_put (p, tc_idx);
return;
}
}
}
#endif
_int_free fast bin
Počni proverom da li je veličina pogodna za fast bin i proveri da li je moguće postaviti je blizu top chunk.
Zatim dodaj oslobođeni chunk na vrh fast bin-a uz izvršavanje nekoliko provera:
- Ako je veličina chunk-a nevažeća (prevelika ili premala), pokrenuće se:
free(): invalid next size (fast) - Ako je dodati chunk već bio top of the fast bin:
double free or corruption (fasttop) - Ako chunk na vrhu ima drugačiju veličinu od chunk-a koji dodajemo:
invalid fastbin entry (free)
_int_free Fast Bin
// From https://github.com/bminor/glibc/blob/f942a732d37a96217ef828116ebe64a644db18d7/malloc/malloc.c#L4556C2-L4631C4
/*
If eligible, place chunk on a fastbin so it can be found
and used quickly in malloc.
*/
if ((unsigned long)(size) <= (unsigned long)(get_max_fast ())
#if TRIM_FASTBINS
/*
If TRIM_FASTBINS set, don't place chunks
bordering top into fastbins
*/
&& (chunk_at_offset(p, size) != av->top)
#endif
) {
if (__builtin_expect (chunksize_nomask (chunk_at_offset (p, size))
<= CHUNK_HDR_SZ, 0)
|| __builtin_expect (chunksize (chunk_at_offset (p, size))
>= av->system_mem, 0))
{
bool fail = true;
/* We might not have a lock at this point and concurrent modifications
of system_mem might result in a false positive. Redo the test after
getting the lock. */
if (!have_lock)
{
__libc_lock_lock (av->mutex);
fail = (chunksize_nomask (chunk_at_offset (p, size)) <= CHUNK_HDR_SZ
|| chunksize (chunk_at_offset (p, size)) >= av->system_mem);
__libc_lock_unlock (av->mutex);
}
if (fail)
malloc_printerr ("free(): invalid next size (fast)");
}
free_perturb (chunk2mem(p), size - CHUNK_HDR_SZ);
atomic_store_relaxed (&av->have_fastchunks, true);
unsigned int idx = fastbin_index(size);
fb = &fastbin (av, idx);
/* Atomically link P to its fastbin: P->FD = *FB; *FB = P; */
mchunkptr old = *fb, old2;
if (SINGLE_THREAD_P)
{
/* Check that the top of the bin is not the record we are going to
add (i.e., double free). */
if (__builtin_expect (old == p, 0))
malloc_printerr ("double free or corruption (fasttop)");
p->fd = PROTECT_PTR (&p->fd, old);
*fb = p;
}
else
do
{
/* Check that the top of the bin is not the record we are going to
add (i.e., double free). */
if (__builtin_expect (old == p, 0))
malloc_printerr ("double free or corruption (fasttop)");
old2 = old;
p->fd = PROTECT_PTR (&p->fd, old);
}
while ((old = catomic_compare_and_exchange_val_rel (fb, p, old2))
!= old2);
/* Check that size of fastbin chunk at the top is the same as
size of the chunk that we are adding. We can dereference OLD
only if we have the lock, otherwise it might have already been
allocated again. */
if (have_lock && old != NULL
&& __builtin_expect (fastbin_index (chunksize (old)) != idx, 0))
malloc_printerr ("invalid fastbin entry (free)");
}
_int_free finalni deo
Ako chunk još nije bio alociran u nijednom binu, pozvati _int_free_merge_chunk
_int_free finalni deo
/*
Consolidate other non-mmapped chunks as they arrive.
*/
else if (!chunk_is_mmapped(p)) {
/* If we're single-threaded, don't lock the arena. */
if (SINGLE_THREAD_P)
have_lock = true;
if (!have_lock)
__libc_lock_lock (av->mutex);
_int_free_merge_chunk (av, p, size);
if (!have_lock)
__libc_lock_unlock (av->mutex);
}
/*
If the chunk was allocated via mmap, release via munmap().
*/
else {
munmap_chunk (p);
}
}
_int_free_merge_chunk
Ova funkcija pokušava da spoji chunk P od SIZE bajta sa svojim susedima. Smesti rezultujući chunk na unsorted bin listu.
Izvode se sledeće provere:
- Ako je chunk top chunk:
double free or corruption (top) - Ako je sledeći chunk izvan granica arene:
double free or corruption (out) - Ako chunk nije označen kao korišćen (u
prev_inusenarednog chunka):double free or corruption (!prev) - Ako sledeći chunk ima previše malu ili preveliku veličinu:
free(): invalid next size (normal) - Ako prethodni chunk nije in use, pokušaće da izvrši konsolidaciju. Međutim, ako se
prev_sizerazlikuje od veličine naznačene u prethodnom chunku:corrupted size vs. prev_size while consolidating
_int_free_merge_chunk code
// From https://github.com/bminor/glibc/blob/f942a732d37a96217ef828116ebe64a644db18d7/malloc/malloc.c#L4660C1-L4702C2
/* Try to merge chunk P of SIZE bytes with its neighbors. Put the
resulting chunk on the appropriate bin list. P must not be on a
bin list yet, and it can be in use. */
static void
_int_free_merge_chunk (mstate av, mchunkptr p, INTERNAL_SIZE_T size)
{
mchunkptr nextchunk = chunk_at_offset(p, size);
/* Lightweight tests: check whether the block is already the
top block. */
if (__glibc_unlikely (p == av->top))
malloc_printerr ("double free or corruption (top)");
/* Or whether the next chunk is beyond the boundaries of the arena. */
if (__builtin_expect (contiguous (av)
&& (char *) nextchunk
>= ((char *) av->top + chunksize(av->top)), 0))
malloc_printerr ("double free or corruption (out)");
/* Or whether the block is actually not marked used. */
if (__glibc_unlikely (!prev_inuse(nextchunk)))
malloc_printerr ("double free or corruption (!prev)");
INTERNAL_SIZE_T nextsize = chunksize(nextchunk);
if (__builtin_expect (chunksize_nomask (nextchunk) <= CHUNK_HDR_SZ, 0)
|| __builtin_expect (nextsize >= av->system_mem, 0))
malloc_printerr ("free(): invalid next size (normal)");
free_perturb (chunk2mem(p), size - CHUNK_HDR_SZ);
/* Consolidate backward. */
if (!prev_inuse(p))
{
INTERNAL_SIZE_T prevsize = prev_size (p);
size += prevsize;
p = chunk_at_offset(p, -((long) prevsize));
if (__glibc_unlikely (chunksize(p) != prevsize))
malloc_printerr ("corrupted size vs. prev_size while consolidating");
unlink_chunk (av, p);
}
/* Write the chunk header, maybe after merging with the following chunk. */
size = _int_free_create_chunk (av, p, size, nextchunk, nextsize);
_int_free_maybe_consolidate (av, size);
}
Beleške napadača i nedavne promene (2023–2025)
- Safe-Linking in tcache/fastbins:
free()stores thefdpointer of singly-linked lists using the macroPROTECT_PTR(pos, ptr) = ((size_t)pos >> 12) ^ (size_t)ptr. Ovo znači da izrada lažnog next pokazivača za tcache poisoning zahteva da napadač zna heap address (npr. leakchunk_addr, zatim koristichunk_addr >> 12kao XOR ključ). Vidi više detalja i PoCs na tcache stranici ispod. - Tcache double-free detection: Pre nego što ubaci chunk u tcache,
free()proverava per-entrye->keyprotiv per-threadtcache_keyi pretražuje bin domp_.tcache_counttražeći duplikate, prekidajući safree(): double free detected in tcache 2kada se nađe. - Recent glibc change (2.42): The tcache grew to accept much larger chunks, controlled by the new
glibc.malloc.tcache_max_bytestunable.free()will now try to cache freed chunks up to that byte limit (mmapped chunks are not cached). Ovo smanjuje koliko često frees završe u unsorted/small/large bins na modernim sistemima.
Brzo kreiranje safe-linked fd (za tcache poisoning)
# Given a leaked heap pointer to an entry located at &entry->next == POS
# compute the protected fd that points to TARGET
protected_fd = TARGET ^ (POS >> 12)
- Za kompletan tcache poisoning walkthrough (i njegova ograničenja pod safe-linking), pogledajte:
Prinuđavanje free poziva da udare u unsorted/small bins tokom istraživanja
Ponekad želite da u potpunosti izbegnete tcache u lokalnom okruženju da biste posmatrali klasično ponašanje _int_free (unsorted bin consolidation, itd.). To možete uraditi sa GLIBC_TUNABLES:
# Disable tcache completely
GLIBC_TUNABLES=glibc.malloc.tcache_count=0 ./vuln
# Pre-2.42: shrink the maximum cached request size to 0
GLIBC_TUNABLES=glibc.malloc.tcache_max=0 ./vuln
# 2.42+: cap the new large-cache threshold (bytes)
GLIBC_TUNABLES=glibc.malloc.tcache_max_bytes=0 ./vuln
Povezano štivo unutar HackTricks:
- First-fit/unsorted ponašanje i overlap trikovi:
- Double-free primitivи i moderne provere:
Upozorenje u vezi hooks: Klasične
__malloc_hook/__free_hookoverwrite tehnike nisu primenljive na moderni glibc (≥ 2.34). Ako ih i dalje vidite u starijim write-upovima, prilagodite se alternativnim targetima (IO_FILE, exit handlers, vtables, itd.). Za pozadinu, proverite stranicu o hooks u HackTricks.
WWW2Exec - __malloc_hook & __free_hook
Reference
- GNU C Library – NEWS for 2.42 (allocator: larger tcache via tcache_max_bytes, mmapped chunks are not cached) https://www.gnu.org/software/libc/NEWS.html#2.42
- Safe-Linking explanation and internals (Red Hat Developer, 2020) https://developers.redhat.com/articles/2020/05/13/new-security-hardening-gnu-c-library
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)
Učite i vežbajte Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
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.
HackTricks