| title | Reverse Engineering | |||||||
|---|---|---|---|---|---|---|---|---|
| type | technique | |||||||
| tags |
|
|||||||
| phase | exploitation | |||||||
| date_created | 2026-06-16 | |||||||
| date_updated | 2026-07-14 | |||||||
| sources |
|
Recovering program logic from compiled binaries (or bytecode) without source: understanding control flow, finding hidden checks, extracting flags/keys, and locating vulnerabilities to feed [[binary-exploitation]]. Core CTF category and the basis of malware analysis.
Static analysis disassembles/decompiles to recover structure; dynamic analysis runs under a debugger to observe real behaviour; symbolic execution explores many paths automatically. Most CTF RE is a hidden comparison (input vs transformed flag) you reverse or solve.
Exploitation (challenge solving; vulnerability discovery for exploit dev; malware triage).
- The binary. Determine arch/format first; matching disassembler/emulator for the target.
file ./bin; checksec --file=./bin # arch, PIE, NX, canary, RELRO
strings -n6 ./bin | less; nm -C ./bin; ldd ./bin
rabin2 -zzqq ./bin # strings + sections (radare)
detect-it-easy / die ./bin # packer/compiler ID; UPX -> upx -d- Ghidra (free, decompiler): import -> auto-analyze -> read decompiled
main; rename vars; follow the comparison that gates success. - radare2 / Cutter:
r2 -A ./bin->afl(list funcs) ->pdf @main(disasm) ->VV(graph). See [[radare2]]. - IDA if available. Look for:
strcmp/memcmpagainst flag, XOR/transform loops,system/execve, format strings.
ltrace ./bin; strace ./bin # lib + syscalls (catch strcmp args, opens)
gdb-gef ./bin -> break *main+N; run; x/s $rdi # inspect compare operands. See [[gdb-gef]]Set a breakpoint on the compare, read both operands -> one is the expected flag.
import angr, claripy
p = angr.Project("./bin"); s = p.factory.entry_state()
sm = p.factory.simulation_manager(s)
sm.explore(find=0x<win_addr>, avoid=0x<fail_addr>)
print(sm.found[0].posix.dumps(0)) # input that reaches "win"Good for many-branch input checks and key constraint solving (z3 backend).
- Python
.pyc->decompyle3/uncompyle6; PyInstaller ->pyinstxtractor. - Java/.NET ->
jadx,dnSpy,ilspy. JS -> deobfuscate (de4js, AST).
- Anti-debug: patch
ptracecheck (ret 0), orgdb set follow-fork;LD_PRELOADa fake. - Packed/obfuscated: dump from memory after unpack (
gdbat OEP); VM-based -> reverse the bytecode interpreter then write a disassembler. - Encoded flag: trace the transform loop, invert it (XOR/add/swap) in Python.
Strip symbols, control-flow obfuscation, packing, anti-debug/anti-VM, server-side checks for license/flag logic.
[[ghidra]], [[radare2]] / Cutter, IDA, [[gdb-gef]], angr, ltrace/strace, pwntools, jadx/dnSpy, binwalk ([[binwalk]]). Pairs with [[binary-exploitation]], [[malware-analysis]] and [[fuzzing]].
Execution guards to expect (and neutralize) in samples:
- Debugger checks:
IsDebuggerPresent, PEB BeingDebugged flag,CheckRemoteDebuggerPresent, timing (rdtsc/GetTickCountdeltas). Patch the check to return 0, orLD_PRELOADa fake on Linuxptrace. - Anti-VM:
CPUIDhypervisor bit, BIOS/disk-model strings, MAC OUI,IN(VMware backdoor). - Sandbox/emulator canaries: scan own process for Defender emulator exports (
MpVmp32Entry,VFS_*,ThrdMgr_*); if found, sleep 10-30 min to time out analysis. - Locale gate:
GetKeyboardLayout/GetUserDefaultLangIDabort on CIS locales before any IOC. - Argument gate: run only if a benign switch like
/i:--type=rendereris present. Hunting: a process that queries several locale/keyboard/anti-VM APIs early then exits with no activity is gated malware.
Shellcode / packed sample workflow:
scdbg.exe -f sc.bin -r # which WinAPIs the shellcode calls, self-decode?
scdbg.exe -f sc.bin -d # dump decoded shellcode
# BlobRunner / jmp2it: allocate the blob, print/loop, attach x64dbg at the address, step
upx -d packed.exe # trivial packers; else dump from memory at OEP
detect-it-easy sample # packer/compiler IDMBA-obfuscated arithmetic: keep the bit-width, verify each rewrite with Z3; CoBRA simplifies
(cobra-cli --mba "(x&y)+(x|y)" --verify -> x+y). Movfuscator (all-mov) -> demovfuscator.
Maldocs hide their real logic behind obfuscation, so extract and read the VBA rather than
running it. Common tricks: junk code guarded by an always-false If to bloat the listing, and
GetObject reading data out of UserForm controls (text boxes nested inside text boxes) so the
payload string never appears in the module body.
Static extraction (oletools, safe, no execution):
olevba --decode sample.doc # dump macros + auto-deobfuscate common encodings
oledump.py sample.doc # list streams; -s <n> -v to dump a macro stream
mraptor sample.doc # flag auto-exec + write + exec = likely maliciousLook for auto-exec triggers (AutoOpen, Document_Open, Workbook_Open), string building via
Chr()/concatenation/StrReverse, Shell/WScript.Shell.Run, and form-field reads. Rebuild
the decoded command by hand from the transform rather than trusting the visible strings, and
pull payload text out of the embedded forms.
- [[crash-analysis]]
- [[exploit-development]]
- [[seh-exploitation]]
- [[windows-exploit-development]]
A shipped desktop game/app is a THIN CLIENT for a server API: the real authority (and often the flag) is server-side, and any on-client "win", cheat-console, or vault animation is a decoy. Decompile the client to recover the whole protocol, then replay it directly.
- Managed builds decompile cleanly: Godot-Mono / Unity / .NET ->
ilspycmd <asm>.dll(or ILSpy / dnSpy); Android ->jadx. Pull out: the base URL, every endpoint + JSON shape, the request-signing key (hardcoded HMAC/secret), and any client-side role/permission derivation the client computes but never sends (e.g. an unusedDeriveStaffRole()that yields a privileged claim the UI won't make). - Forge what the UI won't: with the recovered signing key you can sign arbitrary requests, including the privileged role/params the client hardcodes away from you. That is the intended bypass, not a game hack; client-side gates (cheat code, on-screen "vault opens") are lies.
- Server gotchas, read them from the JSON error bodies (always read 4xx): step time-gates
(
too_fast/need/got-> sleep then retry = "proof of play"), strict step order (wrong_order/expected), and a per-step rotating token/nonce returned in each reply that must be threaded into the next request + its signature (reusing the session's first token ->bad_token).