Flare-On 6 CTF WriteUp (Parte 12)

Esta es la duodécima y última parte de la serie Flare-On 6 CTF WriteUp.

12 – ayuda

El reto dice

¡Eres mi única esperanza, jugador de FLARE-On! Uno de nuestros desarrolladores fue pirateado y no estamos seguros de qué se llevaron. Logramos configurar una captura de paquetes en la red una vez que nos enteramos, pero definitivamente ya estaban en el sistema. Creo que lo que sea que instalaron debe tener errores, parece que bloquearon nuestra caja de desarrollador. Guardamos el archivo de volcado, pero no puedo entenderlo. ¡POR FAVOR, AYUDA!

Tenemos dos archivos –

  • ayuda.dmp – Un volcado de memoria de 2 GB
  • ayuda.pcapng – Captura de paquetes

Identificando la imagen

Para analizar el volcado de memoria, usaremos Volatility junto con WinDbg. Asegúrate de usar la última versión de Volatility en GitHub y no la versión 2.6, que es bastante antigua.

Primero necesitamos identificar la imagen con imageinfo o kdbgscan dominio. Desafortunadamente, para esta imagen específica, ninguno de los comandos funciona de inmediato, como se muestra en el fragmento a continuación.

$ vol.py -f help.dmp imageinfo
Volatility Foundation Volatility Framework 2.6.1
INFO    : volatility.debug    : Determining profile based on KDBG search...
WARNING : volatility.debug    : Alignment of WindowsCrashDumpSpace64 is too small, plugins will be extremely slow
WARNING : volatility.debug    : Alignment of WindowsCrashDumpSpace64 is too small, plugins will be extremely slow
WARNING : volatility.debug    : Alignment of WindowsCrashDumpSpace64 is too small, plugins will be extremely slow
WARNING : volatility.debug    : Alignment of WindowsCrashDumpSpace64 is too small, plugins will be extremely slow
WARNING : volatility.debug    : Alignment of WindowsCrashDumpSpace64 is too small, plugins will be extremely slow
WARNING : volatility.debug    : Alignment of WindowsCrashDumpSpace64 is too small, plugins will be extremely slow
WARNING : volatility.debug    : Alignment of WindowsCrashDumpSpace64 is too small, plugins will be extremely slow

En aquellos casos en los que la Volatilidad no pueda inferir por sí misma, tenemos que especificar manualmente el perfil de imagen que se usará con el --profile bandera. Por ejemplo usando el perfil Win7SP1x64 la volatilidad identifica correctamente la imagen.

$ vol.py -f help.dmp --profile=Win7SP1x64 imageinfo
Volatility Foundation Volatility Framework 2.6.1
INFO    : volatility.debug    : Determining profile based on KDBG search...
          Suggested Profile(s) : Win7SP1x64, Win7SP0x64, Win2008R2SP0x64, Win2008R2SP1x64_24000, Win2008R2SP1x64_23418, Win2008R2SP1x64, Win7SP1x64_24000, Win7SP1x64_23418
                     AS Layer1 : WindowsAMD64PagedMemory (Kernel AS)
                     AS Layer2 : WindowsCrashDumpSpace64 (Unnamed AS)
                     AS Layer3 : FileAddressSpace (/home/bb/Documents/RCE-InfoSec/Flare-on 2019/12 - help/help.dmp)
                      PAE type : No PAE
                           DTB : 0x187000L
                          KDBG : 0xf80002c390a0L
          Number of Processors : 1
     Image Type (Service Pack) : 1
                KPCR for CPU 0 : 0xfffff80002c3ad00L
             KUSER_SHARED_DATA : 0xfffff78000000000L
           Image date and time : 2019-08-02 14:38:33 UTC+0000
     Image local date and time : 2019-08-02 10:38:33 -0400

Analisis preliminar

Volatility admite una gran cantidad de diferentes comandos de análisis. Para abreviar, solo mencionaremos los comandos relevantes, pero se recomienda al lector que los pruebe todos. Para cada uno de los comandos necesitamos especificar el perfil usando el --profile=Win7SP1x64 bandera.

Comando de captura de pantalla

Este comando toma una captura de pantalla de cada escritorio del sistema. Ejecutar el comando genera varias capturas de pantalla, una de las cuales parece interesante.

Figura 1: Una captura de pantalla interesante
Figura 1: Una captura de pantalla interesante

Como se muestra en la Figura 1, Google Chrome y KeyPass se están ejecutando. Una base de datos denominada claves.kdb está abierto actualmente en la instancia de KeyPass.

Comando de módulos

Este comando muestra la lista de módulos del kernel que se cargaron en el sistema en el momento en que se capturó el volcado de memoria.

$ vol.py -f help.dmp --profile=Win7SP1x64 modules

Offset(V)          Name                 Base                             Size File
------------------ -------------------- ------------------ ------------------ ----
0xfffffa800183e890 ntoskrnl.exe         0xfffff80002a49000           0x5e7000 SystemRootsystem32ntoskrnl.exe
0xfffffa800183e7a0 hal.dll              0xfffff80002a00000            0x49000 SystemRootsystem32hal.dll

-- snip--

0xfffffa800428ff30 man.sys              0xfffff880033bc000             0xf000 ??C:UsersFLARE ON 2019Desktopman.sys

Hay un conductor llamado hombre.sys que fue cargado desde el camino C:UsersFLARE ON 2019Desktopman.sys. Dado que los controladores del sistema generalmente no residen en el escritorio, podemos estar bastante seguros de que esto está relacionado con el desafío.

Volcar man.sys

hombre.sys se carga en la dirección 0xfffff880033bc000. Podemos usar Volatility o WinDbg para volcar el módulo. Aquí he usado WinDbg porque descubrí que funciona mejor que Volatility en lo que respecta al volcado de memoria. Asegúrese de que la ruta al servidor de símbolos esté configurada correctamente en WinDbg.

Figura 2: encabezado MZ faltante
Figura 2: encabezado MZ faltante

Podemos usar el db Comando para volcar hexadecimal una región de memoria. Sin embargo, como se muestra en la Figura 2, falta el encabezado MZ en hombre.sys que indican que la página correspondiente debe haber sido paginada de memoria. De todos modos, todavía podemos usar el .writemem comando para volcar una parte considerable de la memoria (por ejemplo, 100 KB) y cargarla como un archivo binario en IDA.

Figura 3: Volcar man.sys
Figura 3: Volcar man.sys

El archivo volcado tiene un tamaño de 60 KB. Buscando cadenas localizamos la ruta al archivo PDB incrustado en el PE.

Figura 4: Ruta del archivo PDB integrado
Figura 4: Ruta del archivo PDB integrado

Volcar otros controladores y DLL

Al buscar las cadenas «flareon_2019» y «pdb», podemos encontrar otros archivos relevantes que están relacionados con el desafío, como se muestra en la Figura 5.

Figura 5: DLL y controladores relacionados con el desafío
Figura 5: DLL y controladores relacionados con el desafío

Lista de conductores

  • stmedit
  • shellcodedriver
  • hombre

Lista de DLL

  • cd
  • criptodll
  • archivado
  • keylogdll
  • reddll
  • captura de pantalla dll

Entre los drivers que ya tenemos hombre.sys. Los otros dos – shellcodedriver y stmedit puede ser localizado en la memoria usando el yarascan comando de volatilidad como se muestra en la Figura 6.

Figura 6: Uso de yarascan
Figura 6: Uso de yarascan

stmedit se encuentra dentro de la memoria del proceso svchost.exe con pid 876. Usando el !process extensión de viento, svchost.exe con pid 876 tiene su EPROCESS en fffffa80034a4b30

Figura 7: Encontrar EPROCESS de svchost
Figura 7: Encontrar EPROCESS de svchost

Saber la dirección de EPROCESS podemos establecer el contexto del proceso usando el .process dominio.

1683482421 75 Flare On 6 CTF WriteUp Parte 12
Figura 8: Configuración del contexto del proceso

Navegando a b260c9 podemos verificar que de hecho contiene la cadena stmedit como se encuentra en Volatility usando yarascan.

Figura 9: Comprobación cruzada de la salida de yarascan
Figura 9: Comprobación cruzada de la salida de yarascan

Desde aquí podemos buscar hacia atrás para ubicar el inicio del encabezado MZ como se muestra en la Figura 10.

Figura 10: Búsqueda hacia atrás del encabezado MZ
Figura 10: Búsqueda hacia atrás del encabezado MZ

A continuación, podemos utilizar el .writemem Comando para volcar stmedit.sys de la misma manera que lo hicimos anteriormente.

Figura 11: Volcado de stmedit
Figura 11: Volcado de stmedit

Aparte de shellcodedriver (falta el encabezado MZ), se puede reutilizar la misma técnica para volcar cada una de las 6 DLL. (El completo shellcodedriver El archivo se encontrará más tarde en el pcap).

Análisis de las DLL y los controladores

Antes de analizar las DLL por separado, existen varias técnicas que son comunes a todas las DLL y controladores.

Ninguna de las DLL importa funciones de WinAPI de forma estática mediante el IAT. En cambio, las funciones se resuelven dinámicamente usando LoadLibrary o analizando PEB_LDR_DATA. Para obtener las direcciones de la API, ya sea GetProcessAddress se utiliza o, en algunos casos, se analiza la tabla de exportación del módulo. Además de fortalecer el análisis, los nombres de las funciones se cifran y solo se descifran en tiempo de ejecución antes de que se llamen.

Por ejemplo, en la Figura 12, el rc4 la función descifra la cadenaCreateFileA. Vale la pena señalar que cada cadena de este tipo está cifrada con una clave diferente.

Figura 12: Descifrado de nombres de API en tiempo de ejecución
Figura 12: Descifrado de nombres de API en tiempo de ejecución

El rc4 función toma cuatro parámetros –

  • Un puntero a la clave
  • Tamaño de la clave en bytes
  • Un puntero al búfer cifrado. Después de que la función regrese, esto mantendrá el contenido descifrado.
  • Tamaño del búfer cifrado

En la Figura 12, la clave y el búfer encriptado son y respectivamente. Esto se puede descifrar usando la biblioteca Python PyCryptodome.

>>> from Crypto.Cipher import ARC4
>>> key = ''.join(map(chr, [0x91, 0xe8, 0xa5, 0x7d]))
>>> ct="".join(map(chr, [0xbd, 0x64, 0x20, 0x46, 0xad, 0xad, 0xe8, 0x7a, 0x39, 0x7c, 0x26]))
>>> 
>>> ARC4.new(key*2).decrypt(ct)
'CreateFileA'

Por último, otra técnica común en todas las DLL es el uso de un despachador de funciones para realizar la llamada a la función WinAPI. El despachador toma una cantidad variable de argumentos según la función de WinAPI a la que desea llamar. Veamos dos ejemplos para que quede claro.

Figura 13: Toma de llamada
Figura 13: Toma de llamada

En la Figura 13, el método call_function es la función dispatcher de la que estamos hablando. Aquí quiere llamar socket que se exporta de Ws2_32.dll. El primer parámetro es un identificador del módulo que contiene la función; el segundo parámetro es un puntero a un búfer que contiene el nombre de la función. El nombre de la función se descifra en tiempo de ejecución como vimos hace un momento. El tercer parámetro indica el número de argumentos que requiere la función que es 3 como socket toma tres argumentos. Después del tercero vienen los argumentos reales de la función.

Figura 14: Llamando a htons
Figura 14: Llamando a htons

Consideremos otro ejemplo como en la Figura 14. El htons función exportada desde Ws2_32.dll toma un solo parámetro. En consecuencia, el tercer parámetro pasado a call_function es 1. Después de eso, tenemos el argumento real para htons – el número de puerto vinculante.

cd.dll

  • Exporta una función llamada c
  • Configura un oyente en el puerto 4444 y genera un hilo para cada conexión entrante
  • Lee 4 bytes del socket. Esto indica el tamaño de la carga útil a punto de seguir
  • Los siguientes 4 bytes son algún tipo de código (Figura 15) basado en el cual envía un IOCTL a un controlador.
Figura 15: Varios códigos IOCTL
Figura 15: Varios códigos IOCTL
  • El controlador al que envía los IOCTL se llama FLID (Figura 16).
Figura 16: El controlador tiene el nombre FLID
Figura 16: El controlador tiene el nombre FLID

criptodll

  • Exporta una función llamada e
  • Esta función toma un solo parámetro: un puntero a una estructura de la siguiente forma
struct Buffer_info
  • La función comprime (LZNT1) y cifra (RC4) src_buffer a dst_buffer
  • Para la compresión utiliza la función NT RtlCompressBuffer
  • La clave utilizada para el cifrado es el nombre de usuario actual obtenido de GetUserNameA

archivado

  • Exporta una función llamada i
  • Contiene funcionalidad para crear, leer, escribir y buscar un archivo como se muestra en la Figura 17.
Figura 17: Funcionalidad en fielddll
Figura 17: Funcionalidad en fielddll

keylogdll

  • Exporta una función llamada l
  • Como sugiere su nombre, la dll implementa la funcionalidad de registro de claves

reddll

  • Exporta una función llamada s
  • Contiene funcionalidad para enviar datos al host 192.168.1.243 en un puerto configurable como se muestra en la Figura 18.
Figura 18: Networkdll puede enviar un dato a 192.168.1.243
Figura 18: Networkdll puede enviar un dato a 192.168.1.243

captura de pantalla dll

  • Exporta una función llamada t
  • Contiene funcionalidad para capturar una captura de pantalla de mapa de bits del escritorio

shellcodedriver

  • Este es un controlador de 32 bits cuyo único propósito es ejecutar una pieza de shellcode en el espacio del núcleo.

stmedit

Figura 19: Clave XOR codificada
Figura 19: Clave XOR codificada

hombre

  • Este controlador maneja los IOCTL de cd.dll como en la Figura 20.
  • La comprensión completa de este controlador no es necesaria para completar el desafío.
Figura 20: man.sys maneja los IOCTL de cd.dll
Figura 20: man.sys maneja los IOCTL de cd.dll

Descifrando el tráfico pcap

Figura 21: Minero de red
Figura 21: NetworkMiner

NetworkMiner es una gran herramienta para obtener un resumen rápido de una captura de paquetes. Usando la herramienta, podemos ver que hay demasiados hosts involucrados. Sin embargo, no todo el tráfico en el pcap es relevante para este desafío. Al analizar reddll ya sabemos que el tráfico al host 192.168.1.243 está relacionado con este desafío. De cd.dll también sabemos que cualquier tráfico al puerto 4444 también es relevante. En general, los siguientes flujos de TCP en el pcap son importantes.

  • Tráfico al host 192.168.1.243 en los puertos 6666, 7777, 8888
  • Tráfico para alojar 192.168.1.244 en el puerto 4444

Para extraer los flujos TCP del pcap, podemos usar tcpflow, que los agrupa automáticamente por host y puerto. Obtenemos 285 flujos en total, de los cuales solo necesitamos considerar el tráfico relevante como se acaba de discutir.

Descifrando el tráfico a 192.168.1.244:4444

Este tráfico es solo XOR encriptado con la clave de 8 bytes (5d f3 4a 48 48 48 dd 23) que encontramos anteriormente. Hay 20 flujos TCP de este tipo. Después de descifrar, destaca un flujo en virtud de su gran tamaño (4 KiB). Este flujo contiene el completo shellcodedriver en el desplazamiento 12 como se muestra en la Figura 22.

Figura 22: controlador de código shell
Figura 22: controlador de código shell

Descifrando el tráfico a 192.168.1.243:6666

Hay 2 flujos de este tipo de tamaños de 50 bytes y 4,53 KiB respectivamente. Primero necesitamos averiguar la clave XOR de 8 bytes. Echemos un vistazo a la secuencia de tamaño de 50 bytes como en la Figura 23.

Figura 23: Flujo de tamaño 50 bytes a 192.168.1.243:6666
Figura 23: Flujo de tamaño 50 bytes a 192.168.1.243:6666

Los primeros 4 bytes (encriptados) son cc 69 94 fa. Podemos suponer que estos bytes indican la longitud del flujo, es decir, los datos que siguen. Esto se debe a que, de lo contrario, el lado receptor no tendrá forma de saber cuántos bytes recv. El tamaño de la secuencia es 50 (0xcc), que ocupa 1 byte de espacio. Si el tamaño está indicado por 4 bytes, los otros tres bytes serán cero. Xoring con cero no tiene efecto, lo que indirectamente significa que los últimos tres bytes revelarán una parte de la clave.

Efectivamente, si hacemos un yarascan por los bytes 69 94 fa en el svchost.exe proceso, obtenemos exactamente 1 golpe que es la tecla XOR (d5 69 94 fa 25 ec df da) como se muestra en la Figura 24.

Figura 24: Clave XOR para el puerto 6666
Figura 24: Clave XOR para el puerto 6666

El tráfico aquí está doblemente encriptado. Después del descifrado XOR, necesitamos descifrar RC4 seguido de descompresión LZNT1. La clave para RC4 es el nombre de usuario obtenido de GetUserNameA. el nombre de usuario es FLARE ON 2019 que podemos obtener de hashdump. Necesitamos agregar un byte nulo al nombre de usuario según los documentos.

Figura 25: Obtención del nombre de usuario
Figura 25: Obtención del nombre de usuario

El siguiente script de Python RC4 se descifra con el nombre de usuario seguido de la descompresión LZNT1.

from Crypto.Cipher import ARC4
import ctypes
import sys

def decompress(data):
	ntdll = ctypes.windll.ntdll
	RtlDecompressBuffer = ntdll.RtlDecompressBuffer

	COMPRESSION_FORMAT_LZNT1 = 0x2
	COMPRESSION_ENGINE_MAXIMUM = 256
	STATUS_SUCCESS  = 0

	RtlDecompressBuffer.argtypes = [
	            ctypes.c_ushort, # USHORT CompressionFormat
	            ctypes.c_void_p, # PUCHAR UncompressedBuffer
	            ctypes.c_ulong,  # ULONG  UncompressedBufferSize
	            ctypes.c_void_p, # PUCHAR CompressedBuffer
	            ctypes.c_ulong,  # ULONG  CompressedBufferSize
	            ctypes.c_void_p, # PULONG FinalUncompressedSize
	]

	RtlDecompressBuffer.restype = ctypes.c_uint	
	finaluncompsize = ctypes.c_ulong(0)
	comp_buffer = ctypes.create_string_buffer(data)
	uncomp_buffer = ctypes.create_string_buffer(len(comp_buffer)*1)

	res = RtlDecompressBuffer(
		COMPRESSION_FORMAT_LZNT1, 
		ctypes.byref(uncomp_buffer), 
		ctypes.c_ulong(len(uncomp_buffer)), 
		ctypes.byref(comp_buffer), 
		ctypes.c_ulong(len(comp_buffer)), 
		ctypes.byref(finaluncompsize)
	)
	print res
	if res == 0:
		return uncomp_buffer[0:finaluncompsize.value]
	else:
		return None

def decrypt(ct, key):
	arc4 = ARC4.new(key)
	pt = arc4.decrypt(ct)
	return pt

def decrypt_and_decompress(data, key):
	decry = decrypt(data, key)
	return decompress(decry)

key = 'FLARE ON 2019' + 'x00'
ct = open(sys.argv[1], 'rb).read()
pt = decrypt_and_decompress(ct, key+'x00')
open('output.bin', 'wb').write(pt)

Después del doble descifrado, uno de los archivos contiene el texto «C:keypasskey.kdb»

Figura 26: Ruta a keys.kdb
Figura 26: Ruta a keys.kdb

Descifrando el tráfico a 192.168.1.243:7777

Hay 12 corrientes de este tipo. La clave XOR de 8 bytes se puede encontrar de manera similar a como lo hicimos para 6666. Es 4a 1f 4b 1c b0 d8 25 c7. También está doblemente encriptado. Después de descifrar, obtenemos archivos de mapa de bits, algunos de los cuales se muestran en la Figura 27 y la Figura 28.

Figura 27: Cuatro mapas de bits descifrados mostrados en mosaico
Figura 27: Cuatro mapas de bits descifrados mostrados en mosaico
Figura 28: Cuatro mapas de bits descifrados mostrados en mosaico
Figura 28: Cuatro mapas de bits descifrados mostrados en mosaico

Mirando las imágenes llegamos a conocer nuestro objetivo. Nuestra codiciada bandera está en una base de datos de KeePass llamada claves.kdb. La contraseña de la base de datos KeyPass tiene un tamaño de 18 caracteres.

Descifrando el tráfico a 192.168.1.243:8888

Hay 5 corrientes. La clave XOR es f7 8f 78 48 47 1a 44 9c. También está doblemente encriptado. El tráfico consiste en datos capturados por el keylogger como podemos ver en la Figura 29.

Figura 29: El keylogger capturó algunas pulsaciones incorrectas
Figura 29: El keylogger capturó algunas pulsaciones incorrectas

También tenga en cuenta que el keylogger capturó algunas pulsaciones de teclas incorrectas. Por ejemplo, en la Figura 26, el usuario escribió nslookup some_blog.com mientras que el keylogger capturó nslookup soeblogcom.

En otro archivo encontramos la cadena «th1sisth33nd111» que parece ser la contraseña para claves.kdb

Figura 30: Contraseña probable de la base de datos de claves
Figura 30: Contraseña probable de la base de datos de claves

Recuperando el KDB

Un archivo KDB 1.x comienza con los bytes 03 D9 A2 9A 65 FB 4B B5. Buscando estos bytes en el volcado podemos localizar el archivo kdb.

Figura 31: Localización del archivo KDB
Figura 31: Localización del archivo KDB

Desafortunadamente, el uso de la contraseña «th1sisth33nd111» no abre el archivo kdb. Esto es posible ya que el registrador de teclas tiene errores y no captura todas las pulsaciones de teclas correctamente. También de las capturas de pantalla sabemos que la longitud de la contraseña es 18, mientras que la contraseña registrada en el teclado tiene un tamaño de 15.

Averiguar la contraseña correcta

Buscando «th3» localizamos una cadena de 16 caracteres que parece ser parte de la contraseña correcta.

Figura 32: Parte de la contraseña correcta
Figura 32: Parte de la contraseña correcta

La contraseña correcta para la base de datos de contraseñas puede deducirse y es «Th!s_iS_th3_3Nd!!!». Abriendo el archivo kdb tenemos por fin la tan codiciada bandera.

Figura 33: La bandera
Figura 33: La bandera

Bandera: [email protected]

Ultimas palabras

Con esto llegamos al final de la serie de artículos de Flare-on 2019 CTF. Salvo los dos últimos, los desafíos de este año fueron un poco más fáciles que el año pasado. Espero que les hayan gustado los escritos. Si tiene alguna consulta o sugerencia, no dude en dejar un comentario a continuación.

Fuente del artículo

Deja un comentario