Botnet Stantinko: estudio de sus técnicas avanzadas de ofuscación

En noviembre del pasado año ya hablábamos en nuestro blog de la funcionalidad añadida de la botnet Stantinko para el minado de criptomonedas, y asimismo, hacíamos mención a las avanzadas técnicas de ofuscación utilizadas en esta campaña, la cual permanece activa desde el año 2012. Ahora los investigadores de ESET han lanzado un nuevo estudio en el que profundizan en estas técnicas de ofuscación, explicadas a continuación en este artículo.

Para impedir su análisis y evadir las técnicas de detección, el nuevo módulo de Stantinko usa varias técnicas de ofuscación:

  • Ofuscación de las cadenas: las cadenas más importantes solo se construyen y se presentan en memoria cuando son necesarias.
  • Ofuscación del flujo de control: se transforma el flujo de control en algo difícil de leer y el orden de ejecución de los bloques básicos resulta impredecible si no se realiza un análisis extenso.
  • Código sin funcionalidad: se añade código que es ejecutado pero que no tiene funcionalidad real en el resultado final. Su cometido es evadir las detecciones de comportamiento.
  • Cadenas y recursos no válidos: se añaden cadenas y recursos que no tienen impacto alguno en la funcionalidad de la botnet.

Entre estas técnicas la que más destaca es la ofuscación de cadenas y del flujo de control, ambas descritas a continuación.

OFUSCACIÓN DE CADENAS

Las cadenas incluidas en el módulo no guardan relación con la funcionalidad real. Su fuente es desconocida y, o bien sirven para construir las cadenas que se usan realmente, o no se usan para nada.

Las cadenas verdaderamente utilizadas por el malware son generadas en la memoria para evitar las técnicas de detección de archivos y así impedir su análisis. Se forman a partir de una reestructuración de los bytes de las cadenas usadas como señuelo, así como mediante el uso de funciones estandarizadas para manipular las cadenas, como por ejemplo strcpy(), strcat(), strncat(), strncpy(), sprintf(), memmove() y sus versiones Unicode.

Debido a que todas las cadenas que se usan en una función concreta siempre se montan de manera secuencial al principio de la función, es posible simular los puntos de entrada de las funciones y extraer las secuencias de los caracteres que pueden ser mostrados en pantalla, los cuales revelarán las cadenas.

Fuente de la imagen: WeLiveSecurity – ESET
Ejemplo de ofuscación de una cadena. En la imagen se destacan 7 cadenas señuelo. Por ejemplo, la que está marcada en rojo genera la cadena «NameService«.

OFUSCACIÓN DEL FLUJO DE CONTROL

La técnica usada para ofuscar el flujo de control es la conocida en inglés como «control-flow flattening«, cuya función es impedir el análisis y evitar detecciones.

Su método de ofuscación más común se consigue al dividir una función única en diferentes bloques básicos. Dichos bloques se envían luego a una declaración switch dentro de un bucle (cada envío está compuesto tan solo de un bloque básico). Existe una variable de control para determinar qué bloque básico debe ser ejecutado en la declaración switch, y su valor inicial es asignado antes que el bucle. A todos los bloques básicos se les asigna un ID que es siempre almacenado por la variable de control para el momento de la ejecución. Todos los bloques básicos configuran el valor de la variable de control de manera que se corresponda con el que les sigue (un bloque básico puede ir seguido de diferentes sucesores, y el sucesor inmediato puede ser seleccionado mediante una condición).

Fuente de la imagen: WeLiveSecurity – ESET
Estructura común del proceso de ofuscación de un flujo de control.

Hay diferentes maneras de intentar resolver este método de ofuscación, como por ejemplo usando la API de microcódigo de IDA. Esta estrategia fue utilizada por Rolf Rolles para identificar los bucles de forma heurística, extraer la variable de control de cada bloque ofuscado y reorganizarlos según las variables de control.

Sin embargo, esta aproximación, así como otras similares, no funcionarían con la ofuscación de Stantinko ya que este malware tiene unas características particulares que difieren de las técnicas de ofuscación comunes del método control-flow flattening. Estas características son:

  • El código se oculta a nivel de código fuente, lo cual a su vez significa que el compilador puede provocar anomalías en el binario resultante.
  • La variable de control se incrementa en bloques de control (esta característica será explicada más adelante), no en bloques básicos.
  • Los envíos contienen bloques básicos múltiples (por ejemplo, cada bloque básico pertenece únicamente a un envío, pero a veces los envíos se entrelazan, lo que quiere decir que comparten algunos bloques básicos).
  • Los bucles pueden ser anidados y sucesivos.
  • Se combinan múltiples funciones.

Estos obstáculos deben de ser resueltos para poder proseguir con el análisis del payload final.

OFUSCACIÓN DEL FLUJO DE CONTROL EN STANTINKO

¿Cómo funciona la técnica control-flow flattening en Stantinko? En la mayoría de las funciones del malware, el código se divide en varios envíos y dos bloques de control (el del inicio y uno en cola) que controlan el flujo de la función.

El bloque de control del inicio decide qué envío se debe ejecutar, y lo hace comprobando la variable de control. El bloque de control en cola incrementa la variable de control con una constante fija y, o bien se redirige al bloque de control inicial, o sale del bucle de ofuscación:

Fuente de la imagen: WeLiveSecurity – ESET
Estructura regular del bucle de ofuscación del flujo de control de Stantinko.

Stantinko parece ofuscar el código de todas las funciones y otros aspectos, como los bucles, pero a veces también lo hace con lo que parecen bloques de código aleatorios. Debido a que aplica bucles de ofuscación de flujo de control tanto en funciones como en otros aspectos, ambos procedimientos se pueden anidar de manera que tienen lugar diferentes flujos consecutivos.

Cuando un bucle de ofuscación del flujo de control se crea al combinar código de varias funciones, la variable de control de la función resultante se inicia con valores diferentes, basándose en qué función (de las originales) es llamada. El valor de la variable de control se pasa a la función resultante como parámetro.

Los investigadores de ESET consiguieron evadir esta técnica de ofuscación al reorganizar los bloques en el binario, un procedimiento que será explicado en la siguiente sección del artículo.

Es importante destacar que se observaron anomalías múltiples en algunos de los bucles de ofuscación que hacen más complicado automatizar el proceso de desofuscación. La mayoría de ellas parecen ser generadas por el compilador, lo cual lleva a pensar que la técnica de ofuscación se lleva a cabo antes de la compilación. Las anomalías que se describen a continuación pueden aparecer de manera separada o combinadas:

  • Primera anomalía: algunos envíos pueden ser código inservible, es decir, dicho código nunca será ejecutado.
  • Segunda anomalía: los bloques básicos dentro de los envíos se pueden entrelazar, lo que quiere decir que pueden contener código conjunto.
Fuente de la imagen: WeLiveSecurity – ESET
Estructura de un bucle de ofuscación con envíos que comparten código.
  • Tercera anomalía: hay saltos directos de los envíos a un bloque fuera del bucle de ofuscación, justo después del bloque en cola, y a bloques que proceden de la función.
Fuente de la imagen: WeLiveSecurity – ESET
Estructura de un bucle de ofuscación cuyo envío sale de dicho bucle. De las posibilidades observadas en el caso del 2º envío, solo una tiene lugar.
  • Cuarta anomalía: pueden existir diferentes colas, o no haber ninguna (en este último caso, la variable de control se incrementa al final de cada envío).
Fuente de la imagen: WeLiveSecurity – ESET
Estructura de un bucle de ofuscación sin ninguna cola (a la izquierda) y con múltiples colas (a la derecha).
  • Quinta anomalía: el bloque de inicio no presenta una tabla de saltos de manera inmediata. En su lugar, puede haber múltiples tablas de saltos y, además, existe una secuencia de ramas (previas a las tablas de saltos) que buscan el binario correcto para su envío.
  • Sexta anomalía: el valor de la variable de control puede ser utilizado dentro de los envíos, lo que quiere decir que la variable de control tiene que ser preservada/calculada incluso en el código desofuscado.
Fuente de la imagen: WeLiveSecurity – ESET
El registro EDI contiene la variable de control que se pasa a EAX y que es utilizada en el envío. El envío está resaltado en rojo.
  • Séptima anomalía: a veces, la cola contiene instrucciones que son cruciales para restaurar los valores correctos de los registros y las variables locales. Durante el proceso de desofuscación se elimina la cola, por lo que hay que asegurarse de que las instrucciones se ejecutan después de cada envío, incluso si no forman parte del mismo.
  • Octava anomalía: existen casos en los que no hay un envío cuyo ID sea igual al valor de la variable de control presente en ese momento.

DESOFUSCACIÓN

La meta principal de este proceso es llegar a componer una función de desofuscación que sea capaz de reorganizar el código a nivel binario para hacerlo fácil de leer durante la etapa de ingeniería inversa, a la vez que se permite que el código final sea posible de ejecutar. Se debe poder reconocer los bloques básicos que pertenecen a cada envío, así como de copiarlos y moverlos de manera aleatoria.

Durante la manipulación de los bloques básicos hay que asegurarse de volver a calcular correctamente las direcciones relativas de los objetivos y las direcciones que forman tablas de saltos legítimas. Ya que la solución ofrecida por los investigadores de ESET no tiene en cuenta los reposicionamientos, hay que asegurarse de que la muestra se carga junto a la dirección. En ESET usaron un framework de ingeniería inversa que proporciona funcionalidades como la manipulación del montaje y un motor de ejecución simbólico.

Los parámetros fundamentales de la función son las direcciones de los bloques de control (el de inicio y el de cola), el rango y etapa de la variable de control, los nombres de los registros, y los posicionamientos de la memoria que contienen la variable de control, el parámetro control_locations, y, por último, la dirección del primer bloque básico que sigue al bucle, nombrado «next_block» por los investigadores de ESET. También es necesario desofuscar la dirección de la función y la dirección donde ésta se almacenará.

Es probable que se produzcan múltiples bloques de cola debido a la anomalía número 4 descrita anteriormente.

La función de desofuscación itera en el rango de la variable de control usando el valor de su fase correspondiente para simular un bucle real de ofuscación de flujo de control; en cada iteración, la función comienza generando un contexto para tratar las anomalías 6 y 7. El contexto iría justo antes del envío correspondiente. Dicho contexto se trata de un bloque básico que contiene instrucciones que asignan registros y direcciones de memoria y que mantiene actualizado el parámetro control_locations. El contexto de la primera iteración simplemente preserva el valor de la variable de control (no es necesario tratar la anomalía número 4).

Los últimos bloques básicos del envío anterior (o, en el caso del primer envío, los bloques básicos que van justo antes del bloque inicial) son redirigidos al contexto previamente creado. El bloque básico inicial de un envío que se debe ejecutar (en cada una de las iteraciones) se determina según el valor actual de la variable de control (ID del envío). El bloque básico correspondiente a cada momento particular se puede identificar ejecutando el algoritmo de búsqueda de binario, el cual busca un bloque básico con el ID que se está usando en ese momento. El estado inicial de la ejecución simbólica tiene asignado el parámetro control_locations en el valor actual de la variable de control.

Los investigadores de ESET detienen la ejecución simbólica en el primer bloque básico que 1) contiene una rama incondicional, o, 2) tiene un destino que no puede ser determinado por la variable de control.

Se podría también simular esta parte o usar un framework que fuese capaz de simplificar el algoritmo de búsqueda de binario y transformarlo en una tabla de saltos que después se convierta en una declaración switch. En estos métodos está presente la anomalía número 5.

El envío en su conjunto (por ejemplo, cada bloque básico que se puede alcanzar desde su bloque básico inicial hasta su bloque de inicio, de cola(s) o next_block) se copia después del contexto de bloque que le precede (se copia en lugar de moverse directamente debido a la anomalía 2).

Existen actualmente dos casos poco comunes que pueden tener lugar debido a la anomalía 3; ambos resultan en el término prematuro de la iteración. Estos casos ocurren cuando un envío:

  • Viene de la función.
  • Apunta a next_block

Finalmente, cuando acaba la iteración, el último bloque básico del envío anterior (o bloques básicos justo antes del bloque inicial, en el caso del primer envío), son redireccionados al primer bloque básico fuera del bucle de ofuscación.

El método descrito resuelve la anomalía 1 automáticamente, ya que los envíos inservibles no serán copiados en el código resultante. Estos cambios se escriben luego en la dirección virtual donde la función desofuscada debería ser almacenada. Si estuviésemos tratando con funciones ofuscadas que han sido combinadas, se apuntarían las referencias de la función objetivo que tenga el mismo valor inicial que el parámetro de la variable de control en la dirección de la función desofuscada.

POSIBLES MEJORAS

La aproximación anteriormente descrita opera exclusivamente a nivel de ensamblaje, lo cual no es suficiente para que el proceso de desofuscación sea totalmente automático. Por ello, los investigadores de ESET consideran que existen posibles mejoras que pueden ser implementadas.

Esto se basa en que un reconocimiento adecuado de los patrones es difícil, principalmente debido a las diferentes optimizaciones del compilador presentes en las ofuscaciones a nivel de código fuente. El patrón de reconocimiento es necesario en este caso, por ejemplo, para completar automáticamente los parámetros de la función principal de desofuscación.

La ventaja de la aproximación utilizada es que el código resultante puede ser ejecutado al momento y se pueden usar herramientas de ingeniería inversa para un análisis más en profundidad.

Esta aproximación se podría mejorar usando representación intermedia progresiva (IR, por sus siglas en inglés), la cual proporciona técnicas de optimización que, entre otras cosas, eliminaría la mayoría de las anomalías generadas por el compilador, y de esa forma permitiría el reconocimiento automático de los parámetros requeridos por la función de desofuscación. Se podría usar también la IR seleccionada tanto para el reconocimiento como para la desofuscación.

La desventaja de esta opción es que el código resultante también estaría en la IR, lo que significa que el análisis posterior tendría que ser realizado también con la susodicha. El número de herramientas que funcionan con dicha representación y sus funcionalidades puede ser bastante limitado, especialmente en lo que a visualización se refiere. Debido a esto, sería difícil analizar una muestra más compleja, especialmente si hay capas adicionales de ofuscación. Tampoco seríamos capaces de ejecutar el código resultante.

CÓDIGO INSERVIBLE

A lo que los investigadores de ESET se refieren con «código inservible» (o «código muerto» si traducimos directamente del inglés) es a código que o no se ejecuta en ningún momento o cuya ejecución no tiene ningún impacto sobre la funcionalidad general del malware. Este tipo de código se encuentra principalmente en los bucles ofuscados, aunque también existen, por ejemplo, exportaciones no utilizadas y no hay manera de distinguirlas de las que son legítimas.

En cuanto al código inservible en el bucle ofuscado, en el caso de Stantinko siempre se encuentra dentro de los envíos que nunca son ejecutados. Puede contener partes modificadas de software legítimo como por ejemplo WinSpy++.

CÓDIGO PRESENTE SIN FUNCIONALIDAD

Incluso después de la desofuscación hay partes del código que no tienen propósito alguno en cuanto a funcionamiento, y se encuentra mezclado con las líneas de código real. Este código es usado, probablemente, para dificultar aún más el análisis o evadir las técnicas de detección de comportamiento.

Para optimizar este tipo de código que no hace nada, habría que, por ejemplo, generar partes disyuntivas que contuviesen todas las llamadas presentes a la API de Windows. El criterio para dividir las partes serían los parámetros de las llamadas en cada una de dichas partes. Después se ejecutarían estas partes con una llamada conjunta preparada en un entorno controlado, considerándose que una parte es funcional si cumple uno de los siguientes cometidos:

  • Hacer algunos cambios al sistema operativo.
  • Tener que conocer un valor inicial del parámetro de una función o una variable global.
  • Asignar el valor del parámetro de una función o una variable global.
  • Afectar directamente de manera general al flujo de control de la función.

CONCLUSIÓN

Lo expuesto en este artículo demuestra que los criminales detrás de la botnet Stantinko mejoran y desarrollan constantemente nuevos módulos que, con frecuencia, contienen técnicas no estandarizadas.

Para más información sobre esta botnet puedes leer el post de noviembre en el que se detalla su funcionamiento y otros aspectos como los IoC.

Más información:

Stantinko’s new cryptominer features unique obfuscation techniques

Nueva funcionalidad de la botnet Stantinko: minado de criptomonedas

Powered by WPeMatico

Gustavo Genez

Informático de corazón y apasionado por la tecnología. La misión de este blog es llegar a los usuarios y profesionales con información y trucos acerca de la Seguridad Informática.