Athena Wiki

Seccomp Bypass

pwnadvanced

Analyze and bypass seccomp (Secure Computing Mode) filters in CTF pwn challenges.

pwnseccompsandboxsyscallorwbypassbpfshellcode

Seccomp Bypass

Seccomp (Secure Computing Mode) is a Linux kernel feature that restricts which syscalls a process can make. CTF challenges use it to block execve — so you can't just spawn a shell. Your job is to figure out what's allowed and use those syscalls to read the flag anyway.

The most common scenario: execve is blocked, but open, read, and write are allowed. You write ORW (Open-Read-Write) shellcode to open the flag file and print it.


Step 1: Identify the Filter

Before writing any exploit, figure out what the seccomp filter blocks:

# Best tool: seccomp-tools
gem install seccomp-tools
seccomp-tools dump ./vuln

This shows the BPF (Berkeley Packet Filter) rules that the kernel uses to decide which syscalls to allow or block.

Other ways to detect seccomp:

strings ./vuln | grep -i seccomp
objdump -d ./vuln | grep "prctl\|seccomp"
strace ./vuln 2>&1 | grep "prctl\|seccomp"

Step 2: Read the Filter

seccomp-tools dump output looks like:

line  CODE  JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004  A = arch
0001: 0x15 0x00 0x09 0xc000003e  if (A != ARCH_X86_64) goto 0011
0002: 0x20 0x00 0x00 0x00000000  A = sys_number
0003: 0x15 0x08 0x00 0x0000003b  if (A == execve) goto 0012 (KILL)
0004: 0x15 0x07 0x00 0x00000002  if (A == open) goto 0012 (KILL)
...
0012: 0x06 0x00 0x00 0x00000000  return KILL

Read this as: "If the syscall number matches, kill the process." The ones that jump to KILL are blocked. Everything else is allowed.

Look for lines with KILL (or SECCOMP_RET_KILL) to find blocked syscalls. Everything not listed is allowed.


Step 3: Choose a Bypass Strategy

The strategy depends on what's blocked:


Bypassing Architecture Checks

Some filters only check x86-64 syscalls but forget about 32-bit mode. If the filter doesn't check for ARCH_I386, you can switch to 32-bit mode and use 32-bit syscall numbers (which are different):

# Switch to 32-bit mode
context.arch = 'i386'
shellcode_32 = asm(shellcraft.i386.linux.execve('/bin/sh', [], []))

# Prepend mode switch instruction
mode_switch = b'\x6a\x23\xcb'   # push 0x23; retf
payload = mode_switch + shellcode_32

This only works if the filter doesn't verify the architecture field or if 32-bit syscalls aren't blocked.


Alternative Syscalls

When a specific syscall is blocked, look for a variant that does the same thing:

BlockedAlternativeSyscall #Notes
open (2)openat (257)257Use dirfd = -100 (AT_FDCWD) for relative paths
openopenat2 (437)437Newer kernels only
read (0)pread64 (17)17Reads from file offset
readreadv (19)19Scatter read
write (1)writev (20)20Scatter write
writesendfile (40)40Kernel-to-kernel copy
execve (59)execveat (322)322Rarely blocked

seccomp-tools Reference

# Dump filter from running binary
seccomp-tools dump ./vuln

# Dump with custom input
seccomp-tools dump ./vuln -f read < payload.bin

# Dump raw BPF filter (if you extract it manually)
seccomp-tools disasm filter.bpf

Checklist

  • seccomp-tools dump ./vuln — see exactly what's blocked
  • execve blocked? → ORW shellcode (open + read + write)
  • open blocked? → try openat (syscall 257)
  • read/write blocked? → try sendfile, mmap, pread64, writev
  • Filter only checks x86-64? → switch to 32-bit mode (push 0x23; retf)
  • Use pwntools shellcraft for clean ORW shellcode generation
  • Test locally with seccomp-tools to verify your bypass before sending to remote

Last updated on

On this page