Análisis del código fuente del ransomware Babuk: Nas y esxi

David García    13 septiembre, 2021

Al igual que sucedió con el malware Mirai (la botnet que surgió en 2016 para apoderarse de dispositivos IoT con la finalidad de ofrecer servicios de denegación de servicio) el código fuente del conocido ransomware Babuk ha sido liberado. ¿Qué curiosidades hay en su código fuente?

Babuk tuvo un pico de actividad a principios de este año. Una de sus víctimas fue el Cuerpo de Policía de Washington D.C. Por las razones que sean (han argumentado enfermedades graves pero nunca nos podemos fiar), el código fuente está disponible, lo que significa que es una oportunidad para poder estudiarlo y aprender sobre este tipo de amenazas. Aunque por otro también representa un recurso más disponible para ser utilizado por otros grupos de cibercrimen. Echemos un vistazo al código para hacernos una idea de su estructura y funcionamiento.

En primer lugar, el código está dividido en tres carpetas a modo de proyectos diferenciados entre si por el objetivo final al que están dirigidos:

‘esxi’, ‘nas’ y ‘windows’. Respectivamente, son proyectos para construir ejecutables para los sistemas Microsoft Windows por un lado (carpeta “windows”) y derivados de UNIX (carpetas ‘esxi’ y ‘nas’) por otro.

Podemos agrupar ‘esxi’ y ‘nas’. Ambos están pensados para sistemas UNIX, la diferencia es, aparte de la forma en la que se construyen (compilan) los proyectos, los archivos que evitan cifrar para inutilizar el sistema. Eso y que el proyecto ‘nas’ está ejecutado en el lenguaje de programación Go, sobre el que ya hablamos aquí respecto de su adopción en el mundo del malware y que esto confirma.

¿Por qué Go?

Porque es muy fácil, tremendamente fácil, realizar una compilación cruzada en Go. Los creadores de Go se centraron en facilitar la vida a los desarrolladores y este fue uno de los puntos en los que lo hicieron increíblemente bien.  Basta con indicarle al compilador de Go en una variable de entorno la arquitectura deseada (siempre que esté soportada por Go, claro):

En concreto, se construirían dos ejecutables, uno para la arquitectura Intel 32 bits y otro para ARM también de 32 bits.

No en vano, ‘nas’ hace reflejo de precisamente dispositivos NAS (2), que suelen implantarse con chips más humildes sin grandes prestaciones computacionales. Dado que allí se guardan archivos es obvio que el interés en cebarse con este tipo de sistemas es prioritario. No obstante, se echa en falta MIPS, arquitectura también popular en los NAS y soportada por Go.

El cifrador de ‘nas’ evita los siguientes componentes de ruta entre archivos y carpetas:

Como hemos comentado, cifrar estos archivos (además de la posible carencia de permisos para escribir en alguno de ellos) inutilizaría los sistemas e incluso podrían causar la parada de los procesos de cifrado.

Por otro lado, ‘esxi’ se centra en sistemas virtualizados, es bastante evidente solo con observar las extensiones de archivos de interés:

Solo que en este caso no los esquiva sino todo lo contrario, son el objetivo. Cifrando los archivos característicos de máquinas virtuales deja de un plumazo a los propietarios de estas privados de su uso.

Rápido en cifrar, lento en recuperar tus archivos

Otra curiosidad es el empleo de concurrencia a la hora de cifrar un sistema. Es obvio que debe ser un proceso rápido, imposibilitando una actuación que pueda advertir de lo que está ocurriendo y pueda existir una ventana de oportunidad para aislar la máquina e incluso desconectarla.

En el caso de ‘nas’, al estar implementado en Go, utiliza un sencillo y potente concepto de paso de mensajes a través de canales incluido en el propio lenguaje. En ‘esxi’ usa una pequeña y elegante librería de fuente abierta que se apoya en ‘pthreads’. En la imagen tenemos el detallede la inclusión directa de la librería multihilo en el proyecto dedicado a cifrar, mientras que se ausenta en su contraparte de descifrado.

Por el contrario, no hay rastro de una orquestación concurrente en los códigos correspondientes a la tarea de descifrado. En el caso de descifrar los archivos secuestrados, el proceso es uno a uno sin ningún tipo de optimización en este sentido. Eso sí, al menos lo hace de forma recursiva y no uno a uno por paso de argumentos…todo un detalle.

Curiosidades de las rutinas de cifrado

Uno de los componentes más importantes es el manejo e implementación o uso de la criptografía. Al final, es el arma que se está empuñando para secuestrar los activos de empresas y personas. En este sentido, en la inmensa mayoría de casos las implementaciones criptográficas vienen de librerías de terceros o incluidas en la librería estándar del lenguaje o plataforma que se use. Las implementaciones propias, además de la alta complejidad que posee realizarlas, habitualmente terminan con errores que pueden ser aprovechables para conseguir revertir el cifrado impuesto (afortunadamente).

‘nas’ usa Chacha20 para cifrar los archivos, empleando para ello el módulo de la librería estándar de Go. ‘esxi’ usa, sin embargo, una implementación del algoritmo SOSEMANUK. Ambos son cifrados simétricos, de flujo, rápidos.

En la implementación de Go (la correspondiente a ‘nas’), existen dos procedimientos de cifrado cuya elección depende del tamaño del archivo. Se cifrará al completo si no sobrepasa los cuatro megabytes, mientras que si está entre aproximadamente cuatro y 20 megabytes solo se cifrarán los primeros cuatro. Si el archivo es mayor a unos 20 megabytes, entonces se efectuará un cifrado por partes (chunks) de diez hasta completar el archivo. Es decir, visto lo anterior, los archivos entre cuatro y 20 megabytes solo son cifrados desde el comienzo (cabecera) hasta los siguientes cuatro megabytes.

Este proceso puede ser visto como una “optimización” en la velocidad de cifrado (sobre todo en ahorro del proceso de apertura de un gran número de archivos). Si son archivos pequeños y numerosos (inferiores a 20 megabytes), simplemente se inutiliza su cabecera, mientras que los archivos grandes sí que son cifrados al completo para impedir cualquier tipo de reparación. En la imagen se observa el detalle de la selección del proceso de cifrado en base al tamaño del archivo.

En el cifrado correspondiente a ‘esxi’ ocurre algo similar, pero no se distingue por tamaño. Directamente se cifran todos los archivos objetivo al completo si pesan menos de 512Mb o solo hasta esa cantidad de bytes desde el inicio del archivo si superan ese tamaño. Suficiente para destrozar una máquina virtual e impedir su uso.

Cifrado

El grupo, evidentemente, posee el control de las claves públicas y privadas de cada operación. Se generan un par vía criptografía elíptica. La pública irá en el cifrador, la privada se incrustará en el descifrador. Son los valores que vemos como ‘m_publ’ y ‘m_priv’ en los archivos correspondientes a cifrador-descifrador:

Cada vez que se procede a cifrar un archivo se crea una clave privada única y aleatoria para ese archivo a través lectura de un generador aleatorio seguro.

A partir de esta clave privada se deriva una clave pública vía criptografía elíptica. Finalmente, la clave compartida (shared), se usará como vector de inicialización del cifrado por flujo ChaCha20:

Inicia el algoritmo con esa clave (en su forma sha256(sha256(shared)), y 12 bytes de esa misma cadena para el nonce) y se procede a cifrar el archivo:

Finalmente, cuando se termina de cifrar el archivo, la clave pública para ese archivo es anexada a este para la posterior recuperación de la clave privada en su contraparte de descifrado:

Además de eso, en la versión ‘nas’, se anexa al archivo un flag particular: “ABBCCDDEEFF0”. Posteriormente, durante el descifrado, se usará para comprobar su presencia. Se inhibirá el descifrado si no se halla este fragmento al final del archivo.

Descifrado

Como dijimos antes, se comprueba la existencia de (ellos lo denominan ‘flag’) una cadena de bytes al final del archivo. Son bytes contiguos. Curiosamente, en la versión ‘nas’, si el tamaño del archivo es inferior a 38 bytes (a pesar de estar cifrado) no se descifrará. La clave pública que vimos mide 32 bytes y el ‘flag’ seis, lo cual suman justo 38 bytes. Es decir, posiblemente se inhibe de descifrar archivos cifrados que estaban… ¿vacíos?

La derivación de la clave compartida (shared) es sencilla. Simplemente se extrae la pública del final del archivo y vía curva elíptica se obtiene la clave compartida de modo similar a como se creaba en el cifrador, pero esta vez usando la clave privada ‘m_priv’, contraparte de ‘m_publ’:

La implementación de ‘esxi’, en lenguaje C, es bastante similar a la empleada en ‘nas’, escrita en Go. Como hemos comentado, se sustituye el cifrado ChaCha20 por SOSEMANUK, no obstante, todo lo relacionado con la compartición y generación de claves sigue siendo válido en esta versión de Babuk orientada a máquinas virtuales.

Nota de rescate

Tanto en la versión ‘nas’ como en la ‘esxi’ se van creando notas de rescate en la que se informa de la extorsión. En el código, el contenido de esta nota es un texto provisional (placeholder). La vemos en su versión esxi y nas.

No deja de ser curioso, y evidente en el caso de las notas, que el nombrado de variables de los dos proyectos tienen poca similitud. Vease ‘notebytes’ frente a ‘ransom_note’. Da la impresión de que haya sido escrito por dos (o más) personas y que ni tan siquiera de hiciera una reescritura de una a otra, sino desarrolladas de forma independiente aun compartiendo diseño en ciertos componentes; algo que no debe sorprender, puesto que se trata de un grupo.

Fallos de programación

El software es software. El código es código. Y no existe un programa en este mundo que no posea errores de programación en las primeras versiones de estos (e incluso a lo largo de todo el ciclo de vida). La herramienta de cifrado de ‘esxi’ está escrita en C y podemos ver algún que otro desliz con la memoria. Por ejemplo, las siguientes reservas de memoria, “mallocs”, no poseen su contraparte “free”:

y

Además, esta última llamada está en una función recursiva, que va a prolongarse tanto como directorios posea el sistema. No es un gran leak de memoria (4097 bytes por directorio más otro tanto si se llega a cifrar un archivo en esa recursión), pero esos mallocs colgados dando vueltas podrían generar un consumo de memoria considerable en sistemas más humildes llegado el caso.

Ese 4097 no es un número al azar. El “path” máximo incluyendo el nombre de archivo en un sistema Linux, por norma general, es de 4096 bytes (el byte suelto es para el carácter nulo).

Es lógico que no se cuiden cosas así en el contexto en el que está creado el software. Recuerda a la anécdota del leak de memoria en el componente de un misil. ¿A quién le preocupaba un leak de memoria en un programa cuyo proceso lo iba a correr una y solo una vez?

Hasta aquí, hemos visto por encima el código fuente de dos componentes aislados de Babuk, sus versiones para sistemas NAS y virtualizados. Hemos dejado a la joya de la corona, Windows, para otro post más adelante.

Como vemos, siempre se aprenden cosas analizando piezas como esta. La ingeniería inversa es una potente herramienta para desentrañar un programa (sobre todo en su vertiente dinámica). Pero disponer del código fuente es otro nivel, no hay barreras si es legible.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *