Libc Heap

Heap Begins

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

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

Basiese Stuk Toekenning

Wanneer daar gevra word dat data in die heap gestoor moet word, word 'n deel van die heap daaraan toegewys. Hierdie ruimte behoort aan 'n bin en slegs die gevraagde data + die ruimte van die bin koppe + minimum bin grootte offset sal vir die stuk gereserveer word. Die doel is om so min moontlik geheue te reserveer sonder om dit moeilik te maak om te vind waar elke stuk is. Hiervoor word die metadata stuk inligting gebruik om te weet waar elke gebruikte/vrye stuk 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 stukke iemand beskikbaar is wat groot genoeg is om aan die versoek te voldoen, sal dit gebruik word

  • Dit kan selfs beteken dat 'n deel van die beskikbare stuk vir hierdie versoek gebruik sal word en die res sal by die stukke lys gevoeg word

  • As daar geen beskikbare stuk in die lys is nie, maar daar steeds ruimte in toegewysde heap geheue is, skep die heap bestuurder 'n nuwe stuk

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

  • As alles misluk, gee malloc null terug.

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

Arenas

In multigeïntegreerde aansoeke moet die heap bestuurder wedren toestande voorkom wat tot ongelukke kan lei. Aanvanklik is dit gedoen deur 'n globale grendel te gebruik om te verseker dat slegs een draad op 'n slag toegang tot die heap kan hê, maar dit het prestasieprobleme veroorsaak as gevolg van die grendel-geïnduseerde bottelnek.

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

Die verstek "hoof" arena hanteer heap operasies vir enkel-draad aansoeke. Wanneer nuwe drade bygevoeg word, ken die heap bestuurder hulle sekondêre arenas toe om kontensie te verminder. Dit probeer eers om elke nuwe draad aan 'n ongebruikte arena te heg, nuwes te skep indien nodig, tot 'n limiet van 2 keer die aantal CPU kerne vir 32-bis stelsels en 8 keer vir 64-bis stelsels. Sodra die limiet bereik is, moet drade arenas deel, wat tot potensiële kontensie kan lei.

In teenstelling met die hoof arena, wat uitbrei deur die brk stelseloproep te gebruik, skep sekondêre arenas "subheape" deur mmap en mprotect te gebruik om die heap gedrag na te boots, wat buigsaamheid bied om geheue vir meervoudige draad operasies te bestuur.

Subheape

Subheape dien as geheue reserwes vir sekondêre arenas in meervoudige draad aansoeke, wat hulle in staat stel om te groei en hul eie heap streke apart van die hoof heap te bestuur. Hier is hoe subheape verskil van die aanvanklike heap en hoe hulle werk:

  1. Aanvanklike Heap vs. Subheape:

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

  • Subheape, wat deur sekondêre arenas gebruik word, word geskep deur mmap, 'n stelseloproep wat 'n gespesifiseerde geheue streek in kaart bring.

  1. Geheue Reservering met mmap:

  • Wanneer die heap bestuurder 'n subheap skep, reserver hy 'n groot blok geheue deur mmap. Hierdie reservering ken nie onmiddellik geheue toe nie; dit dui eenvoudig 'n streek aan wat ander stelselprosesse of toekennings 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. Gegradueerde Uitbreiding met mprotect:

  • Die gereserveerde geheue streek word aanvanklik gemerk as PROT_NONE, wat aandui dat die kernel nie fisiese geheue aan hierdie spasie 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 vorige gereserveerde adresse toe te ken. Hierdie stap-vir-stap benadering laat die subheap toe om soos nodig uit te brei.

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

heap_info

Hierdie struktuur ken relevante inligting van die heap toe. Verder kan heap geheue nie kontinu wees na meer toekennings 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 op te merk 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 strukture van die heaps van drade, is hulle geleë binne eie draad "heap".

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

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

  • Vlae:

#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 **aanwysers** na die **eerste en laaste brokkies** van die klein, groot en ongesorteerde **bins** (die -2 is omdat die indeks 0 nie gebruik word nie)
* Daarom sal die **eerste brokkie** van hierdie bins 'n **agterwaartse aanwyser na hierdie struktuur** hê en die **laaste brokkie** van hierdie bins sal 'n **voorwaartse aanwyser** na hierdie struktuur hê. Dit beteken basies dat as jy hierdie adresse in die hoof arena kan **lek**, sal jy 'n aanwyser na die struktuur in die **libc** hê.
* Die strukture `struct malloc_state *next;` en `struct malloc_state *next_free;` is gekoppelde lyste van arenas
* Die `top` brokkie is die laaste "brokkie", wat basies **al die oorblywende geheue van die heap** is. Sodra die top brokkie "leeg" is, is die heap heeltemal gebruik en moet meer spasie aangevra word.
* Die `last reminder` brokkie kom van gevalle waar 'n presiese grootte brokkie nie beskikbaar is nie en dus 'n groter brokkie opgesplitst word, 'n aanwyser 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 toegewysde en nie-toegewysde 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 brokke ook 'n paar metadatabestanddele, baie goed voorgestel in hierdie afbeelding:

Die metadata is gewoonlik 0x08B wat die huidige brok-grootte aandui deur die laaste 3 bits te gebruik om aan te dui:

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

  • M: As dit 1 is, is hierdie brok deel van 'n spasie toegewys met mmap en nie deel van 'n heap nie

  • P: As dit 1 is, is die vorige brok in gebruik

Dan die spasie vir die gebruikersdata, en uiteindelik 0x08B om die vorige brok-grootte aan te dui wanneer die brok beskikbaar is (of om gebruikersdata te stoor wanneer dit toegewys is).

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

  • fd: Aanwyser na die volgende brok

  • bk: Aanwyser na die vorige brok

  • fd_nextsize: Aanwyser na die eerste brok in die lys wat kleiner is as homself

  • bk_nextsize: Aanwyser na die eerste brok in die lys wat groter is as homself

Merk op hoe die lys op hierdie manier rangskik die behoefte vermy om 'n array te hê waar elke enkele brok geregistreer word.

Brok Aanwysers

Wanneer malloc gebruik word, word 'n aanwyser na die inhoud wat geskryf kan word teruggegee (net na die koppe), maar wanneer brokke bestuur word, is 'n aanwyser na die begin van die koppe (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))

Belyning & minimum grootte

Die wyser na die brokkie 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);
}

Kry brokkie data en verander metadata

Hierdie funksies werk deur 'n verwysing na 'n brokkie te ontvang en is nuttig om metadata te kontroleer/instel:

  • Kontroleer brokkie 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 brokkies

/*
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)))
  • Insluitingsbits

/* 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 voet (wanneer stuknommers 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 blok.

#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

Vinnige Heap Voorbeeld

Vinnige 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 in aan die einde van die hooffunksie en laat ons uitvind waar die inligting gestoor was:

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

Die ekstra spasies wat voorbehou is (0x21-0x10=0x11) kom van die bygevoegde koppe (0x10) en 0x1 beteken nie dat daar 0x21B voorbehou is nie, maar die laaste 3 bietjies van die lengte van die huidige kop het 'n paar spesiale betekenisse. Aangesien die lengte altyd 16-byte uitgelyn is (op 64-bits masjiene), gaan hierdie bietjies 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

Meervoudige draadvoorbeeld

Meervoudige draad

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

Foutopsporing van die vorige voorbeeld wys hoe daar aanvanklik net 1 arena is:

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

Daarna, nadat die eerste draad, die een wat malloc aanroep, aangeroep is, word 'n nuwe arena geskep:

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

en binne-in kan 'n paar stukke gevind word:

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

## Bins & Geheue-toekennings/vrymaak

Kyk wat die bakkies is en hoe hulle georganiseer is en hoe geheue toegewys en vrygemaak 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 betrokke by die heap sal sekere kontroles uitvoer voordat hulle hul aksies uitvoer om te probeer verseker dat die heap nie gekorrup 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