Die heap is basies die plek waar 'n program data kan stoor wanneer dit data aanvra 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 sekere data aangevra word om in die heap gestoor te word, word 'n stuk ruimte in die heap aan dit toegeken. Hierdie ruimte behoort aan 'n bin 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 moeilik 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 versoek te vervul, sal dit gebruik word.
Dit kan selfs beteken dat 'n deel van die beskikbare chunk vir hierdie versoek 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 condities 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 prestasie probleme 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-threaded 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 onbenutte 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 te simuleer, 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:
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.
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.
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 struct allokeer relevante inligting van die heap. Boonop mag heap geheue nie aaneenlopend wees na meer allokasies nie, hierdie struct sal ook daardie inligting stoor.
// From https://github.com/bminor/glibc/blob/a07e000e82cb71238259e674529c37c12dc7d423/malloc/arena.c#L837typedefstruct _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 mprotectedPROT_READ|PROT_WRITE. */size_t pagesize; /* Page size used when allocating the arena. *//* Make sure the following data is properly aligned, particularlythat sizeof (heap_info) + 2 * SIZE_SZ is a multiple ofMALLOC_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 te noem 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
* 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 hierdie adresse in die hoof arena kan l**eak** 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 **alle 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.cstruct 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;};typedefstruct malloc_chunk* mchunkptr;
Soos voorheen kommentaar gelewer, het hierdie stukke ook 'n paar metadata, baie goed verteenwoordig 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 die lys op hierdie manier gekoppel word, wat 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. */#definechunk2mem(p) ((void*)((char*)(p) + CHUNK_HDR_SZ))/* Convert a user mem pointer to a chunk address and extract the right tag. */#definemem2chunk(mem) ((mchunkptr)tag_at (((char*)(mem) - CHUNK_HDR_SZ)))/* The smallest possible chunk */#defineMIN_CHUNK_SIZE (offsetof(struct malloc_chunk, fd_nextsize))/* The smallest size we can malloc is an aligned minimal chunk */#defineMINSIZE \(unsignedlong)(((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#defineMALLOC_ALIGN_MASK (MALLOC_ALIGNMENT -1)// https://github.com/bminor/glibc/blob/a07e000e82cb71238259e674529c37c12dc7d423/sysdeps/i386/malloc-alignment.h#defineMALLOC_ALIGNMENT16// https://github.com/bminor/glibc/blob/master/malloc/malloc.c/* Check if m has acceptable alignment */#definealigned_OK(m) (((unsignedlong)(m) & MALLOC_ALIGN_MASK) ==0)#definemisaligned_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 constantif passed a literal constant. */#definerequest2size(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 resultingvalue is less than PTRDIFF_T. Returns the requested size orMINSIZE in case the value is less than MINSIZE, or 0 if any of theprevious checks fail. */staticinlinesize_tchecked_request2size (size_t req) __nonnull (1){if (__glibc_unlikely (req > PTRDIFF_MAX))return0;/* When using tagged memory, we cannot share the end of the userblock with the header for the next chunk, so ensure that weallocate blocks that are rounded up to the granule size. Takecare not to overflow from close to MAX_SIZE_T to a smallnumber. Ideally, this would be part of request2size(), but thatmust be a macro that produces a compile time constant if passeda 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);}returnrequest2size (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 */#definePREV_INUSE0x1/* extract inuse bit of previous chunk */#defineprev_inuse(p) ((p)->mchunk_size & PREV_INUSE)/* size field is or'ed with IS_MMAPPED if the chunk was obtained with mmap() */#defineIS_MMAPPED0x2/* check for mmap()'ed chunk */#definechunk_is_mmapped(p) ((p)->mchunk_size & IS_MMAPPED)/* size field is or'ed with NON_MAIN_ARENA if the chunk was obtainedfrom a non-main arena. This is only set immediately before handingthe chunk to the user, if necessary. */#defineNON_MAIN_ARENA0x4/* Check for chunk from main arena. */#definechunk_main_arena(p) (((p)->mchunk_size & NON_MAIN_ARENA) ==0)/* Mark a chunk as not being on the main arena. */#defineset_non_main_arena(p) ((p)->mchunk_size |= NON_MAIN_ARENA)
Groottes en wysers na ander stukke
/*Bits to mask off when extracting sizeNote: IS_MMAPPED is intentionally not masked off from size field inmacros for which mmapped chunks should never be seen. This shouldcause helpful core dumps to occur if it is tried by accident bypeople extending or adapting this malloc.*/#defineSIZE_BITS (PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)/* Get size, ignoring use bits */#definechunksize(p) (chunksize_nomask (p) &~(SIZE_BITS))/* Like chunksize, but do not mask SIZE_BITS. */#definechunksize_nomask(p) ((p)->mchunk_size)/* Ptr to next physical malloc_chunk. */#definenext_chunk(p) ((mchunkptr) (((char*) (p)) +chunksize (p)))/* Size of the chunk below P. Only valid if !prev_inuse (P). */#defineprev_size(p) ((p)->mchunk_prev_size)/* Set the size of the chunk below P. Only valid if !prev_inuse (P). */#defineset_prev_size(p, sz) ((p)->mchunk_prev_size = (sz))/* Ptr to previous physical malloc_chunk. Only valid if !prev_inuse (P). */#defineprev_chunk(p) ((mchunkptr) (((char*) (p)) -prev_size (p)))/* Treat space at ptr + offset as a chunk */#definechunk_at_offset(p, s) ((mchunkptr) (((char*) (p)) + (s)))
Stel kop en voetteks in (wanneer stuk nommers in gebruik is)
/* Set size at head, without disturbing its use bit */#defineset_head_size(p, s) ((p)->mchunk_size = (((p)->mchunk_size & SIZE_BITS) | (s)))/* Set size/use field */#defineset_head(p, s) ((p)->mchunk_size = (s))/* Set size at footer (only when chunk is not in use) */#defineset_foot(p, s) (((mchunkptr) ((char*) (p) + (s)))->mchunk_prev_size = (s))
Kry die grootte van die werklike bruikbare data binne die stuk
#pragmaGCCpoisonmchunk_size#pragmaGCCpoisonmchunk_prev_size/* This is the size of the real usable data in the chunk. Not valid fordumped heap chunks. */#definememsize(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 granulesize, 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;}
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 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 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) (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) (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) (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/)