Malware y forense
Hashes de archivos en DFIR y la ética de subir archivos a VirusTotal
Cómo los hashes identifican archivos, por qué SHA-256 reemplazó a MD5, cómo NSRL filtra software conocido y por qué subirlo a VirusTotal lo comparte.
Escribe tu primera regla YARA que coincida con un archivo inofensivo y no con otro, y aprende por qué toda regla necesita pruebas positivas y negativas.
YARA es una herramienta de coincidencia de patrones. Escribes una regla que describe cómo es un archivo, y YARA enumera los archivos que encajan con esa descripción. Lo usan sobre todo los analistas de malware. Escriben reglas para cadenas, estructuras de archivo y secuencias de bytes, y luego escanean carpetas, imágenes de disco y volcados de memoria. El mismo mecanismo sirve para cualquier archivo que controles, por ejemplo una plantilla que nunca debe llevar un marcador interno, o un archivo de configuración que nunca debe contener un token.
YARA no sabe qué es peligroso. Coincide con lo que dice la regla. Una regla es una afirmación sobre bytes, y solo vale lo que valga esa afirmación.
El proyecto está alojado en GitHub, bajo VirusTotal. La guía oficial de escritura, en Read the Docs, es la referencia de sintaxis. VirusTotal también mantiene YARA-X, una reescritura en Rust que conserva la mayor parte del lenguaje de reglas. Los dos se parecen, pero no son idénticos, así que lee las notas de compatibilidad antes de pasar una regla de uno a otro.
Un hash de archivo responde a una pregunta estrecha: ¿este archivo exacto ya es conocido? Si cambias un byte, el hash cambia. El artículo sobre hashes de archivo lo explica con detalle. Una regla YARA hace una pregunta más amplia: ¿este archivo contiene las piezas que esperamos? Una sola regla puede coincidir con muchos archivos que comparten una cadena, una estructura o un patrón de bytes, aunque ninguno comparta hash.
Ahí también empieza el problema. Un hash casi nunca coincide con un archivo que no debería. Una regla descuidada puede coincidir con la mitad de tu servidor de archivos. Por eso lo que hay que aprender no es tanto la sintaxis. Lo importante es escribir una regla que coincida con lo que quieres, y solo con eso, y luego demostrarlo con archivos que controlas.
Toda regla tiene un nombre y, dentro de llaves, hasta tres secciones:
rule Ejemplo_De_Regla {
meta:
description = "Para qué sirve esta regla"
strings:
$texto = "algún texto"
condition:
$texto
}Los nombres de regla usan letras, números y guiones bajos, y no pueden empezar con un número. El nombre aparece en cada reporte de coincidencias, así que elige uno que le diga al lector de qué trata la regla.
La sección meta guarda pares clave-valor para las personas: una descripción, un autor, una fecha, un enlace a la investigación que originó la regla. YARA nunca usa meta para decidir una coincidencia. Aun así, manténla exacta, porque la siguiente persona que abra el archivo va a confiar en ella.
Cada cadena recibe un identificador que empieza con $. La condición se refiere a las cadenas por ese identificador. Hay tres tipos.
Las cadenas de texto van entre comillas dobles:
$marcador = "LAB-TRAINING-MARKER"Los modificadores cambian la forma en que se compara el texto. nocase ignora mayúsculas y minúsculas. wide coincide con texto UTF-16LE, donde cada carácter ASCII va seguido de un byte cero, algo común en binarios de Windows. fullword exige que la coincidencia aparezca sola, no dentro de una palabra más larga.
Las cadenas hexadecimales van entre llaves y coinciden con bytes en crudo:
$mz = { 4D 5A }Esos dos bytes son la firma MZ al inicio de muchos ejecutables de Windows. ?? coincide con cualquier byte, [2-4] admite un hueco de dos a cuatro bytes desconocidos, y ( 90 | 91 ) coincide con uno u otro.
Las expresiones regulares van entre barras:
$version = /LAB-[A-Z]+ v[0-9]+/Las regex son el tipo más flexible y también el más fácil de volver demasiado amplio. Usa una cadena de texto o hexadecimal siempre que pueda expresar el patrón.
La condición es una expresión booleana. Combina cadenas y propiedades del archivo en una sola respuesta: coincide o no coincide.
$marcador es verdadero cuando la cadena aparece al menos una vez.any of them y all of them cubren todas las cadenas de la regla. 2 of ($a, $b, $c) exige dos de tres.$marcador at 0 exige la cadena en el primer byte. $marcador in (0..512) la exige en los primeros 512 bytes.#marcador > 3 cuenta las ocurrencias.filesize < 2KB revisa el tamaño del archivo.uint16(0) == 0x5A4D lee los dos primeros bytes como un entero little-endian y revisa la firma MZ.Toda regla necesita una condición. Una regla con cadenas y sin una condición razonable está rota o no sirve de nada.
-s, YARA también imprime cada cadena encontrada, con el desplazamiento en bytes donde apareció.Las cadenas van primero y la condición después. Una condición como $a and $b es estricta, porque las dos cadenas deben encontrarse antes de que la regla pueda coincidir.
La salida esperada de abajo se describe en lugar de pegarse, porque el formato exacto puede variar entre versiones de YARA. Compara lo que veas en tu terminal con esas descripciones.
Trabaja en una carpeta vacía. En Windows, ejecuta los comandos en Git Bash. El heredoc del paso 3 no funciona igual en PowerShell.
En Debian y Ubuntu:
sudo apt install yaraEn macOS con Homebrew:
brew install yaraEn Windows, descarga el zip de la versión desde https://github.com/VirusTotal/yara/releases, extráelo y pon el ejecutable en tu PATH. El archivo puede llamarse yara64.exe. Renómbralo a yara.exe, o cópialo con ese nombre, para que los comandos de abajo funcionen tal cual.
Verifica la instalación:
yara --versionDeberías ver un número de versión. Este artículo no fija una versión, así que usa la versión más reciente que te dé tu método de instalación.
mkdir yara-lab && cd yara-lab
printf 'LAB-TRAINING-MARKER v1\n' > match.txt
printf 'archivo de texto común\n' > nomatch.txtAmbos archivos son texto plano que tú escribiste. El primero debe coincidir con la regla. El segundo no debe.
Las comillas alrededor de EOF importan. Sin ellas, el shell reemplaza $m por una variable vacía antes de que YARA lea el archivo, y la regla deja de compilar.
cat > lab.yar <<'EOF'
rule Lab_Training_Marker {
meta: description = "Coincide con el marcador del laboratorio"
strings: $m = "LAB-TRAINING-MARKER"
condition: $m
}
EOFLéela en tres partes. La meta dice para qué sirve la regla. La cadena dice que el archivo debe contener el texto del marcador. La condición dice que un archivo coincide cuando esa cadena se encuentra al menos una vez.
yara -s lab.yar match.txtYARA debería imprimir el nombre de la regla y la ruta del archivo. Gracias a -s, también imprime una línea para la cadena encontrada, con el desplazamiento en bytes y el texto hallado. El marcador empieza en el primer byte de match.txt, así que el desplazamiento debe ser 0x0.
yara lab.yar nomatch.txtSalida esperada: nada. YARA imprime solo las coincidencias, así que el silencio significa que el archivo no coincidió.
El silencio solo significa algo si la prueba positiva funcionó. Ejecuta primero el paso 4. Si el paso 4 no imprimió nada, la regla o la instalación están mal, y el silencio del paso 5 no te dice nada.
Un casi-acierto es el archivo negativo más útil, porque falla por una razón concreta y estrecha.
printf 'LAB-TRAINING\n' > near.txt
yara lab.yar near.txtSalida esperada: nada. El archivo tiene el inicio del marcador pero no la cadena completa, así que la cadena no se encuentra y la condición es falsa.
yara -r lab.yar .La opción -r escanea la carpeta de forma recursiva. Espera ver match.txt en la salida. Lo más probable es que también veas lab.yar, el archivo de la regla. Contiene el texto del marcador, así que la regla coincide con su propio código fuente. Es un error común, y la siguiente sección lo explica. Guarda las reglas en una carpeta distinta de los datos que escaneas.
La regla del laboratorio sirve para un marcador de práctica. Las reglas reales suelen necesitar una restricción que la cadena por sí sola no da. Supongamos que solo quieres archivos pequeños que contengan el marcador:
rule Lab_Training_Marker_Pequeno {
strings: $m = "LAB-TRAINING-MARKER"
condition: $m and filesize < 1KB
}Pruébala igual. match.txt debe coincidir. nomatch.txt y near.txt no deben. Cada cambio en las cadenas o en la condición exige volver a correr los dos archivos de prueba.
Esta regla parece razonable:
rule Demasiado_Amplia {
strings: $a = "texto"
condition: $a
}Va a coincidir con nomatch.txt, porque la frase archivo de texto común contiene la palabra texto. Puedes comprobarlo con el archivo negativo que ya creaste. Una regla que coincide con él falló su propia prueba.
Patrones que coinciden demasiado:
texto, error, copyright o este programa.{ 00 } o { 90 }.nocase sobre una palabra común.any of them sobre una lista de cadenas que aparecen en archivos normales.filesize < 10KB, que coincide con la mayoría de los archivos pequeños./.+/.Formas de ajustar una regla:
and, o con 2 of (...), para que ninguna cadena común decida sola el resultado.at o in cuando sepas dónde debería aparecer la cadena.uint16(0) == 0x5A4D.$m antes de que YARA lea el archivo. Mira el paso 3.Esta es la regla que más importa en este artículo. Toda regla YARA necesita los dos tipos de archivo de prueba antes de que alguien la despliegue.
match.txt. En el trabajo real, provienen de muestras que estás autorizado a guardar, según las reglas de manejo de tu organización.near.txt.Una regla pasa cuando coincide con todos los archivos positivos y con ningún negativo. Guarda los dos conjuntos junto a la regla, en control de versiones si tu equipo lo usa, y vuelve a ejecutarlos después de cada edición. Una regla que pasó la prueba positiva pero nunca se probó contra negativos no está probada.
pe, que permite a una regla revisar cabeceras y secciones de un ejecutable de Windows en lugar de bytes en crudo.Malware y forense
Cómo los hashes identifican archivos, por qué SHA-256 reemplazó a MD5, cómo NSRL filtra software conocido y por qué subirlo a VirusTotal lo comparte.