free
Reading time: 11 minutes
tip
AWS Hacking'i öğrenin ve pratik yapın:HackTricks Training AWS Red Team Expert (ARTE)
GCP Hacking'i öğrenin ve pratik yapın: HackTricks Training GCP Red Team Expert (GRTE)
Azure Hacking'i öğrenin ve pratik yapın:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks'i Destekleyin
- abonelik planlarını kontrol edin!
- 💬 Discord grubuna veya telegram grubuna katılın ya da Twitter'da bizi takip edin 🐦 @hacktricks_live.**
- Hacking ipuçlarını paylaşmak için HackTricks ve HackTricks Cloud github reposuna PR gönderin.
Free Order Summary
(Bu özet içinde kontroller açıklanmamıştır ve bazı durumlar kısaltma amacıyla atlanmıştır)
- If the address is null don't do anything
- If the chunk was
mmaped
,munmap
it and finish - Call
_int_free
: - If possible, add the chunk to the tcache
- If possible, add the chunk to the fast bin
- Call
_int_free_merge_chunk
to consolidate the chunk is needed and add it to the unsorted list
Note: Starting with glibc 2.42, the tcache step can also take chunks up to a much larger size threshold (see “Recent glibc changes” below). This changes when a free lands in tcache vs. unsorted/small/large bins.
__libc_free
Free
, __libc_free
'i çağırır.
- Adres Null (0) ise hiçbir şey yapma.
- Pointer tag'ini kontrol et
- Chunk
mmaped
ise,munmap
et ve hepsi bu - Değilse, color ekle ve üzerinde
_int_free
çağır
__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
Bazı kontrollerle başlar ve şunları doğrular:
- pointer'ın aligned olması gerekir; aksi halde
free(): invalid pointer
hatası tetiklenir - size minimumdan küçük olmamalı ve size ayrıca aligned olmalıdır; aksi halde
free(): invalid size
hatası tetiklenir
_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
İlk olarak bu chunk'ı ilgili tcache'de allocate etmeye çalışır. Ancak öncesinde bazı kontroller yapılır. Serbest bırakılan chunk ile aynı indeksteki tcache'deki tüm chunk'lar üzerinde döngüye girer ve:
- Eğer giriş sayısı
mp_.tcache_count
'ten fazlaysa:free(): too many chunks detected in tcache
- Eğer giriş hizalı değilse:
free(): unaligned chunk detected in tcache 2
- Eğer serbest bırakılan chunk zaten daha önce serbest bırakıldıysa ve tcache'de bir chunk olarak mevcutsa:
free(): double free detected in tcache 2
Her şey yolunda giderse, chunk tcache'e eklenir ve fonksiyon döner.
_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
Önce boyutun fast bin için uygun olduğunu ve onu top chunk'a yakın olacak şekilde ayarlamanın mümkün olup olmadığını kontrol edin.
Sonra, serbest bırakılan chunk'ı fast bin'in tepesine eklerken şu kontrolleri yapın:
- Eğer chunk'ın boyutu geçersizse (çok büyük veya küçük) tetikler:
free(): invalid next size (fast)
- Eklenen chunk zaten fast bin'in tepesindeyse:
double free or corruption (fasttop)
- Tepedeki chunk'ın boyutu, eklediğimiz chunk'ın boyutundan farklıysa:
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 finali
Eğer chunk henüz herhangi bir bin'de tahsis edilmemişse, _int_free_merge_chunk
çağır.
_int_free finali
/*
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
Bu fonksiyon, P adlı, SIZE bytes uzunluğundaki chunk'ı komşularıyla birleştirmeye çalışır. Ortaya çıkan chunk'ı unsorted bin listesine koyar.
Bazı kontroller yapılır:
- Eğer chunk top chunk ise:
double free or corruption (top)
- Eğer sonraki chunk arena sınırlarının dışındaysa:
double free or corruption (out)
- Eğer chunk kullanılmak üzere işaretlenmemişse (takip eden chunk'taki
prev_inuse
içinde):double free or corruption (!prev)
- Eğer sonraki chunk çok küçük veya çok büyük bir boyuta sahipse:
free(): invalid next size (normal)
- Eğer önceki chunk kullanımda değilse, konsolidasyona (birleştirmeye) çalışır. Ancak prev_size önceki chunk'ta belirtilen boyuttan farklıysa:
corrupted size vs. prev_size while consolidating
_int_free_merge_chunk kod
// 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);
}
Attacker notes and recent changes (2023–2025)
- Safe-Linking in tcache/fastbins:
free()
tek bağlı listelerinfd
işaretçisini macroPROTECT_PTR(pos, ptr) = ((size_t)pos >> 12) ^ (size_t)ptr
kullanarak depolar. Bu, tcache poisoning için sahte bir next pointer oluşturmanın saldırganın bir heap adresini bilmesini gerektirdiği anlamına gelir (ör. leakchunk_addr
, sonra XOR anahtarı olarakchunk_addr >> 12
kullanılır). Ayrıntılar ve PoC'lar için aşağıdaki tcache sayfasına bakın. - Tcache double-free detection:
free()
bir chunk'ı tcache'e itmeden önce, giriş başınae->key
'i her iş parçacığına aittcache_key
ile kontrol eder ve kopya aramak için bin'imp_.tcache_count
kadar gezer; bulunan durumdafree(): double free detected in tcache 2
hatasıyla abort eder. - Recent glibc change (2.42): tcache artık yeni
glibc.malloc.tcache_max_bytes
tunable tarafından kontrol edilen çok daha büyük chunk'ları kabul edecek şekilde büyüdü.free()
artık bu bayt sınırına kadar serbest bırakılan chunk'ları cachelemeye çalışır (mmapped chunks cachelenmez). Bu, modern sistemlerde free'ların unsorted/small/large bins'e düşme sıklığını azaltır.
Safe-linked fd'nin hızlı oluşturulması (tcache poisoning için)
# 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)
- Tam bir tcache poisoning adım adım rehberi (ve safe-linking altındaki sınırları) için bakın:
Araştırma sırasında free çağrılarının unsorted/small bins'e düşmesini zorlamak
Bazen klasik _int_free
davranışını (unsorted bin consolidation, vb.) gözlemlemek için yerel bir laboratuvarda tcache'i tamamen atlamak istersiniz. Bunu GLIBC_TUNABLES ile yapabilirsiniz:
# 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
HackTricks içinde ilgili okumalar:
- First-fit/unsorted davranışı ve overlap tricks:
- Double-free primitives and modern checks:
Hooks hakkında uyarı: Klasik
__malloc_hook
/__free_hook
üzerine yazma teknikleri modern glibc (≥ 2.34) üzerinde işe yaramıyor. Eğer hâlâ eski yazılarda görüyorsanız, alternatif hedeflere (IO_FILE, exit handlers, vtables, vb.) uyarlayın. Arka plan için HackTricks'teki hooks sayfasına bakın.
WWW2Exec - __malloc_hook & __free_hook
Referanslar
- GNU C Library – 2.42 için NEWS (allocator: tcache_max_bytes sayesinde daha büyük tcache, mmapped chunk'lar önbelleğe alınmıyor) https://www.gnu.org/software/libc/NEWS.html#2.42
- Safe-Linking açıklaması ve iç işleyişi (Red Hat Developer, 2020) https://developers.redhat.com/articles/2020/05/13/new-security-hardening-gnu-c-library
tip
AWS Hacking'i öğrenin ve pratik yapın:HackTricks Training AWS Red Team Expert (ARTE)
GCP Hacking'i öğrenin ve pratik yapın: HackTricks Training GCP Red Team Expert (GRTE)
Azure Hacking'i öğrenin ve pratik yapın:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks'i Destekleyin
- abonelik planlarını kontrol edin!
- 💬 Discord grubuna veya telegram grubuna katılın ya da Twitter'da bizi takip edin 🐦 @hacktricks_live.**
- Hacking ipuçlarını paylaşmak için HackTricks ve HackTricks Cloud github reposuna PR gönderin.