REx@Skill - Habilidad de ejecución de ingeniería inversa agéntica para el descubrimiento de vulnerabilidades binarias
![]()
Ghidra · radare2 · rizin · qemu-user · angr · z3 · AFL++ · valgrind · capa · Unicorn · Triton · Frida · semgrep · clang · Python · Nix
todos fijados, todos reproducibles
1 · Claude Code — omítelo si ya lo tienes.```bash curl -fsSL https://claude.ai/install.sh | bash
**2 · REx@Skill** — 1 skill, 12 referencias, **7 subagentes**, **33 scripts**.```bash
curl -fsSL https://raw.githubusercontent.com/tihanyin/REx-skill/main/install.sh | sh
3 · analizar 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> escribe únicamente en <code>~/.claude/</code> — la skill y sus 33 scripts de pipeline, los 7 agentes, <code>/re-analyze</code>.<br>Nunca instala Claude Code por ti; hace copia de seguridad de todo lo que sobrescribiría, y <code>--uninstall</code> lo elimina limpiamente.</sub>
</div>
<br>
<details>
<summary><b>¿Prefieres clonar?</b> · <i>o instalar en otro lugar, o eliminarlo</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 utiliza herramientas de vanguardia recopiladas por expertos en ingeniería inversa. Cada una está en el conjunto porque se gana su lugar en benchmarks conocidos y desafíos reales de análisis de binarios — no porque fuera conveniente instalarla. Nada aquí está reimplementado; el trabajo propio de la skill es saber qué herramienta responde a la pregunta que tiene delante, y cuánto vale su respuesta.
| Herramienta | Qué hace |
|---|---|
| Ghidra | convierte un binario compilado de vuelta a C legible |
| radare2 / rizin | un segundo decompilador, usado para verificar el primero |
| qemu-user | ejecuta binarios ARM, MIPS, PowerPC y RISC-V en una máquina x86 normal |
| z3 | un solucionador matemático — demuestra si un índice puede salirse de su búfer, o si un divisor puede ser cero |
| angr | determina qué entrada alcanzaría una línea de código dada |
| AFL++ | lanza millones de entradas generadas contra el programa para hacerlo fallar |
| valgrind | detecta errores de memoria que de otro modo no causan ningún error visible |
| libdislocator | hace que leer un byte más allá de un búfer provoque un fallo inmediato |
| capa | enumera lo que el binario puede hacer: cifrar, abrir sockets, inyectarse en procesos |
| floss | encuentra texto oculto que strings pasa por alto |
| Unicorn | ejecuta una sola función con las entradas que elijas, sin ejecutar el programa |
| Triton | sigue el rastro de los datos controlados por el atacante durante una ejecución |
| Frida | observa y modifica un programa mientras se ejecuta |
| semgrep / cppcheck | escanean el C decompilado en busca de patrones maliciosos conocidos |
| pwntools | la biblioteca de ayuda para offsets, análisis de ELF y trabajo de exploits |
Estas son las versiones exactas con las que se construyó y midió:
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
Todas son opcionales. scripts/capabilities.sh informa de lo que tiene esta máquina,
cada script nombra la herramienta que no encuentra, y una herramienta ausente reduce el análisis
a limitations en lugar de fallar silenciosamente.
Tres comandos y todas las herramientas anteriores están en tu PATH, en exactamente estas versiones —
nada que buscar, nada a medio configurar.```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 el repositorio, o lo actualiza si ya tienes uno — el instalador de habilidades de una línea de arriba **no** deja un clon, trabaja desde un checkout temporal y lo elimina, así que la cadena de herramientas y los scripts necesitan uno propio |
| **1** | instala Nix, el gestor de paquetes que hace el pinning — luego **abre una nueva terminal**. *¿Ya tienes Nix? Sáltalo.* Volver a ejecutar el instalador sobre una instalación existente falla con `Found existing plan in /nix/receipt.json`, lo cual es su negativa a tocar lo que ya tienes, no un error que haya que corregir |
| **2** | entra en el shell, desde la raíz del repositorio para que `scripts/` siga a mano. La primera vez descarga mucho; todas las veces siguientes son segundos |
| **3** | lo confirma: `ghidra pyghidra r2 rizin`, `qemu-user architectures: 7`, `angr`, `z3`, `afl` — en lugar de las líneas `MISS` que da una máquina sin nada |
`exit` devuelve tu `PATH` exactamente como estaba. *Probado en Ubuntu 22.04.5 LTS
(x86-64), Determinate Nix 3.22.4.*
<details>
<summary><b>¿Por qué fijar la cadena de herramientas?</b></summary>
- **Ejecuciones comparables.** La salida del decompilador *es* la entrada del analista. Dos personas con dos versiones de Ghidra no están haciendo el mismo experimento, y ninguna puede verificar el resultado de la otra.
- **Nada instalado en tu máquina.** Nix guarda cada paquete bajo un hash de lo que lo construyó, así que entrar en el shell cambia `PATH` y nada más. Sal del shell y tu sistema queda exactamente como estaba.
- **Sigue funcionando dentro de cinco años.** Tres revisiones fijadas reconstruyen toda la cadena de herramientas, que es lo que hace que un número publicado se pueda volver a verificar más adelante.
Tres revisiones reconstruyen toda la cadena de herramientas, en cualquier máquina, en cualquier momento futuro:```
nixpkgs ffb3c9b700e759be2ef13237c9d8f953b32a1e46
nixpkgs-angr ac62194c3917d5f474c1a844b6fd6da2db95077d
capa-rules v9.4.0
devshell/flake.lock es la autoridad; devshell/DEVSHELL.md
enumera cada herramienta con su versión y para qué sirve.
REx@Skill es un método para decidir si un binario tiene un defecto — y demostrarlo. Siete subagentes lo ejecutan, compartiendo un único directorio de evidencias.
Un agente convierte el binario en evidencia: C descompilado, desensamblado, cadenas, un mapa de qué código alcanza a qué, y qué ocurre cuando realmente lo ejecutas. Cinco agentes leen después esa evidencia al mismo tiempo, cada uno cazando un tipo distinto de defecto, y ninguno de ellos puede ver lo que encontraron los demás — cinco lectores compartiendo un descompilador cometen los mismos errores, así que mantenerlos separados compra cinco lecturas independientes en lugar de una opinión repetida cinco veces. Un séptimo lee el código primero, luego sus hallazgos, y decide cuáles se sostienen.
Nada se reporta como un bug a menos que se nombren y localicen cuatro cosas en el binario: dónde entran los datos controlados por el atacante (source), la operación que puede romper (sink), la comprobación que debería haberlo detenido (broken guard), y quién resulta perjudicado (affected principal). Si falta una, sale como una pista no probada, no como un hallazgo.
Cada ejecución entrega las mismas tres cosas: los hallazgos, lo que fue descartado y por qué, y lo que el host no pudo ejecutar.
El conjunto de habilidades se ha probado en diez arquitecturas — x86-64, i686, ARM, AArch64, MIPS y MIPS64 en ambas endiannesses, PowerPC de 32 bits, RISC-V y Apple arm64 — sobre ELF, PE y Mach-O, firmware y blobs en bruto. Nada lo limita ahí: cualquier arquitectura que tu descompilador pueda levantar está dentro del alcance.
| Agente | Se ejecuta | En una línea | |
|---|---|---|---|
| 01 | re-recon | primero, solo | Extrae la evidencia. No caza bugs — un hallazgo con confianza aquí es el modo de fallo. |
| 02 | re-bughunt | siempre | Construye el caso honesto más fuerte de que existe un defecto. Enumera cada sink. |
| 03 | re-safety | siempre | Intenta demostrar que es sólido, e informa de cada obligación que no puede cumplir. |
| 04 | re-arithmetic | si indexa o dimensiona | Tamaño, índice, ancho y signedness a través de límites de llamada — resuelto por un solver, no en la cabeza de alguien. |
| 05 | re-lifecycle | si asigna memoria | Asignación, free, propiedad, init y rutas de error. La ruta de error es la que nadie probó. |
| 06 | re-logic | si autentica | Autorización, máquinas de estado, criptografía, validar-aquí-usar-allá. No hay firma que buscar con grep. |
| 07 | re-reconcile | último, solo | Lee el código antes de leer las conclusiones de nadie, luego adjudica e informa. |
Un directorio por binario, llamado <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
**Por qué el hash está en el nombre.** Dos compilaciones de un programa comparten un nombre de archivo pero no un
SHA-256, así que la evidencia y el binario no pueden desincronizarse silenciosamente: recompila el objetivo y
obtienes un directorio nuevo en lugar de uno contaminado.
`pipeline_status.py` audita ese árbol e informa qué etapas nunca se ejecutaron — porque una
etapa que nunca se ejecutó no deja ningún error detrás, solo un directorio ausente, que se lee
exactamente como "se ejecutó, no encontró nada".
</details>
---
## 4. Inventario de herramientas — qué script llama a qué
#### Descompilar y leer
| Herramienta | Versión | Usada por | Para |
|---|---|---|---|
| **Ghidra** | 12.1.2 | `ghidra_export.py` `batch_decompile.sh` `decompile_addr.py` | el descompilador — la herramienta fundamental |
| **pyghidra** | 3.1.0 | los mismos tres | controlarlo sin interfaz gráfica |
| radare2 | 6.2.0 | `run_tools.sh` `strings_report.py` `brief.py` | JSON de cada comando |
| rizin | 0.9.1 | `capabilities.sh` `preflight.sh` | un **segundo** descompilador — verificación cruzada |
| binutils | 2.46 | `triage.py` `inventory.py` `run_tools.sh` | readelf, objdump, nm, strings, size |
| file | 5.48 | cada punto de entrada | primer comando, siempre |
#### Triage — qué es, qué puede hacer
| Herramienta | Versión | Usada por | Para |
|---|---|---|---|
| **capa** | 9.4.0 | `run_tools.sh` `brief.py` | capacidades a partir de reglas, con direcciones |
| **floss** | 3.1.1 | `strings_report.py` | cadenas que `strings` no puede ver |
| yara | 4.5.7 | `run_tools.sh` `analyze.sh` | empaquetadores, constantes criptográficas, versiones de bibliotecas |
| detect-it-easy | 3.21 | `run_tools.sh` | identificación de empaquetador y compilador |
| checksec | pwntools | `triage.py` `brief.py` | NX / RELRO / canary / PIE |
#### Ejecutar — la palanca individual más grande
| Herramienta | Versión | Usada por | Para |
|---|---|---|---|
| **qemu-user** | 11.1.0 | `dynamic_probe.py` `quick_dynamic.sh` | ejecutar binarios de arquitectura extranjera |
| **9 cross-sysroots** | glibc | `setup_sysroots.sh` | sin ellos qemu ni siquiera puede cargar un binario dinámico extranjero |
| gdb / ltrace / strace | 17.2 | `capabilities.sh` los reporta | trazas; `ltrace` es la herramienta más infrautilizada aquí |
> En una comparación medida, el mismo modelo obtuvo aproximadamente **3× el recall** con
> ejecución disponible que sin ella. Esta fila es la razón por la que los sysroots se incluyen con el flake.
#### Hacer que los bugs silenciosos sean ruidosos
| Herramienta | Versión | Usada por | Para |
|---|---|---|---|
| **valgrind** | 3.27.1 | `sanitize_run.sh` | lo más cercano a ASan para un binario que no puedes recompilar |
| **AFL++** | 5.00c | `fuzz_target.sh` `quick_dynamic.sh` | fuzzing guiado por cobertura, modo QEMU |
| libdislocator | con AFL++ | `sanitize_run.sh` `quick_dynamic.sh` | página por asignación — convierte una lectura OOB silenciosa en un fallo |
| clang | 21.1.8 | `triage.py` | `-fsanitize=...` en código elevado |
#### Resolver y emular
| Herramienta | Versión | Usada por | Para |
|---|---|---|---|
| **z3** | 4.16.0 | `check_bound.py` | resuelve una afirmación de límites — 9 modos más una vía de escape genérica |
| **angr** | 9.2.154 | `symfn.py` | arnés simbólico por función: hijack, escritura OOB, división por cero |
| **unicorn** | 2.1.4 | `emulate.py` | ejecutar UNA función en aislamiento con las entradas que elijas |
| triton | 3.7.0 | disponible | ejecución concolic y taint sobre una traza concreta |
| pwntools | 4.15.0 | `brief.py` `run_tools.sh` | desplazamientos `cyclic()`, análisis de ELF/GOT |
#### Análisis estático sobre C descompilado
| Herramienta | Versión | Usada por | Para |
|---|---|---|---|
| cppcheck | 2.21.1 | `run_tools.sh` `analyze.sh` | tolera código que no compila — la salida del descompilador no compila |
| semgrep | 1.172.0 | `run_tools.sh` `analyze.sh` | reglas de patrones, sin necesidad de compilación |
| flawfinder | 2.0.20 | `capabilities.sh` | léxico; un grep con opiniones |
<details>
<summary><b>También en el shell</b> — firmware, formatos, explotabilidad</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 cada versión: [`devshell/DEVSHELL.md`](https://github.com/tihanyin/rex-skill/blob/main/devshell/DEVSHELL.md)
</details>
---
## 5. Úsalo con algo que no sea Claude
`general-skill/SKILL-RE.md` es **un archivo autocontenido** — 3 750 líneas de Markdown
plano con frontmatter `name`/`description`. Todo lo que tiene el paquete de Claude,
en una sola pieza: el mismo estándar de evidencia, el mismo pipeline, las mismas reglas sobre
hacer fuzzing a una arquitectura extranjera. Clona el repositorio para que `scripts/` quede junto a él, luego:
| Ejecutor | Cómo |
|---|---|
| **Codex** | apunta `AGENTS.md` hacia él, o pégalo como prompt del sistema |
| **opencode** | `{"instructions": ["SKILL-RE.md"]}` en `opencode.json` |
| **Cursor / Windsurf** | colócalo como regla de proyecto |
| **Un bucle de API simple** | es solo Markdown — antepónlo |
| **Un humano** | se lee como un libro de texto; ese era el objetivo |
> **¿Por qué dos formas?** El paquete de Claude es un núcleo de 16 KB más doce archivos de referencia
> cargados bajo demanda — contexto pequeño hasta que una pregunta específica necesita un capítulo
> específico. Solo Claude Code sigue esos punteros, así que todos los demás ejecutores reciben el
> archivo único en su lugar.
---
---
## 6. Qué hay 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 se ejecuta donde se ejecuta Claude Code — Linux, macOS, WSL. Es markdown:
el núcleo, 12 referencias, 7 subagentes y /re-analyze. Nada en ella es
específico de una plataforma, y los scripts son Python y bash portables. Lo que varía son
las herramientas que hay debajo.
El DEVSHELL está construido y probado en Linux — x86-64 (Ubuntu 22.04.5 LTS) y aarch64. macOS es donde se queda corto, porque once de las herramientas no existen en Darwin en absoluto:
qemu-user traduce las syscalls de Linux, así que es Linux por definición, y los
nueve sysroots de arquitecturas cruzadas van con él.
Lo que eso deja en un Mac. Todas las etapas estáticas: descompilación de Ghidra, radare2 y rizin, angr y z3, los analizadores estáticos, triage, strings y alcanzabilidad. Lo que pierdes es la ejecución — la sonda dinámica, las ejecuciones de sanitizers, el fuzzing y cualquier binario de arquitectura foránea. Es una pérdida real: un crash es la evidencia más fuerte que tiene esta metodología, y nada estático lo reemplaza.
Nada pretende lo contrario. scripts/capabilities.sh informa de lo que el host
puede hacer realmente, scripts/pipeline_status.py marca la etapa como ausente, y el
coste recae en las limitations del informe. Una etapa que no se ejecutó nunca se
informa como una etapa que se ejecutó y no encontró nada — véase §9.1 de la skill.
En resumen: analiza en Linux. Lee, planifica y escribe el informe en cualquier sitio.
Norbert Tihanyi · x.com/@TihanyiNorbert
un hallazgo = source · sink · guarda rota · principal afectado.
cualquier cosa menos es una hipótesis.
capabilities.sh | lo que este host puede hacer realmente — comprobar antes de planificar |
analyze.sh | todo el pipeline en orden, pasos 0-5, luego cede el control |
batch_analyze.sh | el mismo pipeline a través de un directorio de objetivos |
pipeline_status.py | auditar un árbol de evidencias: qué etapas se ejecutaron y qué cuesta cada ausencia |
overview.py | la forma de un programa: recuentos, árbol de llamadas, sinks, sources |
brief.py | la salida de cada herramienta para un objetivo, consolidada, con las lagunas nombradas |
fn.py | leer una función en lugar de toda la descompilación |
reach.py | rutas source → sink sobre el grafo de llamadas |
bounds_worklist.py | las afirmaciones aritméticas que hay que resolver, en tres niveles |
check_bound.py | resolver una con z3 — 9 modos más una vía de escape genérica |
symfn.py | arnés simbólico para una función |
emulate.py | ejecutar una función de forma aislada con las entradas que elijas |
quick_dynamic.sh | simplemente ejecutarlo: sin entrada, luego entradas que rompen casi todo |
fuzz_target.sh | fuzzing dirigido al canal que el programa realmente lee |
sanitize_run.sh | asignadores hostiles — hacer que un bug silencioso de heap provoque un crash |
sanitize.py | poner en cuarentena el texto dirigido por el modelo antes de que algo lo lea |
| ausentes en macOS | qemu-user · gdb · gef · ltrace · strace · valgrind · AFL++ · honggfuzz · frida · bpftrace · rr |
| además | la build fijada de capa no pasa su propia suite de tests en Darwin |
| y | el fuzzing de argv interpone __libc_start_main, que es glibc — no hay equivalente en macOS |