Heap, bir programın malloc, calloc gibi fonksiyonları çağırarak veri istediğinde verileri depolayabileceği yerdir. Ayrıca, bu belleğe artık ihtiyaç duyulmadığında free fonksiyonu çağrılarak serbest bırakılır.
Görüldüğü gibi, bu bellek, binary belleğe yüklendikten hemen sonra ( [heap] bölümünü kontrol edin) bulunmaktadır:
Temel Parça Tahsisi
Heap'e depolanacak veri istendiğinde, heap'in bir kısmı buna ayrılır. Bu alan bir bine ait olacak ve yalnızca istenen veri + bin başlıklarının alanı + minimum bin boyutu ofseti parçaya ayrılacaktır. Amaç, her parçanın nerede olduğunu bulmayı karmaşık hale getirmeden mümkün olduğunca az bellek ayırmaktır. Bunun için, kullanılan/her parçanın nerede olduğunu bilmek için parça bilgileri kullanılır.
Kullanılan bine bağlı olarak alanı ayırmak için farklı yöntemler vardır, ancak genel bir metodoloji şöyledir:
Program belirli miktarda bellek isteyerek başlar.
İstek karşılayacak kadar büyük bir parça parça listesinde varsa, kullanılır.
Bu, isteğin bir kısmı için kullanılabilir parçanın kullanılacağı anlamına gelebilir ve geri kalanı parça listesine eklenir.
Liste içinde uygun bir parça yoksa ancak ayrılmış heap belleğinde hala yer varsa, heap yöneticisi yeni bir parça oluşturur.
Yeni bir parça tahsis etmek için yeterli heap alanı yoksa, heap yöneticisi çekirdeğe heap'e ayrılan belleği genişletmesini ister ve ardından bu belleği kullanarak yeni parçayı oluşturur.
Her şey başarısız olursa, malloc null döner.
İstenen belleğin bir eşiği geçerse, mmap kullanılarak istenen bellek eşlenir.
Arenalar
Çoklu iş parçacıklı uygulamalarda, heap yöneticisinin çökmelere yol açabilecek yarış koşullarını önlemesi gerekir. Başlangıçta, bunu sadece bir iş parçacığının aynı anda heap'e erişebileceğinden emin olmak için bir global kilitle yapardı, ancak bu, kilitleme nedeniyle performans sorunlarına yol açtı.
Bunu ele almak için, ptmalloc2 heap tahsisçısı, her birinin kendi veri yapıları ve kilidi olan ayrı bir heap olarak hareket eden "arenaları" tanıttı, böylece farklı arenaları kullansalar bile birden fazla iş parçacığının birbirini etkilemeden heap işlemleri yapmasına izin verir.
Varsayılan "ana" arena, tek iş parçacıklı uygulamalar için heap işlemlerini yönetir. Yeni iş parçacıkları eklendiğinde, heap yöneticisi çekişmeyi azaltmak için onlara ikincil arenalar atar. Her yeni iş parçacığını kullanılmayan bir arenaya eklemeye çalışır, gerektiğinde yeni arenalar oluşturur, 32 bit sistemler için CPU çekirdek sayısının 2 katı ve 64 bit sistemler için 8 katı kadar bir sınıra ulaşana kadar. Sınır aşıldığında, iş parçacıkları arenaları paylaşmak zorunda kalır, potansiyel çekişmelere yol açar.
Ana arenanın aksine, genişleyen brk sistem çağrısını kullanan ana arenalar, çoklu iş parçacıklı işlemler için belleği yönetme esnekliği sağlayan mmap ve mprotect kullanarak "alt heap"ler oluşturur.
Alt Heap'ler
Alt heap'ler, çoklu iş parçacıklı uygulamalardaki ikincil arenalar için bellek rezervleri olarak hizmet eder, böylece bunlar ana heap'ten ayrı olarak kendi heap bölgelerini büyütebilir ve yönetebilir. İşte alt heap'lerin başlangıç heap'inden nasıl farklı olduğu ve nasıl çalıştığı:
Başlangıç Heap'i vs. Alt Heap'ler:
Başlangıç heap'i, programın binary'sinin hemen ardında bulunur ve sbrk sistem çağrısını kullanarak genişler.
İkincil arenalar tarafından kullanılan alt heap'ler, belirli bir bellek bölgesini eşleyen mmap kullanılarak oluşturulur.
mmap ile Bellek Rezervasyonu:
Heap yöneticisi bir alt heap oluşturduğunda, mmap aracılığıyla büyük bir bellek bloğu rezerve eder. Bu rezervasyon hemen bellek tahsis etmez; sadece diğer sistem işlemlerinin veya tahsislerin kullanmaması gereken bir bölgeyi belirler.
Varsayılan olarak, 32 bit işlemler için bir alt heap için ayrılan boyut 1 MB, 64 bit işlemler için ise 64 MB'dir.
mprotect ile Aşamalı Genişleme:
Rezerve edilen bellek bölgesi başlangıçta PROT_NONE olarak işaretlenir, bu da çekirdeğin bu alana henüz fiziksel bellek tahsis etmesi gerekmeyeceğini gösterir.
Alt heap'i "genişletmek" için, heap yöneticisi mprotect kullanarak sayfa izinlerini PROT_NONEdan PROT_READ | PROT_WRITE'a değiştirir, böylece çekirdek önceden rezerve edilen adreslere fiziksel bellek tahsis etmeye zorlanır. Bu adım adım yaklaşım, alt heap'in ihtiyaç duyuldukça genişlemesine olanak tanır.
Tüm alt heap tükenene kadar, heap yöneticisi devam etmek için yeni bir alt heap oluşturur.
heap_info
Bu yapı, heap'in ilgili bilgilerini ayırır. Ayrıca, daha fazla tahsis yapıldıktan sonra heap belleğinin sürekli olmayabileceği durumları da saklar.
// 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
Her heap (ana arena veya diğer thread'lerin arenaları) bir malloc_state yapısına sahiptir.
Önemli bir nokta, ana arena malloc_state yapısının libc içinde global bir değişken olduğudur (bu nedenle libc bellek alanında bulunur).
Thread'lerin arenalarının malloc_state yapıları durumunda, bunlar kendi thread "heap"lerinin içinde bulunur.
Bu yapıdan (aşağıdaki C koduna bakınız) dikkat çekici bazı şeyler vardır:
__libc_lock_define (, mutex); Bu, bu yapıya sadece 1 thread'in erişmesini sağlamak içindir
* `mchunkptr bins[NBINS * 2 - 2];` küçük, büyük ve sırasız **bins**'lerin **ilk ve son parçalarına işaretçiler** içerir (-2, çünkü indeks 0 kullanılmaz)
* Dolayısıyla, bu bins'lerin **ilk parçası** bu yapıya **ters işaretçiye sahip olacak** ve bu bins'lerin **son parçası** bu yapıya **ileri işaretçiye sahip olacak**. Temelde, eğer bu adresleri **ana arenada sızdırabilirseniz**, **libc** içindeki yapıya bir işaretçiye sahip olacaksınız.
* `struct malloc_state *next;` ve `struct malloc_state *next_free;` yapılarının bağlı listeleri vardır
* `top` parçası son "parça"dır, yani temelde **tüm heap hatırlama alanıdır**. Top parçası "boş" olduğunda, heap tamamen kullanılmış olur ve daha fazla alan istenmesi gerekir.
* `last reminder` parçası, tam bir boyutta parça bulunamadığı durumlarda ve bu nedenle daha büyük bir parça bölündüğünde, buraya kalan kısım yerleştirilir.
```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
Bu yapı belirli bir bellek parçasını temsil eder. Çeşitli alanlar ayrılmış ve ayrılmamış parçalar için farklı anlamlara sahiptir.
// 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;
Daha önce belirtildiği gibi, bu parçaların da bazı meta verileri bulunmaktadır, bu meta veriler aşağıdaki resimde çok iyi temsil edilmiştir:
Meta veriler genellikle mevcut parça boyutunu belirten 0x08B değerini gösterir ve son 3 bit şunları belirtmek için kullanılır:
A: 1 ise alt heap'ten gelir, 0 ise ana arenada bulunur
M: 1 ise bu parça mmap ile ayrılan bir alana aittir ve heap'in bir parçası değildir
P: 1 ise önceki parça kullanımdadır
Ardından, kullanıcı verileri için alan ve son olarak parça kullanımda olduğunda önceki parça boyutunu belirtmek için 0x08B değeri bulunur (veya ayrıldığında kullanıcı verilerini saklamak için).
Ayrıca, kullanılabilir olduğunda, kullanıcı verileri aynı zamanda bazı verileri de içerecek şekilde kullanılır:
fd: Bir sonraki parçanın işaretçisi
bk: Önceki parçanın işaretçisi
fd_nextsize: Kendisinden daha küçük olan listenin ilk parçasına işaretçi
bk_nextsize: Kendisinden daha büyük olan listenin ilk parçasına işaretçi
Listeyi bu şekilde bağlamak, her bir parçanın kaydedilmesi gereken bir dizinin oluşturulmasına gerek olmadan listeyi oluşturmanın gerekliliğini ortadan kaldırır.
Parça İşaretçileri
malloc kullanıldığında yazılabilir içeriğe işaret eden bir işaretçi döndürülür (başlık alanından hemen sonra), ancak parçalar yönetilirken, başlık alanının (meta veriler) başlangıcına bir işaretçiye ihtiyaç vardır. Bu dönüşümler için aşağıdaki fonksiyonlar kullanılır:
// 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))
Hizalama ve minimum boyut
Parçanın işaretçisi ve 0x0f değeri 0 olmalıdır.
// 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);}
Parça verilerini alın ve meta verileri değiştirin
Bu işlevler, bir parça işaretçisi alarak çalışır ve meta verileri kontrol etmek/ayarlamak için kullanışlıdır:
Parça bayraklarını kontrol edin
// 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)
Boyutlar ve diğer parçaların işaretçileri
/*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)))
Başlık ve altbilgiyi ayarlayın (parça numaraları kullanıldığında)
/* 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))
Parçanın içindeki gerçek kullanılabilir veri boyutunu alın.
#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;}
Ana fonksiyonun sonunda bir kesme noktası belirleyin ve bilginin nerede saklandığını bulalım:
Görülebileceği gibi, dize panda 0xaaaaaaac12a0 adresinde saklandı (bu adres, x0 içinde malloc tarafından yanıt olarak verilen adresdi). Onun 0x10 byte öncesini kontrol etmek mümkün olduğunda, 0x0'ın önceki parçanın kullanılmadığını (uzunluğu 0) ve bu parçanın uzunluğunun 0x21 olduğunu görmek mümkündür.
Ekstra boşluklar ayrılmıştır (0x21-0x10=0x11) eklenen başlıklardan (0x10) ve 0x1, 0x21B ayrılmış olduğu anlamına gelmez, ancak mevcut başlığın uzunluğunun son 3 bitinin bazı özel anlamları vardır. Uzunluk her zaman 16 bayt hizalı olduğundan (64 bit makinelerde), bu bitler aslında uzunluk numarası tarafından asla kullanılmayacak.
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
Çoklu İş Parçacığı Örneği
Çoklu İş Parçacığı
```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>
Önceki örneği hata ayıkladığınızda başlangıçta sadece 1 arena olduğunu görmek mümkündür:
<figure><img src="../../.gitbook/assets/image (1).png" alt=""><figcaption></figcaption></figure>
Ardından, ilk ipliği çağıran, yani malloc'u çağıran iplik çağrıldıktan sonra yeni bir arena oluşturulur:
<figure><img src="../../.gitbook/assets/image (1) (1).png" alt=""><figcaption></figcaption></figure>
ve içinde bazı parçalar bulunabilir:
<figure><img src="../../.gitbook/assets/image (2).png" alt=""><figcaption></figcaption></figure>
## Bins & Bellek Tahsisleri/Boşaltmaları
Bakın hangi kovaların olduğuna ve nasıl düzenlendiğine ve belleğin nasıl tahsis edildiğine ve boşaltıldığına:
<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 Fonksiyonları Güvenlik Kontrolleri
Heap ile ilgili fonksiyonlar, işlemlerini gerçekleştirmeden önce belirli kontroller yapacak ve heap'in bozulmadığından emin olmaya çalışacaktır:
<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>
## Referanslar
* [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/)