Desmantelando KillDisk: reversión del componente destructivo de BlackEnergy

[post-views]
febrero 29, 2016 · 11 min de lectura
Desmantelando KillDisk: reversión del componente destructivo de BlackEnergy

Omitamos la larga introducción sobre la amenaza BlackEnergy y vayamos directamente a estudiar el componente de malware llamado “ololo.exe”, también conocido por el público como KillDisk. KillDisk es un módulo del marco BlackEnergy destinado a la destrucción de datos y a crear caos/distracción durante las operaciones APT.

https://www.virustotal.com/en/file/11b7b8a7965b52ebb213b023b6772dd2c76c66893fc96a18a9a33c8cf125af80/analysis/Las herramientas principales utilizadas en nuestro análisis de hoy son Process Monitor que es parte de las utilidades de SysInternals de Mark Russinovich y IDA Pro Disassembler. Todas las manipulaciones se realizarán en un entorno virtual basado en el sistema operativo Windows XP. Comenzamos con una configuración inicial rápida de la VM de prueba, encendemos la máquina y creamos un snapshot llamado “Antes de la infección”. ¡Comencemos!

Para llevar seguimiento de todos los eventos relacionados con nuestro objeto de estudio, iniciamos Process Monitor y nos aseguramos de que permanezca visible:BlackEnergy-killdisk-1Y que rastree el proceso necesario:

BlackEnergy-killdisk-2A continuación, cargamos el virus en IDA Pro Disassembler y vemos la siguiente imagen.Esta es la función WinMain, es decir, la función principal, por lo que comenzamos su análisis y vemos que lo primero (y único) que hace es una llamada de procedimiento llamado sub_40E070:

 BlackEnergy-killdisk-3
Así que vayamos directamente a ese procedimiento. Aquí podemos encontrar procedimientos responsables de la extracción de extensiones de archivo en la memoria y el primer procedimiento justo después de ellos que llama a algo interesante llamado sub_40E080:

BlackEnergy-killdisk-4Echemos un vistazo más cercano a su contenido:BlackEnergy-killdisk-5Su interior contiene una llamada de sistema interesante CreateFile y un par de procedimientos sub_40C390 and sub_40C400. Así que es hora de tomar un snapshot de nuestro laboratorio virtual y continuar nuestro viaje!

Examinemos la llamada de función CreateFile, configuramos el BreakPoint y ejecutamos nuestra muestra de estudio:

BlackEnergy-killdisk-6Un pequeño consejo sobre esta función:

CreateFile la función crea o abre un archivo o dispositivo de E/S. Los dispositivos de E/S más comúnmente usados son los siguientes: archivo, flujo de archivo, directorio, disco físico, volumen, búfer de consola (CONIN$ or CONOUT$), unidad de cinta, recurso de comunicaciones, mailslot y tubería con nombre. La función devuelve un identificador que se puede usar para acceder al archivo o dispositivo para varios tipos de E/S dependiendo del archivo o dispositivo y los indicadores y atributos especificados.

Como sabemos, la mayoría de los compiladores pasan argumentos a una función a través de una pila y ya que estamos tratando con un ejemplo escrito inicialmente en C, y de acuerdo con las directivas de C, los argumentos se empujan a la pila de derecha a izquierda de manera que el primer argumento de la función se empuja por último en la pila y termina siendo el primer elemento en la parte superior de la misma. [1].

There is a special register for working with the stack – ESP (Extended Stack Pointer). The ESP, by definition, always points to the top of the stack:

BlackEnergy-killdisk-41Un vistazo a la pila:BlackEnergy-killdisk-7El último argumento que se introdujo en la pila (el de la parte superior) es el primer argumento aceptado por la función (según la sintaxis de CreateFile la función) y contiene el nombre del archivo en nuestro caso \.PHYSICALDRIVE0, por lo tanto, el objetivo es el primer disco físico.BlackEnergy-killdisk-8Ahora analizamos otros argumentos enviados a la función tomando sus valores de la pila:BlackEnergy-killdisk-9Algunas conclusiones:

  • lpFileName – puntero a la cadena ASCIIZ que contiene el nombre (ruta) del archivo que se está abriendo o creando (como ya hemos descubierto);
  • dwDesiredAccess – tipo de acceso al archivo: en nuestro caso el valor es 0C0000000h que significa GENERIC_READ+GENERIC_WRITE or acceso de lectura-escritura;
  • DwShareMode – un modo que permite compartir los archivos con diferentes procesos y este parámetro puede tener diferentes valores; en nuestro caso el valor de 00000003b se traduce enFILE_SHARE_READ+FILE_SHARE_WRITE ootros procesos pueden abrir el archivo para lectura/escritura;
  • IpSecurityAttributes – puntero a la estructura SecurityAttributes (archivo winbase.h) que define la configuración de seguridad relacionada con el objeto de archivo del núcleo, si la seguridad no está configurada se usa un valor de NULL – es responsable de decidir acciones cuando el archivo existe o no existe, en nuestro caso un valor de
  • dwCreationDistribution significa 3 means OPEN_EXISTING or abrir archivo si existe, y devolver un error si no existe;
  • DwFlagsAndAttributes – indicadores y atributos; este parámetro se utiliza para establecer características del archivo creado y se ignora en modo de lectura 0.
  • hTemplateFile – el parámetro se utiliza solo cuando se crea un nuevo archivo 0.
  • Al tener éxito, la función devuelve un controlador del nuevo archivo a ЕАХ Y si la función falló se escribe un NULL en el ЕАХ registro.

Está bien, entonces después de llamar a CreateFile obtenemos un valor distinto de cero para EAX, lo que significa que contiene el controlador del archivo solicitado, por ejemplo, \.PHYSICALDRIVE0:

BlackEnergy-killdisk-26Obtenemos nuestro controlador, así que pasemos a la siguiente llamada, sub_40C390:

BlackEnergy-killdisk-10Al igual que antes, miramos dentro del procedimiento y vemos otra llamada interesante:BlackEnergy-killdisk-11Consejo de función:

DeviceIoControl función envía un código de control directamente a un controlador de dispositivo especificado, provocando que el dispositivo correspondiente realice la operación correspondiente.

Un vistazo a la pila justo antes de llamar a esta función:BlackEnergy-killdisk-11Conclusiones:

  • hDevice – descriptor del dispositivo (aquí reconocemos a un viejo conocido \.PHYSICALDRIVE0);
  • dwIoControlCode – código de operación. En nuestro caso el valor es 0x70000h que significa IOCTL_DISK_GET_DRIVE_GEOMETRY o recuperación de la información sobre la geometría de los discos (cantidad de cilindros, tipo de medio, pistas por cilindro, sectores por pista, bytes por sector);
  • lpInBuffer – búfer con datos de entrada. No necesitamos ningún dato de entrada, así que NULL así es;
  • nInBufferSize – tamaño del búfer de entrada en bytes igual a 0 en nuestro caso;
  • lpOutBuffer – puntero al búfer de salida. Su tipo está establecido por el parámetro dwIoControlCode 0x0011FCA8h;
  • nOutBufferSize – tamaño del búfer de salida en bytes 0x18h (24);
  • lpBytesReturned – un puntero a una variable que contiene la cantidad de bytes que se escriben en la salida 0x0011FCA4h;
  • lpOverlapped – puntero a una estructura OVERLAPPED;
  • A medida que se procesa la llamada, miramos dentro del búfer (en la dirección que se envió como argumento a lpOutBuffer precisamente 0x0011FCA8h):

BlackEnergy-killdisk-12Vamos a interpretar la respuesta:

  • En la dirección de 0x0011FCA4h tenemos un retorno de la cantidad de bytes escritos en el búfer de salida según lo previsto por la función (marcado en verde) obtuvimos tantos como pedimos 24 símbolos.
  • En la dirección de 0x0011FCA8h hemos obtenido información sobre la geometría del primer disco físico (\.PHYSICALDRIVE0):
    • cantidad de cilindros – 0x519h (1305)
    • tipo de medio – 0x0Ch (12) que significa disco duro fijo.
    • pistas por cilindro – 0x0FFh (255)
    • sectores por pista – 0x3Fh (63)
    • bytes por sector – 0x200 (512)

Ahora nos referimos al procedimiento tres, sub_40C400:

BlackEnergy-killdisk-13Dentro del procedimiento, dos llamadas inmediatamente atrajeron nuestra atención, específicamenteSetFilePointerExyWriteFile:

BlackEnergy-killdisk-14Un consejo sobre la función:

SetFilePointerEx la función mueve el puntero de archivo del archivo abierto especificado.

Como es tradición, miramos dentro de la pila de llamadas de la función:BlackEnergy-killdisk-15

  • hfile – nuestro familiar descriptor de dispositivo \.PHYSICALDRIVE0;
  • liDustanceToMove – posición inicial para comenzar una escritura 0;
  • lpNewFilePointer – puntero a una variable que recupera un nuevo puntero de posición del archivo 0x0011FCA8h. En un caso cuando el parámetro está VACÍO (NULL) no se devuelve un nuevo puntero en el archivo;
  • dwMoveMethod – tamaño del búfer de entrada en bytes. En nuestro caso 0x200h (512) – bytes por sector;

Ahora nuestro “monstruo” usa la WriteFile función y llena el archivo con ceros al cual ya estableció un acceso directo y fiable.

Una pista de función:

Una WriteFile función escribe datos en el archivo o dispositivo de entrada/salida (I/O) especificado comenzando con la posición especificada en el archivo. Esta función está diseñada tanto para operación sincrónica como asincrónica.

Lista de argumentos de la función:BlackEnergy-killdisk-16Está bien, para asegurarnos de que no nos perdamos nada interesante, echamos un vistazo al tablero y entre todas las cosas prestamos atención a la pila:BlackEnergy-killdisk-17Todo listo para la acción:

  • 1 – al permanecer en la llamada de función WriteFile
  • 2 – Process Monitor está esperando ansiosamente cualquier mínimo movimiento de los músculos de nuestro “monstruo”
  • 3 – argumentos empujados desde registros a la pila
  • 4 – y bueno, la propia pila
  • Un cuadro dedicado en la ventana Hex View-EBP marcamos un área que está siendo abordada por el segundo argumento de función (búfer de datos) y puede tomar mi palabra en esto: contiene exactamente 0x200h (512) de ceros.

Ejecutamos el comando presionando la tecla F8 y observamos el cambio:BlackEnergy-killdisk-18Como esperábamos, un ojo agudo de Process Monitor es preciso y registra la escritura de 512 bytes comenzando desde la posición 0.

Luego el procedimiento finaliza y se llama nuevamente pero ahora con diferentes valores:

Ahora tomamos los siguientes 512 bytes que son particularmente visibles en 0x200 (a diferencia de los argumentos de pila anteriores que marcamos con un marco azul)BlackEnergy-killdisk-19Y tienen el mismo destino que los anteriores:BlackEnergy-killdisk-20Este tiovivo continúa hasta que la verificación de 0x40C865h dirección devuelve el valor de EBX registro igual a 0x100h (255 veces ):

BlackEnergy-killdisk-21Para asegurarnos de que esto sucedió, ponemos un punto de detención en la instrucción que sigue a nuestro ciclo vicioso de “destrucción”:

BlackEnergy-killdisk-22Como un total recibimos un 256 de operaciones de re-escritura:BlackEnergy-killdisk-23Eso es básicamente todo, nuestro “monstruo” ha hecho su trabajo sucio y cierra el controlador del archivo llamando a la CloseHandle función.

Consejo de función:

Una CloseHandle función cierra un identificador de objeto de un objeto abierto.

Argumentos de función:

BlackEnergy-killdisk-24Nuestra vista tradicional de la preparación de la pila:

BlackEnergy-killdisk-25¿Y qué tenemos en el registro ESI?BlackEnergy-killdisk-26Como se esperaba, nuestro familiar descriptor 0x44h se pasa a la pila como argumento de función.

Vemos los valores devueltos por la función en el registro EAX: BlackEnergy-killdisk-27Si la función termina con éxito, el valor devuelto es no nulo.

Si la función existe con un error, el valor devuelto es nulo.

Esta acción no pasó desapercibida por el ojo atento de nuestro Process Monitor:

BlackEnergy-killdisk-28Todo funcionó con normalidad (si puedes llamar normal a la destrucción de la parte más importante del disco del sistema) ) y el archivo se cierra.

Continuemos:BlackEnergy-killdisk-29Para una percepción más cómoda renombré la función a Eraser. Después de destruir exitosamente los datos en PhysicalDrive0, el contador ubicado en ESI el registro se incrementa en 1, verificado con el valor de 0x0Ah (10) y lanza operación de re-escritura con un nuevo valor:BlackEnergy-killdisk-30y así sucesivamente hasta que se alcance 10.

De esta manera el monstruo sigue caminando por todas las unidades físicas y borrando el primer 512 * 255 = 128kb (esto depende de la cantidad de bytes en un sector que el “monstruo” aprendió al conocer la geometría del disco).

Esta vez, sin embargo, el resultado de la función CreateFileW será el siguiente valor: BlackEnergy-killdisk-31Lo que significa que en nuestro «laboratorio improvisado» no hay un segundo disco físico (o bueno, no existe ningún tipo de disco con un número de secuencia mayor a 0) y el ciclo no tiene nada que re-escribir.part1_op_enAhora que estamos armados con un conocimiento recién adquirido, intentemos deshabilitar la función destructiva que acabamos de aprender modificando el código de manera que nuestra modificación permita eludir la re-escritura de datos:

BlackEnergy-killdisk-33Recordemos el procedimiento sub_40E080 que contiene la llamada de función CreateFileW. Dado que sabemos que el valor de 0xFFFFFFFFh aparecería después de completar la función CreateFileW solo en el caso de que no exista una unidad física y en tal escenario la función “destrucción de datos” realiza una salida, cambiemos una instrucción de rama condicional ubicada en la 0x0040C7E4h a una incondicional:

 BlackEnergy-killdisk-34
IDA Pro es un instrumento genial, así que solo tenemos que escribir un comando.

Lo único que debemos considerar es el tamaño del comando antes y después, así que revisemos cuántos bytes son utilizados por la secuencia condicional:

BlackEnergy-killdisk-35Como podemos ver, una instrucción de bifurcación condicional equivale a 6 bytes, mientras que la secuencia incondicional solo tomará 5 y por lo tanto agregaremos otro comando – NOP, para mantener las cosas iguales:BlackEnergy-killdisk-36NOPea el siguiente comando sin importar lo que contenía antes (aunque veamos un 0 ahí):

BlackEnergy-killdisk-37Hacemos una doble verificación de que las direcciones de comando ahora están igualadas basadas en la dirección del comando siguiente:

BlackEnergy-killdisk-38y presiona cancelar. Hecho.

Después de estas manipulaciones, el procedimiento parecerá simple: entrada y salida, creando un bucle vacío (ya que no sabemos qué otras partes del programa pueden engancharse en él, simplemente lo dejamos en ese estado).BlackEnergy-killdisk-39La prueba final detectada por Process Monitor indica que no se dañaron discos físicos como resultado de la ejecución disecada de KillDisk:

BlackEnergy-killdisk-40El malware ha leído la información sobre el disco, lo ha estudiado y continuó con su actividad que analizaremos minuciosamente en nuestros próximos artículos.

Fuentes:

[1] – Hacker Disassembling Uncovered. Kris Kaspersky, 2009.

Traducido del original por Andrii Bezverkhyi | CEO SOC Prime

¿Fue útil este artículo?

Dale me gusta y compártelo con tus compañeros.
Únase a la plataforma Detection as Code de SOC Prime para mejorar la visibilidad de las amenazas más relevantes para su negocio. Para ayudarle a comenzar y obtener un valor inmediato, reserve una reunión ahora con los expertos de SOC Prime.