Hackers sin cálculo de resúmenes criptográficos: aleatorización de hashes de API para evadir la detección de shellcode de Cobalt Strike

Mientras investigaba técnicas de cálculo de resúmenes criptográficos de Application Programming Interface (API), comúnmente usadas en malware popular (particularmente Metasploit y Cobalt Strike), el equipo del SOC de Huntress descubrió que los hackers se quedan con la configuración predeterminada que viene con las herramientas para hackers. Nuestra investigación sugiere que muchos proveedores de detección/antivirus (AV) se han dado cuenta de esto y han construido su lógica de detección en torno a la presencia de artefactos que dejan esas configuraciones predeterminadas.

Con un poco de experimentación y curiosidad, descubrimos que si se hacen cambios triviales a esas configuraciones predeterminadas, una gran cantidad de proveedores no logran detectar malware que, de otro modo, sería simple y común. Como resultado, el malware simple y común de repente empieza a acercarse al estado FUD. 😅

En esta publicación, analizaremos cómo descubrimos esos pequeños cambios y cómo puedes implementarlos tú mismo para probar tus herramientas de detección. Incluimos un script que automatiza gran parte de este proceso, además de una regla YARA que detecta la mayoría de las modificaciones hechas con esta técnica.

Ya sea que estés en el Equipo Rojo, el Equipo Azul o en algún punto intermedio, esperamos que este blog te dé información útil sobre una técnica interesante de evasión y detección.

Si capturas de pantalla como esta te emocionan, sigue leyendo.

image1

Resumen técnico

Nuestra investigación sugiere que una gran cantidad de proveedores han basado su capacidad de detección de shellcode de Cobalt Strike y Metasploit en la presencia de resúmenes criptográficos ROR13 de API. Al hacer cambios triviales a la lógica ROR13 y actualizar los resúmenes criptográficos en consecuencia, se puede evadir, en apariencia, la detección de una gran cantidad de proveedores sin romper la funcionalidad del código. 

Para detectar este comportamiento, las reglas YARA que antes detectaban resúmenes criptográficos ROR13 se pueden modificar para detectar bloques de código asociados con el cálculo de resúmenes criptográficos típico basado en ROR. Este cambio hacia la detección de bloques ROR puede dar un medio de detección más robusto que detectar únicamente los resúmenes criptográficos.

Resumen gráfico

API Hashing Blog - VirusTotal Detections

Pero primero, un repaso rápido sobre el cálculo de resúmenes criptográficos de API

El cálculo de resúmenes criptográficos de API es una técnica que el malware usa a menudo para disfrazar el uso de API sospechosas (esencialmente funciones) de los ojos atentos de un analista de detección o un ingeniero de seguridad.

Tradicionalmente, si un software necesitaba llamar a una función de la Windows API (por ejemplo, si quería usar CreateFileW para crear un archivo), el software tenía que referenciar el nombre de la API “directamente” en el código. Esto generalmente se ve como en la siguiente captura de pantalla.

image11

Al usar una API “directamente”, el nombre de la API queda presente en el código. Esto le permite a un analista identificar fácilmente qué podría estar haciendo esa pieza de código sospechosa. En este caso, esa acción sospechosa es crear o abrir un archivo.

Cuando se usa una llamada “directa” a una API, también deja la API presente en la tabla de importación del archivo. Esta tabla de importación se puede ver fácilmente con PeStudio o cualquier otra herramienta de análisis, y se ve como en la siguiente captura de pantalla. Nota que también podemos ver las otras API que el malware está usando.

image24

Si eres un atacante que intenta ocultar la creación de un archivo malicioso, ninguna de estas situaciones es ideal. Sería mucho mejor si pudieras ocultar el uso de tu API de un analista que podría ver `CreateFileW` y luego buscar archivos sospechosos.

Si un atacante no quiere que su API aparezca en una tabla de importación, la alternativa es cargar las API dinámicamente (cuando el malware realmente se ejecuta). La forma más fácil de hacerlo es usar una función de Windows llamada GetProcAddress. Este método evita dejar API sospechosas en la tabla de importación, como vimos antes en PeStudio.

Una advertencia rápida al usar carga dinámica es que, aunque la API sospechosa original `CreateFileW` estaría ausente de la tabla de importación, el uso de “GetProcAddress” ahora estará en la tabla de importación en su lugar. Un analista atento que note la presencia de GetProcAddress puede ejecutar el malware en un depurador y descubrir qué se está cargando.

Con un punto de interrupción bien colocado, un analista puede ver los argumentos que se pasan a la función GetProcAddress y descubrir qué se está cargando. Al ejecutar el código sospechoso, un depurador mostraría algo como esto, revelando el uso de CreateFileW e indicándole al analista que debería buscar archivos sospechosos que se hayan podido crear.

image23

Una forma común de evitar ambas situaciones es usar una técnica conocida como cálculo de resúmenes criptográficos de API.

Esta es una técnica en la que los atacantes implementan su propia versión de GetProcAddress que carga una función según un resumen criptográfico en lugar de un nombre. Esto evita dejar API sospechosas en las tablas de importación y evita usar API sospechosas que se puedan ver fácilmente en un depurador.

Si un analista quiere descubrir qué está pasando, tendría que familiarizarse con el ensamblador x86.

Puntos clave

  • Hay múltiples formas de cargar API sospechosas; sin embargo, la mayoría deja indicadores fáciles de encontrar para los analistas de malware
  • El cálculo de resúmenes criptográficos de API usa resúmenes criptográficos únicos en lugar de nombres de funciones. Esto dificulta el análisis de nombres de funciones que apuntan a cadenas o argumentos en los puntos de interrupción

Indicadores de cálculo de resúmenes criptográficos

Ahora que sabemos por qué alguien podría querer usar el cálculo de resúmenes criptográficos de API, podemos ver cómo lidiar con esto al analizar código sospechoso. Es relativamente fácil de identificar, ya que a menudo verás valores hexadecimales aleatorios que se envían a la pila, seguidos de una llamada inmediata a un valor de registro.

Por lo general, esta llamada se parecerá a call rbp, pero el registro podría técnicamente ser cualquier valor. Abajo hay una captura de pantalla tomada de un shellcode de Cobalt Strike donde se usó el cálculo de resúmenes criptográficos de API.

image18

En la captura de pantalla, podemos ver dos valores hexadecimales enviados a un registro antes de un `call rbp`. Estos son los resúmenes criptográficos que se resolverán y se usarán para cargar las funciones sospechosas que usa el malware.

Los resúmenes criptográficos anteriores corresponden a 0x726774c (LoadLibraryA) y 0xa779563a (InternetOpenA).

Si fueras a encontrar el valor de rbp en esta situación, descubrirías que apunta a la implementación “manual” de GetProcAddress, que luego resuelve el resumen criptográfico y llama a la API asociada.

A grandes rasgos, la lógica de resolución del resumen criptográfico es similar al siguiente pseudocódigo.

image4

Además, verás que la lógica de Calculatehash, que se basa en gran medida en el algoritmo de cálculo de resúmenes criptográficos ror13, es similar a esto. El valor de 0xd (13) es importante aquí, ya que más adelante cambiaremos este valor para generar nuevos hashes que puedan evadir la detección.

image8

Esto es una simplificación, y la lógica real es un poco más compleja. Si te interesa entender la lógica con más detalle, hay excelentes artículos sobre el tema de Nviso y Avast.

Después de analizar numerosas muestras de malware que usan cálculo de resúmenes criptográficos de API en shellcode, notamos que las familias de malware similares suelen usar una lógica de cálculo de resúmenes criptográficos extremadamente parecida para calcular y resolver los hashes de API.

En particular, descubrimos que la mayoría de Cobalt Strike, Msfvenom y Metasploit usan exactamente la misma lógica de cálculo de resúmenes criptográficos para resolver los hashes de API. Como utilizan la misma lógica, producen los mismos hashes para cualquier función dada.

Por ejemplo, tanto Cobalt Strike como Metasploit usan el hash 0x726774c al resolver “LoadLibraryA”.

En resumen

  • El cálculo de resúmenes criptográficos de API es relativamente fácil de identificar mediante análisis estático, aunque es difícil averiguar a qué se resuelven los hashes
  • A menudo se usa una lógica de cálculo de resúmenes criptográficos similar entre familias de malware parecidas
  • La misma lógica exacta de cálculo de resúmenes criptográficos suele aparecer en muestras de MsfVenom, Metasploit y Cobalt Strike

Profundizando un poco más

Eventualmente descubrimos que era fácil identificar shellcode generado por Cobalt Strike o Metasploit simplemente buscando en Google los valores de hash presentes en el código.

Si buscáramos en Google el valor 0x726774c (LoadLibraryA), obtendríamos de inmediato resultados del framework Metasploit (que comparte código con Cobalt Strike). Vemos lo mismo si buscamos el hash 0xa779563a (InternetOpenA).

image21

Al generar nuestras propias muestras de shellcode con estos frameworks, observamos que los hashes presentes en nuestros payloads eran consistentemente identificables como los usados por Metasploit y Cobalt Strike.

En resumen

  • Metasploit y Cobalt Strike (al menos por defecto) usan la misma rutina de cálculo de resúmenes criptográficos de API y producen los mismos valores de hash al usar la misma función
  • Estos hashes introducen valores hexadecimales únicos que se pueden usar para identificar fácilmente las familias de malware mediante Google

Reglas YARA

Desde la perspectiva de un analista de seguridad o ingeniero de detección, esto era una gran información. Sin tener que profundizar en el shellcode y el ensamblador, podíamos identificar fácilmente que un payload probablemente pertenecía a Metasploit o a Cobalt Strike.

Esto nos hizo pensar: si estos valores de hash son únicos de herramientas como Cobalt Strike y Metasploit... ¿y si esos hashes son lo suficientemente únicos como para usarse en reglas YARA?

Encontramos un artículo fantástico de Avast que capturaba la misma idea. Su artículo detalla el uso de estos mismos hashes de API para detectar shellcode de Cobalt Strike y Metasploit. A continuación podemos ver una regla YARA de Avast que se basa en gran medida en los hashes que identificamos antes (además de los otros hashes necesarios para un stager HTTP).

image22

Al probar estas reglas YARA contra nuestro shellcode sin procesar de Cobalt Strike y Metasploit (sin ningún codificador habilitado), confirmamos que el conjunto de reglas YARA de Avast detectó e identificó de forma confiable todos nuestros payloads generados. Buenas noticias para el equipo azul, y un gran trabajo del equipo de Threat Intel de Avast.

En resumen

  • Los hashes de API presentes en el shellcode son indicadores confiables que se pueden usar para la detección
  • Los proveedores están usando activamente estos indicadores para detectar shellcode malicioso

Pero ¿qué pasa si se cambian los hashes del shellcode?

A primera vista, el uso de hashes de API para la detección es una gran idea. Pero eso nos hizo pensar: ¿qué pasa si esos valores de hash cambiaran?

Como prueba de concepto inicial, tomamos nuestros payloads y cambiamos los hashes, de forma bastante tosca, a 0x11111111. Sabíamos que esto rompería el shellcode, ya que los hashes dejarían de resolverse, pero nos permitiría comprobar qué tan bien se detecta el shellcode sin la presencia de hashes de API conocidos.

Nuestro nuevo shellcode contendría hashes así en lugar de los hashes reales vistos antes.

image15

Luego hicimos una comprobación de antes y después en un payload HTTP de Cobalt Strike usando Virustotal, y descubrimos que 15 proveedores no lograron detectar el shellcode después de hacer estos cambios.

image26

Como prueba de concepto, esto fue bastante interesante. Pero como atacante, esto es en gran medida inútil. En su estado modificado actual, el shellcode ya no resolvería los hashes y no podría encontrar las API que necesita para ejecutarse, convirtiendo nuestro shellcode en un bonito pisapapeles digital.

En resumen

  • Al menos algunos proveedores están usando hashes de API para detectar Cobalt Strike y malware similar
  • Si se cambian estos valores predeterminados, al menos algunos proveedores dejarán de detectar payloads que antes detectaban
  • Modificar los hashes de API de forma tosca romperá tu código

Pero ¿y si los hashes modificados pudieran resolverse correctamente?

Después de confirmar nuestra sospecha de que los proveedores usaban hashes de API para detectar shellcode, decidimos explorar qué pasaría si los hashes se modificaran de forma menos tosca, de manera que los hashes modificados pudieran seguir resolviéndose y ejecutándose.

Primero, necesitábamos entender exactamente cómo se generaban los hashes. Nuestro equipo del SOC pudo descubrirlo mediante una combinación del código fuente de Metasploit y el análisis de las instrucciones de ensamblador presentes en muestras de shellcode.

Por la naturaleza de cómo funciona el cálculo de resúmenes criptográficos, planteamos la hipótesis de que bastarían cambios menores en la lógica de cálculo de resúmenes criptográficos para producir hashes muy distintos. Al final, en lugar de complicarnos con rutinas de cálculo de resúmenes criptográficos completamente nuevas, decidimos simplemente cambiar el valor de rotación en la lógica existente de 0xd/13 a 0xf/15.

En teoría, esto daría como resultado hashes completamente nuevos, mientras se mantiene en gran medida la misma lógica y estructura de cálculo de resúmenes criptográficos.

image10

Luego creamos un script para generar nuevos hashes según nuestro nuevo valor de rotación de 0xf. Esta lógica se puede encontrar en el script final incluido en esta publicación.

Después de generar nuevos valores hash, actualizamos nuestro shellcode para que correspondiera con nuestros nuevos hashes y nuestro nuevo valor ror de 0xf. Ten en cuenta que la estructura de nuestro shellcode sigue prácticamente intacta; lo único que cambió son los valores de hash y rotación (ror).

image25

Luego confirmamos que nuestro código seguía funcionando como se esperaba. Este proceso se aceleró muchísimo gracias a la herramienta Speakeasy de FireEye.

Abajo se puede ver una captura de pantalla de las APIs que seguían resolviéndose con éxito en nuestro shellcode recién modificado.

image3

Usando una combinación de netcat y la herramienta BlobRunner de OAlabs, hicimos una verificación adicional para confirmar que nuestro shellcode seguía funcionando y haría “call out” como se esperaba.

image19

Después de confirmar que nuestro código definitivamente seguía funcionando, lo subimos a VirusTotal. Y descubrimos que aún nos quedaban dos proveedores, los mismos dos proveedores de nuestras pruebas anteriores con valores ficticios.

image2

Esto fue bastante interesante, ya que ahora se trataba de shellcode de Cobalt Strike funcional, con 15 detecciones menos que antes de modificarlo.

Como verificación de cordura, repetimos el mismo proceso usando un bind shell TCP de Metasploit (sin encoders habilitados). Después de confirmar que el código seguía funcionando, lo enviamos a VirusTotal y descubrimos que 26 proveedores no habían detectado el payload modificado.

image17

image16

Durante nuestro análisis, fue interesante notar que los dos proveedores restantes diferían entre los payloads modificados.

image6

En este punto, también verificamos que las reglas YARA originales ya no detectaran nuestros payloads. Y confirmamos que ya no se estaban detectando.

Las conclusiones en resumen

  • Un gran número de proveedores usa hashes ror13 predeterminados para detectar payloads de Cobalt Strike y Metasploit/Msfvenom.
  • Modificar estos hashes tiene un impacto considerable en las tasas de detección.
  • Cuando se hace correctamente, modificar estos hashes no rompe la funcionalidad del shellcode.
  • Esta técnica funciona bien tanto en Msfvenom como en Cobalt Strike. Por lo tanto, probablemente también funcione en otras familias de malware.

¿Y qué pasa con los proveedores restantes?

En lugar de dejarlo en 2/55, decidimos abordar a los dos proveedores restantes que detectaban nuestro shellcode.

Primero, notamos que los proveedores restantes detectaban shellcode genérico y no específicamente Cobalt Strike o Metasploit. Esto nos llevó a creer que detectaban indicadores genéricos de shellcode, en lugar de algo específico de nuestra familia de malware.

Planteamos la hipótesis de que lo siguiente podría ser el objetivo de los proveedores restantes, ya que son comportamientos típicamente asociados con el shellcode.

  • CLD/0xfc siendo la primera instrucción ejecutada (CLD se usa para restablecer las banderas de dirección usadas en operaciones de copia de bytes/cadenas)
  • Llamadas sospechosas a registros (por ejemplo, call rbp)
  • Presencia de nombres de bibliotecas en cadenas de la pila

Para probarlo, modificamos ligeramente estos indicadores en nuestro payload restante. Lo logramos de la siguiente manera:

  • Movimos la instrucción CLD inicial a otra ubicación en nuestro shellcode, de modo que se siguiera ejecutando pero ya no fuera la primera instrucción. (Suponiendo que CLD se ejecute antes de cualquier operación con cadenas, esto no debería afectar la funcionalidad del shellcode)
  • Insertamos un NOP/0x90 en lugar del CLD original
  • Insertamos un carácter en mayúscula en los argumentos de la llamada inicial a LoadLibraryA. (Dado que LoadLibraryA no distingue mayúsculas de minúsculas, esto no debería romper ninguna funcionalidad)

A continuación, mostramos un antes y un después del shellcode modificado. Observa los cambios menores de “wininet” (todo en minúsculas) a “wIninet” (una I mayúscula). Así como la instrucción CLD ahora ubicada después de nuestro pop rbp.

image7

Luego confirmamos que nuestro shellcode seguía funcionando y lo volvimos a enviar a VirusTotal.

image9

Finalmente, habíamos llegado a 0/55 detecciones sin romper nuestro código.

Luego verificamos lo mismo con Antiscan y descubrimos que también habíamos llegado a cero detecciones para nuestro shellcode de Cobalt Strike, mientras que una copia sin modificar tenía 13 detecciones.

image20

Las conclusiones en resumen

  • Los proveedores definitivamente usan hashes de API para identificar shellcode de Cobalt Strike
  • Eliminar los hashes de API eliminará la mayoría, pero no todas, las detecciones de VirusTotal
  • Sin los hashes, algunos proveedores detectarán otros indicadores genéricos de shellcode
  • Podemos modificar estos indicadores restantes para lograr cero detecciones

Automatizando el proceso

Dado que el proceso de reemplazo de cálculo de resúmenes criptográficos se podía lograr con una búsqueda y reemplazo a nivel de bytes, el equipo del SOC de Huntress desarrolló un script para automatizar el proceso.

Este script…

  • Toma un archivo de shellcode sin procesar como entrada (sin encoders presentes)
  • Automatiza el proceso de reemplazo de hash, usando un valor ror aleatorio entre uno y 255
  • Dado que se usa un valor ror diferente cada vez, se genera un archivo y un hash únicos en cada ejecución, lo que permite crear múltiples archivos para una sola pieza de shellcode

Decidimos no automatizar el proceso de poner en mayúsculas el nombre de la biblioteca y mover el CLD/0xfc, así que deberás hacerlo manualmente si quieres lograr cero detecciones. Ambas actividades se pueden hacer manualmente y con un esfuerzo mínimo usando un editor hexadecimal.

Para usar el script, genera un payload sin procesar con Msfvenom o Cobalt Strike (asegúrate de que tu salida sea raw, NO uses ningún encoder), guárdalo en un archivo binario sin procesar y luego pásalo como argumento al script de Python. El script se encargará del proceso de reemplazo de hash con un valor ror aleatorio y hashes únicos.

Un ejemplo de cómo generar un payload simple de reverse shell usando msfvenom. Observa el uso de “--format raw” para evitar usar encoders.

image13

A continuación, un ejemplo de cómo usar el script para modificar el archivo de shellcode.

image5

Notas y limitaciones de este script

  • Este script solo reemplaza los hashes y la lógica de cálculo de resúmenes criptográficos. Si hay otros indicadores sospechosos en tu shellcode, es posible que necesites encontrar tu propio método para ocultarlos 
  • Este script NO es un codificador, así que igual tendrás que lidiar con caracteres inválidos y bytes nulos dentro de tu shellcode 
  • Usar un codificador público y conocido (como Shikata ga nai) introducirá sus propios indicadores, lo que jugará en tu contra 

Detección de shellcode modificado

Después de confirmar que nuestro script para generar nuevo shellcode funciona para evadir detecciones genéricas, desarrollamos una regla YARA para detectar el shellcode generado por nuestro script.

Abajo incluimos una copia de una regla YARA que detectó todos los payloads de Msfvenom y Cobalt Strike que probamos, sin importar si habían sido modificados por nuestro script. En nuestras pruebas, no obtuvimos falsos positivos dentro de nuestro conjunto de binarios de prueba, pero quizás quieras modificar la regla según tus necesidades si surgen falsos positivos.

Cómo funciona

Dado que las reglas de detección existentes detectan los hashes generados por la rutina de cálculo de resúmenes criptográficos (la cual se puede cambiar fácilmente), esta regla detecta la rutina de cálculo de resúmenes criptográficos en sí misma. Esto permite una detección un poco más robusta del shellcode de Cobalt Strike y Metasploit.

Como con cualquier detección, esta regla no es infalible. Un atacante decidido puede introducir cambios más complejos en la rutina de cálculo de resúmenes criptográficos que anularán esta regla YARA. Permitimos variaciones menores en nuestra regla, pero cambios más complejos igual la vencerán.

Comentarios finales

Claramente, las detecciones no siempre son perfectas, y un atacante decidido siempre podrá escabullirse. Si eres un defensor, asegúrate de probar y actualizar siempre tus reglas de detección (nunca sabes qué podría pasar desapercibido).

Si eres un atacante (un Red Teamer, claro está), no confíes en los valores predeterminados para salir adelante: cambios simples pueden tener un impacto significativo en tus probabilidades de ser detectado.

Y, finalmente, algunas conclusiones clave para los equipos Blue y Red, respectivamente:

Team Blue

  • Prueba y actualiza continuamente tu lógica de detección
  • ¡Realiza caza de amenazas de forma activa! Sin alertas no significa sin malware
  • Revisa una variedad de fuentes de registros: un antivirus podría no haber detectado esto, pero el tráfico de red podría ser evidente 

Team Red

  • ¡No uses valores predeterminados! Experimenta con todo
  • ¡No tengas miedo de familiarizarte con el ensamblador!

Scripts/reglas YARA

YARA

Script principal (mira el script completo aquí)

Referencias

También te podría interesar