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.

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.

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.

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

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.

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.

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

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

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

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

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

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.

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.

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.

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.

- El controlador al que envía los IOCTL se llama FLID (Figura 16).

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
adst_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.

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.

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

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.

Descifrando el tráfico pcap

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.

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.

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.

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.

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»

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.


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.

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

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.

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.

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.

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.