Sherlock: Caza Autónoma de Vulnerabilidades con Modelos Locales de IA
Después de construir SAINT GRAIL, la plataforma que orquesta investigación ofensiva asistida por IA, me hice una pregunta incómoda: ¿podría un sistema autónomo encontrar vulnerabilidades reales sin intervención humana constante? Este artículo documenta la respuesta: Sherlock, un pipeline de caza que combina un orquestador con agentes especializados, modelos locales de 3B parámetros servidos on-premise, y una batería de validación estricta (libFuzzer, AFL++, ASAN, valgrind, gdb). En su primer mes de operación continua confirmó siete vulnerabilidades LOW en cinco objetivos open source: 7-Zip, FFmpeg (×3), libwebp, PCRE2 y libarchive. Aquí está la arquitectura, el método y las lecciones.
/índice_de_la_investigación +
- Resumen de Inteligencia
- De SAINT GRAIL a Sherlock: Por Qué un Cazador Autónomo
- Arquitectura del Sistema
- Modelos Locales: Pequeños, Especializados, Siempre Despiertos
- El Ciclo de Vida de un Hallazgo
- Toolchain de Validación: Dos Herramientas o No Es Real
- Política de Severidad Honesta: Por Qué Publicamos los LOW
- La Cosecha de Agosto: Inventario de Hallazgos
- Divulgación Responsable: Qué Se Publica y Qué No
- Lecciones Operando la Malla IA
- Conclusión
00. Resumen de Inteligencia
Sherlock es un sistema de descubrimiento y validación de vulnerabilidades que opera de forma continua sobre objetivos open source seleccionados por su superficie de ataque: parsers de archivos, demuxers multimedia, motores de expresiones regulares y librerías de compresión. La tesis es simple: la mayor parte del trabajo de bug hunting es repetible —construir harnesses, lanzar campañas de fuzzing, hacer triage de crashes, verificar reproducibilidad, descartar duplicados— y todo lo repetible se puede automatizar con agentes de IA bien dirigidos.
Lo que la IA no sustituye es el juicio: decidir si un crash importa, cuál es su causa raíz, si tiene arreglo upstream y qué vale la pena madurar. Por eso el sistema separa músculo (ejecución masiva, correlación de logs, redacción de fichas) de criterio (políticas de severidad, verificación CVE, decisiones de escalada). El resultado del primer mes: siete hallazgos LOW confirmados y documentados, tres de ellos aún sin fix upstream al momento de escribir este artículo, y cero falsos positivos llegaron a la fase de reporte.
Este artículo es el centro de la serie. Cada hallazgo tiene su propio análisis técnico con evidencias, fragmentos de PoC y diagramas: 7-Zip IsFolderEncrypted, FFmpeg VPK/VQF/Smacker, libwebp VP8L, PCRE2 JIT y libarchive CAB/LZX.
01. De SAINT GRAIL a Sherlock: Por Qué un Cazador Autónomo
SAINT GRAIL nació como plataforma de investigación asistida: un humano dirige, la IA documenta, visualiza y ejecuta PoCs. Su migración a Go (ver resultados) demostró que el enfoque funcionaba para investigación dirigida —los PoCs de TOCTOU en Chromium y CRC bypass en 7-Zip salieron de ese ciclo—. Pero asistida significa que el cuello de botella soy yo: entre sesiones, entre campañas, entre ideas.
Sherlock invierte el modelo. El sistema propone objetivos según criterios públicos (superficie de parsers, densidad histórica de CVEs, actividad upstream), construye sus propios harnesses de fuzzing, ejecuta campañas largas mientras la máquina no hace nada más, y solo escala a atención humana cuando algo supera el filtro de validación. La diferencia práctica es brutal: donde antes había sesiones de caza de unas horas, ahora hay presencia de caza permanente. Los hallazgos no llegan cuando busco; llegan cuando ocurren.
Hay una segunda motivación, menos obvia: formar criterio de severidad honesto. La industria del bug bounty está llena de inflación —un hang se reporta como DoS crítico, un leak teórico como RCE—. Decidí que Sherlock publicaría con severidad estricta: si es un DoS limpio, es LOW; si no hay primitiva estable hacia control de flujo, no es HIGH aunque el crash sea espectacular. Esta serie es también un ejercicio de calibración pública.
02. Arquitectura del Sistema
La arquitectura tiene cuatro capas: objetivos (qué cazar), generación (cómo meter inputs), validación (qué es real), y memoria (qué se aprende). Todo corre en hardware propio; ningún byte de los objetivos ni de los crashes sale de la máquina salvo en forma de reportes ya depurados.
Tres decisiones de diseño explican la mayoría del rendimiento:
- Harnesses generados por agentes con revisión estructural: los workers escriben drivers de libFuzzer leyendo la API pública del objetivo; un paso de verificación comprueba que el driver compila contra las versiones objetivo y que el corpus semilla ejercita rutas reales (cobertura medida, no supuesta).
- Señales amplias, validación estrecha: la campaña captura cualquier anomalía (crash, hang, leak, slow-unit); el triage solo promueve lo que reproduce con dos herramientas independientes. Todo lo demás vuelve al corpus como ruido etiquetado.
- CVE-check antes de celebrar: cada candidato pasa por diffing contra upstream y búsqueda en NVD/GHSA. En un mes evitó celebrar al menos tres "descubrimientos" que eran CVEs conocidos o fixes recientes —el sistema los reclasifica como REFERENCE y aprende el patrón.
03. Modelos Locales: Pequeños, Especializados, Siempre Despiertos
El corazón generativo son modelos locales servidos on-premise con Ollama: derivados afinados de Llama 3.2 3B con prompts de sistema estrictos por rol —un worker genera harnesses, otro redacta fichas de hallazgo en formato rígido, otro resume logs de campañas—. Contexto de 4096 tokens, temperatura baja (0.25) y muestreo conservador: en esta tarea no queremos creatividad, queremos cumplimiento de formato y fidelidad al input.
¿Por qué 3B y no un modelo gigante? Tres razones. Primero, latencia y coste: un mes de campaña genera miles de eventos; clasificarlos con un modelo enorme sería caro y lento, y la tarea (extraer campos de un log sin inventar nada) no necesita capacidad de razonamiento profunda. Segundo, determinismo operativo: con keep-alive infinito y flash attention, los workers responden en milisegundos y nunca duermen. Tercero, privacidad: los crashes contienen mapas de memoria de software de terceros; no hay motivo para enviarlos a nadie.
Para las pocas tareas que sí requieren músculo —diseñar una estrategia de grooming de heap, analizar un diff complejo— el orquestador escala a un modelo externo potente (DeepSeek, ya integrado públicamente en SAINT GRAIL), pero con datos mínimos y solo bajo decisión explícita. La regla es lo local por defecto, lo remoto por excepción.
Los modelos pequeños con roles estrechos y formato obligado fallan menos que uno grande con libertad total. Cuando la salida correcta es "JSON con cinco campos", la inteligencia general sobra y la disciplina falta. Afinar el prompt vale más que agrandar el modelo.
04. El Ciclo de Vida de un Hallazgo
Cada candidato atraviesa estados explícitos. Nada salta pasos: un crash que no puede pasar de LEAD a VALIDANDO muere en silencio y alimenta estadísticas, no reportes.
05. Toolchain de Validación: Dos Herramientas o No Es Real
La regla de oro del sistema: ningún hallazgo se declara CONFIRMED sin reproducirse con al menos dos instrumentos independientes. Un fuzzer que dice timeout no basta —hay que verlo colgado desde fuera (timeout de shell), ver el stack con gdb, o detectarlo con sanitizadores distintos. Así se eliminaron todos los falsos positivos del primer mes:
| Señal | Instrumento primario | Validador independiente | Ejemplo real |
|---|---|---|---|
| Crash de memoria | Build ASAN (heap-buffer-overflow, FPE) | valgrind memcheck + stack en gdb | OOB read en 7-Zip |
| Hang / CPU burn | libFuzzer -timeout=N | timeout POSIX (exit 124) + gdb attach | VP8L en libwebp, Smacker en FFmpeg |
| Fuga de memoria | LeakSanitizer | valgrind --leak-check=full | add_metadata en FFmpeg VQF |
| Memoria no inicializada | A/B contra build parcheada upstream | Divergencia de exit code con mismo input | history guard CAB/LZX |
| Bucle de compilación | Fuzzer JIT con timeout | Control sin JIT termina en 0 ms → aísla la fase | get_framesize en PCRE2 |
Completan la caja de herramientas el patch-diffing sistemático (git diff entre versiones objetivo para saber qué cambió y qué no), la consulta inversa de advisories (GHSA/NVD) para evitar duplicados, y builds gemelas ASAN + producción de cada objetivo: la primera para diagnosticar, la segunda para demostrar que el bug afecta al binario que usa gente real, no solo al instrumentado.
06. Política de Severidad Honesta: Por Qué Publicamos los LOW
La política interna es RCE-first: HIGH exige ejecución de código o primitiva de escritura estable hacia control de PC; MEDIUM exige corrupción de metadatos del allocator o lectura cruzada explotable; todo lo demás —hangs, leaks, FPE limpios, lecturas de 1 byte fuera de límites— es LOW y se trata como tal. Bajo esa regla, seis de los siete hallazgos del mes son LOW, y así se publican.
¿Por qué invertir tiempo en publicar LOWs? Porque el valor de un LOW honesto supera su puntuación: documentan clases enteras de error (validación de dimensiones ausente, error path sin free, bucle sin tope derivable del tamaño del archivo) que aparecen una y otra vez en software real, y porque el DoS barato sigue siendo munición de denegación de servicio contra servicios que parsean archivos de usuarios —proxies de medios, antivirus de correo, plataformas que aceptan uploads—. Un hang reproducible con 34 bytes merece ser conocido.
Cada ficha de esta serie declara su severidad, su estado de fix upstream y su clase CWE. Cuando el fix llega, se actualiza. Cuando el hallazgo resulta duplicado de un advisory reciente, se marca REFERENCE con la ventana de detección —como ocurrió con un caso de libarchive detectado a siete días de su fix upstream—.
07. La Cosecha de Agosto: Inventario de Hallazgos
Siete hallazgos confirmados, cinco objetivos, seis artículos técnicos con evidencias completas. Todos reproducibles, todos con ficha, generador de PoC y análisis de causa raíz:
| Objetivo | Vulnerabilidad | CWE | Estado fix | Análisis |
|---|---|---|---|---|
| 7-Zip 26.00–26.02 | Heap OOB read en IsFolderEncrypted por re-parse divergente de CodersData | CWE-125 | Sin fix / sin CVE | Artículo → |
| FFmpeg 7.1.1 (demuxer VPK) | División por cero → SIGFPE determinista | CWE-369 | Parcheado en 8.1.2 | Artículo → |
| FFmpeg 7.1.1 (demuxer VQF) | Memory leak en error path de add_metadata, escala hasta ~1 GB | CWE-401 | Sin fix | |
| FFmpeg 7.1.1 (demuxer Smacker) | Bucle de 13.5M frames declarados en archivo de 105 B | CWE-400 | Sin fix | |
| libwebp 1.4.0 | Decode VP8L lossless sin check de área máxima → CPU burn | CWE-400 | Verificación upstream pendiente | Artículo → |
| PCRE2 10.48 | Compilación JIT no termina con recursión dentro de negative lookahead | CWE-400 | Verificación upstream pendiente | Artículo → |
| libarchive 3.8.9 | Lectura de heap no inicializada en history guard del path CAB/LZX | CWE-457 | Fix upstream reciente (ventana 7 días) | Artículo → |
Además del inventario público, el sistema acumula leads activos en estados tempranos (un binder expuesto en un stack móvil, un caso BLE de enumeración GATT sin emparejamiento en entorno controlado, una escalada write-primitive en curso sobre FreeType). Esos no se publican hasta validarlos —y algunos quizá nunca lo merezcan.
08. Divulgación Responsable: Qué Se Publica y Qué No
Publicar hallazgos encontrados por un sistema autónomo obliga a ser más estricto que nunca con qué se cuenta. La política editorial de esta serie:
- Mecanismo siempre, arma nunca: cada artículo explica la causa raíz con el detalle suficiente para que un mantenedor la entienda y la arregle, pero los PoCs completos y generadores listos para ejecutar se reservan para issues privadas a mantenedores mientras no exista fix.
- Graduación según estado del fix: el caso de FFmpeg VPK (parcheado en 8.1.2) permite mostrar hexdump y evidencia completa; los casos sin fix describen estructura y comportamiento sin servir el archivo malicioso masticado.
- Evidencia redactada: las capturas de terminal de la serie provienen de logs reales de validación, regenerados con rutas de build anonimizadas. Nada de infraestructura personal, nombres internos ni tooling privado aparece en ninguna imagen.
- Nada de terceros: ningún hallazgo contra servicios ajenos, dispositivos identificables o infraestructura real se publica aquí; eso, si llega algún día, va por canales de coordinación correspondientes.
Un sistema autónomo que caza bugs puede convertirse en fábrica de exploits si alguien le quita la ética del bucle. La decisión de publicar, reportar o archivar es y seguirá siendo humana. La IA propone; el criterio dispone.
09. Lecciones Operando la Malla IA
Un mes de operación continua deja aprendizajes que no aparecen en los papers:
- El dedupe es el producto. De cada diez señales interesantes, seis son duplicados de advisories recientes y dos son falsos positivos del harness. El sistema que mejor filtra gana, no el que más encuentra.
- Los timeouts enseñan más que los crashes. Los SIGFPE y OOB reads vienen con stack claro; los hangs obligan a entender qué fase se cuelga (¿matching runtime o compilación JIT?, ¿decodificación o indexación?) —y ahí vive el insight interesante, como muestran los casos de PCRE2 y Smacker.
- La fecha del fix upstream cambia todo. Distinguir 0-day de dup-race exige leer diffs con fecha, no solo buscar el texto del advisory. Una divergencia detectada a siete días del fix es otra historia que una detectada a siete meses.
- Formato rígido de salida = fiabilidad medible. Exigir JSON con campos fijos a los workers permitió medir tasas de error y corregir prompts como quien corrige tests, sin drama.
- El hardware que sobra por la noche es un activo. Las campañas aprovechan horas valle; el coste marginal de una noche más de fuzzing es cero y los hallazgos no entienden de horarios.
10. Conclusión
Sherlock demuestra que una operación de vulnerability research sostenida no requiere un equipo ni un presupuesto cloud: requiere disciplina de validación, modelos locales baratos bien dirigidos y una política de severidad que no mienta. Los siete LOW de este mes no van a salir en titulares, y precisamente por eso importan: son la base estadística honesta sobre la que construir la búsqueda de los MEDIUM y HIGH que sí cambian risk assessments.
La próxima iteración del sistema apunta a tres frentes: escalada automática de leads con hipótesis de groom de heap, integración del grafo de conocimiento de SAINT GRAIL para correlacionar hallazgos entre objetivos, y campañas dirigidas por cobertura diferencial entre versiones. La cosecha seguirá siendo pública, con severidad honesta y sin armas servidas en bandeja.
/Explora los hallazgos de la serie
Cada vulnerabilidad tiene su análisis técnico completo: causa raíz, evidencia reproducible, fragmentos de PoC y diagramas del mecanismo.
11. Referencias
- libFuzzer — A library for coverage-guided fuzz testing
- American Fuzzy Lop (AFL++)
- AddressSanitizer, LeakSanitizer y familia (Google/sanitizers)
- Valgrind Memcheck — manual de referencia
- Ollama — runtime de modelos locales
- Meta AI — Llama 3.2 (modelos edge 3B)
- MITRE — Common Weakness Enumeration (CWE)
- NIST National Vulnerability Database
- SAINT GRAIL: Plataforma Modular para Investigación de Seguridad Ofensiva con IA (tty503.com)
- SAINT GRAIL: De Python a Go, Resultados Concretos y el Futuro del Bug Hunting Autónomo (tty503.com)
- El Arte de la Paciencia: Abusando del Caché de Sudo (tty503.com)