IDE agentico di reverse engineering con disassembler multi-architettura in puro Rust, decompilatore nativo, debugger e agente LLM per l'analisi di binari e attività CTF.
Ambiente agentico di reverse engineering — un'app desktop di classe Ghidra nello spirito di "Cursor per il reverse engineering". Costruita con Tauri 2 (frontend React + TypeScript) sopra un backend di analisi pluggable. Il default è un motore nativo puro-Rust — nessun processo esterno, nessuna dipendenza copyleft, multi-architettura tramite Capstone. Puoi anche eseguire l'analisi tramite r2 (radare2): installalo, selezionalo come motore, e l'intera app — strumenti dell'agente e UI — lo pilota. Lo strumento dell'agente e la UI sono agnostici rispetto al backend — vedi docs/backends.md.

Workspace Cargo alla radice; l'app desktop è un pacchetto al suo interno.
tauri/ desktop app (Tauri + React)
src/ React frontend
src-tauri/ Tauri Rust backend (engine sessions, agent wiring)
package.json app scripts (Vite, Vitest, Tauri CLI)
crates/
recurse-agent/ agent framework: LLM loop, tool runtime, SQLite memory
recurse-static/ static analysis: ELF/PE/Mach-O parsing, multi-arch disassembly,
CFG and cross-reference recovery, the engine seam
recurse-vtil/ VTIL-inspired de-obfuscation/de-virtualization IL, lifter, optimizer
recurse-mcp/ standalone headless MCP server (stdio) over the engine — no Tauri, no IDA
recurse-debug/ cross-platform debugger (ptrace/Mach/Win32, breakpoints, stepping)
recurse-eval/ headless eval harness (YAML-configured tiers)
justfile single entry point for both halves
recurse-agent è solo l'agente; l'analisi statica risiede in recurse-static e il
debugger in recurse-debug, così l'agente non tira dentro codice di sistema che non
utilizza. Nessun crate dipende da Tauri, e ciascuno si compila/testa in modo autonomo;
recurse-eval pilota l'agente in modalità headless. Tutti sono membri del workspace, quindi un solo
Cargo.lock e una sola target/ coprono l'intero repository.
Ctrl+L)checksec esterno), e conteggi dell'analisi/clear e alla riapertura, e alimentano la sessione successivarecurse-agent) con tracing di debug per turno; lo stesso loop dell'agente gira
nella UI e nell'harness di evallift: eleva una funzione in un IL di de-obfuscation in stile VTIL ed esegue
passate di propagazione/folding/dead-code-elimination/branch-resolution sull'intera routine
— utile quando il disassembly sembra un dispatcher di VM o una catena di
predicati opachi (vedi docs/vtil-lift.md)decompile produce pseudocodice simile al C (if/while
structuring, copertura totale delle istruzioni) dalla stessa pipeline recurse-vtil
— nessuno strumento esterno, r2 non richiestorecurse-mcp standalone: lo stesso Engine su MCP stdio per qualsiasi
agente compatibile MCP (Claude Code, Cursor, Claude Desktop, …) — niente Tauri, niente
licenza IDA, niente bridge Python (vedi docs/recurse-mcp.md)L'analisi passa attraverso un singolo trait Engine (crates/recurse-static/src/engine.rs), quindi il
motore è una scelta, non una dipendenza obbligata:
native (default) — parsing ELF/PE/Mach-O puro-Rust e disassembly multi-architettura
(object + capstone): x86/x86-64, ARM, AArch64, MIPS, PowerPC, RISC-V, SPARC, SystemZ,
M68K, BPF. Nessun processo figlio, nessuno strumento esterno, nessun LGPL nella build. Include un decompilatore
(la pipeline lift → optimize → structure di recurse-vtil — vedi
docs/vtil-lift.md) e un'operazione lift per la de-obfuscation in stile VTIL.Scegli con il menu delle impostazioni, la variabile d'ambiente RECURSE_BACKEND, o la
configurazione salvata. L'agente ottiene un unico strumento analyze neutro rispetto al backend (functions, disasm, graph,
lift, decompile, xrefs, strings, imports, info, più raw per la console del motore) —
filtrato alle operazioni che il motore attivo supporta effettivamente, quindi raw (il native non ha console) è
pubblicizzato solo quando disponibile. La UI consuma tipi di risultato canonici, non il JSON di alcun motore.
Vedi docs/backends.md per il trait, le scelte dei crate e la motivazione
sulla licenza. Aprire un binario grande è veloce perché l'analisi è lazy — la discovery indicizza
le funzioni in modo economico e i basic block vengono decodificati solo quando una funzione viene visualizzata; vedi
docs/lazy-analysis.md.
Attaccare un server MCP a IDA/Ghidra, o incollare disassembly in un agente CLI, funziona per CTF da 5 funzioni e crolla su binari reali. Recurse è un ambiente costruito appositamente, non un wrapper di chatbot:
pdF nel contesto ad ogni turno, niente 0x401023 inventati.npm test — la verifica è
visiva. Le rinomine dell'agente si propagano alla lista delle funzioni, al grafo e al decompile
istantaneamente, così un umano conferma o rifiuta con un clic.| Strumento | Versione (testata) | Installazione |
|---|---|---|
| Node.js | ≥ 20 (23.11 usata) | https://nodejs.org o nvm |
| npm | ≥ 10 | incluso con Node.js |
| Rust | ≥ 1.77 (1.97 usata) | https://rustup.rs |
| cargo | — | incluso con Rust (rustup) |
Verifica:
node --version && npm --version && rustc --version && cargo --version
Il motore native di default è puro Rust — niente da installare. Per usare r2 (radare2)
come motore di analisi, installalo e selezionalo (il menu delle impostazioni, o
RECURSE_BACKEND=r2); Recurse pilota il binario r2 nel tuo PATH. r2 è opzionale, non è
mai richiesto dalla build, e non è mai distribuito con Recurse — porta la tua installazione.
Debian/Ubuntu/Pop!_OS:
sudo apt update
sudo apt install -y libwebkit2gtk-4.1-dev build-essential \
curl wget file libxdo-dev libssl-dev libayatana-appindicator3-dev librsvg2-dev
Altre distro: segui i prerequisiti Tauri ufficiali.
Entrambi i motori ne forniscono uno: native produce pseudocodice tramite recurse-vtil
(lift → optimize → structure — vedi docs/vtil-lift.md),
niente da installare; r2 può fornire il proprio decompilatore più completo
quando il suo plugin è installato.
Le dipendenze dell'app risiedono in tauri/; Rust proviene dalla radice del workspace. just incapsula
entrambi (vedi just --list), oppure pilotali direttamente.
just dev
# equivalente: cd tauri && npm install && npm run tauri dev
Questo avvia il server di sviluppo Vite e lancia la finestra Tauri. La prima compilazione richiede un po' (build Rust); le successive sono veloci.
just build
# equivalente: cd tauri && npm run tauri build
Il bundle finisce in target/release/bundle/ (target del workspace):
.deb / .rpm / .AppImage per Linuxtarget/release/recurseSu Arch e altre distro rolling lo step AppImage richiede una correzione locale una tantum
(linuxdeploy upstream è indietro rispetto alla toolchain della distro) — vedi
docs/linux-appimage-build.md.
just preview
# equivalente: cd tauri && npm run build && npm run preview
just lint # cargo clippy --workspace + eslint
just fmt # cargo fmt --all + prettier
just fmt-check # verifica senza scrivere
just test # cargo test --workspace + vitest
Comandi diretti equivalenti: cargo clippy --workspace --all-targets,
cargo test --workspace alla radice; npm run lint / npm run format /
npm run build dentro tauri/.
L'agente è valutato in modalità headless contro tier di crackme (vedi
crates/recurse-eval/README.md). I tier sono YAML:
filtri di selezione sul dataset, o una lista congelata di hexid, più parametri di esecuzione.
just eval-fetch # scarica i binari del tier
just eval-test # self-test dell'harness (nessuna API key necessaria, nessuna chiamata LLM)
just eval-run # esegue il tier — l'unico modo per eseguire un eval YAML
eval-run è un binario, non un test, quindi cargo test non spende mai denaro o tempo
sull'agente. Endpoint + key vanno in crates/recurse-eval/.env (copia .env.example).
Ogni esecuzione scrive target/eval-traces/<tier>/<backend>/run.log (la narrazione completa)
più un <hexid>.json per task con la conversazione completa per turno. Il
backend (native o r2) è selezionabile per esecuzione — vedi il README di eval.
Il pannello chat dell'agente gira su qualsiasi endpoint compatibile OpenAI. Configura la API key,
la base URL e il modello dalla finestra Model & Provider in-app (persistita in
~/.recurse/recurse.db), o tramite env:
# Provider ospitato (default)
export RECURSE_LLM_API_KEY=sk-or-... # o OPENROUTER_API_KEY
export RECURSE_LLM_ENDPOINT=https://openrouter.ai/api/v1/chat/completions # opzionale
export RECURSE_LLM_MODEL=openrouter/auto # opzionale
Punta l'agente a qualsiasi server compatibile OpenAI locale o self-hosted — Ollama, LM
Studio, llama-server di llama.cpp, vLLM, text-generation-webui, o un gateway remoto.
Imposta la Base URL nella finestra (sia una base URL nuda che una route completa /chat/completions
funzionano) e scegli un modello dal catalogo di quel server, o digita un id di modello
(llama3.1:8b, qwen2.5-coder, …) direttamente:
export RECURSE_LLM_ENDPOINT=http://localhost:11434/v1 # Ollama
export RECURSE_LLM_MODEL=llama3.1:8b
export RECURSE_LLM_API_KEY= # di solito non necessaria in locale
Gli endpoint locali non richiedono API key: quando la key è vuota la richiesta viene inviata senza
header Authorization, e la lista dei modelli viene letta dal {base}/models dell'endpoint stesso.
Un endpoint personalizzato è considerato configurato senza key, quindi la
chat funziona out of the box contro un server locale.
Senza credenziali ricade su un client echo così il wiring resta esercitabile.
L'agente vede il contesto live del binario (arch, bits, tipo) e può pilotare l'intera superficie di analisi (disassembly, xrefs, strings, imports, decompilazione) attraverso la sessione.
Apache-2.0 — © 2026 Aayush Khanna