TLP:CLEARSerie de estudio, defensiva y educativa. Todo el YAML citado pertenece a **capa-rules de Mandiant** (licencia Apache-2.0); las reglas se leen, no se construye nada. Fuente fijada: el ruleset en su commit `aed45e2` (19-jun-2026).
Sobre esta serie. Anatomía de una capacidad es un recorrido en seis partes por capa-rules, el diccionario abierto con el que la herramienta capa describe qué hace un binario. No es un incidente ni un writeup de una familia concreta: es lectura de reglas de detección, ordenada por el kill chain, para entender cómo se traduce una intención de atacante en evidencia dentro del código. Cada parte desarma el patrón detrás de un grupo de reglas y lo cruza con capa-real, un motor file-scope que reimplementé dentro de APT115 —así se ve, en concreto, qué de todo esto se puede leer sin desensamblar y qué no—. Esta Parte 0 pone el marco: la anatomía de una regla y el mapa de la serie.

Qué problema resuelve capa#

Cuando cae un binario desconocido en la mesa de triage, la primera pregunta no es “¿qué familia es?” ni “¿es malicioso?”. Es más humilde y más útil: ¿qué es capaz de hacer esto?. ¿Habla por HTTP? ¿Escribe en una clave de arranque del registro? ¿Cifra ficheros? ¿Se da cuenta de que corre en una máquina virtual? Responder eso a mano exige abrir el binario en un desensamblador y reconocer, uno por uno, los patrones de código que delatan cada comportamiento. Es trabajo experto, lento, y no escala.

capa —de capabilities— es la herramienta de Mandiant (hoy Google) que automatiza esa lectura. Le das un ejecutable y te devuelve una lista de capacidades en lenguaje humano, cada una anclada a dónde en el código aparece y etiquetada con su técnica de MITRE ATT&CK y su comportamiento de MBC:

capa suspicious.exe
+------------------------+--------------------------------------------------+
| ATT&CK Tactic          | ATT&CK Technique                                 |
|------------------------+--------------------------------------------------|
| DEFENSE EVASION        | Obfuscated Files or Information::Software Packing |
| PRIVILEGE ESCALATION   | Process Injection::Thread Execution Hijacking    |
| ...                    | ...                                              |
+------------------------+--------------------------------------------------+

capability                                    namespace
--------------------------------------------  ------------------------------------
packed with UPX                               anti-analysis/packer/upx
inject thread                                 host-interaction/process/inject
encrypt data using RC4 PRGA                   data-manipulation/encryption/rc4
...

Lo interesante para nosotros no es la salida. Es el motor de reglas que la produce. capa no adivina: cada línea de esa tabla salió de que una regla .yml del ruleset abierto coincidió con el binario. Ese ruleset es capa-rules, y es un objeto de estudio precioso: mil reglas escritas por analistas de malware que codifican, de forma legible y versionada, el conocimiento de “cómo se ve tal comportamiento en el código”. Leerlas es leer, destilado, el oficio del reversing.

La tesis de la serie. capa-rules es el diccionario que traduce intención → evidencia. Cada regla toma un comportamiento de alto nivel —una intención del adversario: inyectar, cifrar, persistir— y lo baja a su firma concreta en el binario: una combinación de APIs importadas, cadenas, bytes, secuencias de instrucciones. Toda la serie recorre ese puente en las dos direcciones: qué quiere lograr el atacante, y qué rastro deja en el código que lo delata.

Una regla, por dentro#

Empecemos por la más limpia posible. Esta detecta si un binario está comprimido con UPX, el packer libre más común. Es una regla real de capa-rules, entera:

rule:
  meta:
    name: packed with UPX
    namespace: anti-analysis/packer/upx
    authors:
      - william.ballenthin@mandiant.com
    scopes:
      static: file
      dynamic: file
    att&ck:
      - Defense Evasion::Obfuscated Files or Information::Software Packing [T1027.002]
    mbc:
      - Anti-Static Analysis::Software Packing::UPX [F0001.008]
    examples:
      - CD2CBA9E6313E8DF2C1273593E649682
      - Practical Malware Analysis Lab 01-02.exe_:0x0401000
  features:
    - or:
      - and:
        - format: pe
        - or:
          - section: UPX0
          - section: UPX1
      - and:
        - format: elf
        - or:
          - string: "UPX!"

Regla anti-analysis/packer/upx — capa-rules (Mandiant, Apache-2.0).

Toda regla de capa tiene la misma forma: un bloque meta (metadatos: qué es, quién la escribió, a qué se mapea) y un bloque features (la lógica: qué tiene que verse en el binario para que la regla coincida). Vamos por partes.

El bloque meta#

CampoQué es
nameEl nombre humano de la capacidad. Es lo que ves en la salida de capa.
namespaceLa ruta jerárquica que la clasifica (anti-analysis/packer/upx). Ordena el ruleset como un árbol de carpetas y agrupa capacidades afines.
authorsQuién la escribió. capa-rules es colaborativo; cada regla lleva firma.
scopesEl campo decisivo. Dónde mira capa para evaluar esta regla: static (análisis estático) y dynamic (traza de ejecución). Aquí, ambos son file. Volvemos a esto en detalle abajo —es lo que decide qué se puede leer sin desensamblar—.
att&ckLa técnica de MITRE ATT&CK que representa. Aquí T1027.002 Software Packing.
mbcEl Malware Behavior Catalog: un catálogo hermano de ATT&CK, más granular para comportamientos de malware. Aquí F0001.008 Software Packing::UPX.
references(No presente aquí.) Enlaces a papers o artículos que documentan la técnica.
examplesMuestras reales donde la regla coincide, en formato hash:offset.

Deténganse en ese último campo, porque tiene un detalle encantador. Uno de los ejemplos es:

Practical Malware Analysis Lab 01-02.exe_:0x0401000

Es un binario de laboratorio de Practical Malware Analysis (Sikorski & Honig, 2012), el libro que enseñó a reversear malware a media industria. Muchísimas reglas de capa citan como ejemplo canónico un lab del PMA: el corpus de reglas y la bibliografía clásica están entretejidos. capa no solo detecta comportamientos; te deja un rastro de migas hacia el material didáctico que los explica. Es una de esas señales de que el ruleset lo escribieron docentes del oficio, no solo ingenieros.

El bloque features: un árbol lógico#

features es un árbol de lógica booleana. Las hojas son observaciones concretas sobre el binario; los nodos internos son operadores que las combinan. La regla coincide si el árbol, evaluado de la raíz hacia abajo, da verdadero. La de UPX se lee así:

flowchart TD
    OR{"or"} --> A["and"]
    OR --> B["and"]
    A --> A1["format: pe"]
    A --> A2{"or"}
    A2 --> A2a["section: UPX0"]
    A2 --> A2b["section: UPX1"]
    B --> B1["format: elf"]
    B --> B2["string: UPX!"]

En prosa: «es un PE y tiene una sección llamada UPX0 o UPX1» o bien «es un ELF y contiene la cadena UPX!». Nada más. Ninguna de esas observaciones necesita seguir el flujo del programa: el formato del fichero, los nombres de sección y las cadenas se leen del binario en reposo. Por eso su scope es file —y por eso, como veremos en la serie, un motor que no desensambla puede evaluarla exacta—.

Fíjense en la economía. Un packer, por definición, deja el binario ilegible: comprime el código real y antepone un stub descompresor. capa no intenta descomprimir nada; le basta el rastro estructural que UPX olvida limpiar —los nombres de sección que crea, o su marca UPX!—. Toda buena regla de capa es un ejercicio de este tipo: encontrar la evidencia mínima e inevitable de un comportamiento, la que el atacante no puede quitar sin romper lo que quería hacer.

La gramática de las reglas#

Ese árbol usa solo or y and, pero la gramática de features tiene más operadores. Son pocos y conviene tenerlos claros, porque toda la serie los usa:

  • and / or — la lógica booleana de siempre. and exige todos sus hijos; or, al menos uno.
  • not — niega. Aparece, por ejemplo, en reglas de criptografía que exigen la ausencia de ciertas instrucciones para descartar otro algoritmo parecido.
  • N or more — coincide si al menos N de sus hijos coinciden. Sirve para contar: «dos o más llamadas a movzx», «tres o más de estas cadenas». Es lo que da robustez a las reglas frente a variantes.
  • optional — un hijo descriptivo que documenta lo que suele acompañar al comportamiento, pero no afecta al resultado del match. Es un matiz sutil con mucho peso, y volveremos a él: cuando un requisito duro está bajo optional, deja de ser obligatorio. (En la Parte 2 veremos cómo inject APC pasa el corte precisamente porque su único requisito difícil está marcado optional, mientras que inject thread, casi idéntica, no lo pasa.)
  • matchcomposición. Una regla puede exigir que otra regla coincida como uno de sus hijos. Así capa construye capacidades complejas encadenando primitivas simples: inject thread hace match: allocate or change RWX memory, que a su vez hace match: allocate memory. Es la clave para entender por qué parchear una regla no rompe las que dependen de ella —y por qué una capacidad compuesta puede escaparse aunque sus ingredientes se detecten sueltos—.

Las hojas: qué puede observar una regla#

En el fondo del árbol están las features: los hechos concretos que capa extrae del binario. Son el vocabulario con el que se escribe todo. Los que importan:

FeatureQué observaNecesita desensamblar
apiUna función importada o llamada (kernel32.VirtualAlloc).No (import) / sí (llamada interna)
stringUna cadena, exacta o como regex /.../i.No
substringUn fragmento de cadena.No
bytesUna secuencia de bytes literal (una clave, una tabla-S).No
numberUn operando inmediato: una constante en una instrucción (0x40).
offsetUn desplazamiento de estructura (offset 0x40 de un campo).
mnemonicUna instrucción por su nombre (div, movzx).
instructionUna instrucción con sus operandos concretos.
characteristicUna propiedad estructural del código: nzxor, tight loop, peb access, stack string
format · os · archEl formato del fichero, el SO y la arquitectura de destino.No
section · exportUn nombre de sección o de símbolo exportado.No

La columna de la derecha es, adelantándonos, el corazón de toda la serie. Las features de la mitad de arriba —cadenas, bytes, imports, formato, secciones— se leen del binario en reposo. Las de abajo —constantes numéricas, mnemónicos, instrucciones, características estructurales— solo tienen sentido una vez reconstruido el flujo del programa: hay que saber qué es código y qué es dato, dónde empiezan las funciones, cómo se encadenan los bloques. Esa línea divisoria es física, no de dificultad: no se puede ver un 0x40 “dentro de una instrucción” sin antes decidir dónde está esa instrucción.

Los cuatro scopes: dónde mira capa#

Lo que decide de qué lado de esa línea cae una regla es su scope. capa evalúa cada regla en una de cuatro granularidades, de la más gruesa a la más fina:

flowchart LR
    F["file
todo el binario"] --> Fu["function
una función"] Fu --> BB["basic block
un tramo lineal
sin saltos"] BB --> I["instruction
una instrucción"]
  • file — la regla mira el binario completo: sus imports, todas sus cadenas, sus secciones, su cabecera. No necesita saber nada del flujo. packed with UPX es file-scope.
  • function — la regla mira dentro de una función: exige que ciertas features coincidan en la misma función. Esto ya requiere haber identificado las funciones, es decir, desensamblar y reconstruir el grafo de llamadas.
  • basic block — más fino aún: las features deben coincidir dentro de un mismo bloque básico (un tramo de código lineal, sin saltos que entren o salgan en el medio). Es donde viven muchos patrones de criptografía.
  • instruction — el máximo detalle: la regla mira una instrucción y sus operandos.

Que una regla exija que dos cosas ocurran en la misma función o en el mismo bloque no es un tecnicismo: es lo que le da precisión. Un binario cualquiera importa VirtualAlloc y en algún lado tiene la constante 0x40; eso no dice nada. Pero VirtualAlloc llamada con 0x40 (PAGE_EXECUTE_READWRITE) en el mismo bloque ya es un indicio fuerte de que alguien reserva memoria ejecutable-y-escribible para meter shellcode. El scope es lo que convierte coincidencias sueltas en evidencia.

Cómo se reparte el corpus#

Aquí está el dato que fija el terreno de toda la serie. De las 1052 reglas con nombre del ruleset (aed45e2, 19-jun-2026), por scope estático:

ScopeReglasProporción
function66062,7 %
file19218,3 %
basic block17116,3 %
instruction292,8 %

Solo el 18 % de las reglas son file-scope. El otro 82 % vive dentro de funciones y bloques: para evaluarlas hay que desensamblar el binario y reconstruir su grafo de control (capa se apoya en el CFG que le da vivisect). Esa asimetría no es un defecto de capa; es un reflejo de la realidad. Los comportamientos más interesantes —inyección, criptografía, anti-debugging— no viven en una cadena suelta: viven en cómo se combinan las instrucciones. Y “cómo se combinan” es, por definición, algo que solo se ve tras reconstruir el flujo.

ATT&CK y MBC: dos etiquetas, dos preguntas. Cada regla mapea a los dos catálogos y conviene no confundirlos. ATT&CK responde «¿qué táctica del adversario es esto?» —su vocabulario es el del defensor: Persistence, Defense Evasion, Impact (T####)—. MBC (Malware Behavior Catalog) responde «¿qué comportamiento de malware es, a bajo nivel?» —su vocabulario es el del analista: Software Packing, Encrypt Data, Check for Debugger (B/C/E/F####)—. MBC es más fino justo donde ATT&CK es grueso: distingue RC4 de AES, o check for VM de check for debugger, cosas que a ATT&CK le dan igual. Una regla suele llevar ambas porque contestan preguntas distintas sobre el mismo hecho.

Segunda regla: el mismo esqueleto, otra intención#

Para fijar el patrón, una segunda regla real —el sello del ransomware, borrar las instantáneas de volumen para que no puedas restaurar tus ficheros—:

rule:
  meta:
    name: delete volume shadow copies
    namespace: impact/inhibit-system-recovery
    authors:
      - moritz.raabe@mandiant.com
    scopes:
      static: function
      dynamic: span of calls
    att&ck:
      - Impact::Inhibit System Recovery [T1490]
      - Defense Evasion::Indicator Removal::File Deletion [T1070.004]
    mbc:
      - Impact::Data Destruction::Delete Shadow Copies [E1485.m04]
    examples:
      - B87E9DD18A5533A09D3E48A7A1EFBCF6:0x140006AF0
  features:
    - or:
      - string: /vssadmin.* delete shadows/i
      - string: /vssadmin.* resize shadowstorage/i
      - string: /wmic.* shadowcopy delete/i

Regla impact/inhibit-system-recovery/delete-volume-shadow-copies — capa-rules (Mandiant, Apache-2.0).

El mismo esqueleto: meta + features. Dos técnicas de ATT&CK esta vez —T1490 Inhibit System Recovery y T1070.004 File Deletion— porque el acto sirve a dos tácticas a la vez. Y las features son tres regex de cadenas: los comandos que un atacante ejecuta para arrasar las copias de sombra (vssadmin delete shadows, wmic shadowcopy delete). El /i las hace insensibles a mayúsculas; el .* absorbe los argumentos intermedios.

Dos detalles la separan de la de UPX y valen oro:

  1. Su scope estático es function, no file. La regla no quiere la cadena vssadmin delete shadows en cualquier parte del binario —eso lo tendría hasta un manual—: la quiere dentro de una función, junto al código que la ejecuta. Es la diferencia entre “el binario menciona vssadmin” y “el binario usa vssadmin”. Ese matiz de scope es, otra vez, precisión.
  2. Su scope dinámico es span of calls. capa no solo analiza binarios en reposo; también lee trazas de ejecución. Ahí no hay funciones sino secuencias de llamadas, y el scope se expresa en esos términos. La serie se centra en el análisis estático, pero conviene saber que cada regla declara ambos mundos.

Aunque su scope sea function, esta regla en la práctica es evaluable sin desensamblar: sus únicas hojas son cadenas, y las cadenas se leen del binario entero. Es el primer atisbo de un matiz que recorrerá toda la serie —una regla de función cuyas features son todas “de fichero” se puede aproximar— y nos lleva directo a la implementación.

Del papel al motor: la frontera file-scope#

Leer reglas está muy bien, pero la mejor forma de entender un formato es implementar su intérprete. Dentro de APT115 —mi herramienta de triage en el navegador— reimplementé un subconjunto del motor de capa al que llamo capa-real. La restricción que lo define es dura y deliberada: capa-real no desensambla nada. Solo dispone de lo que un triage estático extrae de un binario en reposo —imports, cadenas, bytes, formato, SO, arquitectura, secciones, exports— exactamente las features de la mitad de arriba de aquella tabla.

Con esa restricción, capa-real compila el ruleset y clasifica cada regla en tres cubos:

  • Exacta — todas sus hojas son file-scope (cadenas, bytes, imports, secciones…) y su scope es file. capa-real la evalúa idéntica a capa. packed with UPX cae aquí.
  • Aproximada — su scope es más fino (function/basic block), pero todas sus hojas resultan ser features de fichero. capa-real la evalúa degradando el scope: exige que las features aparezcan en el binario, sin poder confirmar que están en la misma función. Gana cobertura, cede algo de precisión. delete volume shadow copies cae aquí.
  • Fuera de alcance — depende de al menos una feature que exige desensamblar: un number, un mnemonic, un characteristic. capa-real la descarta antes de evaluar, honestamente, en vez de fingir un veredicto.

El resultado de pasar el ruleset entero por ese filtro —el gate— es el número que da forma a toda la serie:

El gate: 609 de 1052 reglas (57,9 %). capa-real puede evaluar 609 reglas sin desensamblar: 163 exactas (file-scope puro) más 446 aproximadas (de función/bloque, degradadas). Las 464 restantes (44 %) exigen el CFG y quedan fuera. Ese 57,9 % es la respuesta cuantitativa a la pregunta de la serie: cuánto del comportamiento de un binario se puede leer sin desensamblarlo. Bastante más de lo que uno esperaría —y con límites muy nítidos sobre qué queda del otro lado—.

Esa frontera —qué cae de cada lado y por qué— es el hilo conductor. En cada parte, después de desarmar un grupo de reglas, cerraré con un veredicto «En capa-real»: ¿la caza exacta, la aproxima, o necesita desensamblar? Y el porqué siempre será concreto. Ya se intuye la forma de las respuestas: los packers y los detectores de VM por cadenas caerán exactos; la inyección de hilos se escapará porque su corazón es un number: 0x40; el RC4 se escapará porque se reconoce por la forma de sus instrucciones, no por ninguna API. La Parte 5 abre el motor entero: el compilador, el evaluador del árbol y los gotchas de implementación.

El mapa de la serie#

Seis entregas, ordenadas por el kill chain. Cada una es autocontenida —puedes entrar por la que te interese— y todas comparten el tag capa.

  • Parte 0 — Anatomía de una regla (esta). El formato, la gramática, los scopes y la frontera file-scope.
  • Parte 1 — Anti-análisis y loaders. Detección de VM y debuggers, timing, resolución dinámica de APIs, y el ejemplo perfecto de por qué a veces hace falta desensamblar: encrypt data using RC4, un algoritmo que se reconoce por la forma de sus instrucciones.
  • Parte 2 — Inyección y persistencia. inject thread desarmada línea por línea, hollowing, APC; claves Run, servicios, tareas programadas. El contraste fuerte entre detectar los ingredientes y detectar la técnica compuesta.
  • Parte 3 — Evasión, credenciales y descubrimiento. AMSI/ETW tampering, borrar logs, acceso a LSASS y a credenciales de navegador y de la nube, recon del sistema.
  • Parte 4 — C2, colección e impacto. HTTP/socket/DNS, keylogging, captura de pantalla, ransomware, y las huellas de compiladores y packers donde capa-real brilla al 100 %.
  • Parte 5 — De la regla al motor. El interior de capa-real: el compilador, el gate del 57,9 %, el evaluador del árbol lógico, y los límites honestos de leer comportamiento sin un CFG.
Para el defensor. Aunque la serie lea el ruleset “desde dentro”, el valor operativo es directo: capa-rules es, gratis y versionado, el mejor diccionario público de intención-a-evidencia que existe. Si escribes detecciones, esas reglas te dicen qué combinación de APIs, cadenas y constantes delata cada comportamiento —material de primera para traducir a Sigma, YARA o consultas de EDR—. Y saber leer los scopes te dice algo más fino: qué comportamientos puede confirmar tu triage estático y cuáles exigen sí o sí un sandbox o un desensamblado. Es el mismo criterio de precisión vs cobertura que aparece cuando cazas la clase de bug en vez del IoC, como en la serie Nightmare Eclipse.

Referencias#