Libc Heap

Tip

Impara e pratica il hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Impara e pratica il hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporta HackTricks

Nozioni di base sull’heap

L’heap è fondamentalmente il luogo in cui un programma può memorizzare dati quando li richiede chiamando funzioni come malloc, calloc… Inoltre, quando questa memoria non è più necessaria viene resa disponibile chiamando la funzione free.

Come mostrato, si trova subito dopo il caricamento del binario in memoria (controlla la sezione [heap]):

Allocazione base dei chunk

Quando viene richiesto di memorizzare dei dati nell’heap, viene allocato uno spazio dell’heap per essi. Questo spazio appartiene a un bin e verrà riservato per il chunk solo lo spazio richiesto + lo spazio degli header del bin + l’offset del minimo size del bin. L’obiettivo è riservare la minima quantità di memoria possibile senza rendere complicato individuare ogni chunk. Per questo, le informazioni di metadata dei chunk sono usate per sapere dove si trova ogni chunk usato/libero.

Ci sono diversi modi per riservare lo spazio principalmente a seconda del bin usato, ma una metodologia generale è la seguente:

  • Il programma inizia richiedendo una certa quantità di memoria.
  • Se nella lista dei chunk c’è qualcuno disponibile abbastanza grande da soddisfare la richiesta, verrà usato
  • Ciò può significare che parte del chunk disponibile verrà usata per questa richiesta e il resto sarà aggiunto alla lista dei chunk
  • Se non c’è alcun chunk disponibile nella lista ma c’è ancora spazio nella memoria heap allocata, il heap manager crea un nuovo chunk
  • Se non c’è abbastanza spazio nell’heap per allocare il nuovo chunk, il heap manager chiede al kernel di espandere la memoria allocata all’heap e poi usa questa memoria per generare il nuovo chunk
  • Se tutto fallisce, malloc ritorna null.

Nota che se la memoria richiesta supera una soglia, verrà usato mmap per mappare la memoria richiesta.

Arenas

Nelle applicazioni multithreaded, il heap manager deve prevenire le race conditions che potrebbero portare a crash. Inizialmente, questo veniva fatto usando un global mutex per garantire che solo un thread potesse accedere all’heap alla volta, ma ciò causava problemi di performance a causa del collo di bottiglia introdotto dal mutex.

Per risolvere questo, l’heap allocator ptmalloc2 ha introdotto le “arenas”, dove ogni arena agisce come un heap separato con le proprie strutture dati e mutex, permettendo a più thread di eseguire operazioni sull’heap senza interferire tra loro, purché usino arena diverse.

L’arena predefinita “main” gestisce le operazioni dell’heap per applicazioni single-thread. Quando vengono aggiunti nuovi thread, il heap manager assegna loro secondary arenas per ridurre la contention. Cerca prima di collegare ogni nuovo thread a un’arena inutilizzata, creando nuove arena se necessario, fino a un limite di 2 volte il numero di core CPU per sistemi a 32 bit e 8 volte per sistemi a 64 bit. Una volta raggiunto il limite, i thread devono condividere le arena, portando a potenziali contention.

A differenza della main arena, che si espande usando la system call brk, le secondary arenas creano “subheaps” usando mmap e mprotect per simulare il comportamento dell’heap, permettendo flessibilità nella gestione della memoria per operazioni multithread.

Subheaps

I subheaps fungono da riserva di memoria per le secondary arenas nelle applicazioni multithread, permettendo loro di crescere e gestire le proprie regioni di heap separate dall’heap principale. Ecco come i subheaps differiscono dall’heap iniziale e come operano:

  1. Heap iniziale vs Subheaps:
  • L’heap iniziale è situato direttamente dopo il binario del programma in memoria e si espande usando la system call sbrk.
  • I Subheaps, usati dalle secondary arenas, sono creati tramite mmap, una system call che mappa una regione di memoria specificata.
  1. Riserva di memoria con mmap:
  • Quando il heap manager crea un subheap, riserva un grande blocco di memoria tramite mmap. Questa riserva non alloca memoria immediatamente; designa semplicemente una regione che altri processi di sistema o allocazioni non dovrebbero usare.
  • Per default, la dimensione riservata per un subheap è 1 MB per processi a 32 bit e 64 MB per processi a 64 bit.
  1. Espansione graduale con mprotect:
  • La regione di memoria riservata è inizialmente marcata come PROT_NONE, indicando che il kernel non deve ancora allocare memoria fisica per questo spazio.
  • Per “far crescere” il subheap, il heap manager usa mprotect per cambiare i permessi delle pagine da PROT_NONE a PROT_READ | PROT_WRITE, inducendo il kernel ad allocare memoria fisica agli indirizzi precedentemente riservati. Questo approccio graduale permette al subheap di espandersi secondo necessità.
  • Una volta che l’intero subheap è esaurito, il heap manager crea un nuovo subheap per continuare le allocazioni.

heap_info

Questa struct contiene informazioni rilevanti sull’heap. Inoltre, la memoria heap potrebbe non essere continua dopo ulteriori allocazioni; questa struct memorizzerà anche tale informazione.

// From https://github.com/bminor/glibc/blob/a07e000e82cb71238259e674529c37c12dc7d423/malloc/arena.c#L837

typedef struct _heap_info
{
mstate ar_ptr; /* Arena for this heap. */
struct _heap_info *prev; /* Previous heap. */
size_t size;   /* Current size in bytes. */
size_t mprotect_size; /* Size in bytes that has been mprotected
PROT_READ|PROT_WRITE.  */
size_t pagesize; /* Page size used when allocating the arena.  */
/* Make sure the following data is properly aligned, particularly
that sizeof (heap_info) + 2 * SIZE_SZ is a multiple of
MALLOC_ALIGNMENT. */
char pad[-3 * SIZE_SZ & MALLOC_ALIGN_MASK];
} heap_info;

malloc_state

Ogni heap (main arena o gli altri arena dei thread) ha una malloc_state struttura.
È importante notare che la struttura main arena malloc_state è una variabile globale nella libc (quindi situata nello spazio di memoria della libc).
Nel caso delle strutture malloc_state degli heap dei thread, sono situate all’interno dello “heap” del thread stesso.

Ci sono alcune cose interessanti da notare su questa struttura (vedi codice C sotto):

  • __libc_lock_define (, mutex); Serve a garantire che questa struttura dell’heap sia accessibile da un solo thread alla volta

  • Flags:

#define NONCONTIGUOUS_BIT (2U)

#define contiguous(M) (((M)->flags & NONCONTIGUOUS_BIT) == 0) #define noncontiguous(M) (((M)->flags & NONCONTIGUOUS_BIT) != 0) #define set_noncontiguous(M) ((M)->flags |= NONCONTIGUOUS_BIT) #define set_contiguous(M) ((M)->flags &= ~NONCONTIGUOUS_BIT)


- The `mchunkptr bins[NBINS * 2 - 2];` contiene **puntatori** ai **primo e ultimo chunk** dei bins small, large e unsorted (il -2 è perché l'indice 0 non è usato)
- Di conseguenza, il **primo chunk** di questi bins avrà un **puntatore all'indietro a questa struttura** e l'**ultimo chunk** di questi bins avrà un **puntatore in avanti** a questa struttura. Questo significa, in pratica, che se puoi leak questi indirizzi nella main arena avrai un puntatore alla struttura nella **libc**.
- Le struct `struct malloc_state *next;` e `struct malloc_state *next_free;` sono liste concatenate di arena
- Il chunk `top` è l'ultimo "chunk", che è fondamentalmente **tutto lo spazio rimanente dell'heap**. Una volta che il top chunk è "vuoto", l'heap è completamente utilizzato e deve richiedere altro spazio.
- Il chunk `last reminder` proviene dai casi in cui non è disponibile un chunk di dimensione esatta e quindi un chunk più grande viene diviso; la parte rimanente viene collocata qui.
```c
// From https://github.com/bminor/glibc/blob/a07e000e82cb71238259e674529c37c12dc7d423/malloc/malloc.c#L1812

struct malloc_state
{
/* Serialize access.  */
__libc_lock_define (, mutex);

/* Flags (formerly in max_fast).  */
int flags;

/* Set if the fastbin chunks contain recently inserted free blocks.  */
/* Note this is a bool but not all targets support atomics on booleans.  */
int have_fastchunks;

/* Fastbins */
mfastbinptr fastbinsY[NFASTBINS];

/* Base of the topmost chunk -- not otherwise kept in a bin */
mchunkptr top;

/* The remainder from the most recent split of a small request */
mchunkptr last_remainder;

/* Normal bins packed as described above */
mchunkptr bins[NBINS * 2 - 2];

/* Bitmap of bins */
unsigned int binmap[BINMAPSIZE];

/* Linked list */
struct malloc_state *next;

/* Linked list for free arenas.  Access to this field is serialized
by free_list_lock in arena.c.  */
struct malloc_state *next_free;

/* Number of threads attached to this arena.  0 if the arena is on
the free list.  Access to this field is serialized by
free_list_lock in arena.c.  */
INTERNAL_SIZE_T attached_threads;

/* Memory allocated from the system in this arena.  */
INTERNAL_SIZE_T system_mem;
INTERNAL_SIZE_T max_system_mem;
};

malloc_chunk

Questa struttura rappresenta un particolare chunk di memoria. I vari campi hanno significati diversi per i chunk allocati e non allocati.

// https://github.com/bminor/glibc/blob/master/malloc/malloc.c
struct malloc_chunk {
INTERNAL_SIZE_T      mchunk_prev_size;  /* Size of previous chunk, if it is free. */
INTERNAL_SIZE_T      mchunk_size;       /* Size in bytes, including overhead. */
struct malloc_chunk* fd;                /* double links -- used only if this chunk is free. */
struct malloc_chunk* bk;
/* Only used for large blocks: pointer to next larger size.  */
struct malloc_chunk* fd_nextsize; /* double links -- used only if this chunk is free. */
struct malloc_chunk* bk_nextsize;
};

typedef struct malloc_chunk* mchunkptr;

Come commentato precedentemente, questi chunk hanno anche alcuni metadata, ben rappresentati in questa immagine:

https://azeria-labs.com/wp-content/uploads/2019/03/chunk-allocated-CS.png

I metadata sono solitamente 0x08B che indicano la dimensione corrente del chunk usando gli ultimi 3 bit per indicare:

  • A: Se 1 proviene da una subheap, se 0 è nell’arena principale
  • M: Se 1, questo chunk fa parte di uno spazio allocato con mmap e non di un heap
  • P: Se 1, il chunk precedente è in uso

Poi, lo spazio per i dati utente, e infine 0x08B per indicare la dimensione del chunk precedente quando il chunk è disponibile (o per memorizzare dati utente quando è allocato).

Inoltre, quando disponibile, i dati utente vengono usati anche per contenere alcuni campi:

  • fd: Puntatore al chunk successivo
  • bk: Puntatore al chunk precedente
  • fd_nextsize: Puntatore al primo chunk nella lista che è più piccolo di esso
  • bk_nextsize: Puntatore al primo chunk nella lista che è più grande di esso

https://azeria-labs.com/wp-content/uploads/2019/03/chunk-allocated-CS.png

Tip

Nota come collegare la lista in questo modo evita la necessità di avere un array in cui ogni singolo chunk viene registrato.

Puntatori dei Chunk

Quando si usa malloc viene restituito un puntatore al contenuto su cui è possibile scrivere (immediatamente dopo gli header), tuttavia, quando si gestiscono i chunk, è necessario un puntatore all’inizio degli header (metadata).
Per queste conversioni vengono usate queste funzioni:

// https://github.com/bminor/glibc/blob/master/malloc/malloc.c

/* Convert a chunk address to a user mem pointer without correcting the tag.  */
#define chunk2mem(p) ((void*)((char*)(p) + CHUNK_HDR_SZ))

/* Convert a user mem pointer to a chunk address and extract the right tag.  */
#define mem2chunk(mem) ((mchunkptr)tag_at (((char*)(mem) - CHUNK_HDR_SZ)))

/* The smallest possible chunk */
#define MIN_CHUNK_SIZE        (offsetof(struct malloc_chunk, fd_nextsize))

/* The smallest size we can malloc is an aligned minimal chunk */

#define MINSIZE  \
(unsigned long)(((MIN_CHUNK_SIZE+MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK))

Allineamento & min size

Il puntatore al chunk e 0x0f devono essere 0.

// From https://github.com/bminor/glibc/blob/a07e000e82cb71238259e674529c37c12dc7d423/sysdeps/generic/malloc-size.h#L61
#define MALLOC_ALIGN_MASK (MALLOC_ALIGNMENT - 1)

// https://github.com/bminor/glibc/blob/a07e000e82cb71238259e674529c37c12dc7d423/sysdeps/i386/malloc-alignment.h
#define MALLOC_ALIGNMENT 16


// https://github.com/bminor/glibc/blob/master/malloc/malloc.c
/* Check if m has acceptable alignment */
#define aligned_OK(m)  (((unsigned long)(m) & MALLOC_ALIGN_MASK) == 0)

#define misaligned_chunk(p) \
((uintptr_t)(MALLOC_ALIGNMENT == CHUNK_HDR_SZ ? (p) : chunk2mem (p)) \
& MALLOC_ALIGN_MASK)


/* pad request bytes into a usable size -- internal version */
/* Note: This must be a macro that evaluates to a compile time constant
if passed a literal constant.  */
#define request2size(req)                                         \
(((req) + SIZE_SZ + MALLOC_ALIGN_MASK < MINSIZE)  ?             \
MINSIZE :                                                      \
((req) + SIZE_SZ + MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK)

/* Check if REQ overflows when padded and aligned and if the resulting
value is less than PTRDIFF_T.  Returns the requested size or
MINSIZE in case the value is less than MINSIZE, or 0 if any of the
previous checks fail.  */
static inline size_t
checked_request2size (size_t req) __nonnull (1)
{
if (__glibc_unlikely (req > PTRDIFF_MAX))
return 0;

/* When using tagged memory, we cannot share the end of the user
block with the header for the next chunk, so ensure that we
allocate blocks that are rounded up to the granule size.  Take
care not to overflow from close to MAX_SIZE_T to a small
number.  Ideally, this would be part of request2size(), but that
must be a macro that produces a compile time constant if passed
a constant literal.  */
if (__glibc_unlikely (mtag_enabled))
{
/* Ensure this is not evaluated if !mtag_enabled, see gcc PR 99551.  */
asm ("");

req = (req + (__MTAG_GRANULE_SIZE - 1)) &
~(size_t)(__MTAG_GRANULE_SIZE - 1);
}

return request2size (req);
}

Nota che per calcolare lo spazio totale necessario viene aggiunto SIZE_SZ una sola volta perché il campo prev_size può essere usato per memorizzare dati; pertanto è necessario soltanto l’header iniziale.

Ottenere i dati di un chunk e modificare i metadata

Queste funzioni funzionano ricevendo un pointer a un chunk e sono utili per verificare/impostare i metadata:

  • Verificare le flags del chunk
// From https://github.com/bminor/glibc/blob/master/malloc/malloc.c


/* size field is or'ed with PREV_INUSE when previous adjacent chunk in use */
#define PREV_INUSE 0x1

/* extract inuse bit of previous chunk */
#define prev_inuse(p)       ((p)->mchunk_size & PREV_INUSE)


/* size field is or'ed with IS_MMAPPED if the chunk was obtained with mmap() */
#define IS_MMAPPED 0x2

/* check for mmap()'ed chunk */
#define chunk_is_mmapped(p) ((p)->mchunk_size & IS_MMAPPED)


/* size field is or'ed with NON_MAIN_ARENA if the chunk was obtained
from a non-main arena.  This is only set immediately before handing
the chunk to the user, if necessary.  */
#define NON_MAIN_ARENA 0x4

/* Check for chunk from main arena.  */
#define chunk_main_arena(p) (((p)->mchunk_size & NON_MAIN_ARENA) == 0)

/* Mark a chunk as not being on the main arena.  */
#define set_non_main_arena(p) ((p)->mchunk_size |= NON_MAIN_ARENA)
  • Dimensioni e pointers ad altri chunks
/*
Bits to mask off when extracting size

Note: IS_MMAPPED is intentionally not masked off from size field in
macros for which mmapped chunks should never be seen. This should
cause helpful core dumps to occur if it is tried by accident by
people extending or adapting this malloc.
*/
#define SIZE_BITS (PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)

/* Get size, ignoring use bits */
#define chunksize(p) (chunksize_nomask (p) & ~(SIZE_BITS))

/* Like chunksize, but do not mask SIZE_BITS.  */
#define chunksize_nomask(p)         ((p)->mchunk_size)

/* Ptr to next physical malloc_chunk. */
#define next_chunk(p) ((mchunkptr) (((char *) (p)) + chunksize (p)))

/* Size of the chunk below P.  Only valid if !prev_inuse (P).  */
#define prev_size(p) ((p)->mchunk_prev_size)

/* Set the size of the chunk below P.  Only valid if !prev_inuse (P).  */
#define set_prev_size(p, sz) ((p)->mchunk_prev_size = (sz))

/* Ptr to previous physical malloc_chunk.  Only valid if !prev_inuse (P).  */
#define prev_chunk(p) ((mchunkptr) (((char *) (p)) - prev_size (p)))

/* Treat space at ptr + offset as a chunk */
#define chunk_at_offset(p, s)  ((mchunkptr) (((char *) (p)) + (s)))
  • bit in uso
/* extract p's inuse bit */
#define inuse(p)							      \
((((mchunkptr) (((char *) (p)) + chunksize (p)))->mchunk_size) & PREV_INUSE)

/* set/clear chunk as being inuse without otherwise disturbing */
#define set_inuse(p)							      \
((mchunkptr) (((char *) (p)) + chunksize (p)))->mchunk_size |= PREV_INUSE

#define clear_inuse(p)							      \
((mchunkptr) (((char *) (p)) + chunksize (p)))->mchunk_size &= ~(PREV_INUSE)


/* check/set/clear inuse bits in known places */
#define inuse_bit_at_offset(p, s)					      \
(((mchunkptr) (((char *) (p)) + (s)))->mchunk_size & PREV_INUSE)

#define set_inuse_bit_at_offset(p, s)					      \
(((mchunkptr) (((char *) (p)) + (s)))->mchunk_size |= PREV_INUSE)

#define clear_inuse_bit_at_offset(p, s)					      \
(((mchunkptr) (((char *) (p)) + (s)))->mchunk_size &= ~(PREV_INUSE))
  • Imposta head e footer (quando chunk nos in uso
/* Set size at head, without disturbing its use bit */
#define set_head_size(p, s)  ((p)->mchunk_size = (((p)->mchunk_size & SIZE_BITS) | (s)))

/* Set size/use field */
#define set_head(p, s)       ((p)->mchunk_size = (s))

/* Set size at footer (only when chunk is not in use) */
#define set_foot(p, s)       (((mchunkptr) ((char *) (p) + (s)))->mchunk_prev_size = (s))
  • Ottieni la dimensione dei dati effettivamente utilizzabili all’interno del chunk
#pragma GCC poison mchunk_size
#pragma GCC poison mchunk_prev_size

/* This is the size of the real usable data in the chunk.  Not valid for
dumped heap chunks.  */
#define memsize(p)                                                    \
(__MTAG_GRANULE_SIZE > SIZE_SZ && __glibc_unlikely (mtag_enabled) ? \
chunksize (p) - CHUNK_HDR_SZ :                                    \
chunksize (p) - CHUNK_HDR_SZ + (chunk_is_mmapped (p) ? 0 : SIZE_SZ))

/* If memory tagging is enabled the layout changes to accommodate the granule
size, this is wasteful for small allocations so not done by default.
Both the chunk header and user data has to be granule aligned.  */
_Static_assert (__MTAG_GRANULE_SIZE <= CHUNK_HDR_SZ,
"memory tagging is not supported with large granule.");

static __always_inline void *
tag_new_usable (void *ptr)
{
if (__glibc_unlikely (mtag_enabled) && ptr)
{
mchunkptr cp = mem2chunk(ptr);
ptr = __libc_mtag_tag_region (__libc_mtag_new_tag (ptr), memsize (cp));
}
return ptr;
}

Esempi

Esempio rapido di Heap

Esempio rapido di heap da https://guyinatuxedo.github.io/25-heap/index.html ma in arm64:

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

void main(void)
{
char *ptr;
ptr = malloc(0x10);
strcpy(ptr, "panda");
}

Imposta un breakpoint alla fine della funzione main e scopriamo dove sono state memorizzate le informazioni:

È possibile vedere che la stringa panda è stata memorizzata a 0xaaaaaaac12a0 (che era l’indirizzo restituito da malloc dentro x0). Controllando 0x10 byte prima, è possibile vedere che 0x0 rappresenta che il chunk precedente non è utilizzato (lunghezza 0) e che la lunghezza di questo chunk è 0x21.

Lo spazio extra riservato (0x21-0x10=0x11) deriva dagli header aggiunti (0x10) e 0x1 non significa che sia stato riservato 0x21B, ma che gli ultimi 3 bit della lunghezza dell’header corrente hanno alcuni significati speciali. Poiché la lunghezza è sempre allineata a 16 byte (nelle macchine a 64 bit), questi bit in realtà non verranno mai usati nel valore della lunghezza.

0x1:     Previous in Use     - Specifies that the chunk before it in memory is in use
0x2:     Is MMAPPED          - Specifies that the chunk was obtained with mmap()
0x4:     Non Main Arena      - Specifies that the chunk was obtained from outside of the main arena

Esempio di multithreading

Multithreading ```c #include #include #include #include #include

void* threadFuncMalloc(void* arg) { printf(“Hello from thread 1\n”); char* addr = (char*) malloc(1000); printf(“After malloc and before free in thread 1\n”); free(addr); printf(“After free in thread 1\n”); }

void* threadFuncNoMalloc(void* arg) { printf(“Hello from thread 2\n”); }

int main() { pthread_t t1; void* s; int ret; char* addr;

printf(“Before creating thread 1\n”); getchar(); ret = pthread_create(&t1, NULL, threadFuncMalloc, NULL); getchar();

printf(“Before creating thread 2\n”); ret = pthread_create(&t1, NULL, threadFuncNoMalloc, NULL);

printf(“Before exit\n”); getchar();

return 0; }

</details>

Con il Debugging dell'esempio precedente è possibile vedere come all'inizio ci sia solo 1 arena:

<figure><img src="../../images/image (1) (1) (1) (1) (1) (1) (1) (1) (1).png" alt=""><figcaption></figcaption></figure>

Poi, dopo aver chiamato il primo thread, quello che chiama malloc, viene creata una nuova arena:

<figure><img src="../../images/image (1) (1) (1) (1) (1) (1) (1) (1) (1) (1).png" alt=""><figcaption></figcaption></figure>

e al suo interno si possono trovare alcuni chunks:

<figure><img src="../../images/image (2) (1) (1) (1) (1) (1).png" alt=""><figcaption></figcaption></figure>

## Bins & Memory Allocations/Frees

Controlla quali sono i bins, come sono organizzati e come la memoria viene allocata e liberata in:


<a class="content_ref" href="bins-and-memory-allocations.md"><span class="content_ref_label">Bins & Memory Allocations</span></a>

## Heap Functions Security Checks

Le funzioni coinvolte nell'heap eseguiranno determinati controlli prima di svolgere le loro azioni per cercare di assicurarsi che l'heap non sia stato corrotto:


<a class="content_ref" href="heap-memory-functions/heap-functions-security-checks.md"><span class="content_ref_label">Heap Functions Security Checks</span></a>

## musl mallocng exploitation notes (Alpine)

- **Slab group/slot grooming for huge linear copies:** mallocng sizeclasses use mmap()'d groups whose slots are fully `munmap()`'d when empty. Per copie lineari lunghe (~0x15555555 bytes), mantieni lo span mappato (evita buchi provenienti da gruppi rilasciati) e posiziona la victim allocation adiacente allo source slot.
- **Cycling offset mitigation:** In caso di riuso degli slot mallocng può avanzare l'inizio dei dati utente di multipli di `UNIT` (0x10) quando lo slack ospita un header extra di 4 byte. Questo sposta gli overwrite offsets (es., LSB pointer hits) a meno che tu non controlli i conteggi di riuso o non ti limiti a stride senza slack (es., gli oggetti Lua `Table` a stride 0x50 mostrano offset 0). Ispeziona gli offset con `mchunkinfo` di muslheap:
```gdb
pwndbg> mchunkinfo 0x7ffff7a94e40
... stride: 0x140
... cycling offset : 0x1 (userdata --> 0x7ffff7a94e40)
  • Prefer runtime-object corruption over allocator metadata: mallocng mixes cookies/guarded out-of-band metadata, quindi prendi di mira oggetti di livello superiore. In Redis’s Lua 5.1, Table->array points to an array of TValue tagged values; overwriting the LSB of a pointer in TValue->value (e.g., with the JSON terminator byte 0x22) can pivot references without touching malloc metadata.
  • Debugging stripped/static Lua on Alpine: Compila una Lua corrispondente, elenca i simboli con readelf -Ws, strippa i simboli di funzione via objcopy --strip-symbol per esporre i layout delle struct in GDB, poi usa pretty-printers compatibili con Lua (GdbLuaExtension per Lua 5.1) insieme a muslheap per controllare i valori di stride/reserved/cycling-offset prima di innescare l’overflow.

Casi di studio

Studia primitive specifiche dell’allocatore derivate da bug reali:

Virtualbox Slirp Nat Packet Heap Exploitation

Gnu Obstack Function Pointer Hijack

Riferimenti

Tip

Impara e pratica il hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Impara e pratica il hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporta HackTricks