mirror of https://github.com/pwndbg/pwndbg.git
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
217 lines
6.9 KiB
Python
217 lines
6.9 KiB
Python
import os
|
|
import stat
|
|
import tempfile
|
|
|
|
import gdb
|
|
import pytest
|
|
|
|
import pwndbg
|
|
import tests
|
|
|
|
HEAP_BINARY = tests.binaries.get('heap_bugs.out')
|
|
HEAP_CODE = tests.binaries.get('heap_bugs.c')
|
|
_, OUTPUT_FILE = tempfile.mkstemp()
|
|
|
|
|
|
def binary_parse_breakpoints(binary_code):
|
|
"""
|
|
Find comments with breakpoints in binary code
|
|
and map them to function's cmd line ids
|
|
"""
|
|
# map bug id to function name (f.e: 2 -> invalid_pointer_misaligned())
|
|
with open(binary_code, 'r') as f:
|
|
func_names = {}
|
|
for line in f:
|
|
if 'case ' in line:
|
|
bug_id = int(line.split(':')[0].split()[-1])
|
|
func_name = line.split(':')[1].split(';')[0].strip()
|
|
func_names[bug_id] = func_name
|
|
|
|
# map bug id to breakpoint line numbers
|
|
with open(binary_code, 'r') as f:
|
|
breakpoints = {}
|
|
lines = f.readlines()
|
|
line_no = 0
|
|
|
|
# find functions
|
|
while line_no < len(lines) and len(breakpoints) < len(func_names):
|
|
line = lines[line_no]
|
|
line_no += 1
|
|
for bug_id, func_name in func_names.items():
|
|
if 'void {}'.format(func_name) in line:
|
|
|
|
# find break1 and break2 inside function
|
|
b1, b2 = None, None
|
|
while line_no < len(lines) and (b1 is None or b2 is None):
|
|
line = lines[line_no]
|
|
line_no += 1
|
|
|
|
if 'break1' in line:
|
|
b1 = line_no
|
|
if 'break2' in line:
|
|
b2 = line_no
|
|
|
|
breakpoints[bug_id] = (b1, b2)
|
|
|
|
return breakpoints
|
|
|
|
|
|
# breakpoints: (line after setup_heap, line before the one triggering the bug)
|
|
breakpoints = binary_parse_breakpoints(HEAP_CODE)
|
|
|
|
|
|
def setup_heap(start_binary, bug_no):
|
|
"""
|
|
Start binary
|
|
Pause after (valid) heap is set-up
|
|
Save valid chunks
|
|
Continue up until buggy code line
|
|
"""
|
|
global breakpoints
|
|
|
|
# for communication python<->HEAP_BINARY
|
|
try:
|
|
os.remove(OUTPUT_FILE)
|
|
except FileNotFoundError:
|
|
pass
|
|
|
|
start_binary(HEAP_BINARY, str(bug_no), '> {}'.format(OUTPUT_FILE))
|
|
gdb.execute('break ' + str(breakpoints[bug_no][0]))
|
|
gdb.execute('break ' + str(breakpoints[bug_no][1]))
|
|
|
|
gdb.execute('continue')
|
|
gdb.execute('continue')
|
|
|
|
chunks = {}
|
|
with open(OUTPUT_FILE, 'r') as f:
|
|
chunk_id = 'a'
|
|
for _ in range(7):
|
|
chunk = int(f.readline().split('=')[1], 16)
|
|
chunks[chunk_id] = chunk
|
|
chunk_id = chr(ord(chunk_id) + 1)
|
|
return chunks
|
|
|
|
|
|
def test_try_free_invalid_overflow(start_binary):
|
|
chunks = setup_heap(start_binary, 1)
|
|
|
|
result = gdb.execute('try_free {}'.format(hex(chunks['a'])), to_string=True)
|
|
assert 'free(): invalid pointer -> &chunk + chunk->size > max memory' in result
|
|
os.remove(OUTPUT_FILE)
|
|
|
|
|
|
def test_try_free_invalid_misaligned(start_binary):
|
|
chunks = setup_heap(start_binary, 2)
|
|
|
|
result = gdb.execute('try_free {}'.format(hex(chunks['a'] + 2)), to_string=True)
|
|
assert 'free(): invalid pointer -> misaligned chunk' in result
|
|
os.remove(OUTPUT_FILE)
|
|
|
|
|
|
def test_try_free_invalid_size_minsize(start_binary):
|
|
chunks = setup_heap(start_binary, 3)
|
|
|
|
result = gdb.execute('try_free {}'.format(hex(chunks['a'])), to_string=True)
|
|
assert 'free(): invalid size -> chunk\'s size smaller than MINSIZE' in result
|
|
os.remove(OUTPUT_FILE)
|
|
|
|
|
|
def test_try_free_invalid_size_misaligned(start_binary):
|
|
chunks = setup_heap(start_binary, 4)
|
|
|
|
result = gdb.execute('try_free {}'.format(hex(chunks['a'])), to_string=True)
|
|
assert 'free(): invalid size -> chunk\'s size is not aligned' in result
|
|
os.remove(OUTPUT_FILE)
|
|
|
|
|
|
def test_try_free_double_free_tcache(start_binary):
|
|
chunks = setup_heap(start_binary, 5)
|
|
|
|
result = gdb.execute('try_free {}'.format(hex(chunks['a'])), to_string=True)
|
|
assert 'Will do checks for tcache double-free' in result
|
|
os.remove(OUTPUT_FILE)
|
|
|
|
|
|
def test_try_free_invalid_next_size_fast(start_binary):
|
|
chunks = setup_heap(start_binary, 6)
|
|
|
|
result = gdb.execute('try_free {}'.format(hex(chunks['a'])), to_string=True)
|
|
assert 'free(): invalid next size (fast)' in result
|
|
os.remove(OUTPUT_FILE)
|
|
|
|
|
|
def test_try_free_double_free(start_binary):
|
|
chunks = setup_heap(start_binary, 7)
|
|
|
|
result = gdb.execute('try_free {}'.format(hex(chunks['a'])), to_string=True)
|
|
assert 'double free or corruption (fasttop)' in result
|
|
os.remove(OUTPUT_FILE)
|
|
|
|
|
|
def test_try_free_invalid_fastbin_entry(start_binary):
|
|
chunks = setup_heap(start_binary, 8)
|
|
|
|
result = gdb.execute('try_free {}'.format(hex(chunks['c'])), to_string=True)
|
|
assert 'invalid fastbin entry (free)' in result
|
|
os.remove(OUTPUT_FILE)
|
|
|
|
|
|
def test_try_free_double_free_or_corruption_top(start_binary):
|
|
setup_heap(start_binary, 9)
|
|
|
|
ptr_size = pwndbg.arch.ptrsize
|
|
top_chunk = int(pwndbg.heap.current.get_arena()['top']) + 2 * ptr_size
|
|
|
|
result = gdb.execute('try_free {}'.format(hex(top_chunk)), to_string=True)
|
|
assert 'double free or corruption (top)' in result
|
|
os.remove(OUTPUT_FILE)
|
|
|
|
|
|
def test_try_free_double_free_or_corruption_out(start_binary):
|
|
chunks = setup_heap(start_binary, 10)
|
|
|
|
result = gdb.execute('try_free {}'.format(hex(chunks['d'])), to_string=True)
|
|
assert 'double free or corruption (out)' in result
|
|
os.remove(OUTPUT_FILE)
|
|
|
|
|
|
def test_try_free_double_free_or_corruption_prev(start_binary):
|
|
chunks = setup_heap(start_binary, 11)
|
|
|
|
result = gdb.execute('try_free {}'.format(hex(chunks['d'])), to_string=True)
|
|
assert 'double free or corruption (!prev)' in result
|
|
os.remove(OUTPUT_FILE)
|
|
|
|
|
|
def test_try_free_invalid_next_size_normal(start_binary):
|
|
chunks = setup_heap(start_binary, 12)
|
|
|
|
result = gdb.execute('try_free {}'.format(hex(chunks['d'])), to_string=True)
|
|
assert 'free(): invalid next size (normal)' in result
|
|
os.remove(OUTPUT_FILE)
|
|
|
|
|
|
def test_try_free_corrupted_consolidate_backward(start_binary):
|
|
chunks = setup_heap(start_binary, 13)
|
|
|
|
result = gdb.execute('try_free {}'.format(hex(chunks['e'])), to_string=True)
|
|
assert 'corrupted size vs. prev_size while consolidating' in result
|
|
os.remove(OUTPUT_FILE)
|
|
|
|
|
|
def test_try_free_corrupted_consolidate_backward(start_binary):
|
|
chunks = setup_heap(start_binary, 13)
|
|
|
|
result = gdb.execute('try_free {}'.format(hex(chunks['e'])), to_string=True)
|
|
assert 'corrupted size vs. prev_size while consolidating' in result
|
|
os.remove(OUTPUT_FILE)
|
|
|
|
|
|
@pytest.mark.skip(reason="Needs review. In the heap.py on the line 972 the condition is true always. The heap_bug.c file has the function: corrupted_unsorted_chunks()")
|
|
def test_try_free_corrupted_unsorted_chunks(start_binary):
|
|
chunks = setup_heap(start_binary, 14)
|
|
|
|
result = gdb.execute('try_free {}'.format(hex(chunks['f'])), to_string=True)
|
|
assert 'free(): corrupted unsorted chunks' in result
|
|
os.remove(OUTPUT_FILE)
|