martes, 18 de febrero de 2014

Análisis del algoritmo de descifrado del malware Careto

Esta semana, Kaspersky ha descubierto un "nuevo" malware que llevaría activo desde el año 2007. Este malware, según Kaskersky, es el más avanzado y sofisticado que han visto, desbancando de las primeras posiciones a IceFrog y otros. Este malware provendría de una entidad gubernamental porque las técnicas de ofuscación y ocultación empleadas nunca se han encontrado en un malware asociado a grupos de crimeware. Según Kaspersky, el malware estaba dirigido a instituciones del gobierno, embajadas, compañías petrolíferas, organizaciones de investigación, etc.

El objetivo principal de este malware era recoger datos de los ordenadores y móviles con sistemas Symbian. El malware podría interceptar el tráfico de red, registrar las pulsaciones del teclado, conversaciones de Skype, analizar el tráfico WiFi, robar información de móviles Nokia (Symbian), llaves PGP, hacer capturas de pantalla, etc. Es decir, el malware estaba siendo usado para espiar otras entidades. Kaspersky hace una descripción muy detallada en su página.

En este artículo, nos gustaría introducir un algoritmo extraído tras un proceso de ingeniería inversa que sirve para descifrar cadenas y que no se encuentra en la documentación de Kaspersky. Careto usa este algoritmo al principio de su ejecución para recoger el nombre de las funciones a importar, las llaves de registro donde guardar la DLL para tener persistencia al reiniciar, la llave usada por el algoritmo RC4, etc. En primer lugar presentaremos una vista global del algoritmo y luego explicaremos cada una de sus funcionalidades.

(tomado de Kaspersky)


El algoritmo para descifrar se compone de tres bloques: el generador del vector usado por el tercer bloque, el decodificador aplicado sobre la cadena y el "transformador" para descifrar dicha cadena. El algoritmo es un algoritmo de flujo. Es decir, que va a iterar cada carácter de la cadena cifrada decodificándolos y descifrándolos uno a uno. Es un algoritmo que nunca hemos visto, algo que le hace muy interesante. El flujo del algoritmo está representado en la imagen siguiente:



El primer paso del algoritmo consiste en construir un vector de 260 bytes. Este vector se compone de los mismos bytes para descifrar cada cadena. Como el vector era el mismo para descifrar todas las cadenas, lo hemos copiado a un programa escrito en Python. El vector es el siguiente:

vector = [0x59, 0x77, 0xd8, 0xd1, 0x8a, 0x1a, 0x43, 0x76, 0x9d, 0x7c, 0x35, 0x97, 0xb9, 0x68, 0xa3, 0x19, 0xa6, 0x30, 0xa2, 0x92, 0xb1, 0x84, 0x4b, 0xf4, 0x04, 0xf9, 0x90, 0x44, 0xfc, 0xad, 0x81, 0x4a, 0x54, 0xaa, 0xa9, 0xc3, 0x7b, 0xb6, 0x9e, 0xdc, 0x5b, 0x29, 0x6f, 0x5f, 0x3d, 0xe5, 0x45, 0xb2, 0xe6, 0xeb, 0xc2, 0x86, 0x12, 0xf0, 0xb5, 0x95, 0x7e, 0x2b, 0x09, 0x87, 0xcf, 0x3f, 0xa5, 0x42, 0x10, 0x79, 0x2e, 0xc8, 0xf1, 0xc6, 0x8b, 0xc0, 0x07, 0xaf, 0x6c, 0xb8, 0x93, 0x1b, 0x2d, 0x99, 0xec, 0x7a, 0xb7, 0xe0, 0xac, 0x74, 0xea, 0xc9, 0x8f, 0x98, 0xa4, 0xd4, 0x4e, 0xfe, 0x05, 0x6e, 0xc5, 0x21, 0x7d, 0x94, 0x82, 0xae, 0xd7, 0x85, 0x48, 0x1d, 0x47, 0xd9, 0x58, 0x55, 0xee, 0x9a, 0x00, 0x65, 0xdd, 0x53, 0x6d, 0xed, 0xb4, 0x1c, 0xba, 0x9b, 0xc7, 0x70, 0x27, 0xfd, 0x2a, 0xef, 0x25, 0x67, 0xa7, 0xbf, 0x83, 0xe3, 0x38, 0x9f, 0x8e, 0x66, 0x71, 0xe9, 0xb3, 0x78, 0x56, 0x89, 0x3c, 0xca, 0x06, 0x5d, 0xe8, 0x13, 0xf7, 0x03, 0xd0, 0x75, 0xa1, 0xce, 0x01, 0xdf, 0x0a, 0x36, 0xa0, 0x69, 0xe7, 0xff, 0x39, 0x0c, 0x0b, 0x4c, 0xf6, 0x64, 0xcd, 0x73, 0xbb, 0xb0, 0xd6, 0x3b, 0xd2, 0x16, 0xe1, 0xf8, 0x91, 0xc1, 0x15, 0x7f, 0xbd, 0xbe, 0x8c, 0x18, 0x5c, 0x24, 0xc4, 0x33, 0x2c, 0x96, 0x28, 0x08, 0xde, 0x8d, 0xcb, 0xcc, 0x2f, 0x80, 0x6a, 0xd3, 0xfa, 0xd5, 0x3a, 0x14, 0x02, 0xf2, 0x17, 0x32, 0x9c, 0xda, 0xdb, 0x3e, 0x88, 0xa8, 0xe2, 0x40, 0xe4, 0x49, 0x23, 0x11, 0x46, 0x26, 0x41, 0xab, 0x50, 0x4f, 0x4d, 0x0e, 0x0d, 0xfb, 0x0f, 0xf3, 0x51, 0xf5, 0x5a, 0x34, 0x22, 0x57, 0x37, 0x52, 0xbc, 0x61, 0x60, 0x5e, 0x1f, 0x1e, 0x31, 0x20, 0x63, 0x62, 0x72, 0x6b, 0x77, 0xd1, 0x1a, 0x76, 0xc8]

El segundo paso consiste en decodificar la cadena. El algoritmo va cogiendo bytes de dos en dos de la cadena cifrada. Cada dos bytes, el algoritmo va a quitar el offset 0x80 de esos dos bytes para luego extraer los bits del primer byte que van a representar los cuatro bits de mayor peso del carácter final y los bits del segundo carácter que van a representar los cuatro bits de menor peso del carácter final. En otras palabras, antes de decodificar la cadena cifrada, cada byte está entre 0x80 y 0x8f.

Abajo se muestra un ejemplo con dos bytes, 0x83 y 0x8a. Después de la decodificación, obtenemos el byte 0x3a.



Hemos escrito en Python la función "decode_bytes" para decodificar la cadena cifrada. Esta función toma la cadena cifrada ("bytes_to_decipher") y devuelve la cadena decodificada ("decoded_bytes").

def decode_bytes(bytes_to_decipher):
        decoded_bytes = list()
        for i in range(0, len(bytes), 2):
               decoded_bytes.append( ((bytes[i])-0x80) <<4 | ((bytes[i+1])-0x80) )

            return decoded_bytes

El tercer paso consiste en descifrar la cadena decodificada llamando a la función "transform" escrita en Python, representada más abajo. Primero de todo, la función "transform" va a guardar el estado de un byte y barajar una parte del vector de 260 bytes. Luego, va a calcular dos índices del vector que llamaremos "index_1" e "index_2" y obtener los bytes correspondientes. La función aplica un doble XOR: el primer XOR entre los dos caracteres obtenidos y un segundo XOR entre el resultado del anterior y el byte de la cadena decodificada. Finalmente, la función va a guardar el carácter decodificado y descifrarlo en el vector de 260 bytes. Se efectúa una llamada a la función "transform" para cada byte a descifrar. En otras palabras, a cada ejecución de la función "transform", el vector de 260 bytes tiene un estado diferente dependiendo de los estados precedentes (que a su vez depende de los bytes descifrados y decodificados previamente). Es decir, no podemos usar el mismo vector para cifrar y descifrar haciendo el algoritmo más difícil para generar nuevas cadenas cifradas. Pensamos que el primer byte decodificado es usado como semilla para inicializar el vector de 260 bytes. En efecto, el algoritmo después de descifrar la cadena borra el primer byte descifrado. Además después de descifrar todas las cadenas nos hemos dado cuenta que el primer byte no tiene ninguna representación visual.

def transform (decoded_byte):
        global vector

        #Save the value
        i = vector[vector[0x104]]

        #Shuffle values
        vector[0x101] = (vector[0x101] + vector[vector[0x100]]) & 0xFF
        vector[0x100] = vector[0x100] + 1

        vector[vector[0x104]] = vector[vector[0x101]]
        vector[vector[0x101]] = vector[vector[0x103]]
        vector[vector[0x103]] = vector[vector[0x100]]
        vector[vector[0x100]] = i

        vector[0x102] = (vector[0x102] + vector[i]) & 0xFF

        #Decipher the decoded byte
        index_1 = vector[(vector[vector[0x103]] + vector[vector[0x102]] + vector[vector[0x104]]) & 0xFF]
        index_2 = (vector[vector[0x100]] + vector[vector[0x101]]) & 0xFF

        deciphered_byte = (vector[index_1] ^ vector[index_2]) ^   decoded_byte

        #Save the changes
        vector[0x103] = deciphered_byte
        vector[0x104] =  decoded_byte


        return deciphered_byte

Por ejemplo, una cadena cifrada que corresponde a la llave RC4:

[0x8d, 0x85, 0x86, 0x8a, 0x8f, 0x80, 0x88, 0x83, 0x8d, 0x82, 0x88, 0x85, 0x86, 0x8f, 0x8f, 0x87, 0x8d, 0x82, 0x83, 0x82, 0x8c, 0x8e, 0x83, 0x8d, 0x89, 0x82, 0x86, 0x87, 0x82, 0x83, 0x83, 0x81]

Una vez descifrada:

"#!$7be&.Kaw-12[}"

Para concluir, el malware usa un algoritmo de descifrado muy elaborado, en nuestro laboratorio no habíamos visto antes nada similar. El algoritmo hace difícil la generación de nuevas cadenas cifradas. Pensamos que el creador o creadores querían evitar que otra gente usase el malware por su cuenta cambiando los parámetros del malware, por ejemplo, las entradas del registro, la llave RC4, etc. Evitar el cambio de los parámetros hace al malware más vulnerable a la detección de los antivirus. Pensamos que el algoritmo fue elaborado por un experto en criptografía reforzando la convicción de Kaskersky sobre el hecho de que una entidad gubernamental estaría detrás de este malware.

SHA-256 de la muestra: 
19e818d0da361c4feedd456fca63d68d4b024fbbd3d9265f606076c7ee72e8f8

Laurent Delosières