diff --git a/tests/binaries/heap_malloc_chunk.c b/tests/binaries/heap_malloc_chunk.c new file mode 100644 index 000000000..14d721be7 --- /dev/null +++ b/tests/binaries/heap_malloc_chunk.c @@ -0,0 +1,71 @@ +/* For testing the malloc_chunk command. + * + * Move chunks into each bin type so that the test can run the malloc_chunk command on each different type of free chunk. + */ + +#include + +#define INTERNAL_SIZE_T size_t +#define SIZE_SZ (sizeof (INTERNAL_SIZE_T)) +#define CHUNK_HDR_SZ (2 * SIZE_SZ) +#define mem2chunk(mem) ((void*)(mem) - CHUNK_HDR_SZ) + +void break_here(void) {} + +void* allocated_chunk = NULL; +void* tcache_chunk = NULL; +void* fast_chunk = NULL; +void* small_chunk = NULL; +void* large_chunk = NULL; +void* unsorted_chunk = NULL; + +int main(void) +{ + void* chunks[6] = {0}; + + // Request 8 fastbin-sized chunks, free these later to populate both the tcache (if present) and a fastbin. + for (int i=0; i<6; i++) + { + chunks[i] = malloc(0x18); + } + + void* tcache_ = malloc(0x18); + void* fast = malloc(0x18); + + void* remainder_me = malloc(0x418); + malloc(0x18); + + void* large = malloc(0x418); + malloc(0x18); + + void* unsorted = malloc(0x418); + malloc(0x18); + + // Populate 0x200 smallbin & 0x400 largebin. + // Use remaindering to avoid tcache (if present). + free(remainder_me); + void* before_remainder = malloc(0x208); + free(large); + malloc(0x428); + + // Populate the unsortedbin. + free(unsorted); + + // Populate 0x20 tcachebin (if present) & fastbin. + for (int i=0; i<6; i++) + { + free(chunks[i]); + } + + free(tcache_); + free(fast); + + allocated_chunk = mem2chunk(remainder_me); + tcache_chunk = mem2chunk(tcache_); + fast_chunk = mem2chunk(fast); + small_chunk = mem2chunk(before_remainder + 0x210); + large_chunk = mem2chunk(large); + unsorted_chunk = mem2chunk(unsorted); + + break_here(); +} diff --git a/tests/test_heap.py b/tests/test_heap.py index e587af593..212156f76 100644 --- a/tests/test_heap.py +++ b/tests/test_heap.py @@ -13,6 +13,7 @@ _, OUTPUT_FILE = tempfile.mkstemp() HEAP_VIS = tests.binaries.get("heap_vis.out") HEAP_FIND_FAKE_FAST = tests.binaries.get("heap_find_fake_fast.out") +HEAP_MALLOC_CHUNK = tests.binaries.get("heap_malloc_chunk.out") def binary_parse_breakpoints(binary_code): @@ -415,3 +416,94 @@ def test_find_fake_fast_command(start_binary): # A gdb.MemoryError raised here indicates a regression from PR #1145 gdb.execute("find_fake_fast (void*)&fake_chunk+0x70") + + +def test_malloc_chunk_command(start_binary): + start_binary(HEAP_MALLOC_CHUNK) + gdb.execute("break break_here") + gdb.execute("continue") + + allocated_chunk = pwndbg.gdblib.memory.poi( + pwndbg.heap.current.malloc_chunk, gdb.lookup_symbol("allocated_chunk")[0].value() + ) + tcache_chunk = pwndbg.gdblib.memory.poi( + pwndbg.heap.current.malloc_chunk, gdb.lookup_symbol("tcache_chunk")[0].value() + ) + fast_chunk = pwndbg.gdblib.memory.poi( + pwndbg.heap.current.malloc_chunk, gdb.lookup_symbol("fast_chunk")[0].value() + ) + small_chunk = pwndbg.gdblib.memory.poi( + pwndbg.heap.current.malloc_chunk, gdb.lookup_symbol("small_chunk")[0].value() + ) + large_chunk = pwndbg.gdblib.memory.poi( + pwndbg.heap.current.malloc_chunk, gdb.lookup_symbol("large_chunk")[0].value() + ) + unsorted_chunk = pwndbg.gdblib.memory.poi( + pwndbg.heap.current.malloc_chunk, gdb.lookup_symbol("unsorted_chunk")[0].value() + ) + + allocated_result = gdb.execute("malloc_chunk allocated_chunk", to_string=True).splitlines() + tcache_result = gdb.execute("malloc_chunk tcache_chunk", to_string=True).splitlines() + fast_result = gdb.execute("malloc_chunk fast_chunk", to_string=True).splitlines() + small_result = gdb.execute("malloc_chunk small_chunk", to_string=True).splitlines() + large_result = gdb.execute("malloc_chunk large_chunk", to_string=True).splitlines() + unsorted_result = gdb.execute("malloc_chunk unsorted_chunk", to_string=True).splitlines() + + allocated_expected = [ + "Allocated chunk | PREV_INUSE", + f"Addr: {allocated_chunk.address}", + f"Size: 0x{int(allocated_chunk['mchunk_size' if 'mchunk_size' in (f.name for f in allocated_chunk.type.fields()) else 'size']):02x}", + "", + ] + + tcache_expected = [ + f"Free chunk ({'tcache' if pwndbg.heap.current.has_tcache else 'fastbins'}) | PREV_INUSE", + f"Addr: {tcache_chunk.address}", + f"Size: 0x{int(tcache_chunk['mchunk_size' if 'mchunk_size' in (f.name for f in tcache_chunk.type.fields()) else 'size']):02x}", + f"fd: 0x{int(tcache_chunk['fd']):02x}", + "", + ] + + fast_expected = [ + "Free chunk (fastbins) | PREV_INUSE", + f"Addr: {fast_chunk.address}", + f"Size: 0x{int(fast_chunk['mchunk_size' if 'mchunk_size' in (f.name for f in fast_chunk.type.fields()) else 'size']):02x}", + f"fd: 0x{int(fast_chunk['fd']):02x}", + "", + ] + + small_expected = [ + "Free chunk (smallbins) | PREV_INUSE", + f"Addr: {small_chunk.address}", + f"Size: 0x{int(small_chunk['mchunk_size' if 'mchunk_size' in (f.name for f in small_chunk.type.fields()) else 'size']):02x}", + f"fd: 0x{int(small_chunk['fd']):02x}", + f"bk: 0x{int(small_chunk['bk']):02x}", + "", + ] + + large_expected = [ + "Free chunk (largebins) | PREV_INUSE", + f"Addr: {large_chunk.address}", + f"Size: 0x{int(large_chunk['mchunk_size' if 'mchunk_size' in (f.name for f in large_chunk.type.fields()) else 'size']):02x}", + f"fd: 0x{int(large_chunk['fd']):02x}", + f"bk: 0x{int(large_chunk['bk']):02x}", + f"fd_nextsize: 0x{int(large_chunk['fd_nextsize']):02x}", + f"bk_nextsize: 0x{int(large_chunk['bk_nextsize']):02x}", + "", + ] + + unsorted_expected = [ + "Free chunk (unsortedbin) | PREV_INUSE", + f"Addr: {unsorted_chunk.address}", + f"Size: 0x{int(unsorted_chunk['mchunk_size' if 'mchunk_size' in (f.name for f in unsorted_chunk.type.fields()) else 'size']):02x}", + f"fd: 0x{int(unsorted_chunk['fd']):02x}", + f"bk: 0x{int(unsorted_chunk['bk']):02x}", + "", + ] + + assert allocated_result == allocated_expected + assert tcache_result == tcache_expected + assert fast_result == fast_expected + assert small_result == small_expected + assert large_result == large_expected + assert unsorted_result == unsorted_expected