- Great article: https://azeria-labs.com/heap-exploitation-part-1-understanding-the-glibc-heap-implementation/ - https://heap-exploitation.dhavalkapil.com/diving_into_glibc_heap/bins_chunks - https://0x434b.dev/overview-of-glibc-heap-exploitation-techniques/#basics ## Allocation strat: - When calling malloc (and similar functions), the returned buffer on the heap is called a **chunk**. - On 32bit, chunks are **8-byte aligned** - On 64bit, chunks are **16-byte aligned**: - If request size is `n` in 64bit - Then real chunk size is: `(n + 8 + 15) & ~15` - A chunk includes: chunk metadata and actual buffer where user can use - The order how glibc find/alloc a chunk: - Looking at previously free'd chunks. If one of them has enough size, return it. Glibc manages previously free'd chunks using some link lists called **bin** - Looking at the free space at the end of the heap (this space is called "top chunk" or "remainder chunk"), if there is enough space --> create a new chunk there. - If there is not enough space at the end of the heap --> ask the OS to alloc more space using `sbrk()`, then create a new chunk at the newly provided space. If `sbrk()` fails, then use `mmap()`. If `mmap()` also fails then gives up. - If the allocation requests size is very very large --> use `mmap()` instead of create a chunk on the heap. ## Arena: - Basically a strat to prevent race condition while malloc, while also retain some performace for multi-thread programs. - "Each arena is essentially an entirely different heap that manages its own chunk allocation and free bins completely separately" --> Each thread has its own arena. - "The initial (“main”) arena of the program only contains the heap we’ve already seen, and for single-threaded applications this is the only arena that the heap manager will ever use" - If there are too much threads (2x cpu-cores in 32-bit processes and 8x cpu-cores in 64-bit processes), many threads have to share an arena and the mutex strat is used to prevent race condition. - Read more about how arenas are allocated and managed on the articles above. ## Chunk metadata: This pic says it all. ![](https://hackmd.io/_uploads/SyLdK-O1p.png) Note that the chunk size and 3 "A", "M", "P" bits are stored in the same size_t field ## Bins: - Linked list containing free'd chunks ### Small bins: - There are **62** small bins. - Each small bin contains chunk with **same size** - Every chunk less than 512 bytes on 32-bit (which are 16, 24, ... 504 bytes) or less than 1024 bytes on 64-bit (which are 32, 48,... 1008 bytes) has its own corresponding small bin. - Insertions happen at the **HEAD** while removals happen at the **TAIL** (in a FIFO manner). ### Large bins: - There are **63** large bins. - Each large bin stores chunks within a size range. - In each bin, chunks are sorted by size in decreasing order. ### Unsorted bin: - There is **1** unsorted bin. - Unlike other bins, this bin is unsort (duh) - When a chunk is free'd, instead of putting it to small/large bins, the heap manager will merge it with its neighbors, then throw it in unsorted bin. - During malloc, each chunks in unsorted bin is checked if it fits the request. If yes, use it. If no, it is put into small/large bin. - Insertion happens at the **HEAD** of the list, removals at **TAIL** (FIFO) - To leak a libc address, a common technique is to get a chunk into the unsorted bin, because then the forward and backward pointers will point to the main_arena struct inside the struct. ### Fast bin: - There are **10** fast bins. - They cover the following chunk size: 16, 24, 32, 40, 48, 56, 64, 72, 80, and 88 bytes (on 32bit, metadata included). - Chunks less than 0x80 bytes (on x64?) are assigned to the “fastbin”. According to: https://www.bordergate.co.uk/heap-fastbin-exploitation/ - The chunks inside fast bins are not really free'd: they are not merged to their neighbors and are still marked as in use. - The chunks inside fast bin is *truly* free'd when: - There is a malloc request is made that is larger than a fastbin can service (i.e., for chunks over 512 bytes or 1024 bytes on 64-bit) - Freeing any chunk over 64KB (where 64KB is a heuristically chosen value) - `malloc_trim` or `mallopt` are called by the program. - Chunks are inserted and removed at the **HEAD** (LIFO) ### Tcache bin: - Each thread has 64 tcache bins. - In the case that 2 or more threads has to share an arena, tcache bins help reducing the waiting time for mutex lock. - Each tcache bin contains a maximum of 7 same-size chunks ranging from 24 to 1032 bytes on 64-bit systems and 12 to 516 bytes on 32-bit systems (metadata size not included in size??). - Same as unsorted bin, chunks in tcache bin are not truly free'd - https://hackmd.io/@5Mo2wp7RQdCOYcqKeHl2mw/ByTHN47jf - Chunks in tcache bin are taken out at `libc_malloc`, not `int_malloc`. However, chunks are put inside tcache inside `int_malloc` ==> So if there is a chunk in tcache when we call malloc(), `_int_malloc()` will never be called. ## Heap exploitation techniques: Maybe helpful? - https://heap-exploitation.dhavalkapil.com/attacks - Helpful tips for pwn: https://github.com/thezakman/CTF-Heaven/blob/master/extra/pwn-tips.md - https://tc.gts3.org/cs6265/tut/tut09-02-advheap.html # __malloc_hook, __free_hook: - Legacy technique, __malloc_hook, __free_hook is removed in glibc 2.34. - malloc_hook is called when malloc() is called, similar to free_hook - __malloc_hook is also called when triggering double free corruption: https://blog.osiris.cyber.nyu.edu/2017/09/30/csaw-ctf-2017-auir/