> Breaking the Sandbox: Memory Mapping & Seccomp Escape_
Quote:Quick TL;DR: The service
mmap()
s a fixed RWX page,fork()
s, appliesseccomp
only in the child, and the parent executes attacker-supplied bytes from that RWX page. Proof-of-concept: send compact shellcode that opens and prints the flag (non-interactive). No ROP, no libc leaks — one-shot shellcode.
## Overview
- >Target:
nc 34.107.44.96 11773
(remote service) - >Goal: read the flag and print it to the socket (non-interactive).
- >Constraints: the service reads up to 0x100 bytes from the connection into a fixed RWX buffer and then executes those bytes.
- >Key weakness:
fork()
occurs before the child applies seccomp; only the child is sandboxed.
This is a classic CTF trick — the service tries to be clever with seccomp but forgets the order of operations. The execution occurs in the unsandboxed parent.
## RE (what matters)
From the decompilation (relevant excerpts):
terminalpuVar1 = (undefined8 *)mmap((void *)0x2875890000,0x200000,7,0x22,-1,0); ... sandbox_call(); void sandbox_call(long *param_1) { // fill mapped region with 0x90 fork(); read(0, (void *)(*param_1 + 8), 0x100); // read attacker bytes into base+8 if (is_parent) { (*(code *)(psVar4 + 1))(); // call base+8 -> execute injected bytes } else { sandbox_check(param_1); // child applies seccomp } }
- >Mapping: fixed base
0x28758a0000
(absolute addressing possible). - >Execution: the parent executes pointer
base+8
after reading up to0x100
bytes there. - >Seccomp: applied to child only. Parent keeps syscalls.
Result: Parent executes your code unsandboxed; you can do open/read/write
as long as parent has permissions.
## Exploit strategy
Goals:
- >Avoid interactive shell — the CTF expects flag output.
- >Fit payload into
0x100
bytes. - >Use absolute addresses inside the mapped region to store small pointer table + strings + loop code.
- >Try multiple likely flag paths in a compact loop; on success read and write the flag back to the socket.
Why this works:
- >The mapped region is RWX and at a fixed address: we can place code and data together and reference them by absolute addresses.
- >The parent executes the injected code unsandboxed, so syscalls like
open
,read
,write
succeed if permitted by OS permissions.
## PoC payload (concept)
- >
Layout inside the single
read
payload:- >Code (loop + syscalls)
- >Pointer table (QWORDs) pointing into the string area
- >Concatenated null-terminated path strings
- >
Loop (pseudocode):
- >for each pointer in table:
- >fd = open(ptr, O_RDONLY)
- >if fd >= 0:
- >n = read(fd, BUF, 0x100)
- >write(1, BUF, n)
- >exit(0)
- >exit(1)
- >for each pointer in table:
- >
Addresses used (from binary):
- >MMAP_BASE =
0x28758a0000
- >BUF =
MMAP_BASE + 0x100
- >PTR_TABLE =
MMAP_BASE + 0x140
- >DATA =
MMAP_BASE + 0x180
- >MMAP_BASE =
The PoC assembly assembles into a single ≤0x100-byte blob which is sent straight to the target; the parent executes it and returns the flag contents.
## Exploit script (what we used)
We built a Python (pwntools) script that:
- >Assembles compact loop-based shellcode.
- >Packs pointer table + strings after the code.
- >Automatically trims the candidate list until the full payload fits into 0x100 bytes.
- >Sends the payload and prints whatever the server returns.
This approach avoids heavy per-string writes (which blow up the code size) and relies on placing the strings as data.
(Full exploit code omitted here — included in appendix if you want the exact script.)
## Robustness & edge cases
- >Why not use
rsp
for buffers? Usingrsp
can overwrite the shellcode or stack and crash the parent. We instead used a fixed buffer inside the mapped region to be safe. - >Why not ROP/mprotect? No need: the mapping is RWX. ROP would be heavier and unnecessary.
- >Seccomp concerns: Parent is unsandboxed per decompilation; if the challenge had applied seccomp to the parent as well, this would fail. Always confirm which process gets filters.
- >Path unknown: Since the binary doesn't hardcode the flag path, we probe likely locations in one compact payload. If none succeed, send additional batches with extended candidate lists.
## Lessons learned / mitigation
- >Order matters: applying seccomp to the wrong process defeats the point of the sandbox. Always ensure policies are applied to all code paths that execute untrusted input.
- >Avoid RWX when possible: use W^X (write xor execute) or mmap without exec to store untrusted data.
- >Don't execute network-provided bytes: code should not execute untrusted input; if you must, run it in a properly isolated sandbox (separate user, namespace, cgroup, and seccomp filters applied to the executing process).
- >Use capability drops & least privilege: reduce available syscalls and capabilities for processes that must interact with untrusted input.
## Appendix: example candidate paths tried
- >/flag
- >/flag.txt
- >/home/ctf/flag
- >/home/ctf/flag.txt
- >/tmp/flag
- >/root/flag
- >/etc/flag
- >/app/flag
- >/var/flag
- >/opt/flag
- >/challenge/flag
(Exploit auto-trims the list to fit payload size.)
## Closing
This was a clean challenge: a clever mix of mmap and seccomp, but the ordering mistake made it solvable without complicated exploitation techniques. The simplest, most reliable PoC: send compact shellcode that opens likely flag files and prints the contents. Keep your sandboxes correctly ordered next time.