Libc Heap
Tip
Lernen & üben Sie AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Lernen & üben Sie Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Unterstützen Sie HackTricks
- Überprüfen Sie die Abonnementpläne!
- Treten Sie der 💬 Discord-Gruppe oder der Telegram-Gruppe bei oder folgen Sie uns auf Twitter 🐦 @hacktricks_live.
- Teilen Sie Hacking-Tricks, indem Sie PRs an die HackTricks und HackTricks Cloud GitHub-Repos senden.
Heap-Grundlagen
Der Heap ist im Grunde der Bereich, in dem ein Programm Daten ablegt, wenn es per Funktionsaufruf wie malloc, calloc… Speicher anfordert. Wenn dieser Speicher nicht mehr benötigt wird, wird er durch den Aufruf der Funktion free wieder freigegeben.
Wie gezeigt befindet er sich direkt hinter der Stelle, an der das Binary im Speicher geladen wird (siehe den Abschnitt [heap]):
.png)
Grundlegende Chunk-Allokation
Wenn Daten im Heap gespeichert werden sollen, wird dafür ein Bereich des Heaps zugewiesen. Dieser Bereich gehört zu einem bin und es werden nur die angeforderten Daten + der Platz für die bin-Header + die minimale bin-Größen-Offset für den Chunk reserviert. Ziel ist es, so wenig Speicher wie möglich zu reservieren, ohne die Lokalisierung der einzelnen Chunks zu erschweren. Dazu werden Metadaten des Chunks verwendet, um zu wissen, wo sich belegte/freie Chunks befinden.
Es gibt verschiedene Möglichkeiten, den Platz zu reservieren, hauptsächlich abhängig vom verwendeten bin, aber eine allgemeine Vorgehensweise ist die folgende:
- Das Programm beginnt mit der Anforderung einer bestimmten Menge an Speicher.
- Wenn in der Liste der Chunks ein ausreichend großer verfügbarer Chunk vorhanden ist, wird dieser verwendet.
- Das kann bedeuten, dass ein Teil des verfügbaren Chunks für diese Anforderung genutzt wird und der Rest der Chunk-Liste hinzugefügt wird.
- Wenn in der Liste kein verfügbarer Chunk vorhanden ist, aber noch Platz im bereits allozierten Heap-Speicher vorhanden ist, erzeugt der Heap-Manager einen neuen Chunk.
- Wenn nicht genug Heap-Speicher vorhanden ist, um den neuen Chunk anzulegen, fordert der Heap-Manager das Kernel auf, den dem Heap zugewiesenen Speicher zu vergrößern, und verwendet dann diesen Speicher für den neuen Chunk.
- Wenn alles fehlschlägt, gibt
mallocnull zurück.
Beachte, dass bei Überschreiten einer bestimmten Größenschwelle mmap verwendet wird, um den angeforderten Speicher zu mappen.
Arenas
In multithreaded Anwendungen muss der Heap-Manager race conditions verhindern, die zu Abstürzen führen könnten. Anfangs geschah dies durch einen globalen mutex, der sicherstellte, dass jeweils nur ein Thread auf den Heap zugreifen konnte, jedoch führte dies aufgrund des durch den Mutex verursachten Engpasses zu Performance-Problemen.
Um dem entgegenzuwirken führte der ptmalloc2-Heap-Allocator sogenannte “arenas” ein, bei denen jede arena als separater Heap mit seinen eigenen Datenstrukturen und eigenem mutex fungiert, sodass mehrere Threads Heap-Operationen ausführen können, ohne sich gegenseitig zu stören, solange sie unterschiedliche arenas verwenden.
Die standardmäßige “main” arena übernimmt die Heap-Operationen für Single-Thread-Anwendungen. Wenn neue Threads hinzugefügt werden, weist der Heap-Manager ihnen secondary arenas zu, um die Konkurrenz um Ressourcen zu reduzieren. Er versucht zunächst, jeden neuen Thread an eine ungenutzte arena anzuhängen und erzeugt bei Bedarf neue, bis zu einem Limit von 2× der Anzahl der CPU-Kerne bei 32-Bit-Systemen bzw. 8× bei 64-Bit-Systemen. Sobald das Limit erreicht ist, müssen Threads arenas teilen, was zu möglicher Konkurrenz führt.
Im Gegensatz zur main-arena, die sich mittels des Systemaufrufs brk erweitert, erstellen secondary arenas “subheaps” mithilfe von mmap und mprotect, um das Verhalten des Heaps zu simulieren, was Flexibilität bei der Speicherverwaltung für multithreaded Operationen ermöglicht.
Subheaps
Subheaps dienen als Speicherreserve für secondary arenas in multithreaded Anwendungen und erlauben es ihnen, zu wachsen und ihre eigenen Heap-Regionen getrennt vom main Heap zu verwalten. So unterscheiden sich Subheaps vom initialen Heap und so funktionieren sie:
- Initial Heap vs. Subheaps:
- Der initiale Heap befindet sich direkt hinter dem Binary des Programms im Speicher und erweitert sich mittels des Systemaufrufs
sbrk. - Subheaps, die von secondary arenas verwendet werden, werden über
mmaperzeugt — ein Systemaufruf, der einen bestimmten Speicherbereich mapped.
- Memory Reservation with
mmap:
- Wenn der Heap-Manager einen Subheap erzeugt, reserviert er einen großen Speicherblock mittels
mmap. Diese Reservierung allokiert nicht sofort physischen Speicher; sie kennzeichnet lediglich einen Bereich, den andere Systemprozesse oder Allokationen nicht verwenden sollten. - Standardmäßig beträgt die reservierte Größe für einen Subheap 1 MB bei 32-Bit-Prozessen und 64 MB bei 64-Bit-Prozessen.
- Gradual Expansion with
mprotect:
- Der reservierte Speicherbereich wird initial mit
PROT_NONEmarkiert, was bedeutet, dass der Kernel diesem Bereich noch keinen physischen Speicher zuweisen muss. - Um den Subheap zu “vergrößern”, verwendet der Heap-Manager
mprotect, um die Seitenberechtigungen vonPROT_NONEaufPROT_READ | PROT_WRITEzu ändern, wodurch der Kernel physischen Speicher für die zuvor reservierten Adressen allokiert. Dieser schrittweise Ansatz erlaubt es dem Subheap, bei Bedarf zu wachsen. - Sobald der gesamte Subheap erschöpft ist, erzeugt der Heap-Manager einen neuen Subheap, um die Allokation fortzusetzen.
heap_info
Dieses struct enthält relevante Informationen zum Heap. Außerdem ist Heap-Speicher nach mehreren Allokationen möglicherweise nicht durchgängig; dieses struct speichert auch diese Information.
// 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
Jeder heap (main arena oder other threads arenas) hat eine malloc_state Struktur.
Es ist wichtig zu beachten, dass die main arena malloc_state Struktur eine globale Variable in der libc ist (daher im libc memory space gelegen).
Im Fall der malloc_state Strukturen der heaps von Threads befinden sie sich innerhalb des jeweiligen Thread “heap”.
Es gibt einige interessante Dinge aus dieser Struktur zu beachten (siehe C-Code unten):
-
__libc_lock_define (, mutex);dient dazu sicherzustellen, dass auf diese Struktur vom heap jeweils nur ein Thread gleichzeitig zugreift -
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];` enthält **pointers** zu den **first and last chunks** der small, large und unsorted **bins** (das -2 ist, weil Index 0 nicht verwendet wird)
- Daher wird der **first chunk** dieser bins einen **backwards pointer to this structure** haben und der **last chunk** dieser bins einen **forward pointer** zu dieser Struktur. Was im Grunde bedeutet, dass wenn du l**eak these addresses in the main arena** kannst, du einen Pointer auf die Struktur in der **libc** haben wirst.
- Die structs `struct malloc_state *next;` und `struct malloc_state *next_free;` sind verkettete Listen von arenas
- Der `top` chunk ist der letzte "chunk", der im Grunde **den gesamten verbleibenden heap-Space** darstellt. Sobald der top chunk "empty" ist, ist der heap komplett belegt und es muss zusätzlicher Platz angefordert werden.
- Der `last reminder` chunk entsteht in Fällen, in denen kein Chunk mit exakter Größe verfügbar ist und daher ein größerer Chunk aufgeteilt wird; ein Pointer auf den verbleibenden Teil wird hier abgelegt.
```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
Diese Struktur repräsentiert einen bestimmten Chunk im Speicher. Die verschiedenen Felder haben unterschiedliche Bedeutungen für zugewiesene und nicht zugewiesene Chunks.
// 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;
Wie zuvor kommentiert, enthalten diese Chunks außerdem einige Metadaten, die in diesem Bild sehr gut dargestellt sind:
.png)
https://azeria-labs.com/wp-content/uploads/2019/03/chunk-allocated-CS.png
Die Metadaten sind üblicherweise 0x08B und geben die aktuelle Chunk-Größe an; die letzten 3 Bits haben folgende Bedeutungen:
A: Wenn 1, kommt es aus einem subheap; wenn 0, befindet es sich in der main arenaM: Wenn 1, gehört dieser Chunk zu einem Bereich, der mit mmap alloziert wurde und nicht zu einem heapP: Wenn 1, ist der vorherige Chunk in Benutzung
Dahinter folgt der Platz für die user data, und schließlich 0x08B, das die Größe des vorherigen Chunks angibt, wenn der Chunk verfügbar ist (oder zum Speichern von user data, wenn er alloziert ist).
Außerdem wird, wenn verfügbar, der user data-Bereich auch verwendet, um einige Pointer zu speichern:
fd: Zeiger auf den nächsten Chunkbk: Zeiger auf den vorherigen Chunkfd_nextsize: Zeiger auf den ersten Chunk in der Liste, der kleiner ist als er selbstbk_nextsize: Zeiger auf den ersten Chunk in der Liste, der größer ist als er selbst
.png)
https://azeria-labs.com/wp-content/uploads/2019/03/chunk-allocated-CS.png
Tip
Beachte, wie das Verknüpfen der Liste auf diese Weise die Notwendigkeit eines Arrays vermeidet, in dem jeder einzelne Chunk registriert werden müsste.
Chunk Pointers
When malloc is used a pointer to the content that can be written is returned (just after the headers), however, when managing chunks, it’s needed a pointer to the begining of the headers (metadata).
For these conversions these functions are used:
// 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))
Ausrichtung & Mindestgröße
Der pointer zum chunk und 0x0f müssen 0 sein.
// 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);
}
Beachte, dass zur Berechnung des benötigten Gesamtplatzes SIZE_SZ nur einmal hinzugefügt wird, weil das Feld prev_size zum Speichern von Daten verwendet werden kann; daher wird nur der initiale Header benötigt.
Chunk-Daten abrufen und Metadaten ändern
Diese Funktionen erhalten einen Pointer auf einen chunk und sind nützlich, um Metadaten zu prüfen/zu setzen:
- Chunk-Flags prüfen
// 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)
- Größen und Zeiger auf andere 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)))
- Insue Bit
/* 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))
- Setze head und footer (wenn chunk nos verwendet werden
/* 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))
- Ermittle die Größe der tatsächlich nutzbaren Daten im 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;
}
Beispiele
Schnelles Heap-Beispiel
Schnelles Heap-Beispiel von https://guyinatuxedo.github.io/25-heap/index.html aber in arm64:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void main(void)
{
char *ptr;
ptr = malloc(0x10);
strcpy(ptr, "panda");
}
Setze einen Breakpoint am Ende der main-Funktion und lass uns herausfinden, wo die Information gespeichert wurde:
.png)
Man kann sehen, dass der String panda bei 0xaaaaaaac12a0 gespeichert wurde (welche Adresse von malloc in x0 zurückgegeben wurde). Wenn man 0x10 Bytes davor prüft, sieht man, dass 0x0 bedeutet, dass der vorherige Chunk nicht verwendet wird (Länge 0) und dass die Länge dieses Chunks 0x21 ist.
Der zusätzlich reservierte Platz (0x21-0x10=0x11) kommt von den hinzugefügten Headern (0x10) und 0x1 bedeutet nicht, dass 0x21B reserviert wurde, sondern die letzten 3 Bits des Längenfeldes des aktuellen Header haben besondere Bedeutungen. Da die Länge immer 16-Byte-ausrichtet ist (in 64-Bit-Maschinen), werden diese Bits in der tatsächlichen Längenangabe nie verwendet.
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
Multithreading-Beispiel
Multithreading
```c #includevoid* 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>
Beim Debuggen des vorherigen Beispiels sieht man, dass es anfangs nur 1 arena gibt:
<figure><img src="../../images/image (1) (1) (1) (1) (1) (1) (1) (1) (1).png" alt=""><figcaption></figcaption></figure>
Dann, nach dem Aufruf des ersten thread, der malloc aufruft, wird eine neue arena erstellt:
<figure><img src="../../images/image (1) (1) (1) (1) (1) (1) (1) (1) (1) (1).png" alt=""><figcaption></figcaption></figure>
und darin sind einige chunks zu finden:
<figure><img src="../../images/image (2) (1) (1) (1) (1) (1).png" alt=""><figcaption></figcaption></figure>
## Bins & Memory Allocations/Frees
Untersuche, welche bins vorhanden sind, wie sie organisiert sind und wie memory alloziert und freed wird 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
Funktionen, die den heap betreffen, führen vor ihren Aktionen bestimmte Prüfungen durch, um sicherzustellen, dass der heap nicht korrumpiert ist:
<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 verwenden mmap()'d groups, deren slots bei Leerheit vollständig mit `munmap()` freigegeben werden. Für lange lineare Kopien (~0x15555555 bytes) sollte der Bereich gemappt bleiben (Lücken durch released groups vermeiden) und die victim allocation direkt neben dem source slot platziert werden.
- **Cycling offset mitigation:** Bei slot reuse kann mallocng den user-data-Start um Vielfache von `UNIT` (0x10) vorziehen, wenn im slack Platz für einen zusätzlichen 4-Byte-Header ist. Das verschiebt overwrite offsets (z. B. LSB pointer hits), es sei denn, du kontrollierst die reuse counts oder beschränkst dich auf strides ohne slack (z. B. zeigen Lua `Table` objects bei stride 0x50 Offset 0). Untersuche Offsets mit muslheap’s `mchunkinfo`:
```gdb
pwndbg> mchunkinfo 0x7ffff7a94e40
... stride: 0x140
... cycling offset : 0x1 (userdata --> 0x7ffff7a94e40)
- Bevorzuge Laufzeit-Objektkorruption statt Allocator-Metadaten: mallocng mischt cookies/geschützte Out-of-Band-Metadaten, also ziele auf höherstufige Objekte. In Redis’s Lua 5.1 zeigt
Table->arrayauf ein Array vonTValue-getaggten Werten; das Überschreiben des LSB eines Zeigers inTValue->value(z. B. mit dem JSON-Terminator-Byte0x22) kann Referenzen umleiten, ohne malloc-Metadaten zu berühren. - Debugging von gestripptem/static Lua auf Alpine: Baue ein passendes Lua, liste Symbole mit
readelf -Wsauf, entferne Funktionssymbole viaobjcopy --strip-symbol, um struct layouts in GDB offenzulegen, und benutze dann Lua-aware pretty-printers (GdbLuaExtension for Lua 5.1) plus muslheap, um stride/reserved/cycling-offset Werte zu prüfen, bevor der Overflow ausgelöst wird.
Fallstudien
Untersuche allocator-spezifische Primitive, die aus realen Bugs abgeleitet sind:
Virtualbox Slirp Nat Packet Heap Exploitation
Gnu Obstack Function Pointer Hijack
Referenzen
- https://azeria-labs.com/heap-exploitation-part-1-understanding-the-glibc-heap-implementation/
- https://azeria-labs.com/heap-exploitation-part-2-glibc-heap-free-bins/
- Pumping Iron on the Musl Heap – Real World CVE-2022-24834 Exploitation on an Alpine mallocng Heap
- musl mallocng enframe (v1.2.4)
- muslheap GDB plugin
- GdbLuaExtension (Lua 5.1 support)
Tip
Lernen & üben Sie AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Lernen & üben Sie Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Unterstützen Sie HackTricks
- Überprüfen Sie die Abonnementpläne!
- Treten Sie der 💬 Discord-Gruppe oder der Telegram-Gruppe bei oder folgen Sie uns auf Twitter 🐦 @hacktricks_live.
- Teilen Sie Hacking-Tricks, indem Sie PRs an die HackTricks und HackTricks Cloud GitHub-Repos senden.


