diff --git a/tests/binaries/heap_vis.c b/tests/binaries/heap_vis.c new file mode 100644 index 000000000..f8e920631 --- /dev/null +++ b/tests/binaries/heap_vis.c @@ -0,0 +1,21 @@ +#include +#include + +void break_here() {} + +int main() { + void* allocs[6] = {0}; + + allocs[0] = malloc(10); + allocs[1] = malloc(10); + + break_here(); + + allocs[2] = malloc(40); + + break_here(); + + free(allocs[1]); + + break_here(); +} diff --git a/tests/binaries/makefile b/tests/binaries/makefile index 3da5fa4b3..9fd395daa 100644 --- a/tests/binaries/makefile +++ b/tests/binaries/makefile @@ -81,6 +81,9 @@ heap_bins: heap_bins.c -Wl,--dynamic-linker=${GLIBC_2_33}/ld-linux-x86-64.so.2 \ -g -O0 -o heap_bins.out heap_bins.c +heap_vis: heap_vis.c + ${CC} -g -O0 -o heap_vis.out heap_vis.c + clean : @echo "[+] Cleaning stuff" @rm -f $(COMPILED) $(LINKED) $(COMPILED_ASM) $(LINKED_ASM) $(COMPILED_GO) diff --git a/tests/test_heap.py b/tests/test_heap.py index a72d569cb..ff6787b69 100644 --- a/tests/test_heap.py +++ b/tests/test_heap.py @@ -12,6 +12,8 @@ HEAP_BINARY = tests.binaries.get('heap_bugs.out') HEAP_CODE = tests.binaries.get('heap_bugs.c') _, OUTPUT_FILE = tempfile.mkstemp() +HEAP_VIS = tests.binaries.get('heap_vis.out') + def binary_parse_breakpoints(binary_code): """ @@ -214,3 +216,238 @@ def test_try_free_corrupted_unsorted_chunks(start_binary): result = gdb.execute('try_free {}'.format(hex(chunks['f'])), to_string=True) assert 'free(): corrupted unsorted chunks' in result os.remove(OUTPUT_FILE) + + +def test_vis_heap_chunk_command(start_binary): + start_binary(HEAP_VIS) + gdb.execute('break break_here') + gdb.execute('continue') + + # TODO/FIXME: Shall we have a standard method to do this kind of filtering? + # Note that we have `pages_filter` in pwndbg/pwndbg/commands/vmmap.py heh + heap_page = next(page for page in pwndbg.vmmap.get() if page.objfile == '[heap]') + + # Just a sanity check... + assert (heap_page.start & 0xfff) == 0 + + result = gdb.execute('vis_heap_chunk 1', to_string=True).splitlines() + + # We will use `heap_addr` variable to fill in proper addresses below + heap_addr = heap_page.start + heap_end = heap_page.end + + def heap_iter(offset=0x10): + nonlocal heap_addr + heap_addr += offset + return heap_addr + + def top_chunk_size(prev_inuse=True): + # We need to subtract -0x10 because we will execute this + # after a heap_iter call that will add +0x10 to heap_addr + size = heap_end - heap_addr #- 0x10 + + # Account for PREV_INUSE flag + if prev_inuse: + size |= 1 + + return size + + def hexdump_16B(gdb_symbol): + from pwndbg.commands.heap import bin_ascii + + first, second = gdb.execute('x/16xb %s' % gdb_symbol, to_string=True).splitlines() + first = [int(v, 16) for v in first.split(':')[1].split('\t')[1:]] + second = [int(v, 16) for v in second.split(':')[1].split('\t')[1:]] + + return bin_ascii(first + second) + + expected = ['', + '%#x\t0x0000000000000000\t0x0000000000000291\t................' % heap_iter(0), + '%#x\t0x0000000000000000\t0x0000000000000000\t................' % heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................' % heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................' % heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................' % heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................' % heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................' % heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................' % heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................' % heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................' % heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................' % heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................' % heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................' % heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................' % heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................' % heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................' % heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................' % heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................' % heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................' % heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................' % heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................' % heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................' % heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................' % heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................' % heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................' % heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................' % heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................' % heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................' % heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................' % heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................' % heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................' % heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................' % heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................' % heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................' % heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................' % heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................' % heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................' % heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................' % heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................' % heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................' % heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................' % heap_iter(), + '%#x\t0x0000000000000000' % heap_iter() + ] + assert result == expected + + del result + + ## Test vis_heap_chunk with count=2 + result2 = gdb.execute('vis_heap_chunk 2', to_string=True).splitlines() + + # Note: we copy expected here but we truncate last line as it is easier + # to provide it in full here + expected2 = expected[:-1] + [ + '%#x\t0x0000000000000000\t0x0000000000000021\t........!.......' % heap_iter(0), + '%#x\t0x0000000000000000\t0x0000000000000000\t................' % heap_iter(), + '%#x\t0x0000000000000000' % heap_iter() + ] + assert result2 == expected2 + + del expected + del result2 + + ## Test vis_heap_chunk with count=3 + result3 = gdb.execute('vis_heap_chunk 3', to_string=True).splitlines() + + # Note: we copy expected here but we truncate last line as it is easier + # to provide it in full here + expected3 = expected2[:-1] + [ + '%#x\t0x0000000000000000\t0x0000000000000021\t........!.......' % heap_iter(0), + '%#x\t0x0000000000000000\t0x0000000000000000\t................' % heap_iter(), + '%#x\t0x0000000000000000\t%#018x\t........1.......\t <-- Top chunk' % (heap_iter(), top_chunk_size()) + ] + assert result3 == expected3 + + del expected2 + del result3 + + ## Test vis_heap_chunk with count=4 + result4 = gdb.execute('vis_heap_chunk 4', to_string=True).splitlines() + + # Since on this breakpoint we only have 4 chunks, the output should probably be the same? + # TODO/FIXME: Shall we maybe print user that there are only 3 chunks? + assert result4 == expected3 + + del result4 + + ## Test vis_heap_chunk with no flags + result_all = gdb.execute('vis_heap_chunk', to_string=True).splitlines() + assert result_all == expected3 + + del result_all + + # Continue, so that another allocation is made + gdb.execute('continue') + + ## Test vis_heap_chunk with count=4 again + result4_b = gdb.execute('vis_heap_chunk 4', to_string=True).splitlines() + + expected4_b = expected3[:-1] + [ + '%#x\t0x0000000000000000\t0x0000000000000031\t........1.......' % heap_iter(0), + '%#x\t0x0000000000000000\t0x0000000000000000\t................' % heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................' % heap_iter(), + '%#x\t0x0000000000000000\t%#018x\t................\t <-- Top chunk' % (heap_iter(), top_chunk_size()) + ] + + assert result4_b == expected4_b + + del expected3 + del result4_b + + ## Test vis_heap_chunk with no flags + result_all2 = gdb.execute('vis_heap_chunk', to_string=True).splitlines() + assert result_all2 == expected4_b + + del result_all2 + + ## Continue, so that alloc[1] is freed + gdb.execute('continue') + + result_all3 = gdb.execute('vis_heap_chunk', to_string=True).splitlines() + + # The tcache chunks have two fields: next and key + # We are fetching it from the glibc's TLS tcache variable :) + tcache_next = int(gdb.parse_and_eval('tcache->entries[0]->next')) + tcache_key = int(gdb.parse_and_eval('tcache->entries[0]->key')) + + tcache_hexdump = hexdump_16B('tcache->entries[0]') + freed_chunk = '%#x\t%#018x\t%#018x\t%s\t ' % (heap_iter(-0x40), tcache_next, tcache_key, tcache_hexdump) + freed_chunk += '<-- tcachebins[0x20][0/1]' + + heap_addr = heap_page.start + + # This is not ideal, but hopefully it works on different builds // feel free to name it better + some_addr = heap_addr + 0x2c0 + some_addr_hexdump = hexdump_16B(hex(heap_addr + 0x90)) + + expected_all3 = ['', + '%#x\t0x0000000000000000\t0x0000000000000291\t................'% heap_iter(0), + '%#x\t0x0000000000000001\t0x0000000000000000\t................'% heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................'% heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................'% heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................'% heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................'% heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................'% heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................'% heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................'% heap_iter(), + '%#x\t%#018x\t0x0000000000000000\t%s' % (heap_iter(), some_addr, some_addr_hexdump), + '%#x\t0x0000000000000000\t0x0000000000000000\t................'% heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................'% heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................'% heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................'% heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................'% heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................'% heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................'% heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................'% heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................'% heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................'% heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................'% heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................'% heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................'% heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................'% heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................'% heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................'% heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................'% heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................'% heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................'% heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................'% heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................'% heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................'% heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................'% heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................'% heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................'% heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................'% heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................'% heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................'% heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................'% heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................'% heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................'% heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000021\t........!.......'% heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................'% heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000021\t........!.......'% heap_iter(), + '%#x\t%#018x\t%#018x\t%s\t <-- tcachebins[0x20][0/1]'% (heap_iter(), tcache_next, tcache_key, tcache_hexdump), + '%#x\t0x0000000000000000\t0x0000000000000031\t........1.......'% heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................'% heap_iter(), + '%#x\t0x0000000000000000\t0x0000000000000000\t................'% heap_iter(), + '%#x\t0x0000000000000000\t%#018x\t................\t <-- Top chunk'% (heap_iter(), top_chunk_size()) + ] + + assert result_all3 == expected_all3