mirror of https://github.com/pwndbg/pwndbg.git
Try heap (#744)
* start try_free_parser * try_free_parser done, tests written * add try_free to FEATURES.md * rename current_heap to allocatorpull/747/head
parent
42815836cc
commit
e3b910c5d5
|
Before Width: | Height: | Size: 211 KiB After Width: | Height: | Size: 211 KiB |
|
After Width: | Height: | Size: 21 KiB |
@ -0,0 +1,13 @@
|
||||
## Command: try_free ##
|
||||
```
|
||||
usage: try_free [-h] [addr]
|
||||
```
|
||||
Check what would happen if free was called with given address
|
||||
|
||||
| Positional Argument | Info |
|
||||
|---------------------|------|
|
||||
| addr | Address passed to free |
|
||||
|
||||
| Optional Argument | Info |
|
||||
|---------------------|------|
|
||||
| --help | show this help message and exit |
|
||||
@ -0,0 +1,247 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* copy-paste from malloc.c */
|
||||
# define INTERNAL_SIZE_T size_t
|
||||
#define SIZE_SZ (sizeof (INTERNAL_SIZE_T))
|
||||
#define MALLOC_ALIGNMENT (2 * SIZE_SZ < __alignof__ (long double) \
|
||||
? __alignof__ (long double) : 2 * SIZE_SZ)
|
||||
|
||||
typedef struct malloc_chunk {
|
||||
INTERNAL_SIZE_T prev_size; /* Size of previous chunk (if P == 0). */
|
||||
INTERNAL_SIZE_T size; /* Size in bytes, including overhead. 3LSB: N,M,P*/
|
||||
/* A(NON_MAIN_ARENA), M(IS_MMAPPED), P(PREV_INUSE) */
|
||||
|
||||
struct malloc_chunk* fd; /* double links -- used only if free. */
|
||||
struct malloc_chunk* bk;
|
||||
|
||||
/* Only used for large blocks: pointer to next larger size. */
|
||||
struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
|
||||
struct malloc_chunk* bk_nextsize;
|
||||
} malloc_chunk;
|
||||
|
||||
/* common heap setup */
|
||||
#define setup_heap char *a = malloc(50); \
|
||||
char *b = malloc(50); \
|
||||
char *c = malloc(50); \
|
||||
char *d = malloc(4000); \
|
||||
char *e = malloc(4000); \
|
||||
char *f = malloc(4000); \
|
||||
char *g = malloc(4000); \
|
||||
malloc_chunk *a_real = (malloc_chunk*)(a - 2*SIZE_SZ); \
|
||||
malloc_chunk *b_real = (malloc_chunk*)(b - 2*SIZE_SZ); \
|
||||
malloc_chunk *c_real = (malloc_chunk*)(c - 2*SIZE_SZ); \
|
||||
malloc_chunk *d_real = (malloc_chunk*)(d - 2*SIZE_SZ); \
|
||||
malloc_chunk *e_real = (malloc_chunk*)(e - 2*SIZE_SZ); \
|
||||
malloc_chunk *f_real = (malloc_chunk*)(f - 2*SIZE_SZ); \
|
||||
malloc_chunk *g_real = (malloc_chunk*)(g - 2*SIZE_SZ); \
|
||||
int *tmp; \
|
||||
int tmp2, tmp3; \
|
||||
printf("a=%p\nb=%p\nc=%p\nd=%p\ne=%p\nf=%p\ng=%p\n", a, b, c, d, e, f, g);
|
||||
|
||||
/*
|
||||
Every function MUST have two comments: "break1" and "break2"
|
||||
One after setup, second just before line triggering the bug
|
||||
*/
|
||||
|
||||
void invalid_pointer_overflow() {
|
||||
// free(): invalid pointer
|
||||
setup_heap
|
||||
// break1
|
||||
|
||||
tmp2 = a_real->size;
|
||||
a_real->size = 0xffffffffffffff00;
|
||||
// break2
|
||||
free(a);
|
||||
a_real->size = tmp2;
|
||||
}
|
||||
|
||||
void invalid_pointer_misaligned() {
|
||||
// free(): invalid pointer
|
||||
setup_heap
|
||||
// break1
|
||||
|
||||
// break2
|
||||
free(a+2);
|
||||
}
|
||||
|
||||
void invalid_size_minsize() {
|
||||
// free(): invalid size
|
||||
setup_heap
|
||||
// break1
|
||||
|
||||
tmp2 = a_real->size;
|
||||
a_real->size = 8;
|
||||
// break2
|
||||
free(a);
|
||||
a_real->size = tmp2;
|
||||
}
|
||||
|
||||
void invalid_size_misaligned() {
|
||||
// free(): invalid size
|
||||
setup_heap
|
||||
// break1
|
||||
|
||||
tmp2 = a_real->size;
|
||||
a_real->size = 24;
|
||||
// break2
|
||||
free(a);
|
||||
a_real->size = tmp2;
|
||||
}
|
||||
|
||||
void invalid_next_size_fast() {
|
||||
// free(): invalid next size (fast)
|
||||
setup_heap
|
||||
// break1
|
||||
|
||||
tmp2 = a_real->size;
|
||||
tmp3 = a[32 - 2*SIZE_SZ + SIZE_SZ];
|
||||
a_real->size = 32;
|
||||
a[32 - 2*SIZE_SZ + SIZE_SZ] = (size_t*)3;
|
||||
// break2
|
||||
free(a);
|
||||
a[32 - 2*SIZE_SZ + SIZE_SZ] = (size_t*)tmp3;
|
||||
a_real->size = tmp2;
|
||||
}
|
||||
|
||||
void double_free_tcache() {
|
||||
// free(): double free detected in tcache 2
|
||||
setup_heap
|
||||
// break1
|
||||
|
||||
free(a);
|
||||
// break2
|
||||
free(a);
|
||||
}
|
||||
|
||||
void double_free_fastbin() {
|
||||
// double free or corruption (fasttop)
|
||||
setup_heap
|
||||
// break1
|
||||
|
||||
void *ptrs[10];
|
||||
for (int i = 0; i < 10; ++i)
|
||||
ptrs[i] = malloc(50);
|
||||
for (int i = 0; i < 10; ++i)
|
||||
free(ptrs[i]);
|
||||
|
||||
free(a);
|
||||
// break2
|
||||
free(a);
|
||||
}
|
||||
|
||||
void invalid_fastbin_entry() {
|
||||
// invalid fastbin entry (free)
|
||||
// not working, dunno why
|
||||
// maybe because of 'have_lock == 0'
|
||||
setup_heap
|
||||
// break1
|
||||
|
||||
void *ptrs[10];
|
||||
for (int i = 0; i < 10; ++i)
|
||||
ptrs[i] = malloc(50);
|
||||
for (int i = 0; i < 10; ++i)
|
||||
free(ptrs[i]);
|
||||
|
||||
free(a);
|
||||
a_real->size = 88;
|
||||
// break2
|
||||
free(c);
|
||||
}
|
||||
|
||||
void double_free_or_corruption_top() {
|
||||
// double free or corruption (top)
|
||||
setup_heap
|
||||
// break1
|
||||
|
||||
malloc_chunk *top_chunk_real = (malloc_chunk*) (((size_t)c_real + c_real->size) & (~7));
|
||||
char *top_chunk = (char*) ((size_t)top_chunk_real + 2*SIZE_SZ);
|
||||
// break2
|
||||
free(top_chunk);
|
||||
}
|
||||
|
||||
void double_free_or_corruption_out() {
|
||||
// double free or corruption (out)
|
||||
setup_heap
|
||||
// break1
|
||||
|
||||
d_real->size = 0xffffff00;
|
||||
// break2
|
||||
free(d);
|
||||
}
|
||||
|
||||
void double_free_or_corruption_prev() {
|
||||
// double free or corruption (!prev)
|
||||
setup_heap
|
||||
// break1
|
||||
|
||||
e_real->size &= ~1;
|
||||
// break2
|
||||
free(d);
|
||||
}
|
||||
|
||||
void invalid_next_size_normal() {
|
||||
// free(): invalid next size (normal)
|
||||
setup_heap
|
||||
// break1
|
||||
|
||||
e_real->size = 1;
|
||||
// break2
|
||||
free(d);
|
||||
}
|
||||
|
||||
void corrupted_consolidate_backward() {
|
||||
// corrupted size vs. prev_size while consolidating
|
||||
setup_heap
|
||||
// break1
|
||||
|
||||
free(d);
|
||||
d_real->size = 0xaa;
|
||||
// break2
|
||||
free(e);
|
||||
}
|
||||
|
||||
void corrupted_unsorted_chunks() {
|
||||
// free(): corrupted unsorted chunks
|
||||
setup_heap
|
||||
// break1
|
||||
|
||||
free(d); // it goes to unsorted
|
||||
d_real->bk = a_real;
|
||||
// break2
|
||||
free(f);
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char const *argv[]) {
|
||||
setvbuf(stdout, NULL, _IONBF, 0);
|
||||
|
||||
if (argc < 2) {
|
||||
printf("Usage: %s bug_to_trigger\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int choice;
|
||||
sscanf(argv[1], "%d", &choice);
|
||||
switch(choice) {
|
||||
case 1: invalid_pointer_overflow(); break;
|
||||
case 2: invalid_pointer_misaligned(); break;
|
||||
case 3: invalid_size_minsize(); break;
|
||||
case 4: invalid_size_misaligned(); break;
|
||||
case 5: double_free_tcache(); break;
|
||||
case 6: invalid_next_size_fast(); break;
|
||||
case 7: double_free_fastbin(); break;
|
||||
case 8: invalid_fastbin_entry(); break;
|
||||
case 9: double_free_or_corruption_top(); break;
|
||||
case 10: double_free_or_corruption_out(); break;
|
||||
case 11: double_free_or_corruption_prev(); break;
|
||||
case 12: invalid_next_size_normal(); break;
|
||||
case 13: corrupted_consolidate_backward(); break;
|
||||
case 14: corrupted_unsorted_chunks(); break;
|
||||
default: printf("Unknown\n");
|
||||
}
|
||||
|
||||
puts("END");
|
||||
return 0;
|
||||
}
|
||||
Binary file not shown.
@ -0,0 +1,213 @@
|
||||
import gdb
|
||||
import pwndbg
|
||||
import tests
|
||||
import stat
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
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)
|
||||
Loading…
Reference in new issue