Back to skills
SKILL.md
Ctf Reverse
ASecurityReverse engineering techniques for CTF challenges. Use when analyzing binaries, game clients, obfuscated code, or esoteric languages.
- 4 stars
- 0 votes
- 0 copies
- 11 views
- Added September 12, 2026
Works with
Security analysis
100/100Pro scans all 2 files and shows the line behind each finding
npx -y skills add aibot88/sec_skill_store --skill ctf-reverse --agent claude-codeAre you the author of Ctf Reverse?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/aibot88-ctf-reverse)---
name: ctf-reverse
description: Reverse engineering techniques for CTF challenges. Use when analyzing binaries, game clients, obfuscated code, or esoteric languages.
user-invocable: false
allowed-tools: ["Bash", "Read", "Write", "Edit", "Glob", "Grep", "Task", "WebFetch", "WebSearch"]
---
# CTF Reverse Engineering
Quick reference for RE challenges. For detailed techniques, see supporting files.
## Additional Resources
- [tools.md](tools.md) - Tool-specific commands (GDB, Ghidra, radare2, IDA)
- [patterns.md](patterns.md) - Common patterns, VMs, obfuscation, anti-debugging
---
## Problem-Solving Workflow
1. **Start with strings extraction** - many easy challenges have plaintext flags
2. **Try ltrace/strace** - dynamic analysis often reveals flags without reversing
3. **Map control flow** before modifying execution
4. **Automate manual processes** via scripting (r2pipe, Python)
5. **Validate assumptions** by comparing decompiler outputs
## Quick Wins (Try First!)
```bash
# Plaintext flag extraction
strings binary | grep -E "flag\{|CTF\{|pico"
strings binary | grep -iE "flag|secret|password"
rabin2 -z binary | grep -i "flag"
# Dynamic analysis - often captures flag directly
ltrace ./binary
strace -f -s 500 ./binary
# Hex dump search
xxd binary | grep -i flag
# Run with test inputs
./binary AAAA
echo "test" | ./binary
```
## Initial Analysis
```bash
file binary # Type, architecture
checksec --file=binary # Security features (for pwn)
chmod +x binary # Make executable
```
## Memory Dumping Strategy
**Key insight:** Let the program compute the answer, then dump it.
```bash
gdb ./binary
start
b *main+0x198 # Break at final comparison
run
# Enter any input of correct length
x/s $rsi # Dump computed flag
x/38c $rsi # As characters
```
## Decoy Flag Detection
**Pattern:** Multiple fake targets before real check.
**Identification:**
1. Look for multiple comparison targets in sequence
2. Check for different success messages
3. Trace which comparison is checked LAST
**Solution:** Set breakpoint at FINAL comparison, not earlier ones.
## GDB PIE Debugging
PIE binaries randomize base address. Use relative breakpoints:
```bash
gdb ./binary
start # Forces PIE base resolution
b *main+0xca # Relative to main
run
```
## Comparison Direction (Critical!)
**Two patterns:**
1. `transform(flag) == stored_target` - Reverse the transform
2. `transform(stored_target) == flag` - Flag IS the transformed data!
**Pattern 2 solution:** Don't reverse - just apply transform to stored target.
## Common Encryption Patterns
- XOR with single byte - try all 256 values
- XOR with known plaintext (`flag{`, `CTF{`)
- RC4 with hardcoded key
- Custom permutation + XOR
- XOR with position index (`^ i` or `^ (i & 0xff)`) layered with a repeating key
## Quick Tool Reference
```bash
# Radare2
r2 -d ./binary # Debug mode
aaa # Analyze
afl # List functions
pdf @ main # Disassemble main
# Ghidra (headless)
analyzeHeadless project/ tmp -import binary -postScript script.py
# IDA
ida64 binary # Open in IDA64
```
## Binary Types
### Python .pyc
```python
import marshal, dis
with open('file.pyc', 'rb') as f:
f.read(16) # Skip header
code = marshal.load(f)
dis.dis(code)
```
### WASM
```bash
wasm2c checker.wasm -o checker.c
gcc -O3 checker.c wasm-rt-impl.c -o checker
```
### Android APK
```bash
apktool d app.apk -o decoded/ # Best - decodes resources
jadx app.apk # Decompile to Java
grep -r "flag" decoded/res/values/strings.xml
```
### .NET
- dnSpy - debugging + decompilation
- ILSpy - decompiler
### Packed (UPX)
```bash
upx -d packed -o unpacked
```
## Anti-Debugging Bypass
Common checks:
- `IsDebuggerPresent()` (Windows)
- `ptrace(PTRACE_TRACEME)` (Linux)
- `/proc/self/status` TracerPid
- Timing checks
Bypass: Set breakpoint at check, modify register to bypass conditional.
## S-Box / Keystream Patterns
**Xorshift32:** Shifts 13, 17, 5
**Xorshift64:** Shifts 12, 25, 27
**Magic constants:** `0x2545f4914f6cdd1d`, `0x9e3779b97f4a7c15`
## Custom VM Analysis
1. Identify structure: registers, memory, IP
2. Reverse `executeIns` for opcode meanings
3. Write disassembler mapping opcodes to mnemonics
4. Often easier to bruteforce than fully reverse
5. Look for the bytecode file loaded via command-line arg
**VM challenge workflow (C'est La V(M)ie):**
```python
# 1. Find entry point: entry() → __libc_start_main(FUN_xxx, ...)
# 2. Identify loader function (reads .bin file into global buffer)
# 3. Find executor with giant switch statement (opcode dispatch)
# 4. Map each case to instruction: MOVI, ADD, XOR, CMP, JZ, READ, PRINT, HLT...
# 5. Write disassembler, annotate output
# 6. Identify flag transform (often reversible byte-by-byte)
```
**Common VM opcodes to look for:**
| Pattern in decompiler | Likely instruction |
|-----------------------|-------------------|
| `global[param1] = param2` | MOVI (move immediate) |
| `global[p1] = global[p2]` | MOVR (move register) |
| `global[p1] ^= global[p2]` | XOR |
| `global[p1] op global[p2]; set flag` | CMP |
| `if (flag) IP = param` | JZ/JNZ |
| `read(stdin, &global[p1], 1)` | READ |
| `write(stdout, &global[p1], 1)` | PRINT |
## Python Bytecode Reversing
**Pattern (Slithering Bytes):** Given `dis.dis()` output of a flag checker.
**Key instructions:**
- `LOAD_GLOBAL` / `LOAD_FAST` — push name/variable onto stack
- `CALL N` — pop function + N args, call, push result
- `BINARY_SUBSCR` — pop index and sequence, push `seq[idx]`
- `COMPARE_OP` — pop two values, compare (55=`!=`, 40=`==`)
- `POP_JUMP_IF_TRUE/FALSE` — conditional branch
**Reversing XOR flag checkers:**
```python
# Pattern: ord(flag[i]) ^ KEY == EXPECTED[i]
# Reverse: chr(EXPECTED[i] ^ KEY) for each position
# Interleaved tables (odd/even indices):
odd_table = [...] # Values for indices 1, 3, 5, ...
even_table = [...] # Values for indices 0, 2, 4, ...
flag = [''] * 30
for i, val in enumerate(even_table):
flag[i*2] = chr(val ^ key_even)
for i, val in enumerate(odd_table):
flag[i*2+1] = chr(val ^ key_odd)
```
## Signal-Based Binary Exploration
**Pattern (Signal Signal Little Star):** Binary uses UNIX signals as a binary tree navigation mechanism.
**Identification:**
- Multiple `sigaction()` calls with `SA_SIGINFO`
- `sigaltstack()` setup (alternate signal stack)
- Handler decodes embedded payload, installs next pair of signals
- Two types: Node (installs children) vs Leaf (prints message + exits)
**Solving approach:**
1. Hook `sigaction` via `LD_PRELOAD` to log signal installations
2. DFS through the binary tree by sending signals
3. At each stage, observe which 2 signals are installed
4. Send one, check if program exits (leaf) or installs 2 more (node)
5. If wrong leaf, backtrack and try sibling
```c
// LD_PRELOAD interposer to log sigaction calls
int sigaction(int signum, const struct sigaction *act, ...) {
if (act && (act->sa_flags & SA_SIGINFO))
log("SET %d SA_SIGINFO=1\n", signum);
return real_sigaction(signum, act, oldact);
}
```
## Malware Anti-Analysis Bypass via Patching
**Pattern (Carrot):** Malware with multiple environment checks before executing payload.
**Common checks to patch:**
| Check | Technique | Patch |
|-------|-----------|-------|
| `ptrace(PTRACE_TRACEME)` | Anti-debug | Change `cmp -1` to `cmp 0` |
| `sleep(150)` | Anti-sandbox timing | Change sleep value to 1 |
| `/proc/cpuinfo` "hypervisor" | Anti-VM | Flip `JNZ` to `JZ` |
| "VMware"/"VirtualBox" strings | Anti-VM | Flip `JNZ` to `JZ` |
| `getpwuid` username check | Environment | Flip comparison |
| `LD_PRELOAD` check | Anti-hook | Skip check |
| Fan count / hardware check | Anti-VM | Flip `JLE` to `JGE` |
| Hostname check | Environment | Flip `JNZ` to `JZ` |
**Ghidra patching workflow:**
1. Find check function, identify the conditional jump
2. Click on instruction → `Ctrl+Shift+G` → modify opcode
3. For `JNZ` (0x75) → `JZ` (0x74), or vice versa
4. For immediate values: change operand bytes directly
5. Export: press `O` → choose "Original File" format
6. `chmod +x` the patched binary
**Server-side validation bypass:**
- If patched binary sends system info to remote server, patch the data too
- Modify string addresses in data-gathering functions
- Change format strings to embed correct values directly
## Expected Values Tables
**Locating:**
```bash
objdump -s -j .rodata binary | less
# Look near comparison instructions
# Size matches flag length
```
## x86-64 Gotchas
**Sign extension:** `0xffffffc7` behaves differently in XOR vs addition
```python
# For XOR: use low byte
esi_xor = esi & 0xff
# For addition: use full value with overflow
result = (r13 + esi) & 0xffffffff
```
## Iterative Solver Pattern
```python
for pos in range(flag_length):
for c in range(256):
computed = compute_output(c, current_state)
if computed == EXPECTED[pos]:
flag.append(c)
update_state(c, computed)
break
```
**Uniform transform shortcut:** if changing one input byte only changes one output byte,
build a 0..255 mapping by repeating a single byte across the whole input, then invert.
## Unicorn Emulation (Complex State)
```python
from unicorn import *
from unicorn.x86_const import *
mu = Uc(UC_ARCH_X86, UC_MODE_64)
# Map segments, set up stack
# Hook to trace register changes
mu.emu_start(start_addr, end_addr)
```
**Mixed-mode pitfall:** if a 64-bit stub jumps into 32-bit code via `retf/retfq`, you must
switch to a UC_MODE_32 emulator and copy **GPRs, EFLAGS, and XMM regs**; missing XMM state
will corrupt SSE-based transforms.
## Multi-Stage Shellcode Loaders
**Pattern (I Heard You Liked Loaders):** Nested shellcode with XOR decode loops and anti-debug.
**Debugging workflow:**
1. Break at `call rax` in launcher, step into shellcode
2. Bypass ptrace anti-debug: step to syscall, `set $rax=0`
3. Step through XOR decode loop (or break on `int3` if hidden)
4. Repeat for each stage until final payload
**Flag extraction from `mov` instructions:**
```python
# Final stage loads flag 4 bytes at a time via mov ebx, value
# Extract little-endian 4-byte chunks
values = [0x6174654d, 0x7b465443, ...] # From disassembly
flag = b''.join(v.to_bytes(4, 'little') for v in values)
```
## Timing Side-Channel Attack
**Pattern (Clock Out):** Validation time varies per correct character (longer sleep on match).
**Exploitation:**
```python
import time
from pwn import *
flag = ""
for pos in range(flag_length):
best_char, best_time = '', 0
for c in string.printable:
io = remote(host, port)
start = time.time()
io.sendline((flag + c).ljust(total_len, 'X'))
io.recvall()
elapsed = time.time() - start
if elapsed > best_time:
best_time = elapsed
best_char = c
io.close()
flag += best_char
```
## Godot Game Asset Extraction
**Pattern (Steal the Xmas):** Encrypted Godot .pck packages.
**Tools:**
- [gdsdecomp](https://github.com/GDRETools/gdsdecomp) - Extract Godot packages
- [KeyDot](https://github.com/Titoot/KeyDot) - Extract encryption key from Godot executables
**Workflow:**
1. Run KeyDot against game executable → extract encryption key
2. Input key into gdsdecomp
3. Extract and open project in Godot editor
4. Search scripts/resources for flag data
## Unstripped Binary Information Leaks
**Pattern (Bad Opsec):** Debug info and file paths leak author identity.
**Quick checks:**
```bash
strings binary | grep "/home/" # Home directory paths
strings binary | grep "/Users/" # macOS paths
file binary # Check if stripped
readelf -S binary | grep debug # Debug sections present?
```
## Custom Mangle Function Reversing
**Pattern (Flag Appraisal):** Binary mangles input 2 bytes at a time with intermediate state, compares to static target.
**Approach:**
1. Extract static target bytes from `.rodata` section
2. Understand mangle: processes pairs with running state value
3. Write inverse function (process in reverse, undo each operation)
4. Feed target bytes through inverse → recovers flag
## Hex-Encoded String Comparison
**Pattern (Spider's Curse):** Input converted to hex, compared against hex constant.
**Quick solve:** Extract hex constant from strings/Ghidra, decode:
```bash
echo "4d65746143..." | xxd -r -p
```
Files in this skill
- SKILL.md
- source.json
Attribution
Comments
Loading comments…
Newsletter
Get the weekly list of safe new skills, and the ones we pulled.