Heap, temelde bir programın malloc, calloc gibi fonksiyonları çağırarak veri talep ettiğinde verileri depolayabileceği yerdir. Ayrıca, bu bellek artık gerekli olmadığında free fonksiyonu çağrılarak kullanılabilir hale getirilir.
Görüldüğü gibi, bu bellek, ikili dosyanın belleğe yüklendiği yerin hemen sonrasındadır (bakınız [heap] bölümü):
Temel Chunk Tahsisi
Heap'te depolanması istenen bazı veriler için heap'ten bir alan tahsis edilir. Bu alan bir bin'e ait olacak ve yalnızca istenen veri + bin başlıklarının alanı + minimum bin boyutu ofseti chunk için ayrılacaktır. Amaç, her chunk'ın nerede olduğunu bulmayı karmaşık hale getirmeden mümkün olan en az bellek ayırmaktır. Bunun için, her kullanılan/boş chunk'ın nerede olduğunu bilmek için metadata chunk bilgisi kullanılır.
Alanı ayırmanın farklı yolları vardır, esasen kullanılan bin'e bağlıdır, ancak genel bir metodoloji şudur:
Program belirli bir miktar bellek talep ederek başlar.
Eğer chunk listesinde talebi karşılayacak kadar büyük bir alan varsa, bu kullanılacaktır.
Bu, mevcut chunk'ın bir kısmının bu talep için kullanılacağı ve geri kalanının chunk listesine ekleneceği anlamına gelebilir.
Eğer listede mevcut bir chunk yoksa ancak tahsis edilmiş heap belleğinde hala alan varsa, heap yöneticisi yeni bir chunk oluşturur.
Eğer yeni chunk'ı tahsis etmek için yeterli heap alanı yoksa, heap yöneticisi çekirdekten heap'e tahsis edilen belleği genişletmesini ister ve ardından bu belleği yeni chunk'ı oluşturmak için kullanır.
Her şey başarısız olursa, malloc null döner.
Talep edilen bellek bir eşiği geçerse, mmap talep edilen belleği haritalamak için kullanılacaktır.
Arenalar
Çok iş parçacıklı uygulamalarda, heap yöneticisi çöküşe yol açabilecek yarış koşullarını önlemelidir. Başlangıçta, yalnızca bir iş parçacığının heap'e erişebilmesini sağlamak için küresel bir mutex kullanılıyordu, ancak bu mutex kaynaklı darboğaz nedeniyle performans sorunlarına yol açtı.
Bunu çözmek için, ptmalloc2 heap tahsis edici "arenalar" tanıttı; burada her arena, kendi veri yapıları ve mutex ile ayrı bir heap olarak işlev görür ve farklı arenaları kullandıkları sürece birden fazla iş parçacığının heap işlemleri gerçekleştirmesine olanak tanır.
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 bunlara ikincil arenalar atar ve rekabeti azaltır. İlk olarak, her yeni iş parçacığını kullanılmayan bir arenaya bağlamaya çalışır, gerekirse yeni arenalar oluşturur; bu, 32-bit sistemler için CPU çekirdeklerinin sayısının 2 katı ve 64-bit sistemler için 8 katı ile sınırlıdır. Sınır aşıldığında, iş parçacıkları arenaları paylaşmak zorundadır, bu da potansiyel rekabete yol açar.
Ana arenanın brk sistem çağrısını kullanarak genişlemesinin aksine, ikincil arenalar mmap ve mprotect kullanarak "alt heap'ler" oluşturur ve çok iş parçacıklı işlemler için bellek yönetiminde esneklik sağlar.
Alt Heap'ler
Alt heap'ler, çok iş parçacıklı uygulamalarda ikincil arenalar için bellek rezervleri olarak hizmet eder ve ana heap'ten ayrı olarak kendi heap bölgelerini büyütmelerine ve yönetmelerine olanak tanır. İşte alt heap'lerin başlangıç heap'inden nasıl farklılaştığı ve nasıl çalıştığı:
Başlangıç Heap'i vs. Alt Heap'ler:
Başlangıç heap'i, programın ikili dosyasının bellekte hemen arkasında yer alır ve sbrk sistem çağrısını kullanarak genişler.
İkincil arenalar tarafından kullanılan alt heap'ler, belirli bir bellek bölgesini haritalayan mmap aracılığıyla 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; yalnızca diğer sistem süreçlerinin veya tahsislerin kullanmaması gereken bir bölgeyi belirler.
Varsayılan olarak, bir alt heap için rezerve edilen boyut 32-bit süreçler için 1 MB ve 64-bit süreçler için 64 MB'dır.
mprotect ile Aşamalı Genişleme:
Rezerve edilen bellek bölgesi başlangıçta PROT_NONE olarak işaretlenir; bu, çekirdeğin bu alana fiziksel bellek tahsis etmesi gerekmediğini gösterir.
Alt heap'i "büyütmek" için, heap yöneticisi mprotect kullanarak sayfa izinlerini PROT_NONE'dan PROT_READ | PROT_WRITE'a değiştirir ve bu, çekirdeğin daha önce rezerve edilen adreslere fiziksel bellek tahsis etmesini sağlar. Bu adım adım yaklaşım, alt heap'in gerektiği gibi genişlemesine olanak tanır.
Tüm alt heap tükendiğinde, heap yöneticisi tahsise devam etmek için yeni bir alt heap oluşturur.
heap_info
Bu yapı, heap'in ilgili bilgilerini tahsis eder. Ayrıca, daha fazla tahsisten sonra heap belleği kesintili olmayabilir; bu yapı aynı zamanda bu bilgiyi de saklayacaktır.
// 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 arenaları) bir malloc_state yapısına sahiptir.Ana arena malloc_state yapısının libc'de global bir değişken olduğunu belirtmek önemlidir (bu nedenle libc bellek alanında yer alır).
Thread'lerin heap'lerinin malloc_state yapıları ise kendi thread "heap"lerinin içinde yer alır.
Bu yapıdan bazı ilginç noktalar vardır (aşağıdaki C koduna bakın):
__libc_lock_define (, mutex); Bu yapıdan heap'e erişimin aynı anda 1 thread tarafından yapılmasını sağlamak için vardır.
* `mchunkptr bins[NBINS * 2 - 2];` **küçük, büyük ve sıralanmamış** **bins**'lerin **ilk ve son chunk'larına** **işaretçiler** içerir (0 indeksinin kullanılmadığı için -2).
* Bu nedenle, bu bins'lerin **ilk chunk'ı** bu yapıya **geriye işaret eden bir işaretçi** ve bu bins'lerin **son chunk'ı** bu yapıya **ileri işaretçi** bulunduracaktır. Bu, eğer **ana arenada bu adresleri sızdırabilirseniz** yapıya bir işaretçi elde edeceğiniz anlamına gelir.
* `struct malloc_state *next;` ve `struct malloc_state *next_free;` yapıları arena'ların bağlı listeleridir.
* `top` chunk, temelde **tüm heap hatırlatma alanı** olan son "chunk"tır. `top` chunk "boş" olduğunda, heap tamamen kullanılmıştır ve daha fazla alan talep etmesi gerekir.
* `last reminder` chunk, tam boyutlu bir chunk mevcut olmadığında ve bu nedenle daha büyük bir chunk bölündüğünde ortaya çıkar; burada kalan kısmın işaretçisi yer alır.
```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. Farklı alanlar, tahsis edilmiş ve tahsis edilmemiş 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 vardır, bu görüntüde çok iyi temsil edilmiştir:
Meta veri genellikle 0x08B olup, mevcut parça boyutunu belirtmek için son 3 biti kullanır:
A: 1 ise bir alt yığın, 0 ise ana arenadadır
M: 1 ise, bu parça mmap ile tahsis edilen bir alanın parçasıdır ve bir yığın parçası değildir
P: 1 ise, önceki parça kullanımdadır
Sonra, kullanıcı verileri için alan ve nihayet 0x08B, parça mevcut olduğunda önceki parça boyutunu belirtmek için (veya tahsis edildiğinde kullanıcı verilerini depolamak için) kullanılır.
Ayrıca, mevcut olduğunda, kullanıcı verileri ayrıca bazı verileri içermek için kullanılır:
fd: Bir sonraki parçaya işaretçi
bk: Önceki parçaya işaretçi
fd_nextsize: Listede kendisinden daha küçük olan ilk parçaya işaretçi
bk_nextsize: Listede kendisinden daha büyük olan ilk parçaya işaretçi
Listeyi bu şekilde bağlamanın, her bir parçanın kaydedilmesi gereken bir diziye ihtiyaç duymayı önlediğine dikkat edin.
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ıkların hemen ardından), ancak parçaları yönetirken, başlıkların (meta verilerin) başlangıcına işaret eden bir işaretçiye ihtiyaç vardır.
Bu dönüşümler için bu 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 & minimum boyut
Chunk'a işaretçi ve 0x0f 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);}
Not edin ki, toplam gereken alanı hesaplamak için yalnızca SIZE_SZ 1 kez eklenir çünkü prev_size alanı veri depolamak için kullanılabilir, bu nedenle yalnızca başlangıç başlığı gereklidir.
Parça verilerini al ve meta verileri değiştir
Bu fonksiyonlar bir parçaya işaretçi alarak çalışır ve meta verileri kontrol etmek/ayarlamak için faydalıdır:
Parça bayraklarını kontrol et
// 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)
Diğer parçaların boyutları ve 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 altbilgi 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 verinin 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ı ayarlayın ve bilgilerin nerede saklandığını bulalım:
Panda dizesinin 0xaaaaaaac12a0 adresinde saklandığını görebiliyoruz (bu, x0 içindeki malloc tarafından verilen yanıttı). 0x10 byte öncesini kontrol ettiğimizde, 0x0 değerinin önceki parçanın kullanılmadığını (uzunluk 0) temsil ettiğini ve bu parçanın uzunluğunun 0x21 olduğunu görebiliyoruz.
Rezerve edilen ekstra alan (0x21-0x10=0x11), eklenen başlıklardan (0x10) gelmektedir ve 0x1, 0x21B olarak rezerve edildiği anlamına gelmez, ancak mevcut başlığın uzunluğunun son 3 biti bazı özel anlamlara sahiptir. Uzunluk her zaman 16-byte hizalı olduğundan (64 bit makinelerde), bu bitler aslında uzunluk numarası tarafından asla kullanılmayacaktır.
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ıklarken, başlangıçta yalnızca 1 arena olduğunu görebiliriz:
<figure><img src="../../.gitbook/assets/image (1) (1) (1) (1) (1) (1).png" alt=""><figcaption></figcaption></figure>
Daha sonra, malloc'u çağıran ilk iş parçacığı çağrıldığında, yeni bir arena oluşturulur:
<figure><img src="../../.gitbook/assets/image (1) (1) (1) (1) (1) (1) (1).png" alt=""><figcaption></figcaption></figure>
Ve içinde bazı parçalar bulunabilir:
<figure><img src="../../.gitbook/assets/image (2) (1) (1) (1).png" alt=""><figcaption></figcaption></figure>
## Bins & Bellek Tahsisleri/Serbest Bırakmaları
Bins'lerin ne olduğunu ve nasıl organize olduklarını ve belleğin nasıl tahsis edildiğini ve serbest bırakıldığını kontrol edin:
<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, eylemlerini gerçekleştirmeden önce heap'in bozulmadığından emin olmak için belirli kontroller yapacaktı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/)