free
Reading time: 12 minutes
tip
Leer en oefen AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Leer en oefen GCP Hacking:
HackTricks Training GCP Red Team Expert (GRTE)
Leer en oefen Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Ondersteun HackTricks
- Kyk na die subskripsie planne!
- Sluit aan by die 💬 Discord groep of die telegram groep of volg ons op Twitter 🐦 @hacktricks_live.
- Deel hacking truuks deur PRs in te dien na die HackTricks en HackTricks Cloud github repos.
Opsomming van free-volgorde
(Geen kontroles word in hierdie opsomming verduidelik en sommige gevalle is weggelaat vir bondigheid)
- As die adres null is, doen niks
- As die chunk mmaped was, munmap dit en klaar
- Roep
_int_freeaan: - Indien moontlik, voeg die chunk by die tcache
- Indien moontlik, voeg die chunk by die fast bin
- Roep
_int_free_merge_chunkop om die chunk te konsolideer indien nodig en voeg dit by die unsorted list
Let wel: Vanaf glibc 2.42 kan die tcache-stap ook chunks tot 'n baie groter grootte-drempel inneem (sien “Recent glibc changes” hieronder). Dit verander wanneer 'n free in tcache versus unsorted/small/large bins beland.
__libc_free
Free roep __libc_free aan.
- As die gegewe adres Null (0) is, doen niks.
- Kontroleer pointer tag
- As die chunk
mmapedis,munmapdit en dit is alles - Indien nie, voeg die kleur by en roep
_int_freedaarop aan
__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 begin
Dit begin met 'n paar kontroles om seker te maak:
- die pointer is uitgelyk, anders word fout
free(): invalid pointeropgegooi - die size is nie minder as die minimum nie en die size is ook uitgelyk, anders word fout:
free(): invalid sizeveroorsaak
_int_free begin
// 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
Eerstens sal dit probeer om hierdie chunk aan die verwante tcache toe te wys. Daar word egter vooraf 'n paar kontrole uitgevoer. Dit sal deur al die chunks van die tcache loop wat in dieselfde indeks as die vrygestelde chunk is, en:
- As daar meer inskrywings as
mp_.tcache_countis:free(): too many chunks detected in tcache - As die inskrywing nie gealigneer is nie: free():
unaligned chunk detected in tcache 2 - As die vrygestelde chunk reeds vrygestel is en as dit as 'n chunk in die tcache voorkom:
free(): double free detected in tcache 2
As alles goed gaan, word die chunk by die tcache gevoeg en die funksie keer terug.
_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
Begin deur te kontroleer dat die grootte geskik is vir fast bin en kyk of dit moontlik is om dit naby die top chunk te plaas.
Voeg dan die freed chunk bo-aan die fast bin by terwyl 'n paar kontroles uitgevoer word:
- As die grootte van die chunk ongeldig is (te groot of te klein) veroorsaak:
free(): invalid next size (fast) - As die bygevoegde chunk reeds die top van die fast bin was:
double free or corruption (fasttop) - As die grootte van die chunk bo-aan verskil van die grootte van die chunk wat ons byvoeg:
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 afsluiting
As die chunk nog nie in enige bin toegewys is nie, roep _int_free_merge_chunk aan
_int_free afsluiting
/*
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
Hierdie funksie sal probeer om chunk P van SIZE bytes met sy bure saam te smelt. Plaas die resulterende chunk op die unsorted bin list.
Sommige kontroles word uitgevoer:
- As die chunk die top chunk is:
double free or corruption (top) - As die volgende chunk buite die grense van die arena is:
double free or corruption (out) - As die chunk nie as gebruik gemerk is nie (in die
prev_inusevan die volgende chunk):double free or corruption (!prev) - As die volgende chunk te klein of te groot is:
free(): invalid next size (normal) - As die vorige chunk nie in gebruik is nie, sal dit probeer konsolideer. Maar as die
prev_sizeverskil van die grootte aangedui in die vorige chunk: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);
}
Aanvaller-notas en onlangse veranderings (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. Dit beteken dat die vervaardiging van 'n vals next pointer vir tcache poisoning vereis dat die aanvaller 'n heap-adres ken (bv. leakchunk_addr, gebruik danchunk_addr >> 12as die XOR-sleutel). Sien meer besonderhede en PoCs op die tcache-bladsy hieronder. - Tcache double-free detection: Voordat 'n chunk in tcache gedruk word, kontroleer
free()die per-entrye->keyteen die per-threadtcache_keyen loop die bin tot bymp_.tcache_countom duplikate te soek, en maak abort metfree(): double free detected in tcache 2as dit gevind word. - Recent glibc change (2.42): Die tcache het uitgebreek om baie groter chunks te aanvaar, beheer deur die nuwe
glibc.malloc.tcache_max_bytestunable.free()sal nou probeer om vrygestelde chunks te cache tot dié byte-grens (mmapped chunks word nie gecache nie). Dit verminder hoe gereeld frees op moderne stelsels in unsorted/small/large bins beland.
Vinnige opstelling van 'n safe-linked fd (vir 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)
- Vir 'n volledige tcache poisoning-stap-vir-stap gids (en sy beperkings onder safe-linking), sien:
Forseer frees om unsorted/small bins te tref tydens navorsing
Soms wil jy tcache heeltemal vermy in 'n lokale lab om die klassieke _int_free-gedrag waar te neem (unsorted bin consolidation, ens.). Jy kan dit doen met 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
Verwante leesstof binne HackTricks:
- First-fit/unsorted gedrag en overlap tricks:
- Double-free primitiewe en moderne kontroles:
Waarskuwing oor hooks: Klassieke
__malloc_hook/__free_hookoorskryf-tegnieke is nie bruikbaar op moderne glibc (≥ 2.34) nie. As jy dit steeds in ouer write-ups sien, pas aan na alternatiewe teikens (IO_FILE, exit handlers, vtables, ens.). Vir agtergrond, kyk die bladsy oor hooks in HackTricks.
WWW2Exec - __malloc_hook & __free_hook
Verwysings
- GNU C Library – NEWS vir 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 verduideliking en interne werking (Red Hat Developer, 2020) https://developers.redhat.com/articles/2020/05/13/new-security-hardening-gnu-c-library
tip
Leer en oefen AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Leer en oefen GCP Hacking:
HackTricks Training GCP Red Team Expert (GRTE)
Leer en oefen Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Ondersteun HackTricks
- Kyk na die subskripsie planne!
- Sluit aan by die 💬 Discord groep of die telegram groep of volg ons op Twitter 🐦 @hacktricks_live.
- Deel hacking truuks deur PRs in te dien na die HackTricks en HackTricks Cloud github repos.
HackTricks