Solución al Desafío ESET #30 sobre Ingeniería Reversa

Hace dos semanas abrimos el Desafío ESET #30 y ya es hora de publicar los resultados. Si bien no hay una única forma de resolverlo, hoy presentaré una de las soluciones que se basa en análisis estático, es decir, en no ejecutar las instrucciones o hacer debugging de la aplicación. Dado que el problema fundamental del desafío radica en descifrar URLs que se encuentran cifradas, mi solución consiste en entender cómo funciona el algoritmo de descifrado, para poder emularlo.

El ganador del Desafío ESET en esta ocasión es Daniel Correa, cuya solución pueden ver en la sección de comentarios del Desafío. Daniel pudo reconstruir el binario a partir del archivo de IDA, para así depurar la parte del ejecutable donde se hace la descarga desde las URLs. Por su rápida (y correcta) respuesta, le enviaremos un libro de seguridad informática para que pueda seguir aprendiendo sobre Ingeniería Reversa y otros temas apasionantes.

El segundo lugar es para Javier Vicente Vallejo, quien también resolvió el desafío cargando las secciones del ejecutable en memoria y depurándolas. Javier llegó un par de días después con su respuesta, pero contestó, además, todas las preguntas extra, con lo cual le haremos entrega de una licencia de la última versión de la más completa solución de seguridad, ESET Smart Security. También pueden ver su respuesta en los 3 comentarios que Javier realizó en el Desafío.

Resolución oficial del Desafío ESET #30

Cuando comienza el desafío tenemos en nuestras manos un archivo de extensión “.IDB” que abrimos con IDA. Lo primero que sucede al abrirlo es que IDA nos avisa que el archivo está corrupto, indicio de que ha sido manipulado o se ha dañado. No obstante, si procedemos a abrir el archivo, veremos que el código y las secciones del ejecutable se interpretan correctamente.

Luego, si vamos al comienzo del código, podremos ver los datos que se muestran a continuación:

md5

Se observa que el archivo desensamblado se llama calc.exe y que contamos con su MD5. Si investigamos su reporte en VirusTotal, nos encontraremos con que ese MD5 corresponde a la calculadora de Microsoft Windows. Aquí es donde varios de los participantes han caído en la trampa y han dejado de analizar el archivo.

Si observamos un poco más, encontraremos algunas inconsistencias. Para empezar, el binario tiene dos exports: CPlApplet y DllEntryPoint, con lo cual es un tipo de DLL, no un EXE; en particular el binario que estamos analizando es un archivo CPL. Además si vemos los imports y las librerías involucradas, podríamos preguntarnos, por ejemplo, por qué la calculadora de Windows tendría llamadas a rutinas de conexión a Internet de la librería wininet:

imports

Esto responde a la pregunta de por qué el archivo de base de datos de IDA está corrupto: lo he modificado a mano para que muestre un MD5 falso. Como imaginarán, también he cambiado el nombre del ejecutable antes de analizarlo con IDA a “calc.exe”, pero como se dijo antes, el binario original es un archivo de extensión “.CPL”, no “.EXE”.

Dado que el objetivo principal del desafío es encontrar a qué URLs se conecta el ejecutable analizado, para continuar vamos a buscar imports que trabajen con URLs. Como vemos en la imagen de arriba, cualquiera de los imports de wininet nos sirve, por lo que buscaremos desde qué parte del código son llamados:

xrefAPI

Estamos con suerte: solamente se llama a InternetOpenUrlA desde un lugar en el código. Esto nos indica que, o el ejecutable abre una sola URL o la rutina que contiene esta llamada es invocada para cada URL a la que se quiera conectar. Por lo pronto, si nos dirigimos a esa parte del código, vemos lo siguiente:

internetopenurla

El parámetro lpszUrl de InternetOpenUrlA es el que contiene la cadena de texto con la URL. Si ahora vamos leyendo desde la llamada hacia arriba, vemos que esa cadena está en el registro EAX y estamos realizando un análisis estático, por lo que no podemos saber cuál es la URL. Sin embargo, vemos unas líneas más arriba que EAX contiene el valor almacenado en [ebp+var_4] a la hora de la llamada (hay una llamada a la rutina sub_404978 en el medio, pero podemos obviarla, ya que su código es corto y sencillo, y solamente comprueba que la cadena con la URL no esté vacía).

Si vamos más arriba, buscando dónde se utiliza var_4, vemos que es una de las variables locales de la rutina, y que es inicializada con el valor que hay en EAX al comenzar la rutina. Dicho en otras palabras, var_4 se inicializa con uno de los argumentos pasados a la rutina en EAX, que es la URL que estamos buscando.

Si no lo habíamos notado antes, en este punto nos queda claro que el ejecutable que estamos analizando está hecho en Delphi, ya que las rutinas locales no reciben sus argumentos a través del stack, sino en los registros del procesador. Por lo tanto, si buscamos las xrefs, podremos ver qué valores son pasados a esa rutina:

xrefDescarga

Nuevamente estamos con suerte, ya que se llama a la rutina en dos puntos del código. De ahí podemos inferir que el ejecutable trata de conectarse a dos URLs distintas. Si vamos al primer xref, tendremos lo que se ve en la imagen que sigue.

He renombrado nuestra rutina sub_44E7C0 a descargar_URL para mayor simplicidad.

codigoPpal

Vemos que antes de llamar a descargar_URL, se coloca en EAX (registro en el que descargar_URL espera la cadena de texto con la URL) el resultado de la ejecución de sub_44E5A0. A su vez, sub_44E5A0 recibe dos argumentos: una variable local y una cadena larga que parece estar cifrada. Es justamente esa variable local la que luego es asignada a EAX, con lo cual ya podemos elaborar una hipótesis: sub_44E5A0 toma una cadena cifrada, la descifra y coloca el resultado en un buffer, que en este caso es var_14; podemos cambiar el nombre de sub_44E5A0 por descifrarStr.

Si echamos un rápido vistazo al resto del código en esa parte, veremos que hay varias strings cifradas, algunas más cortas y otras más largas, pero siempre se llama a descifrarStr luego de la aparición de esas strings. Para la string más larga que observamos en la imagen anterior, y que parece ser una de las URLs, vemos que luego de ser descifrada y descargada, se llama a ShellExecute más abajo.

Por ello, podemos pensar que lo que se descarga desde esa URL es otro archivo ejecutable. Algún ávido lector de nuestro blog se habrá percatado que esta forma de strings cifradas ya había sido descripta en esta entrada y en esta otra. Así, hemos llegado al problema principal del desafío.

Entendiendo cómo se descifran las cadenas cifradas (y cómo aplicarlo a las dos URLs)

El código principal de la rutina de descifrado se muestra en la siguiente imagen. He agregado algunos comentarios y cambiado los nombres de variables y subrutinas para que sea más fácil de entender la resolución (el archivo original del desafío no contiene comentarios).

bucleDescifrado

Lo importante es hacer un primer análisis con una visión general, sin centrarnos en los detalles. Así, identificamos el bucle de descifrado por la flecha verde que va hacia arriba y vemos que fundamentalmente hay dos operaciones: XOR y SUB entre caracteres de la string cifrada y de una clave de cifrado. Pero, ¿dónde está la clave?

Está hardcodeada en la misma rutina de descifrado, hacia el principio. No se alcanza a ver en la imagen anterior, pero sí en la que sigue:

clave

En el algoritmo de descifrado vemos cómo los caracteres de la string cifrada se toman de a dos por vez, mientras que, en el caso de la clave, se recorre de a un caracter por vez. Esto se debe a que ese par de caracteres de la string cifrada son tomados como un número de dos dígitos en base hexadecimal (la conversión es hecha por la subrutina hex_from_ascii). Por su parte, de cada caracter de la clave se toma su representación en código ASCII, en hexadecimal. De esta manera es posible realizar una operación XOR entre ambos números.

El algoritmo se describe en forma esquemática a continuación. Podemos observar que el paso 1 no empieza por el primer par de caracteres de la string cifrada, sino que toma el segundo par. Es en el segundo paso cuando se utiliza el par precedente de caracteres, substrayendo ese número del resultado obtenido del paso anterior. En este punto, el resultado de la resta es tomado como la representación de un carácter en ASCII.

En nuestro ejemplo vemos que el resultado corresponde a la letra “h”.

algoritmoDesc

Podemos ver también en el código que, en el caso de que la operación de sustracción diese como resultado un número negativo (lo que ocurre siempre que el minuendo es menor que el sustraendo), se suma el valor 0xFF al minuendo antes de realizar la sustracción. A continuación se ilustra cómo continúa el procedimiento para los primeros 4 caracteres de la string cifrada:

calculosDesc

Ahora que conocemos cómo funciona el algoritmo de descifrado, podemos continuar haciendo los cálculos a mano, o implementar el procedimiento en algún lenguaje de programación. A continuación les dejo mi versión de la rutina “descifrar” que he implementado en Python:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
def isHexCapitalized(string):
	val = True	
	for c in string:
		char = ord(c)
		if (char < 48 or char > 57) and (char < 65 or char > 70):
			val = False
			break
	return val


def descifrar(key, ciphertext):
	llave = key
	cifrada = ciphertext
	descifrada =  ''

	if not isHexCapitalized(cifrada):
		return descifrada

	sub = int(cifrada[:2], 16)
	cifrada = cifrada[2:]

	while cifrada != '':
		xor2 = int(cifrada[:2], 16)
		xor1 = ord(llave[:1])
		llave = llave[1:]
		if llave == '':
			llave = key

		char = xor1 ^ xor2
		if char < sub:
			char = char + 255
	
		char = char - sub
		if char < 32 and char > 126:
			descifrada = ''
			break

		descifrada = descifrada + chr(char)
		sub = int(cifrada[:2], 16)
		cifrada = cifrada[2:]

	return descifrada

 

La ejecución resulta en las siguientes URLs:

python

Por último, para responder a la pregunta de si este ejecutable es malicioso o no, debemos observar la rutina principal, aquella que llama a descargar_URL y ShellExecute, y que mencionamos anteriormente. De ese análisis entenderemos que el ejecutable es un malware de tipo TrojanDownloader. La parte maliciosa es bastante lineal y solamente descarga y ejecuta otras dos amenazas; el ejecutable no busca persistir en el sistema ni realizar otras acciones complejas.

Si bien a primera vista pareciera que se descarga una imagen (extensión “.PNG”) de la primera URL, al ver el código puede observarse que el archivo se escribe en el disco como “Desk.exe” y luego se ejecuta con una llamada a ShellExecute.

¡Espero que les haya gustado el desafío! Si quieren saber más acerca del funcionamiento de los archivos CPL, estén atentos al artículo que estaremos publicando próximamente: “CPL malware en Brasil: entre troyanos bancarios y correos maliciosos”.

¡Gracias por participar y hasta la próxima!

Autor , ESET

Síguenos