Add partial overwrite support to find_fake_fast command (#2667)

* Add partial overwrite candidate to test binary

* Add --partial-overwrite option

* Add partial overwrite test case

* Implement partial overwrite feature
pull/2672/head
CptGibbon 11 months ago committed by GitHub
parent 882cd5ad7f
commit 02590b6134
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -804,6 +804,12 @@ parser.add_argument(
default=False, default=False,
help="Does the GLIBC fastbin size field bug affect the candidate size field width?", help="Does the GLIBC fastbin size field bug affect the candidate size field width?",
) )
parser.add_argument(
"--partial-overwrite",
"-p",
action="store_true",
help="Consider partial overwrite candidates, default behavior only shows word-size overwrites.",
)
@pwndbg.commands.ArgparsedCommand(parser, category=CommandCategory.PTMALLOC2) @pwndbg.commands.ArgparsedCommand(parser, category=CommandCategory.PTMALLOC2)
@ -815,6 +821,7 @@ def find_fake_fast(
max_candidate_size: int | None = None, max_candidate_size: int | None = None,
align: bool = False, align: bool = False,
glibc_fastbin_bug: bool = False, glibc_fastbin_bug: bool = False,
partial_overwrite: bool = False,
) -> None: ) -> None:
"""Find candidate fake fast chunks overlapping the specified address.""" """Find candidate fake fast chunks overlapping the specified address."""
allocator = pwndbg.aglib.heap.current allocator = pwndbg.aglib.heap.current
@ -867,7 +874,11 @@ def find_fake_fast(
max_candidate_size &= ~(allocator.malloc_align_mask) max_candidate_size &= ~(allocator.malloc_align_mask)
if partial_overwrite:
search_start = (target_address - max_candidate_size + size_sz) - (size_sz - 1)
else:
search_start = target_address - max_candidate_size + size_sz search_start = target_address - max_candidate_size + size_sz
search_end = target_address search_end = target_address
if pwndbg.aglib.memory.peek(search_start) is None: if pwndbg.aglib.memory.peek(search_start) is None:
@ -916,8 +927,14 @@ def find_fake_fast(
continue continue
candidate_address = search_start + i candidate_address = search_start + i
if partial_overwrite:
if (candidate_address + size_field) > target_address:
malloc_chunk(candidate_address - size_sz, fake=True)
else:
if (candidate_address + size_field) >= (target_address + size_sz): if (candidate_address + size_field) >= (target_address + size_sz):
malloc_chunk(candidate_address - size_sz, fake=True) malloc_chunk(candidate_address - size_sz, fake=True)
else: else:
break break

@ -7,7 +7,7 @@
#include <sys/mman.h> #include <sys/mman.h>
// Part of this test requires unmapped virtual memory at a specific alignment. // Part of this test requires unmapped virtual memory at a specific alignment.
#define LIKELY_UNMAPPED_MEMORY_ADDRESS (void*)0x500000000000 #define LIKELY_UNMAPPED_MEMORY_ADDRESS (void *)0x500000000000
/* GLIBC's HEAP_MAX_SIZE constant, used to align the aforementioned unmapped virtual memory. /* GLIBC's HEAP_MAX_SIZE constant, used to align the aforementioned unmapped virtual memory.
* HEAP_MAX_SIZE is defined at: * HEAP_MAX_SIZE is defined at:
@ -21,7 +21,7 @@
void break_here(void) {} void break_here(void) {}
// Fake chunk size field for use with issue #1142 test. // Fake chunk size field for use with issue #1142 test.
char* fake_chunk = NULL; char *fake_chunk = NULL;
// This buffer will contain the fake chunk sizes // This buffer will contain the fake chunk sizes
unsigned long buf[64] __attribute__((aligned(0x10))); unsigned long buf[64] __attribute__((aligned(0x10)));
@ -33,26 +33,28 @@ unsigned long target_address;
* Put the value of `size` at `distance` bytes before the address of * Put the value of `size` at `distance` bytes before the address of
* `target_address` * `target_address`
*/ */
void setup_mem(unsigned long size, unsigned distance) { void setup_mem(unsigned long size, unsigned distance)
{
memset(buf, 0, sizeof(buf)); memset(buf, 0, sizeof(buf));
target_address = 0; target_address = 0;
char *chunk_size_addr = (char*)&target_address - distance; char *chunk_size_addr = (char *)&target_address - distance;
*(unsigned long*)chunk_size_addr = size; *(unsigned long *)chunk_size_addr = size;
} }
int main(void) { int main(void)
{
assert((unsigned long)&target_address - (unsigned long)buf == sizeof(buf)); assert((unsigned long)&target_address - (unsigned long)buf == sizeof(buf));
/* Test whether the find_fake_fast command can deal with a fake chunk that has a set /* Test whether the find_fake_fast command can deal with a fake chunk that has a set
* NON_MAIN_ARENA flag, but no heap_info struct (the struct would reside in unmapped memory). * NON_MAIN_ARENA flag, but no heap_info struct (the struct would reside in unmapped memory).
* Issue #1142 * Issue #1142
*/ */
void* aligned_memory = NULL; void *aligned_memory = NULL;
for (void* requested_address = LIKELY_UNMAPPED_MEMORY_ADDRESS; requested_address > 0; requested_address -= HEAP_MAX_SIZE) for (void *requested_address = LIKELY_UNMAPPED_MEMORY_ADDRESS; requested_address > 0; requested_address -= HEAP_MAX_SIZE)
{ {
// Attempt to find unmapped memory aligned to HEAP_MAX_SIZE. // Attempt to find unmapped memory aligned to HEAP_MAX_SIZE.
void* mmapped_address = mmap(requested_address, 2*getpagesize(), PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); void *mmapped_address = mmap(requested_address, 2 * getpagesize(), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (mmapped_address == requested_address) if (mmapped_address == requested_address)
{ {
aligned_memory = mmapped_address; aligned_memory = mmapped_address;
@ -60,7 +62,7 @@ int main(void) {
} }
else else
{ {
munmap(mmapped_address, 2*getpagesize()); munmap(mmapped_address, 2 * getpagesize());
} }
} }
assert(aligned_memory != NULL); assert(aligned_memory != NULL);
@ -74,7 +76,7 @@ int main(void) {
assert(unmapped == 0); assert(unmapped == 0);
// Initialize malloc so heap commands can run. // Initialize malloc so heap commands can run.
void* m = malloc(0x18); void *m = malloc(0x18);
// A valid aligned fastbin chunk with no flags set // A valid aligned fastbin chunk with no flags set
setup_mem(0x20, 0x8); setup_mem(0x20, 0x8);
@ -133,4 +135,9 @@ int main(void) {
// fastbin size bug, this is still valid // fastbin size bug, this is still valid
setup_mem(0xAABBCCDD00000020, 0x8); setup_mem(0xAABBCCDD00000020, 0x8);
break_here(); break_here();
// A fastbin chunk that overlaps only the first byte of the target address
// Used to test the --partial-overwrite option
setup_mem(0x8000, 0x80);
break_here();
} }

@ -164,3 +164,10 @@ def test_find_fake_fast_command(start_binary):
result = gdb.execute("find_fake_fast &target_address --glibc-fastbin-bug", to_string=True) result = gdb.execute("find_fake_fast &target_address --glibc-fastbin-bug", to_string=True)
check_result(result, 0xAABBCCDD00000020) check_result(result, 0xAABBCCDD00000020)
gdb.execute("continue") gdb.execute("continue")
# setup_mem(0x8000, 0x80)
result = gdb.execute("find_fake_fast &target_address", to_string=True)
check_no_results(result)
result = gdb.execute("find_fake_fast &target_address --partial-overwrite", to_string=True)
check_result(result, 0x80)

Loading…
Cancel
Save