REx@Skill - Skill di esecuzione agentica per il reverse engineering finalizzata alla scoperta di vulnerabilità nei binari
![]()
Ghidra · radare2 · rizin · qemu-user · angr · z3 · AFL++ · valgrind · capa · Unicorn · Triton · Frida · semgrep · clang · Python · Nix
tutti bloccati, tutti riproducibili
1 · Claude Code — salta se lo hai già.```bash curl -fsSL https://claude.ai/install.sh | bash
**2 · REx@Skill** — 1 skill, 12 riferimenti, **7 subagent**, **33 script**.```bash
curl -fsSL https://raw.githubusercontent.com/tihanyin/REx-skill/main/install.sh | sh
3 · analizzare un binario.```bash claude
/re-analyze path/to/binary # one target /re-analyze path/to/directory/ # a whole corpus, evidence gathered in parallel
<div align="center">
<sub><code>install.sh</code> scrive solo in <code>~/.claude/</code> — la skill e i suoi 33 script di pipeline, i 7 agenti, <code>/re-analyze</code>.<br>Non installa mai Claude Code al posto tuo; esegue il backup di tutto ciò che sovrascriverebbe, e <code>--uninstall</code> lo rimuove in modo pulito.</sub>
</div>
<br>
<details>
<summary><b>Preferisci clonare?</b> · <i>oppure installalo altrove, o rimuovilo</i></summary>```bash
git clone https://github.com/tihanyin/REx-skill && cd REx-skill
./install.sh # into ~/.claude
./install.sh --prefix ~/.config/claude # somewhere else
./install.sh --uninstall # take it back out
![]()
Ghidra · radare2 · rizin · qemu-user · angr · z3 · AFL++ · valgrind · capa
![]()
Unicorn · Triton · Frida · semgrep · clang · Python · Nix · Linux · Claude
REx@Skill utilizza strumenti all'avanguardia raccolti da esperti di reverse engineering. Ognuno è presente nell'insieme perché si guadagna il suo posto su benchmark noti e su reali sfide di analisi binaria — non perché fosse comodo da installare. Nulla qui è reimplementato; il compito della skill stessa è sapere quale strumento risponde alla domanda che ha davanti, e quanto vale la sua risposta.
| Strumento | Cosa fa |
|---|---|
| Ghidra | riconverte un binario compilato in C leggibile |
| radare2 / rizin | un secondo decompilatore, usato per verificare il primo |
| qemu-user | esegue binari ARM, MIPS, PowerPC e RISC-V su una normale macchina x86 |
| z3 | un risolutore matematico — dimostra se un indice può uscire dal suo buffer, o se un divisore può essere zero |
| angr | determina quale input raggiungerebbe una data riga di codice |
| AFL++ | lancia milioni di input generati contro il programma per farlo andare in crash |
| valgrind | cattura bug di memoria che altrimenti non causano errori visibili |
| libdislocator | fa andare in crash immediatamente la lettura di un byte oltre un buffer |
| capa | elenca cosa può fare il binario: cifrare, aprire socket, iniettare nei processi |
| floss | trova testo nascosto che il semplice strings non rileva |
| Unicorn | esegue una singola funzione sugli input che scegli, senza eseguire il programma |
| Triton | segue dove viaggiano i dati controllati dall'attaccante durante un'esecuzione |
| Frida | osserva e modifica un programma mentre è in esecuzione |
| semgrep / cppcheck | analizzano il C decompilato alla ricerca di pattern dannosi noti |
| pwntools | la libreria di supporto per offset, parsing ELF e attività di exploit |
Queste sono le versioni esatte con cui è stato costruito e misurato:
Ghidra 12.1.2 · radare2 6.2.0 · rizin 0.9.1 · qemu-user 11.1.0 + 9 cross-sysroots · angr 9.2.154 · z3 4.16.0 · AFL++ 5.00c · valgrind 3.27.1 · capa 9.4.0 · Unicorn 2.1.4 · Triton 3.7.0 · Frida 17.17.0 · clang 21.1.8 · semgrep 1.172.0 · floss 3.1.1 · yara 4.5.7 · binwalk 3.1.0 · pwntools 4.15.0 · cppcheck 2.21.1
Ognuno è opzionale. scripts/capabilities.sh riporta ciò che questa macchina ha,
ogni script indica lo strumento che non riesce a trovare, e uno strumento mancante restringe l'analisi
in limitations invece di fallire silenziosamente.
Tre comandi e ogni strumento sopra è nel tuo PATH, esattamente a queste versioni —
niente da cercare, niente lasciato a metà configurazione.```bash
git clone https://github.com/tihanyin/REx-skill 2>/dev/null || git -C REx-skill pull
cd REx-skill
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install --no-confirm
nix develop ./devshell
scripts/capabilities.sh
| | |
|---|---|
| **0** | clona il repo, o lo aggiorna se ne hai già uno — l'installer one-line delle skill qui sopra **non** lascia un clone dietro di sé, lavora da un checkout temporaneo e lo rimuove, quindi la toolchain e gli script ne richiedono uno proprio |
| **1** | installa Nix, il package manager che fa il pinning — poi **apri un nuovo terminale**. *Hai già Nix? Salta questo passaggio.* Rieseguire l'installer su un'installazione esistente fallisce con `Found existing plan in /nix/receipt.json`, il che significa che si rifiuta di toccare ciò che hai già, non è un errore da correggere |
| **2** | entra nella shell, dalla root del repo così `scripts/` resta a portata di mano. La prima volta scarica molto; ogni volta successiva sono secondi |
| **3** | lo conferma: `ghidra pyghidra r2 rizin`, `qemu-user architectures: 7`, `angr`, `z3`, `afl` — invece delle righe `MISS` che dà una macchina nuda |
`exit` ripristina il tuo `PATH` esattamente com'era. *Testato su Ubuntu 22.04.5 LTS
(x86-64), Determinate Nix 3.22.4.*
<details>
<summary><b>Perché fare il pinning della toolchain?</b></summary>
- **Esecuzioni confrontabili.** L'output del decompilatore *è* l'input dell'analista. Due persone
su due versioni di Ghidra non stanno facendo lo stesso esperimento, e nessuna delle due può verificare
il risultato dell'altra.
- **Niente installato sulla tua macchina.** Nix tiene ogni pacchetto sotto un hash di
ciò che l'ha costruito, quindi entrare nella shell cambia il `PATH` e nient'altro. Esci dalla
shell e il tuo sistema è esattamente com'era.
- **Funziona ancora tra cinque anni.** Tre revisioni fissate ricostruiscono l'intera
toolchain, ed è ciò che rende un numero pubblicato ricontrollabile in seguito.
Tre revisioni ricostruiscono l'intera toolchain, su qualsiasi macchina, in qualsiasi momento nel
futuro:```
nixpkgs ffb3c9b700e759be2ef13237c9d8f953b32a1e46
nixpkgs-angr ac62194c3917d5f474c1a844b6fd6da2db95077d
capa-rules v9.4.0
devshell/flake.lock è l'autorità; devshell/DEVSHELL.md
elenca ogni strumento con la sua versione e a cosa serve.
REx@Skill è un metodo per decidere se un binario ha un difetto — e dimostrarlo. Sette subagenti lo eseguono, condividendo un'unica directory di evidenze.
Un agente trasforma il binario in evidenze: C decompilato, disassembly, stringhe, una mappa di quale codice raggiunge quale altro, e cosa succede quando lo si esegue davvero. Cinque agenti leggono poi quelle evidenze contemporaneamente, ciascuno a caccia di un diverso tipo di difetto, e nessuno di loro può vedere cosa hanno trovato gli altri — cinque lettori che condividono un unico decompilatore commettono gli stessi errori, quindi tenerli separati garantisce cinque letture indipendenti invece di un'unica opinione ripetuta cinque volte. Un settimo legge prima il codice, poi le loro scoperte, e decide quali reggono.
Nulla viene segnalato come bug a meno che quattro cose non siano nominate e localizzate nel binario: dove entrano i dati controllati dall'attaccante (source), l'operazione che possono rompere (sink), il controllo che avrebbe dovuto fermarli (broken guard), e chi viene danneggiato (affected principal). Se ne manca una, viene emesso come pista non dimostrata, non come finding.
Ogni esecuzione produce le stesse tre cose: i finding, ciò che è stato escluso e perché, e ciò che l'host non è riuscito a eseguire.
Il skillset è stato testato su dieci architetture — x86-64, i686, ARM, AArch64, MIPS e MIPS64 in entrambe le endianness, PowerPC a 32 bit, RISC-V e Apple arm64 — su ELF, PE e Mach-O, firmware e blob grezzi. Nulla lo limita a queste: qualsiasi architettura che il tuo decompilatore riesca a sollevare rientra nello scope.
| Agente | Esecuzione | In una riga | |
|---|---|---|---|
| 01 | re-recon | per primo, da solo | Estrae le evidenze. Non caccia bug — un finding sicuro qui è la modalità di fallimento. |
| 02 | re-bughunt | sempre | Costruisce il caso più forte e onesto che un difetto esista. Enumera ogni sink. |
| 03 | re-safety | sempre | Cerca di dimostrarlo corretto, e riporta ogni obbligo che non riesce a soddisfare. |
| 04 | re-arithmetic | se indicizza o dimensiona | Dimensione, indice, larghezza e signedness attraverso i confini delle chiamate — risolti da un solver, non nella testa di qualcuno. |
| 05 | re-lifecycle | se alloca | Allocazione, free, ownership, init e percorsi di errore. Il percorso di errore è quello che nessuno ha testato. |
| 06 | re-logic | se autentica | Autorizzazione, macchine a stati, crittografia, validate-here-use-there. Nessuna firma da cercare con grep. |
| 07 | re-reconcile | per ultimo, da solo | Legge il codice prima di leggere le conclusioni di chiunque altro, poi giudica e riporta. |
Una directory per binario, denominata <filename>-<first 8 hex of its SHA-256>:```
results/
├── index.json every sha256 analysed -> its directory
└── httpd-4f2a9c1e/ <- "httpd", sha256 4f2a9c1e...
├── decomp/ decompiled C ├── reach/ source -> sink paths
├── disasm/ disassembly, real VAs ├── bounds/ arithmetic to discharge
├── meta/ function map + base ├── sanitize/ hostile-allocator runs
├── strings/ inventory, by family ├── fuzz/ coverage-guided search
├── dynamic/ crafted-input battery ├── quarantine/ text aimed at YOU
└── notes/ the threat model └── pipeline.json what ran, what did not
**Perché l'hash è nel nome.** Due build di un programma condividono un nome file ma non un
SHA-256, quindi evidenza e binario non possono allontanarsi silenziosamente: ricompila il target e
ottieni una nuova directory invece di una inquinata.
`pipeline_status.py` controlla quell'albero e segnala quali fasi non sono mai state eseguite — perché una
fase che non è mai stata eseguita non lascia errori dietro di sé, solo una directory assente, che si legge
esattamente come "eseguita, non trovato nulla".
</details>
---
## 4. Inventario degli strumenti — quale script chiama cosa
#### Decompilare e leggere
| Strumento | Versione | Usato da | Per |
|---|---|---|---|
| **Ghidra** | 12.1.2 | `ghidra_export.py` `batch_decompile.sh` `decompile_addr.py` | il decompilatore — lo strumento portante |
| **pyghidra** | 3.1.0 | gli stessi tre | pilotarlo headless |
| radare2 | 6.2.0 | `run_tools.sh` `strings_report.py` `brief.py` | JSON da ogni comando |
| rizin | 0.9.1 | `capabilities.sh` `preflight.sh` | un **secondo** decompilatore — controverifica |
| binutils | 2.46 | `triage.py` `inventory.py` `run_tools.sh` | readelf, objdump, nm, strings, size |
| file | 5.48 | ogni punto di ingresso | primo comando, ogni volta |
#### Triage — cos'è, cosa può fare
| Strumento | Versione | Usato da | Per |
|---|---|---|---|
| **capa** | 9.4.0 | `run_tools.sh` `brief.py` | capacità dalle regole, con indirizzi |
| **floss** | 3.1.1 | `strings_report.py` | stringhe che `strings` non riesce a vedere |
| yara | 4.5.7 | `run_tools.sh` `analyze.sh` | packer, costanti crittografiche, versioni di librerie |
| detect-it-easy | 3.21 | `run_tools.sh` | identificazione di packer e compilatore |
| checksec | pwntools | `triage.py` `brief.py` | NX / RELRO / canary / PIE |
#### Eseguire — la leva singola più grande
| Strumento | Versione | Usato da | Per |
|---|---|---|---|
| **qemu-user** | 11.1.0 | `dynamic_probe.py` `quick_dynamic.sh` | eseguire binari di architetture straniere |
| **9 cross-sysroots** | glibc | `setup_sysroots.sh` | senza di essi qemu non può nemmeno caricare un binario dinamico straniero |
| gdb / ltrace / strace | 17.2 | `capabilities.sh` li segnala | trace; `ltrace` è lo strumento più sottoutilizzato qui |
> In un confronto misurato lo stesso modello ha ottenuto circa **3× il recall** con
> l'esecuzione disponibile rispetto a senza. Questa riga è il motivo per cui i sysroot vengono forniti con il flake.
#### Rendere rumorosi i bug silenziosi
| Strumento | Versione | Usato da | Per |
|---|---|---|---|
| **valgrind** | 3.27.1 | `sanitize_run.sh` | la cosa più vicina ad ASan per un binario che non puoi ricompilare |
| **AFL++** | 5.00c | `fuzz_target.sh` `quick_dynamic.sh` | fuzzing guidato dalla copertura, modalità QEMU |
| libdislocator | con AFL++ | `sanitize_run.sh` `quick_dynamic.sh` | una pagina per allocazione — trasforma una lettura OOB silenziosa in un fault |
| clang | 21.1.8 | `triage.py` | `-fsanitize=...` su codice sollevato |
#### Risolvere ed emulare
| Strumento | Versione | Usato da | Per |
|---|---|---|---|
| **z3** | 4.16.0 | `check_bound.py` | soddisfa un'affermazione sui limiti — 9 modalità più una via di fuga generica |
| **angr** | 9.2.154 | `symfn.py` | harness simbolico per funzione: hijack, scrittura OOB, divisione per zero |
| **unicorn** | 2.1.4 | `emulate.py` | eseguire UNA funzione in isolamento su input che scegli tu |
| triton | 3.7.0 | disponibile | esecuzione concolica e taint su una trace concreta |
| pwntools | 4.15.0 | `brief.py` `run_tools.sh` | offset `cyclic()`, parsing ELF/GOT |
#### Analisi statica su C decompilato
| Strumento | Versione | Usato da | Per |
|---|---|---|---|
| cppcheck | 2.21.1 | `run_tools.sh` `analyze.sh` | tollera codice che non compila — l'output del decompilatore non compila |
| semgrep | 1.172.0 | `run_tools.sh` `analyze.sh` | regole di pattern, nessuna build necessaria |
| flawfinder | 2.0.20 | `capabilities.sh` | lessicale; un grep con opinioni |
<details>
<summary><b>Anche nella shell</b> — firmware, formati, sfruttabilità</summary>
`binwalk` 3.1.0 · `unsquashfs` · `sasquatch` · `jefferson` · `ubi_reader` ·
`kaitai-struct-compiler` 0.11 · `tshark` 4.6.8 · `hexyl` · `pev` 0.81 ·
`osslsigncode` · `diffoscope` 328 · `patchelf` 0.15.2 · `ROPgadget` 7.7 ·
`one_gadget` 1.9.0 · `honggfuzz` · `radamsa` 0.7 · `bitwuzla` 0.9.1 · `rr` 5.9.0 ·
`bpftrace` 0.26.0 · `upx` 5.2.0
Inventario completo con ogni versione: [`devshell/DEVSHELL.md`](https://github.com/tihanyin/rex-skill/blob/main/devshell/DEVSHELL.md)
</details>
---
## 5. Usalo con qualcosa di diverso da Claude
`general-skill/SKILL-RE.md` è **un unico file autocontenuto** — 3 750 righe di puro
Markdown con frontmatter `name`/`description`. Tutto ciò che ha il bundle Claude,
in un unico pezzo: lo stesso standard di evidenza, la stessa pipeline, le stesse regole sul
fuzzing di un'architettura straniera. Clona il repo in modo che `scripts/` si trovi accanto ad esso, poi:
| Runner | Come |
|---|---|
| **Codex** | punta `AGENTS.md` ad esso, oppure incollalo come system prompt |
| **opencode** | `{"instructions": ["SKILL-RE.md"]}` in `opencode.json` |
| **Cursor / Windsurf** | inseriscilo come regola di progetto |
| **Un semplice loop API** | è solo Markdown — anteponilo |
| **Un umano** | si legge come un manuale; era questo il punto |
> **Perché due forme?** Il bundle Claude è un core da 16 KB più dodici file di riferimento
> caricati su richiesta — contesto ridotto finché una domanda specifica non richiede un capitolo
> specifico. Solo Claude Code segue quei puntatori, quindi ogni altro runner riceve il
> singolo file.
---
---
## 6. Cosa c'è dentro```
.
├── claude-skill/ the Claude Code form
│ ├── skills/reverse-engineering/
│ │ ├── SKILL.md 16 KB core, loaded on every trigger
│ │ └── references/ 12 files, pulled in on demand
│ ├── agents/ 7 subagents
│ └── commands/ /re-analyze — orchestrates all three phases
│
├── general-skill/ the portable form
│ ├── SKILL-RE.md the whole methodology, one file, 32 sections
│ └── AGENTS.md points any agent at it
│
├── scripts/ 32 tools — the pipeline and its parts
├── devshell/ flake.nix + flake.lock + DEVSHELL.md
├── images/ logo and figures
└── install.sh one command into ~/.claude
La skill funziona ovunque funzioni Claude Code — Linux, macOS, WSL. È markdown:
il core, 12 reference, 7 subagent e /re-analyze. Nulla in essa è
specifico di una piattaforma, e gli script sono Python e bash portabili. Ciò che varia sono
gli strumenti sottostanti.
La DEVSHELL è costruita e testata su Linux — x86-64 (Ubuntu 22.04.5 LTS) e aarch64. macOS è dove si assottiglia, perché undici degli strumenti non esistono affatto su Darwin:
qemu-user traduce le syscall Linux, quindi è Linux per definizione, e le
nove sysroot cross-architettura se ne vanno con esso.
Cosa resta su un Mac. Ogni fase statica: decompilazione Ghidra, radare2 e rizin, angr e z3, gli analizzatori statici, triage, strings e raggiungibilità. Ciò che perdi è l'esecuzione — la sonda dinamica, le esecuzioni con sanitizer, il fuzzing e qualsiasi binario di architettura straniera. È una perdita reale: un crash è la prova più forte che questa metodologia abbia, e nulla di statico lo sostituisce.
Niente finge il contrario. scripts/capabilities.sh riporta ciò che l'host
può effettivamente fare, scripts/pipeline_status.py segna la fase come assente, e il
costo finisce nelle limitations del report. Una fase che non è stata eseguita non viene mai
riportata come una fase che è stata eseguita e non ha trovato nulla — vedi §9.1 della skill.
In breve: analizza su Linux. Leggi, pianifica e scrivi il report ovunque.
Norbert Tihanyi · x.com/@TihanyiNorbert
un finding = source · sink · guardia infranta · principal colpito.
qualsiasi cosa di meno è un'ipotesi.
capabilities.sh | cosa può effettivamente fare questo host — verifica prima di pianificare |
analyze.sh | l'intera pipeline in ordine, passi 0-5, poi passa la mano |
batch_analyze.sh | la stessa pipeline su una directory di target |
pipeline_status.py | verifica un albero di evidenze: quali fasi sono state eseguite e quanto costa ogni assenza |
overview.py | la forma di un programma: conteggi, albero delle chiamate, sink, source |
brief.py | l'output di ogni tool per un target, consolidato, con le lacune nominate |
fn.py | leggi una funzione invece dell'intera decompilazione |
reach.py | percorsi source → sink sul grafo delle chiamate |
bounds_worklist.py | le asserzioni aritmetiche da dimostrare, in tre livelli |
check_bound.py | dimostrane una con z3 — 9 modalità più una via di fuga generica |
symfn.py | harness simbolico per una funzione |
emulate.py | esegui una funzione in isolamento su input che scegli tu |
quick_dynamic.sh | eseguilo e basta: senza input, poi con input che rompono quasi tutto |
fuzz_target.sh | fuzzing mirato al canale che il programma legge effettivamente |
sanitize_run.sh | allocatori ostili — fai crashare un bug silenzioso dell'heap |
sanitize.py | metti in quarantena il testo diretto dal modello prima che qualcosa lo legga |
| assenti su macOS | qemu-user · gdb · gef · ltrace · strace · valgrind · AFL++ · honggfuzz · frida · bpftrace · rr |
| inoltre | la build pinnata di capa non supera la propria suite di test su Darwin |
| e | il fuzzing di argv si inserisce su __libc_start_main, che è glibc — non esiste un equivalente su macOS |