Adds a keyword-bucketed strings dump to the re-lief MCP server, turning
the manual-grep step that today lives in the LLM's head into a
catalog-driven, deterministic lookup. Superset of extract_strings
(same {ascii, utf16le, totals, truncated} shape for backward compat)
plus a by_category block with 11 semantic categories (anti_debug,
hwid, crypto, network, registry, process, file, fingerprint,
activation, obfuscation, misc).
The categorization vocabulary lives in a new
data/drm-indicators.yaml::string_categories section. Two seed
categories (anti_debug, hwid) inherit their keyword lists from
existing catalog sections via a seed_from / seed_field YAML pointer
— when a future agent adds a new HWID API to hwid_apis.high_signal,
the categorizer picks it up on next MCP-server reload with zero
Python change. The YAML is the single source of truth for both the
indicator set that re-drm-fingerprint reads and the keyword set
that the categorizer reads.
Five skills (re-static-triage, re-malware-triage, re-drm-fingerprint,
re-vm-reverse, re-format-decode) had their manual-grep step replaced
with a call to re-lief.categorize_strings. No new workflow steps
were added — the categorizer IS the string scan.
ANTI-TAMPER-TAXONOMY.md gains a "Recognizing the patterns in
arbitrary binaries" section that documents Pattern A (encrypted-VM
bytecode interpreter: 7 section-name co-occurrence + W^X .idata +
.text virt>>raw + .ecode lazy-decrypt stub + vendor-tagged PDB +
late-bound export tail + 8+ HWID APIs) and Pattern B
(hardware-fingerprinting routine in a third-party launcher
activation library: ordinal-only exports + WinHTTP + OpenSSL +
HWID-vector APIs + split anti-debug surface) in vendor-neutral
category terms. No vendor / publisher / game / PDB-path literals
appear in any shipped file.
Tests: 7 new soft-skip tests in test_re_lief_categorize_strings.py
covering the result shape, the seed_from inheritance, the bundled
Activation64.dll high-signal hits, the legacy extract_strings
wrapper, and the GameAssembly full-section vs skip_sections paths.
All always-on tests (leakage, frontmatter, server registration,
smoke) continue to pass. ./verify.sh is green.
20 KiB
MCP Servers Reference
The RE-AI plugin ships 10 MCP servers. Each one exposes a set of tools to Claude Code via the standard Model Context Protocol stdio JSON-RPC transport.
| Server | Wraps | Status |
|---|---|---|
re-lief |
LIEF (in-process) | always available |
re-llm-decompile |
OpenAI-compatible HTTP | needs a running endpoint |
re-rizin |
rizin / rz-bin CLI | needs rizin on PATH |
re-capa |
capa (Mandiant) CLI | needs pip install flare-capa |
re-mitm2swagger |
mitmproxy + mitmproxy2swagger | needs pip install mitmproxy mitmproxy2swagger |
re-kaitai |
kaitai-struct-compiler + kaitaistruct | needs kaitai-struct-compiler on PATH |
re-gdb |
GDB + GEF | needs gdb on PATH and ~/.gdb/gef.py |
re-triton |
Triton (in-process) | needs pip install triton |
re-il2cpp |
Unity global-metadata.dat reader (pure Python) |
always available |
re-winedbg |
Wine winedbg gdbserver + gdb client | needs wine + winedbg on PATH (Linux/macOS) |
Servers that are missing dependencies will report it cleanly from their check_<name>() tool rather than crashing. The plugin is usable in degraded mode with only the no-deps servers (re-lief, re-llm-decompile, and re-il2cpp).
re-lief
Pure Python (no system deps). Wraps LIEF for cross-format binary analysis: PE, ELF, MachO, COFF, DEX, ART, OAT in a single, normalized API. Also includes a Capstone fallback for disassembly.
Tools
| Tool | Description |
|---|---|
check_lief |
Health check — return LIEF version, supported formats |
parse_binary |
Auto-detect format and return normalized header |
get_sections |
Section list with permissions, virtual vs raw size, entropy, W^X flag |
get_imports_exports |
Symbol-level import/export tables |
get_imphash |
PE import hash (MD5 of normalized import table) |
get_overlay |
Appended data after the last section (PE) |
get_authenticode |
PE signature details |
list_dex_classes |
All classes in a Dalvik DEX file |
list_dex_methods |
Methods of a DEX class by FQN |
list_oat_art |
Methods in an OAT/ART file |
disasm_capstone |
Capstone disassembly (works for any LIEF-parsed binary) |
extract_strings |
ASCII + UTF-16LE strings, section-aware |
categorize_strings |
ASCII + UTF-16LE strings, section-aware, bucketed into 11 keyword categories from data/drm-indicators.yaml::string_categories. Superset of extract_strings (same ascii / utf16le / totals / truncated shape, plus a by_category block). |
normalize_for_diff |
Structural snapshot for cross-binary diffing |
categorize_strings — keyword-bucketed strings dump
A superset of extract_strings: same {ascii, utf16le, totals, truncated} shape, plus a by_category block keyed by semantic category (anti_debug, hwid, crypto, network, registry, process, file, fingerprint, activation, obfuscation, misc). Categories are loaded from data/drm-indicators.yaml::string_categories at module import time; the anti_debug and hwid categories inherit their keyword lists from drm-indicators.yaml::anti_debug_indicators.checks[].name and hwid_apis.high_signal[].api respectively (a seed_from: pointer in the YAML). When the catalog is updated, the categorizer picks the new keywords up on next MCP server reload.
Why use it instead of extract_strings: the manual keyword-grep that the v2.4 skills did in the LLM's head is now a deterministic lookup. The categorization is consistent across runs (no LLM variance) and the result is JSON-serializable directly into the triage report.
Memory note: on a 500+ MB binary (e.g. a Unity IL2CPP GameAssembly.dll wrapped by an encrypted-VM bytecode interpreter), pass skip_sections=[".idata", ".xtls", ".xpdata", ".udata", ".xdata", ".didata", ".ecode", ".00cfg"] to skip the encrypted-VM bytecode regions. Note: on the bundled Input/rhinehartpcfg/ sample, the import-table strings live inside those sections, so skipping them blinds the categorizer to the imports. Use skip_sections for memory-bound runs; use the full section walk for completeness.
Replaces v1 code
The pefile + capstone code from backend/analysis/native.py was ported into parsers.py and disasm.py. LIEF supersedes pefile (same data for PE, plus ELF/MachO/DEX/ART/OAT). The string-extraction algorithm (ASCII + UTF-16LE, regex-driven) is salvaged from v1 and generalized.
LIEF API quirks (v0.16+)
- The format enum is
lief.Binary.FORMATS(notlief.FORMATSorlief.Formats) Sectionis a base class; concrete sections areELF.Section,PE.Section,MachO.Section— each with its ownFLAGSconstanthas_dynamic,has_relro,has_bind_nowwere dropped from the public API in 0.17. We work around this withgetattr(elf, name, False)- LIEF 0.17.6
Binaryhas no.stringsproperty. A common mistake is to dob = lief.parse(path); b.strings— it raisesAttributeError. Usere-lief.categorize_strings(orre-lief.extract_strings/re-rizin.list_stringsfor the unfiltered flat list).
re-llm-decompile
HTTP-only (no system deps). Wraps any OpenAI-compatible chat-completions API — LLM4Decompile served via vLLM, Ollama with OPENAI_COMPAT=1, OpenAI itself, etc.
Tools
| Tool | Description |
|---|---|
check_endpoint |
Hit /v1/models, return the list of available models |
decompile_function |
Send disassembly to the LLM, return C-like pseudocode |
explain_function |
Have the LLM explain disassembly (no rewrite) |
rename_variables |
LLM proposes better names for compiler-generated symbols |
summarize_binary |
Whole-binary summary from strings + imports + entry-point disasm |
Configuration
| Env var | Default | Purpose |
|---|---|---|
LLM_DECOMPILE_ENDPOINT |
http://localhost:11434/v1 |
OpenAI-compatible base URL |
LLM_DECOMPILE_MODEL |
llm4decompile |
Model name to request |
LLM_DECOMPILE_API_KEY |
(empty) | API key (use sk-... for OpenAI; empty for Ollama) |
Choosing a model
- LLM4Decompile 22B (Ref): best quality for Linux x86_64 binaries, ~44GB VRAM (or AWQ/GPTQ).
- LLM4Decompile 6.7B (Ref): good middle ground, ~14GB VRAM.
- Ollama + Qwen2.5-Coder 7B: general-purpose code model, lower decompile quality but good at explanation.
- Claude / GPT (via this server): not recommended — call Claude directly through Claude Code.
re-rizin
Subprocess wrapper around the rizin CLI. 12 tools covering the most common static-analysis operations.
Tools
| Tool | Description |
|---|---|
check_rizin |
Confirm rizin + rz-bin are installed |
get_file_info |
rz-bin -I — arch, bits, type, pic, canary, nx |
list_imports_exports |
rz-bin -i / -E — symbol tables |
list_strings |
rz-bin -z/-zz/-zzz — ASCII / UTF-16 / all |
analyze_function |
aa + afl — list all functions |
disassemble_function |
pdf — full disassembly of one function |
decompile_function |
pdc — pseudo-C decompile (lower quality than IDA/Ghidra) |
get_xrefs |
axt / axf — cross-references |
search_bytes |
/x — hex pattern search |
find_crypto_constants |
Detect AES / SHA / CRC tables |
emulate_esil |
aefi — ESIL emulation |
get_cfg_graph |
agf — DOT-format CFG |
Install
apt install rizin # Debian/Ubuntu
brew install rizin # macOS
scoop install rizin # Windows
re-capa
Subprocess wrapper around capa (Mandiant). 4 tools for capability detection with MITRE ATT&CK and MBC mappings.
Tools
| Tool | Description |
|---|---|
check_capa |
Version + rules path |
detect_capabilities |
Full capa report (JSON or vverbose) |
extract_mbc |
Just the Malware Behavior Catalog mappings |
find_interesting |
High-confidence / unique matches only |
Install
pip install flare-capa
re-mitm2swagger
Wraps mitmproxy (live capture) + mitmproxy2swagger (spec derivation). 8 tools.
Tools
| Tool | Description |
|---|---|
check_mitm |
Confirm mitmdump is on PATH |
start_capture |
Spawn mitmdump in the background |
stop_capture |
Stop a previously-started capture |
parse_flows |
Read a mitmproxy flow file |
har_to_swagger |
Convert HAR → OpenAPI 3.0 spec (with path templating) |
flow_to_swagger |
mitmproxy2swagger on a flow file → OAS |
filter_flows |
Filter flows by method/host/path/status/content-type |
extract_secrets |
Heuristically find tokens, JWTs, API keys |
Path templating rules
har_to_swagger applies these in order:
/<digits>→/{id}/<uuid>→/{uuid}/<base64-looking>→/{token}/<sha1/256 hex>→/{hash}
re-kaitai
Wraps the kaitai-struct-compiler (system) + kaitaistruct (Python). 6 tools for custom binary format reverse engineering.
Tools
| Tool | Description |
|---|---|
check_compiler |
Confirm kaitai-struct-compiler is installed |
list_known_formats |
List bundled .ksy formats |
download_format |
Download a .ksy from the kaitai-formats gallery |
compile_format |
Compile .ksy → Python at runtime |
parse_with_format |
Parse a binary with a compiled or precompiled format |
visualize |
Same as parse_with_format, named for intent |
diff_parses |
Parse two files and return a structural diff |
Install
brew install kaitai-struct-compiler # macOS
scoop install kaitai-struct-compiler # Windows
# Linux: download prebuilt from https://github.com/kaitai-io/kaitai_struct_compiler/releases
pip install kaitaistruct
re-gdb
Wraps GDB + GEF for dynamic analysis. 14 tools covering session lifecycle, breakpoints, stepping, memory, and GEF-specific commands.
Tools
| Tool | Description |
|---|---|
check_gdb |
Confirm gdb + GEF |
start_session |
Open a session, optionally load a binary |
end_session |
Tear down a session |
run_to_breakpoint |
Set a BP and run |
step_count |
Single-step N times, return registers |
read_memory |
x/N fmt ADDR |
gef_heap |
GEF heap chunks |
gef_canary |
GEF canary |
gef_registers |
GEF registers |
gef_vmmap |
GEF vmmap |
gef_nearpc |
GEF nearpc |
gef_pattern_create |
Cyclic pattern generator |
gef_pattern_offset |
Find offset of a value in a pattern |
attach_pid |
Attach to a running process |
Safety
Never run unsigned binaries on a host you care about. Use a sandbox.
Cross-platform
The server uses pexpect (POSIX) or pywinpty (Windows) for the GDB subprocess. On Windows gdb is rough; prefer WSL.
re-triton
Wraps the Triton library for symbolic execution. 6 tools.
Tools
| Tool | Description |
|---|---|
check_triton |
Confirm Triton is importable |
emulate_function |
Concrete emulation (no sym) |
symbolic_explore |
Symbolic execution through a function |
solve_constraint |
Z3-based constraint solver |
taint_analysis |
Taint tracking through a function |
find_magic_bytes |
Solve for input that produces target bytes |
Note
Triton operates on raw machine code, not files. Tools accept code_b64 (base64-encoded bytes) — the caller extracts the relevant bytes from the binary. Best-effort on Windows.
re-il2cpp
Pure-Python (no system deps). Reads Unity's global-metadata.dat to recover the C# class/method/field names that the IL2CPP compiler stripped from the game binary, AND walks the 7 binary record tables to return a structured class graph (parent types, method/field/property/event counts, type indices, tokens). Pairs with re-rizin for RVA cross-referencing. The optional resolve_method_rva tool parses GameAssembly.dll directly (requires pip install re-il2cpp[rva]).
Tools
| Tool | Description |
|---|---|
check_il2cpp |
Read the metadata header — version, magic, file size |
list_strings |
Pull strings from the unprotected C# symbol table (filter by substring) |
search_strings |
Substring search of the symbol table (returns index + string) |
list_namespaces |
Bucketed namespace list with class counts |
list_classes |
Class FQN list (optionally filtered by namespace prefix) |
get_type_definitions |
Walk the binary typeDefinitions table; return structured records (parent, type_index, method/field/property/event counts, flags, token) |
get_methods |
Typed methods of a class (token, parameter count, return type index) |
get_fields |
Typed fields of a class (token, type index) |
get_parameters |
Typed parameters of a method in declaration order |
get_properties |
Properties of a class (with has_getter / has_setter flags) |
get_events |
Events of a class (with has_add / has_remove / has_raise flags) |
get_images |
Assembly images (e.g. Assembly-CSharp.dll, UnityEngine.CoreModule.dll) with type ranges |
resolve_method_rva |
Resolve a method FQN to its GameAssembly.dll RVA (requires lief) |
Limitations
- Type indices are returned as raw int32s.
return_type_indexandtype_indexin method/field records point into the runtimeIl2CppTypetable; full name resolution requires readings_Il2CppMetadataRegistration::types[]from GameAssembly.dll, which is a v2.3.0 follow-up. Cross-reference againstget_type_definitions'stype_indexfield for the most common case. - RVA resolution requires non-stripped GameAssembly.dll. Shipped Unity release builds strip the
s_Il2CppCodeRegistrationssymbol; for these,resolve_method_rvareturns the structured data plus the IL2CPP mangled name to use withre-rizin.search_bytes. - Metadata versions 24-29 only (Unity 2019.4 - 2022.3 LTS). Unity 6 / metadata v30+ uses a different on-disk format and is not supported.
- No mmap fallback to disk if the file is huge. Memory-maps the file at full size; 10.9 MB works fine, 500+ MB would be a problem (no game ships a
global-metadata.datthat large today, but watch for it).
Example
# 1. Verify the file
re_il2cpp.check_il2cpp(metadata_path=".../global-metadata.dat")
# -> {version: 27, magic: "0xFAB11BAF", file_size_bytes: 10900000, ...}
# 2. Top-level inventory
re_il2cpp.list_namespaces(metadata_path="...", limit=20)
# 3. Classes in a specific game namespace
re_il2cpp.list_classes(metadata_path="...", namespace="Game")
# 4. Typed structure of a class
re_il2cpp.get_type_definitions(metadata_path="...", namespace="Game", limit=20)
# 5. Methods of a specific class
re_il2cpp.get_methods(metadata_path="...", class_fqn="Game.SaveGameManager")
# 6. Parameters of a method
re_il2cpp.get_parameters(metadata_path="...", method_fqn="Game.SaveGameManager.Save")
# 7. Resolve a method to its GameAssembly.dll RVA
re_il2cpp.resolve_method_rva(
metadata_path=".../global-metadata.dat",
gameassembly_path=".../GameAssembly.dll",
method_fqn="Game.SaveGameManager.Save",
)
# -> {fqn, class_fqn, name, image_name, method_index,
# pointer_table_rva, function_rva, rva_status, source}
# 8. Disassemble the function (if function_rva was returned)
re_rizin.disassemble_function(
gameassembly_path, function="<function_rva from step 7>", max_insns=200,
)
re-winedbg
Drives the winedbg gdbserver (a debugger shim that ships with Wine) plus a GDB client subprocess, so a Linux or macOS host can attach to a Windows .exe and observe its behavior at runtime. Reuses re-gdb.gdb_mi.GDBSession for the gdb-client side. Runs the .exe in a per-session WINEPREFIX under ~/.cache/re-ai-wine/<session>/; the global ~/.wine is never touched, and end_session refuses to wineserver -k any prefix outside that cache root. 19 tools.
Tools
| Tool | Description |
|---|---|
check_winedbg |
Confirm wine + winedbg + gdb are installed. Returns {status, wine_path, winedbg_path, gdb_path, *_version}. On Windows: status: ERROR, error: host_not_supported (re-winedbg is Linux/macOS only). |
launch_under_wine |
Run a .exe under Wine (no debugger attached); returns the host-side PID for later attach_pid use. |
start_winedbg_gdbserver |
Spawn winedbg --gdb <port> <exe>; the binary is paused at its entry point. |
attach_winedbg_gdbserver |
Open a GDB client subprocess and target remote the gdbserver. Populates the per-module base-address cache from info sharedlibrary. |
set_breakpoint |
By symbol, *<addr>, or <module>+0x<RVA> — RVA is resolved via the per-module base cache. |
remove_breakpoint |
By breakpoint id. |
continue_execution |
Resume; return the next stopped event as {reason, frame_addr, frame_func}. |
step_into / step_over / step_out |
Single-step primitives (stepi / nexti / finish). |
read_registers / write_register |
info registers parsed into a {name: hex} dict / set $reg = <val>. |
read_memory / write_memory |
x/... / set {<type>}<addr> = <val>. Use write_memory for runtime NOP-out, hook install, or in-memory decryption-stub replacement. |
info_modules |
info sharedlibrary parsed (drives the RVA cache). |
info_threads |
info threads. |
backtrace |
bt <n>. |
gef_trace_breakpoint |
Server-side commands N; silent; printf "<fmt>", $<reg>; continue; end with a hit counter. Replaces the manual GDB-command workaround the v1 re-vm-reverse skill used. |
end_session |
Close GDB client, stop the gdbserver, wineserver -k the per-session prefix (refuses to kill ~/.wine), kill the wine process tree. Idempotent. |
Install
install.sh (POSIX) installs wine + winedbg via apt / dnf / brew on a best-effort basis. Set RE_AI_SKIP_WINE=1 to opt out. On Windows, re-winedbg is not useful (the user has the native Windows debuggers; winedbg is a Linux/macOS compatibility shim) and check_winedbg returns {status: ERROR, error: host_not_supported}.
# Linux (Debian / Ubuntu)
sudo apt-get install -y wine wine64 winedbg
# Linux (Fedora / RHEL)
sudo dnf install -y wine winedbg
# macOS
brew install --cask wine-stable
Per-session WINEPREFIX
Every start_winedbg_gdbserver / launch_under_wine call creates a fresh random WINEPREFIX under ~/.cache/re-ai-wine/<session-id>/ (overridable via the wine_prefix arg). The global ~/.wine is never touched, and end_session will refuse to wineserver -k any prefix that does not start with ~/.cache/re-ai-wine/. This guarantees we don't kill another Wine session the user has running.
Typical end-to-end
# 1. Confirm wine / winedbg / gdb are reachable
re_winedbg.check_winedbg()
# -> {status: "OK", wine_path: "/usr/bin/wine", winedbg_path: "/usr/bin/winedbg", ...}
# 2. Spawn winedbg gdbserver, paused at entry
re_winedbg.start_winedbg_gdbserver(exe="/path/to/Foo.exe", port=0, session="analyze")
# -> {port: 41357, command_line: "winedbg --gdb 41357 -- /path/to/Foo.exe", ...}
# 3. Connect gdb client + populate the module base cache
re_winedbg.attach_winedbg_gdbserver(session="analyze", host="127.0.0.1", port=41357, exe="/path/to/Foo.exe")
# 4. Set an RVA-resolved breakpoint
re_winedbg.set_breakpoint(session="analyze", target="GameAssembly.dll+0x1234567")
# 5. Continue to the breakpoint
re_winedbg.continue_execution(session="analyze", timeout_s=10)
# -> {event: {reason: "breakpoint-hit", frame_addr: "0x...", frame_func: "..."}}
# 6. Inspect state
re_winedbg.read_registers(session="analyze")
re_winedbg.read_memory(session="analyze", addr="0x...", count=32, fmt="hex")
# 7. Trace a hot handler (server-side printf + continue)
re_winedbg.gef_trace_breakpoint(
session="analyze", target="*<dispatcher_addr>",
register="$rcx", format="idx=%d\\n", max_hits=1000,
)
# -> {hits: [{n: 0, regs: {rcx: "0x2a"}}, {n: 1, regs: {rcx: "0x3b"}}, ...], truncated: false}
# 8. Tear down
re_winedbg.end_session(session="analyze")