Reverse Engineering¶
Table of Contents¶
Overview¶
Techniques for analyzing compiled binaries, disassembling executables, and recovering original logic or hidden flags.
Topics Covered¶
PyInstaller unpacking — Extracting Python source from PyInstaller-compiled executables using pyinstxtractor.
Disassembly / decompilation — Analyzing compiled binaries with static tools to understand program logic.
Java bytecode analysis — Decompiling and analyzing .class files from Java challenges.
Assembly analysis — Reading and understanding x86/x64 assembly output.
Obfuscated code — Reversing obfuscated JavaScript or other languages to recover plaintext logic.
Go binary analysis — Decompiling Go ELF executables with Ghidra and a Go-specific plugin to recover map initializations, command dispatch tables, and other logic.
Recovering deleted binaries — Extracting a running process's executable from the Linux /proc filesystem even after the file has been deleted from disk.
Quick Reference¶
PyInstaller Unpacking¶
# Extract contents of a PyInstaller executable
python pyinstxtractor.py target.exe
# Output goes to target.exe_extracted/
# IMPORTANT: run with the exact Python version used to build the executable
# (visible in pyinstxtractor output as "Python version: X.Y")
# Mismatched version skips PYZ extraction — most real code lives in PYZ
python3.13 pyinstxtractor.py target.exe
# Decompile the main .pyc file
pip install decompile3
decompyle3 target.exe_extracted/target.pyc
# Or use pycdc for bytecode disassembly (supports Python 3.13+)
pycdc target.pyc
# Disassemble to raw bytecode (use when decompilation fails)
pycdas target.pyc > target.pyasm
When decompilation fails (e.g., Python 3.13 unsupported opcodes):
Use pycdas to get the raw bytecode disassembly and analyze it manually. The [Names], [Locals+Names], and [Constants] sections in the .pyasm output make functions largely readable without a full decompiler. Look for function names, constants, and opcodes like BINARY_OP, CALL, and STORE_FAST to reconstruct logic.
As a fallback, paste the .pyasm bytecode into an AI model (e.g., DeepSeek, Claude) and ask it to reconstruct the Python source. This can recover readable code even when no decompiler supports the target version.
Java Decompilation¶
Use javap to generate Bytecode from the .class file:
Use the Fernflower decompiler by simply dragging the .class into the editor on an IDE to get readable Java source code.
Static Binary Analysis¶
# Strings in a binary
strings binary | grep -i flag
# Identify file type
file binary
exiftool binary
# Disassemble with objdump
objdump -d binary | less
# Open in Ghidra or IDA for full decompilation
Go Binary Analysis with Ghidra¶
Go binaries compiled without stripping (not stripped) retain symbol names, making Ghidra decompilation much more readable than with C/C++ binaries.
Setup¶
- Install Ghidra.
- Install the Ghidra Golang Analyzer Extension for improved Go type recovery and function naming.
Key things to look for in Go binaries¶
main.init — Go's package-level initialization function. This is where global maps and lookup tables are populated, making it an ideal place to find hardcoded command IDs, dispatch tables, or configuration values:
// Example: CAN bus command ID map found in main.init
runtime.mapassign_fast32(&datatype.Map.map[uint32]uint32, phVar1, 0x201);
*extraout_RAX = 0x281; // key=0x201, value=0x281
runtime.mapassign_fast32(&datatype.Map.map[uint32]uint32, phVar1, 0x202);
*extraout_RAX_00 = 0x282; // key=0x202, value=0x282
The pattern runtime.mapassign_fast32(map, key) followed by a pointer assignment *extraout_RAX = value represents a map entry key → value.
Useful Ghidra workflow for Go binaries¶
- Import binary → select ELF → run auto-analysis.
- Install GolangAnalyzerExtension → re-analyze.
- Window → Symbol Tree → filter for "main." → browse main package functions.
- Focus on:
main.init,main.main,main.handle*,main.process*. - In decompiler: look for
runtime.mapassign_*calls to extract map contents.
Tips
- Go function names are fully qualified (
main.handleCommand, not justhandleCommand). - String constants often appear as
runtime.newobject+ pointer patterns. - Map reads use
runtime.mapaccess1_fast32/runtime.mapaccess2_fast32.
Recovering a Deleted Binary via /proc¶
On Linux, when a process's executable is deleted from disk after the process starts, the binary remains accessible through the /proc filesystem as long as the process is running. The kernel holds the file open via the process's file descriptor.
- Confirm the process is running and binary is deleted: Output example:
- Verify the binary is readable (check ELF header):
- Transfer the binary off the server for local analysis:
- Option 1: via netcat (if nc is available on server)
- Option 2: via
base64over the shell
- After transfer and file is available locally, verify the file:
References¶
Challenges¶
| Source | Name |
|---|---|
| Holiday Hack Challenge 2025, Act III | Hack-a-Gnome |
| Holiday Hack Challenge 2025, Act III | Free Ski |
| Immersive Labs, Haunted Hollow | Teacup Trouble |
| Immersive Labs, Return to Haunted Hollow | Delving Deeper |
| Immersive Labs, Return to Haunted Hollow | Confusing Code |
Web Sites¶
- Ghidra — NSA's free software reverse engineering (SRE) suite
- Ghidra Golang Analyzer Extension — Ghidra plugin for Go binary analysis
- pyinstxtractor
- HackTricks — Reversing
- dogbolt.org — online multi-decompiler