hacktricks/binary-exploitation/heap/README.md

13 KiB

Heap

Concetti di Heap

L'heap è essenzialmente il luogo in cui un programma sarà in grado di memorizzare dati quando richiede dati 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 di Chunk di Base

Quando viene richiesto di memorizzare alcuni dati nell'heap, viene allocato uno spazio nell'heap. Questo spazio apparterrà a un bin e solo i dati richiesti + lo spazio degli header del bin + l'offset della dimensione minima del bin verranno riservati per il chunk. L'obiettivo è riservare la memoria minima possibile senza rendere complicato trovare dove si trova ciascun chunk. Per questo, le informazioni sui metadati del chunk vengono utilizzate per sapere dove si trova ciascun chunk utilizzato/libero.

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

  • Il programma inizia richiedendo una certa quantità di memoria.
  • Se nella lista dei chunk c'è qualcuno abbastanza grande da soddisfare la richiesta, verrà utilizzato.
  • Questo potrebbe significare che parte del chunk disponibile verrà utilizzata per questa richiesta e il resto verrà aggiunto alla lista dei chunk.
  • Se non c'è alcun chunk disponibile nella lista ma c'è ancora spazio nella memoria dell'heap allocata, il gestore dell'heap crea un nuovo chunk.
  • Se non c'è abbastanza spazio nell'heap per allocare il nuovo chunk, il gestore dell'heap chiede al kernel di espandere la memoria allocata all'heap e quindi utilizza questa memoria per generare il nuovo chunk.
  • Se tutto fallisce, malloc restituisce null.

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

Aree

Nelle applicazioni multithread, il gestore dell'heap deve prevenire le condizioni di gara che potrebbero portare a crash. Inizialmente, ciò veniva fatto utilizzando un mutex globale per garantire che solo un thread potesse accedere all'heap alla volta, ma ciò causava problemi di prestazioni a causa del collo di bottiglia indotto dal mutex.

Per affrontare questo problema, l'allocatore dell'heap ptmalloc2 ha introdotto "arene", dove ogni arena funge da un heap separato con le sue proprie strutture dati e mutex, consentendo a più thread di eseguire operazioni sull'heap senza interferire l'uno con l'altro, purché utilizzino arene diverse.

L'arena "principale" predefinita gestisce le operazioni sull'heap per le applicazioni single-threaded. Quando vengono aggiunti nuovi thread, il gestore dell'heap assegna loro arene secondarie per ridurre la contesa. Prima tenta di collegare ciascun nuovo thread a un'arena inutilizzata, creandone di nuove se necessario, fino a un limite di 2 volte i core della CPU per i sistemi a 32 bit e 8 volte per i sistemi a 64 bit. Una volta raggiunto il limite, i thread devono condividere le arene, portando a una potenziale contesa.

A differenza dell'arena principale, che si espande utilizzando la chiamata di sistema brk, le arene secondarie creano "subheap" utilizzando mmap e mprotect per simulare il comportamento dell'heap, consentendo flessibilità nella gestione della memoria per operazioni multithread.

Subheap

I subheap fungono da riserve di memoria per le arene secondarie nelle applicazioni multithread, consentendo loro di crescere e gestire le proprie regioni di heap separatamente dall'heap principale. Ecco come i subheap differiscono dall'heap iniziale e come operano:

  1. Heap Iniziale vs. Subheap:
  • L'heap iniziale si trova direttamente dopo il binario del programma in memoria e si espande utilizzando la chiamata di sistema sbrk.
  • I subheap, utilizzati dalle arene secondarie, vengono creati tramite mmap, una chiamata di sistema che mappa una regione di memoria specificata.
  1. Riserva di Memoria con mmap:
  • Quando il gestore dell'heap crea un subheap, riserva un grande blocco di memoria tramite mmap. Questa riserva non alloca immediatamente memoria; semplicemente designa una regione che altri processi di sistema o allocazioni non dovrebbero utilizzare.
  • Per impostazione predefinita, la dimensione riservata per un subheap è di 1 MB per i processi a 32 bit e 64 MB per i processi a 64 bit.
  1. Espansione Graduale con mprotect:
  • La regione di memoria riservata è inizialmente contrassegnata come PROT_NONE, indicando che il kernel non deve ancora allocare memoria fisica a questo spazio.
  • Per "far crescere" il subheap, il gestore dell'heap utilizza mprotect per cambiare le autorizzazioni della pagina da PROT_NONE a PROT_READ | PROT_WRITE, spingendo il kernel ad allocare memoria fisica agli indirizzi precedentemente riservati. Questo approccio step-by-step consente al subheap di espandersi secondo necessità.
  • Una volta esaurito l'intero subheap, il gestore dell'heap crea un nuovo subheap per continuare l'allocazione.

malloc_state

Ogni heap (arena principale o arene di altri thread) ha una struttura malloc_state.
È importante notare che la struttura malloc_state dell'arena principale è 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 del "heap" del thread stesso.

Ci sono alcune cose interessanti da notare da questa struttura (vedi codice C di seguito):

  • Il mchunkptr bins[NBINS * 2 - 2]; contiene puntatori ai primi e ultimi chunk dei bins piccoli, grandi e non ordinati (il -2 è perché l'indice 0 non viene utilizzato)
  • Pertanto, il primo chunk di questi bin avrà un puntatore all'indietro a questa struttura e l'ultimo chunk di questi bin avrà un puntatore in avanti a questa struttura. Ciò significa fondamentalmente che se è possibile rilevare questi indirizzi nell'arena principale, si avrà un puntatore alla struttura nella libc.
  • Le strutture struct malloc_state *next; e struct malloc_state *next_free; sono liste collegate di arene
  • Il chunk top è l'ultimo "chunk", che è fondamentalmente tutto lo spazio rimanente dell'heap. Una volta che il chunk top è "vuoto", l'heap è completamente utilizzato e ha bisogno di richiedere più spazio.
  • L'ultimo chunk di rimanenza proviene dai casi in cui non è disponibile un chunk di dimensioni esatte e quindi viene diviso un chunk più grande, una parte rimanente del puntatore viene posizionata qui.
// From https://heap-exploitation.dhavalkapil.com/diving_into_glibc_heap/malloc_state
struct malloc_state
{
/* Serialize access.  */
__libc_lock_define (, mutex);
/* Flags (formerly in max_fast).  */
int flags;

/* 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;
};

typedef struct malloc_state *mstate;

malloc_chunk

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

// From https://heap-exploitation.dhavalkapil.com/diving_into_glibc_heap/malloc_chunk
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 già commentato in precedenza, questi chunk hanno anche alcuni metadati, molto ben rappresentati in questa immagine:

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

Di solito i metadati sono 0x08B, indicando la dimensione corrente del chunk utilizzando gli ultimi 3 bit per indicare:

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

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

Inoltre, quando disponibili, i dati dell'utente vengono utilizzati anche per contenere alcune informazioni:

  • Puntatore al chunk successivo
  • Puntatore al chunk precedente
  • Dimensione del prossimo chunk nella lista
  • Dimensione del chunk precedente nella lista

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

{% hint style="info" %} Nota come organizzare la lista in questo modo evita la necessità di avere un array in cui ogni singolo chunk viene registrato. {% endhint %}

Esempio Veloce di Heap

Esempio veloce 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 principale e scopriamo dove sono state memorizzate le informazioni:

È possibile vedere che la stringa panda è stata memorizzata a 0xaaaaaaac12a0 (che è stato l'indirizzo restituito da malloc all'interno di x0). Controllando 0x10 byte prima è possibile vedere che il 0x0 rappresenta che il chunk precedente non è in uso (lunghezza 0) e che la lunghezza di questo chunk è 0x21.

Gli spazi extra riservati (0x21-0x10=0x11) provengono dagli header aggiunti (0x10) e 0x1 non significa che sono stati riservati 0x21 byte ma gli ultimi 3 bit della lunghezza dell'header corrente hanno alcuni significati speciali. Poiché la lunghezza è sempre allineata su 16 byte (nelle macchine a 64 bit), questi bit non verranno mai utilizzati dal numero di 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

Bins e Assegnazioni/Liberazioni di Memoria

Verifica quali sono i bins e come sono organizzati e come la memoria viene assegnata e liberata in:

{% content-ref url="bins-and-memory-allocations.md" %} bins-and-memory-allocations.md {% endcontent-ref %}

Controlli di Sicurezza delle Funzioni di Heap

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

{% content-ref url="heap-functions-security-checks.md" %} heap-functions-security-checks.md {% endcontent-ref %}

Riferimenti