Use when analyzing a Rust binary without source code, reverse engineering Rust executables or libraries, demangling Rust symbols, recovering likely crate namespaces, tracing panic or unwind paths, identifying FFI boundaries, or mapping behavior from ELF, Mach-O, PE, `.so`, `.dylib`, `.dll`, `.a`, `.rlib`, or object files.
How this skill is triggered — by the user, by Claude, or both
Slash command
/rust-reverse-engineering:rust-reverse-engineeringThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Reverse engineer Rust-compiled binaries methodically. Start with fast triage, confirm that the target is actually Rust, recover namespaces and runtime clues, then move into static or dynamic analysis with a clear hypothesis.
agents/openai.yamlreferences/dynamic-analysis-notes.mdreferences/rust-patterns.mdreferences/setup-guide.mdreferences/static-analysis-workflow.mdreferences/triage-and-fingerprinting.mdscripts/check-deps.shscripts/collect-artifacts.shscripts/demangle-symbols.shscripts/export-ghidra-pseudocode.shscripts/find-rust-patterns.shscripts/ghidra-job.shscripts/ghidra/ExportRustPseudocode.javascripts/install-dep.shscripts/macho-slice.shscripts/triage.shReverse engineer Rust-compiled binaries methodically. Start with fast triage, confirm that the target is actually Rust, recover namespaces and runtime clues, then move into static or dynamic analysis with a clear hypothesis.
Do not jump straight into decompiler output and treat it as source truth. Rust binaries often contain compiler-generated glue, monomorphized generic code, panic paths, and async state machines that will drown the signal if you skip the triage stage.
Use this skill when the user wants to:
.rlib, or object filegdb, or lldbDo not use this skill when:
Required command-line tools:
filestringsreadelf or llvm-readelf for ELF, or otool for Mach-Onm or llvm-nmobjdump or llvm-objdump, or otool on macOSRecommended:
rustfiltgdb or lldbVerify availability first:
bash <path-to-skill>/scripts/check-deps.sh
If tooling is missing, install one dependency at a time:
bash <path-to-skill>/scripts/install-dep.sh rustfilt
bash <path-to-skill>/scripts/install-dep.sh ghidra
If automatic installation is not possible on the current machine, read references/setup-guide.md.
Run the dependency checker first:
bash <path-to-skill>/scripts/check-deps.sh
Parse the output looking for:
INSTALL_REQUIRED:INSTALL_OPTIONAL:If required tooling is missing, install it one dependency at a time:
bash <path-to-skill>/scripts/install-dep.sh <dependency>
The installer:
apt, dnf, or pacman on Linux when availablecargo install rustfilt for rustfilt when cargo existsFor optional tooling, recommend at least:
rustfiltgdb or lldbghidraRe-run check-deps.sh after installation attempts. Do not proceed until all required dependencies show OK.
Do not keep the analysis ephemeral. Create an artifact bundle on disk before doing interpretation.
bash <path-to-skill>/scripts/collect-artifacts.sh /path/to/binary
The script writes a reusable output directory:
<target>-reverse-output/
├── input/
├── triage/
├── symbols/
├── headers/
├── disassembly/
├── decompiled/
│ └── ghidra/
└── reports/
Treat this directory as the source of truth for the rest of the workflow. It should contain:
triage/summary.txt — file type, architecture, strip/debug-info status, Rust confidencetriage/strings.txt and triage/interesting-strings.txt — raw and filtered stringssymbols/raw.txt, symbols/demangled.txt, symbols/namespaces.txt, symbols/imports.txt, symbols/exports.txtheaders/* — readelf, otool, or PE header dumpsdisassembly/disassembly.txt — best-effort disassembly when availabledecompiled/ghidra/status.txt, summary.txt, functions.tsv, decompile-errors.tsv, and functions/*.c — headless Ghidra pseudocode artifacts when availablereports/summary.md and reports/pattern-hits.txtIf you need a quick rerun or a narrower pass, the underlying triage script is still available.
bash <path-to-skill>/scripts/triage.sh /path/to/binary
Capture these facts before doing anything else:
Container and file type
.rlibSymbol quality
Debug-info availability
Rust confidence
core::, alloc::, std::, or crate-like namespacesDo not describe the binary as “definitely Rust” from a single clue. Use multiple, independent signals.
Dump symbols and demangle them as early as possible.
bash <path-to-skill>/scripts/demangle-symbols.sh /path/to/binary
Focus on these buckets:
core::, alloc::, std::, panic, unwinding, formattingmain, _start, exported C ABI functions, imported libc / Win32 / networking APIsBuild a short namespace inventory:
- runtime crates:
- likely app crates:
- likely third-party crates:
- exported ABI functions:
- imported platform APIs:
Use references/triage-and-fingerprinting.md for the triage rubric and references/rust-patterns.md for common Rust-specific fingerprints.
For targeted bucket searches across the generated artifacts, run:
bash <path-to-skill>/scripts/find-rust-patterns.sh <target>-reverse-output
Recover structure conservatively.
Look for:
Entry and boundary functions
_start, main, CRT glue, loader entry pointsextern "C" / no_mangle style boundariesSubsystem anchors
Call-shape clues
Critical rule: do not assume undocumented Rust layouts are stable.
Option, Result, Vec, String, trait objects, and closure layouts as hypotheses until confirmed by debug info, repeated call-site evidence, or explicit repr(C) / repr(transparent) style interop boundaries.Once triage is complete, load the target into Ghidra, IDA, or Binary Ninja.
If analyzeHeadless is installed, collect-artifacts.sh should already materialize a decompiler bundle under decompiled/ghidra/. Treat those .c files as working pseudocode and review aids, not as original Rust source.
If the target is a universal Mach-O, thin it to the slice you actually want to analyze before trusting any disassembly or decompiler output. The automation now does this for you, defaulting to the host architecture when --macho-arch is omitted and recording the chosen slice in input/analysis-target.txt and decompiled/ghidra/analysis-target.txt. If the host slice is not the slice you want, pass --macho-arch explicitly.
Do not treat the export as complete until decompiled/ghidra/status.txt says STATE: completed or decompiled/ghidra/complete.marker exists. The export now writes progress and partial summary state during the run, so interrupted batches are clearly marked as partial or failed instead of looking empty.
Inside Codex, long-running Ghidra exports must stay attached to a live session and be allowed to run until STATE: completed. Do not interrupt them just because they are slow. Do not stop because “enough signal was gathered”, because the expected remaining value seems low, or because the auto-analysis phase looks quiet for a while. If the process is alive and decompiled/ghidra/runner-status.txt is still getting fresh heartbeats, the job is still in progress.
Use decompiled/ghidra/runner-status.txt to distinguish “slow but alive” from “actually stopped”, and use decompiled/ghidra/warning-summary.txt to see whether a small set of hotspot functions is dominating the Unable to resolve constructor / pcode error noise in headless.log. A fresh runner heartbeat together with PROCESS_ALIVE: yes means the export is still live. For detached jobs, ghidra-job.sh status now derives RUNNER_HEARTBEAT_AGE_SECONDS, RUNNER_HEARTBEAT_STALE, and LIVENESS_HINT so you can separate a slow analysis phase from a dead wrapper.
Recommended order:
main / startup gluePrefer strings, imports, exports, and clear subsystem anchors over huge decompiler functions. In stripped Rust binaries, anchor-based analysis is more reliable than trying to understand every generic helper.
Use references/static-analysis-workflow.md for the detailed workflow.
When stripped code or compiler-generated glue makes static analysis ambiguous, move to a debugger.
Use gdb or lldb to:
mainDynamic analysis is especially valuable for:
Use references/dynamic-analysis-notes.md for breakpoint ideas and debugger notes.
At the end, deliver both the artifact bundle and a structured report.
The artifact bundle should exist on disk and include at least:
Captured input and triage
input/Recovered symbol artifacts
Low-level evidence
Human-readable report
reports/summary.mdThen, in the chat response, deliver:
Binary fingerprint
Namespace inventory
Entry points and boundaries
Recovered subsystems
Key call flows
Open questions
Reject these shortcuts:
Always mention the output directory first, then summarize the findings.
Use this format when reporting to the user:
## Reverse output
- **Output dir**: `path/to/target-reverse-output`
- **Summary report**: `path/to/target-reverse-output/reports/summary.md`
## Rust binary summary
- **Target**: `path/to/binary`
- **Format / arch**: `ELF x86-64`
- **Symbols**: `partially stripped`
- **Debug info**: `DWARF absent`
- **Rust confidence**: `high`
## Namespaces
- **Runtime crates**: `core`, `alloc`, `std`
- **Likely app crates**: `my_app`, `engine`
- **Likely third-party crates**: `tokio`, `serde_json`, `reqwest`
## Entry points and boundaries
- **Startup**: `_start -> ... -> main`
- **Exports**: `plugin_init`, `process_request`
- **Imports**: `connect`, `send`, `recv`, `pthread_create`
- **FFI edges**: `plugin_init`, `process_request`
## Key findings
1. `process_request` appears to drive the request parsing path.
2. Networking likely flows through a `reqwest`/`hyper`-adjacent stack.
3. A large dispatcher near `...::poll` is a strong async-state-machine candidate.
## Next best move
- break on `process_request`
- trace the async poll path
- inspect nearby strings/xrefs for request routing or serialization formats
references/setup-guide.mdreferences/triage-and-fingerprinting.mdreferences/rust-patterns.mdreferences/static-analysis-workflow.mdreferences/dynamic-analysis-notes.mdCreates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub jingjing2222/rust-reverse-engineering-skill --plugin rust-reverse-engineering