Loading...
Loading...
Reverse engineering techniques for CTF challenges. Use when analyzing binaries, game clients, obfuscated code, or esoteric languages.
npx skill4agent add ramzxy/ctf ctf-reverse# 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" | ./binaryfile binary # Type, architecture
checksec --file=binary # Security features (for pwn)
chmod +x binary # Make executablegdb ./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 charactersgdb ./binary
start # Forces PIE base resolution
b *main+0xca # Relative to main
runtransform(flag) == stored_targettransform(stored_target) == flagflag{CTF{^ i^ (i & 0xff)# 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 IDA64import marshal, dis
with open('file.pyc', 'rb') as f:
f.read(16) # Skip header
code = marshal.load(f)
dis.dis(code)wasm2c checker.wasm -o checker.c
gcc -O3 checker.c wasm-rt-impl.c -o checkerapktool d app.apk -o decoded/ # Best - decodes resources
jadx app.apk # Decompile to Java
grep -r "flag" decoded/res/values/strings.xmlupx -d packed -o unpackedIsDebuggerPresent()ptrace(PTRACE_TRACEME)/proc/self/status0x2545f4914f6cdd1d0x9e3779b97f4a7c15executeIns# 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)| Pattern in decompiler | Likely instruction |
|---|---|
| MOVI (move immediate) |
| MOVR (move register) |
| XOR |
| CMP |
| JZ/JNZ |
| READ |
|
dis.dis()LOAD_GLOBALLOAD_FASTCALL NBINARY_SUBSCRseq[idx]COMPARE_OP!===POP_JUMP_IF_TRUE/FALSE# 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)sigaction()SA_SIGINFOsigaltstack()sigactionLD_PRELOAD// 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);
}| Check | Technique | Patch |
|---|---|---|
| Anti-debug | Change |
| Anti-sandbox timing | Change sleep value to 1 |
| Anti-VM | Flip |
| "VMware"/"VirtualBox" strings | Anti-VM | Flip |
| Environment | Flip comparison |
| Anti-hook | Skip check |
| Fan count / hardware check | Anti-VM | Flip |
| Hostname check | Environment | Flip |
Ctrl+Shift+GJNZJZOchmod +xobjdump -s -j .rodata binary | less
# Look near comparison instructions
# Size matches flag length0xffffffc7# For XOR: use low byte
esi_xor = esi & 0xff
# For addition: use full value with overflow
result = (r13 + esi) & 0xfffffffffor 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)
breakfrom 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)retf/retfqcall raxset $rax=0int3mov# 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)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_charstrings 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?.rodataecho "4d65746143..." | xxd -r -p