Libc Heap

Heap Basics

Die heap is basies die plek waar 'n program data kan stoor wanneer dit data aanroep deur funksies soos malloc, calloc... Boonop, wanneer hierdie geheue nie meer nodig is nie, word dit beskikbaar gemaak deur die funksie free aan te roep.

Soos getoon, is dit net na waar die binêre in geheue gelaai word (kyk die [heap] afdeling):

Basic Chunk Allocation

Wanneer daar data aangevra word om in die heap gestoor te word, word 'n stuk van die heap aan dit toegeken. Hierdie ruimte sal aan 'n bin behoort en slegs die aangevraagde data + die ruimte van die bin koppe + minimum bin grootte offset sal gereserveer word vir die chunk. Die doel is om slegs die minimum geheue te reserveer sonder om dit ingewikkeld te maak om te vind waar elke chunk is. Hiervoor word die metadata chunk inligting gebruik om te weet waar elke gebruikte/vrye chunk is.

Daar is verskillende maniere om die ruimte te reserveer, hoofsaaklik afhangende van die gebruikte bin, maar 'n algemene metodologie is die volgende:

  • Die program begin deur 'n sekere hoeveelheid geheue aan te vra.

  • As daar in die lys van chunks iemand beskikbaar is wat groot genoeg is om die aanvraag te vervul, sal dit gebruik word.

  • Dit kan selfs beteken dat 'n deel van die beskikbare chunk vir hierdie aanvraag gebruik sal word en die res aan die chunks lys bygevoeg sal word.

  • As daar nie enige beskikbare chunk in die lys is nie, maar daar steeds ruimte in die toegeken geheue is, sal die heap bestuurder 'n nuwe chunk skep.

  • As daar nie genoeg heap ruimte is om die nuwe chunk toe te ken nie, vra die heap bestuurder die kernel om die geheue wat aan die heap toegeken is, uit te brei en dan hierdie geheue te gebruik om die nuwe chunk te genereer.

  • As alles misluk, keer malloc null terug.

Let daarop dat as die aangevraagde geheue 'n drempel oorskry, mmap gebruik sal word om die aangevraagde geheue te kaart.

Arenas

In multithreaded toepassings moet die heap bestuurder race conditions voorkom wat tot crashes kan lei. Aanvanklik is dit gedoen deur 'n globale mutex te gebruik om te verseker dat slegs een thread die heap op 'n slag kan benader, maar dit het prestasieprobleme veroorsaak weens die mutex-geïnduseerde bottleneck.

Om dit aan te spreek, het die ptmalloc2 heap toewysingsprogram "arenas" bekendgestel, waar elke arena as 'n afsonderlike heap optree met sy eie data strukture en mutex, wat verskeie threads toelaat om heap operasies uit te voer sonder om mekaar te steur, solank hulle verskillende arenas gebruik.

Die standaard "hoof" arena hanteer heap operasies vir enkel-draad toepassings. Wanneer nuwe threads bygevoeg word, ken die heap bestuurder hulle sekondêre arenas toe om mededinging te verminder. Dit probeer eers om elke nuwe thread aan 'n ongebruikte arena te koppel, en skep nuwe as dit nodig is, tot 'n limiet van 2 keer die aantal CPU-kerns vir 32-bis stelsels en 8 keer vir 64-bis stelsels. Sodra die limiet bereik is, moet threads arenas deel, wat tot potensiële mededinging lei.

In teenstelling met die hoof arena, wat uitbrei deur die brk stelselaanroep, skep sekondêre arenas "subheaps" deur mmap en mprotect te gebruik om die heap gedrag na te boots, wat buigsaamheid in die bestuur van geheue vir multithreaded operasies toelaat.

Subheaps

Subheaps dien as geheue voorrade vir sekondêre arenas in multithreaded toepassings, wat hulle toelaat om te groei en hul eie heap gebiede apart van die hoof heap te bestuur. Hier is hoe subheaps verskil van die aanvanklike heap en hoe hulle werk:

  1. Aanvanklike Heap vs. Subheaps:

  • Die aanvanklike heap is direk na die program se binêre in geheue geleë, en dit brei uit deur die sbrk stelselaanroep.

  • Subheaps, wat deur sekondêre arenas gebruik word, word geskep deur mmap, 'n stelselaanroep wat 'n gespesifiseerde geheuegebied kaart.

  1. Geheue Reservasie met mmap:

  • Wanneer die heap bestuurder 'n subheap skep, reserveer dit 'n groot blok geheue deur mmap. Hierdie reservasie allokeer nie onmiddellik geheue nie; dit dui eenvoudig 'n gebied aan wat ander stelsels of allokasies nie moet gebruik nie.

  • Standaard is die gereserveerde grootte vir 'n subheap 1 MB vir 32-bis prosesse en 64 MB vir 64-bis prosesse.

  1. Geleidelike Uitbreiding met mprotect:

  • Die gereserveerde geheuegebied is aanvanklik gemerk as PROT_NONE, wat aandui dat die kernel nie fisiese geheue aan hierdie ruimte hoef toe te ken nie.

  • Om die subheap te "groei", gebruik die heap bestuurder mprotect om bladsy toestemmings van PROT_NONE na PROT_READ | PROT_WRITE te verander, wat die kernel aanmoedig om fisiese geheue aan die voorheen gereserveerde adresse toe te ken. Hierdie stap-vir-stap benadering laat die subheap toe om uit te brei soos nodig.

  • Sodra die hele subheap uitgeput is, skep die heap bestuurder 'n nuwe subheap om voort te gaan met allokasie.

heap_info

Hierdie struktuur allokeer relevante inligting van die heap. Boonop mag heap geheue nie aaneenlopend wees na meer allokasies nie, hierdie struktuur sal ook daardie inligting stoor.

// 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

Elke heap (hoof arena of ander threads arenas) het 'n malloc_state struktuur. Dit is belangrik om te noem dat die hoof arena malloc_state struktuur 'n globale veranderlike in die libc is (dus geleë in die libc geheue ruimte). In die geval van malloc_state struktuur van die heaps van threads, is hulle geleë binne eie thread "heap".

Daar is 'n paar interessante dinge om op te let van hierdie struktuur (sien C kode hieronder):

  • __libc_lock_define (, mutex); Is daar om te verseker dat hierdie struktuur van die heap deur 1 thread op 'n slag benader word

  • Vlaggies:

#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)

* Die `mchunkptr bins[NBINS * 2 - 2];` bevat **pointers** na die **eerste en laaste chunks** van die klein, groot en onsorteerde **bins** (die -2 is omdat die indeks 0 nie gebruik word nie)
* Daarom, die **eerste chunk** van hierdie bins sal 'n **terugwysende pointer na hierdie struktuur** hê en die **laaste chunk** van hierdie bins sal 'n **voorwaartse pointer** na hierdie struktuur hê. Wat basies beteken dat as jy kan l**eak hierdie adresse in die hoof arena** sal jy 'n pointer na die struktuur in die **libc** hê.
* Die structs `struct malloc_state *next;` en `struct malloc_state *next_free;` is verknopte lyste van arenas
* Die `top` chunk is die laaste "chunk", wat basies **al die heap herinnering ruimte** is. Sodra die top chunk "leeg" is, is die heap heeltemal gebruik en dit moet meer ruimte aan vra.
* Die `last reminder` chunk kom van gevalle waar 'n presiese grootte chunk nie beskikbaar is nie en daarom 'n groter chunk gesplinter word, 'n pointer oorblywende deel word hier geplaas.
```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

Hierdie struktuur verteenwoordig 'n spesifieke stuk geheue. Die verskillende velde het verskillende betekenisse vir toegewyde en nie-toegewyde stukke.

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

Soos voorheen opgemerk, het hierdie stukke ook 'n paar metadata, baie goed voorgestel in hierdie beeld:

Die metadata is gewoonlik 0x08B wat die huidige stuk grootte aandui met die laaste 3 bits om aan te dui:

  • A: As 1 kom dit van 'n subheap, as 0 is dit in die hoof arena

  • M: As 1, is hierdie stuk deel van 'n ruimte wat met mmap toegeken is en nie deel van 'n heap nie

  • P: As 1, is die vorige stuk in gebruik

Dan, die ruimte vir die gebruikersdata, en uiteindelik 0x08B om die vorige stuk grootte aan te dui wanneer die stuk beskikbaar is (of om gebruikersdata te stoor wanneer dit toegeken word).

Boonop, wanneer beskikbaar, word die gebruikersdata ook gebruik om 'n paar data te bevat:

  • fd: Wys na die volgende stuk

  • bk: Wys na die vorige stuk

  • fd_nextsize: Wys na die eerste stuk in die lys wat kleiner is as homself

  • bk_nextsize: Wys na die eerste stuk in die lys wat groter is as homself

Let op hoe om die lys op hierdie manier te verbind die behoefte aan 'n array waar elke enkele stuk geregistreer word, voorkom.

Stuk Wysers

Wanneer malloc gebruik word, word 'n wys na die inhoud wat geskryf kan word, teruggestuur (net na die koptekste), egter, wanneer stukke bestuur word, is 'n wys na die begin van die koptekste (metadata) nodig. Vir hierdie omskakelings word hierdie funksies gebruik:

// 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))

Alignering & min grootte

Die wysiger na die stuk en 0x0f moet 0 wees.

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

Let op dat vir die berekening van die totale ruimte wat nodig is, word SIZE_SZ slegs 1 keer bygevoeg omdat die prev_size veld gebruik kan word om data te stoor, daarom is slegs die aanvanklike kop nodig.

Kry Chunk data en verander metadata

Hierdie funksies werk deur 'n wysiger na 'n chunk te ontvang en is nuttig om metadata te kontroleer/te stel:

  • Kontroleer chunk vlae

// 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)
  • Groottes en wysers na ander stukke

/*
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))
  • Stel kop en voetteks in (wanneer stuk nommers in gebruik is)

/* 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))
  • Kry die grootte van die werklike bruikbare data binne die stuk

#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;
}

Voorbeelde

Vinige Heap Voorbeeld

Vinige heap voorbeeld van https://guyinatuxedo.github.io/25-heap/index.html maar in arm64:

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

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

Stel 'n breekpunt aan die einde van die hooffunksie en kom ons vind uit waar die inligting gestoor is:

Dit is moontlik om te sien dat die string panda gestoor is by 0xaaaaaaac12a0 (wat die adres was wat as antwoord deur malloc binne x0 gegee is). Deur 0x10 bytes voor dit te kyk, is dit moontlik om te sien dat die 0x0 verteenwoordig dat die vorige stuk nie gebruik word (lengte 0) en dat die lengte van hierdie stuk 0x21 is.

Die ekstra spasie wat gereserveer is (0x21-0x10=0x11) kom van die bygevoegde koptekste (0x10) en 0x1 beteken nie dat dit 0x21B gereserveer is nie, maar die laaste 3 bits van die lengte van die huidige kop het 'n paar spesiale betekenisse. Aangesien die lengte altyd 16-byte uitgelijnd is (in 64-bits masjiene), gaan hierdie bits eintlik nooit deur die lengtenommer gebruik word nie.

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 Voorbeeld

Multithread

```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>

Deur die vorige voorbeeld te debugeer, is dit moontlik om te sien hoe daar aan die begin slegs 1 arena is:

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

Dan, na die aanroep van die eerste draad, die een wat malloc aanroep, word 'n nuwe arena geskep:

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

en binne dit kan 'n paar chunks gevind word:

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

## Bins & Geheue Toewysings/Vrystellings

Kyk wat die bins is en hoe hulle georganiseer is en hoe geheue toegeken en vrygestel word in:

<div data-gb-custom-block data-tag="content-ref" data-url='bins-and-memory-allocations.md'>

[bins-and-memory-allocations.md](bins-and-memory-allocations.md)

</div>

## Heap Funksies Sekuriteitskontroles

Funksies wat betrokke is by die heap sal sekere kontroles uitvoer voordat hulle hul aksies uitvoer om te probeer seker te maak dat die heap nie gekorrumpeer is nie:

<div data-gb-custom-block data-tag="content-ref" data-url='heap-memory-functions/heap-functions-security-checks.md'>

[heap-functions-security-checks.md](heap-memory-functions/heap-functions-security-checks.md)

</div>

## Verwysings

* [https://azeria-labs.com/heap-exploitation-part-1-understanding-the-glibc-heap-implementation/](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/](https://azeria-labs.com/heap-exploitation-part-2-glibc-heap-free-bins/)

Last updated