Salim Bitam

PHANTOMPULSE: anatomía de un RATA de C2 de la cadena de bloques secuestrable

Elastic Security Labs presenta un análisis detallado de ingeniería inversa de PHANTOMPULSE, el RAT de larga duración entregado a víctimas del sector cripto a través del conjunto de intrusiones REF6598.

La cobertura previa de Elastic Security Labs sobre REF6598 documentó un conjunto de intrusiones cuya cadena de herramientas de Windows aterrizó mediante abuso de plugins de Obsidian, escaló mediante un cargador PE en memoria (PHANTOMPULL) y terminó con un RAT (PHANTOMPULSE). Esa publicación se centraba en la entrega. Esta publicación analiza la etapa final: PHANTOMPULSE, un implante que incluye tres técnicas de inyección de procesos, resuelve su C2 mediante entradas de transacciones de Ethereum/Base/Optimism y evita el UAC mediante la técnica de schuac pública. El análisis pone a la luz un canal C2 de blockchain que puede ser sometido, una primitiva unificada de punto de interrupción de hardware que desactiva AMSI / WLDP / ETW y las huellas digitales generalizadas de desarrollo asistido por IA en las cadenas de depuración del implante.

Conclusiones clave

  • PHANTOMPULSE implementa tres técnicas de inyección adaptadas de recientes PoCs de seguridad ofensiva pública.
  • AMSI, WLDP y ETW se evitan mediante una única primitiva HWBP compartida
  • El resolver C2 de blockchain no tiene verificación del remitente, lo que permite a un defensor anular la URL C2 de cada implante publicando una sola transacción
  • Indicadores estables de desarrollo asistido por IA presentes en el binario

Una nota sobre el desarrollo asistido por IA

PHANTOMPULSE muestra fuertes huellas dactilares de la asistencia de codificación de IA, visibles a lo largo de las cadenas de depuración.

Las señales más claras:

  • Numeración estructurada de pasos en registros operativos: [STEP 1] Staged mode — payload downloaded from C2 at runtime, [STEP 1/3] Scheduled Task (DotNetSvcUpdateTask, logon + every 3 min), [STEP 2/3] Boot Task (DotNetSvcCoreTask, INTERACTIVE_TOKEN + BootTrigger), [UNINSTALL 4/6] Removing persist_loader DLL + registry PE data..., [REPAIR] Reinstalling boot task (INTERACTIVE_TOKEN)....
  • Trazado de funciones ENTER/DONE: "[HEIS] encrypt_text_only ENTER" / "[HEIS] encrypt_text_only DONE", "KeylogResolveAPIs: ENTER". El estilo de diagnóstico de los LLMs se emplea por defecto al generar nuevas funciones.
  • Diagnóstico verboso: "FindHostProcessEx: scan stats: total=%lu sessSkip=%lu openFail=%lu native=%lu wow64=%lu mapReject=%lu dbgReject=%lu sess=%lu", "ManualMap: thread hijacked and resumed — DLL injection via thread hijack complete". Salida autoexplicativa, inusualmente habladora para malware.
  • Guiones en Em en cuerdas de Do: "elevate: FAIL — no deployed DLL path", ">>> .elevate: NOT proxy — spawning trusted host to handle elevation".

Cadena de ejecución

MainEntryLogic es la función de orquestación que ejecuta la secuencia completa de inicialización antes de entrar en el bucle C2:

start
 └─ WinMain
     └─ MainEntryLogic
         1. DynInit                // Bootstrap API resolution
         2. ElevationStateCheck    // ".elevate" marker detection, routes by token elevation state
         3. SingleInstanceCheck    // XOR-decrypted mutex, exit if already running
         4. EvasionInit            // Direct syscalls + ETW HWBP
         5. SyscallResolverInit    // CPUID + hash-based kernel32 resolution
         6. SleepMaskInit          // Sleep obfuscation setup
         7. ComputeMachineID       // DJB2(module name) ^ volume serial
         8. IsRunningHollowed      // Process hollowing self-check
         9. CollectSysInfo         // CPU, GPU, RAM, OS, AV, apps
        10. FilelessPersist        // Drop stub DLL, registry artifact
        11. InstallPersistence     // Three scheduled tasks via COM ITaskService
        12. C2Loop_Init → C2Loop_Main

Al inicio, el implante DJB2 hace hash del nombre de usuario y de la computadora y busca cada uno en una tabla precomputada. Una coincidencia sale del proceso. Forzar la tabla contra listas públicas anti-sandbox recuperó 20 de las 61 entradas: WDAGUtilityAccount (Windows Defender Application Guard), varios nombres de VM por defecto DESKTOP-XXXXXXX y las personalidades de Joe Sandbox (abby, patex, george, john, lisa, frank, RDhJ0CNFevzX).

Evasión de la defensa

Llamadas directas a sistemas y envoltorios de API

PHANTOMPULSE resuelve funciones ntdll recorriendo PEB→Ldr con hashes DJB2, extrae Números de Servicio del Sistema (SSNs) del prólogo de cada función NT y crea stubs privados de llamadas de sistema. Estos muñones están envueltos en ayudantes de nivel superior que se emplean en el resto del implante:

  • NtCreateFile_Wrap
  • NtWriteFile_Wrap
  • NtClose_Wrap
  • NtCreateSection_Wrap
  • NtMapViewOfSection_Wrap
  • NtProtectVirtualMemory_Wrap
  • NtWriteVirtualMemory_Wrap

El resto del implante llama a estos envoltorios en lugar de exportaciones kernel32/ntdll , anulando los ganchos de ntdll en modo usuario (reemplazos de IAC, desvíos en línea o parches de trampolín) que los productos EDR inyectan en la superficie documentada de la API.

Una única función auxiliar enruta cada escritura de disco a través de NtCreateFile + NtWriteFile directamente, con errores de borrado y reintento al acceder a la escritura.

Ofuscación de cadenas y configuración

PHANTOMPULSE emplea cuatro capas XOR para diferentes artefactos:

¿QuéClaveDonde vive la llave
URL de reservación C2, mutex, nombres de archivo de ruta de caída16 bytes: F7 7C 8E 40 DF C1 7B E5 E7 4D 86 79 D5 B3 53 41Incrustado en .rdata
Nombres host de proveedores de blockchain (UTF-16 LE)8 bytes: 5A 3C 7E 1D 9F 2B 4E 8AIncrustado en .rdata
Nombre de elevación COM, carga útil de archivo de registro de teclas0xE95CA237, calculado en tiempo de ejecución para mantener la constante fuera de .rdataCalculado, no almacenado
URL C2 extraída de una transacción blockchain inputLa dirección del monedero resolver en sí mismaReutilizado desde la clave de búsqueda pública

AMSI, WLDP y ETW se desvían mediante puntos de interrupción por hardware

PHANTOMPULSE desactiva AMSI, la comprobación de code-trust de la Política de Bloqueo de Windows, y la telemetría ETW mediante una única primitiva compartida: un punto de interrupción de hardware plantado en cada entrada de API, interceptado por un manejador de excepciones vectorizado que falsifica el valor de retorno sin parche en línea.

SlotAPI de destinoRetorno suplantado (RAX)
DR0WldpQueryDynamicCodeTrust0 (S_OK)
DR1AmsiScanBuffer0x80070057 (E_INVALIDARG)
DR2EtwEventWrite0 (STATUS_SUCCESS)

El mecanismo, paso a paso:

  1. El implante resuelve la API objetivo. AMSI y WLDP pasan por LoadLibraryA + búsqueda de exportación basada en hash; ETW emplea un paseo PEB→Ldr ya que ntdll ya está cargado.
  2. El descriptor HWBP (dirección de la API objetivo, modo, valor de retorno suplantado) se escribe en una de las cuatro ranuras de 40 bytes en una tabla global de ranuras.
  3. Un hilo auxiliar suspende el hilo destino, llama a NtGetContextThread / NtSetContextThread para escribir DR0–DR3 + DR7 y luego continúa. (Si el manejador de excepciones vectorizado del implante ya está instalado, se eleva un STATUS_BREAKPOINT en proceso en su lugar, permitiendo que el VEH lea la tabla de ranuras y programe los DRs sin un hilo auxiliar.)
  4. Cuando se llama a la API protegida, la CPU eleva Debug Exception en la primera instrucción de la función.
  5. El manejador vectorizado de excepciones del implante intercepta el Debug Exception, recorre su tabla de 4 ranuras para encontrar la dirección de disparo y modifica el contexto del hilo: CONTEXT.Rax se establece al valor de retorno suplantado por ranura CONTEXT.Rip se redirige a un thunk "skip" prealmacenado que regresa al llamador.
  6. El manejador responde EXCEPTION_CONTINUE_EXECUTION. El llamante ve el RAX suplantado como si la API se ejecutó.

El despachador sirve dos rutas. Un manejador (VEH_Dispatcher) procesa tanto las llamadas de RaiseException(STATUS_BREAKPOINT) propias del implante (usadas para iniciar y reprogramar los registros de DR desde la tabla de ranuras) como las excepciones STATUS_SINGLE_STEP que se activan cuando se llama a una API protegida. El código de excepción controla la rama: STATUS_BREAKPOINT activa la programación de DR STATUS_SINGLE_STEP activa la suplantación.

El manejador tampoco está registrado directamente. AddVectoredExceptionHandler recibe un pequeño golpe JMP asignado en tiempo de ejecución en una página de MEM_PRIVATE nueva (VirtualAlloc + VirtualProtect a PAGE_EXECUTE_READ). El thunk es un salto indirecto JMP [RIP-relative] (código de operación de 6 bytes FF 25 00 00 00 00) seguido en línea por la dirección del despachador. Como nunca se escriben bytes en AmsiScanBuffer, WldpQueryDynamicCodeTrust EtwEventWrite, la detección basada en firma que escanea parches de prólogo pasa esto por alto por completo.

Variante de construcción: subsistemas activos y latentes

Existen varios subsistemas en el binario como código o cadenas, pero no están activos en esta versión. Esta es una versión simplificada de una base de código más grande.

  • Desconexión NTDLL: Las cadenas de depuración de un subsistema que se desengancha están en .rdata (UnhookNtdll: ntdll base = %p, applied %d relocation fixups to .text), pero nada las referencia. Muerto en esta variante.
  • Cargador de blob PE residente en el registro: las versiones anteriores almacenaban el PE de la siguiente etapa dentro del registro. Esta compilación no, pero la rutina de desinstalación sigue limpiando el blob del registro heredado.
  • Persistencia del secuestro COM: nunca se instaló con esta versión. La lógica de limpieza para ello permanece en la rutina de desinstalación.
  • Persistencia del monitor de impresión: mismo patrón que el secuestro COM; Ruta de instalación ausente, ruta de desinstalación conservada.

Los tres últimos (cargador de blob del registro, secuestro COM, monitor de impresión) muestran el patrón contrario: lógica de limpieza sin lógica de instalación, mantenida para compatibilidad con despliegues anteriores.

CaracterísticaConstrucción de cargas útilesEvidencia
Llamadas directas al sistema (extracción del SSN)ActivoExtracción del SSN + generación de stub confirmada
Bypass HWBP AMSI / WLDP / ETWActivoDR0/DR1/DR2 mediante primitiva de hilo auxiliar compartido
Inyección de procesos de tres víasActivoPhantomInject, DbgNexum, ManualMap son todas funcionales
Resolución C2 de la cadena de bloquesActivoSe consultan tres proveedores de Blockscout
Desenganche NTDLLCódigo muertoCadenas presentes, cero referencias de código
Cifrado HEISDeshabilitadoCifrado/descifrado de código stubbed
Registry-resident PE blob loaderSolo legadoSolo se limpia durante la desinstalación
Persistencia del secuestro de COMSolo legadoLimpiado durante la desinstalación, nunca instalado
Persistencia del monitor de impresiónSolo legadoLimpiado durante la desinstalación, nunca instalado
Cuerdas señueloActivo4 cadenas señuelo sin referencia en .rdata

Mando y control

Resolución C2 de la cadena de bloques

PHANTOMPULSE descentraliza la búsqueda de C2 a través de tres proveedores de Blockscout:

  • eth.blockscout[.]com (Ethereum L1)
  • base.blockscout[.]com (Base L2)
  • optimism.blockscout[.]com (Optimismo L2)

La dirección de la billetera 0xc117688c530b660e15085bF3A2B664117d8672aA está descifrada por XOR desde el almacenamiento con una clave de 16 bytes. Para cada proveedor, el implante emite un HTTPS GET (puerto 443, errores de certificado SSL ignorados), extrae el campo input de la última transacción, la decodifica en hexadecimal, descifra por XOR usando los bytes de dirección del monedero como clave y valida que el resultado comienza con http. En caso de fallo total, vuelve a la URL codificada https://panel.fefea22134[.]net.

El resolver no verifica al remitente de la transacción. Solo comprueba que el último input decodificado empiece con http. Cualquiera puede enviar una transacción a esa cartera con su propia URL codificada en XOR bajo los bytes de la cartera, y cada instancia PHANTOMPULSE de esa campaña que haga encuestas después se resuelve a esa URL. Para los defensores de red, esto es un sumidero viable a costa de una sola transacción.

Puntos finales y latido cardíaco

Cinco rutas API se construyen en tiempo de ejecución, cifradas de nuevo en memoria con una clave por sesión:

CaminoMétodoTipo de contenidoObjetivo
/v1/telemetry/reportPUBLICACIÓNapplication/jsonLatido cardíaco con telemetría del sistema completo
/v1/telemetry/tasks/<machine_id>GETObtención de comandos
/v1/telemetry/upload/PUBLICACIÓNimage/bmpCaptura de pantalla / subida de archivo
/v1/telemetry/resultPUBLICACIÓNapplication/jsonEntrega de resultados por mando
/v1/telemetry/keylog/PUBLICACIÓNtext/plainCarga de datos de keylog

El latido envía un perfil completo del sistema como JSON:

{
  "machine_id": "<uint32>",
  "status": "online",
  "cpu": "<model>",
  "gpu": "<description>",
  "ram_mb": "<uint32>",
  "os": "<version>",
  "username": "<user>",
  "computer_name": "<name>",
  "cores": "<uint32>",
  "screen_w": "<int>",
  "screen_h": "<int>",
  "privilege": "<user|admin|admin_nouac|system>",
  "build": "payloads",
  "public_ip": "<ip>",
  "av_list": ["<av1>", "<av2>"],
  "apps": ["<app1>", "<app2>"],
  "last_cmd": "<cmd>",
  "last_cmd_result": "<result>"
}

Se analizan dos campos de respuesta: "status":"deleted" activa la desinstalación completa; "ip":"<value>" llena la caché de IP pública, pero solo si el descubrimiento local (ipif[.]org / icanhazip[.]com/ checkip.amazonaws[.]com) Aún no lo llenó.

Cadencia y resiliencia de bucles

  • Sueño: aleatorio uniforme en [20, 40] segundos
  • Auto-curación: se ejecuta en la iteración 2, luego cada décima iteración
  • Tic de monitorización de salud: primera llamada después de que el implante se active, luego cada quinta iteración después. Llena una estructura local de información del sistema (CPU%, RAM, versión del SO, tiempo de funcionamiento, nombre de la computadora).
  • Umbral de fallo: 10 fallos consecutivos de latidos cardíacos desencadenan un resetear automático para recuperación SSL/TLS atascada
  • Re-resolución: en caso de fallo, se ejecuta la re-resolución de blockchain; si la URL resuelta cambia, el contador de fallos se resetear
  • IP pública: api4.ipify[.]orgipv4.icanhazip[.]comcheckip.amazonaws[.]com
  • Comprobación de conectividad: sondas microsoft[.]com, google[.]com, cloudflare[.]com github[.]com

Despacho de mando

El despachador de comandos enruta los comandos mediante hash DJB2. Ocho comandos en total:

HashMandarComportamiento
0x04CF1142injectInyecta shellcode/DLL/EXE. Rutas por tipo: código de shell→ PhantomInject; DLL → ManualMap; EXE → DbgNexum. Los bypass HWBP de AMSI y WLDP se instalan en la primera llamada de inject (el ETW HWBP ya está en funcionamiento desde EvasionInit).
0x7C95D91AdropSuelta el archivo en el disco y ejecuta. Soporta DLL, EXE, shellcode (inyección APC) y cargas útiles MSI.
0x9A37F083screenshotCaptura GDI, escalado a 960px de ancho, subido como BMP.
0x08DEDEF0keylogInicia o detiene el keylogger en línea.
0x4EE251FFuninstallLimpieza y terminación en 6 pasos.
0x65CCC50BelevateBypass UAC mediante la técnica schuac (IElevatedFactoryServer::ServerCreateElevatedObject(CLSID_TaskScheduler)); registra una tarea elevada transitoria que relanza el implante.
0xB3B5B880downgradeSYSTEM → transición administrativa elevada.
0x20CE3BC8(resetear automático)Auto-terminación en cascada: NtTerminateProcess(-1, 0) llamada syscall directa primero; si eso no se resuelve, vuelve a ExitProcess(0). La persistencia resetear el implante en el siguiente tick de tarea programada. Operativamente equivalente a un resetear suave.

El octavo manejador no tiene ningún registro de depuración que lo nombre. Se autotermina; La tarea programada reactiva el implante en el siguiente tick. La ausencia de andamiaje al estilo LLM (cadenas de depuración) hace que este sea uno de los pocos manejadores en el binario que parece ser agregado por el autor humano en lugar de generado por LLM.

Técnicas de inyección

PHANTOMPULSE incluye tres técnicas de inyección, una por tipo de carga útil. El comando inject C2 enruta shellcode a PhantomInject, DLLs a ManualMapy EXEs a DbgNexum.

Los bypass de punto de interrupción hardware AMSI/WLDP se instalan en la primera llamada inject , antes de que se ejecute cualquier inyector.

Tipo de cargaInyectorEstrategia
ShellcodePhantomInjectMódulos entrando dbghelp.dll a través de SEC_IMAGE
EXEDbgNexumDebug-API máquina de estados
DLLManualMapMapeo manual completo de PE

PhantomInject: módulo pisoteando dbghelp.dll

El pisoteamiento de módulos evita MEM_PRIVATE asignación mapeando una DLL legítima de Windows como SEC_IMAGE y sobrscribiendo .text:

  1. Adquiere SeDebugPrivilege (mediante OpenProcessToken / LookupPrivilegeValueW / AdjustTokenPrivileges), luego recorre la instantánea del proceso para uno de los siete candidatos a proceso host (coincidencia insensible a mayúsculas y minúsculas). Probado en orden de prioridad: sihost.exe, taskhostw.exe, backgroundTaskHost.exe, RuntimeBroker.exe, dllhost.exe, ctfmon.exe, explorer.exe.

  2. Abre dbghelp.dll vía NtOpenFile, crea SEC_IMAGE sección, y se mapea al objetivo mediante NtMapViewOfSection

  3. Analiza la copia local para .text RVA y tamaño, y luego la libera

  4. Selecciona y suspende un hilo, captura el contexto

  5. Construye un trampolín de 82 bytes de salvar, callar, restaurar

  6. Escribe shellcode + trampolín en .text de la DLL mapeada

  7. Invierte la protección a PAGE_EXECUTE_READ

  8. Reapunta RIP al trampolín y reanuda el hilo

Para un escáner de memoria, el resultado parece un hilo ejecutar dentro de dbghelp.dlllegítimo, una región de imagen respaldada por archivo con la ruta de archivo correcta, nombre de sección y hash de primera página.

DbgNexum: API de depuración como controlador de ejecución

DbgNexum gestiona las cargas útiles EXE. En lugar de escribir código ejecutable en el destino desde el principio, emplea la API de depuración de Windows para ejecutar una excepción a la vez: una cadena ROP cuyos gadgets son APIs completas de Windows en el destino.

La técnica no es original de PHANTOMPULSE. Es una versión literal de dis0rder0x00/DbgNexum, una prueba de concepto pública publicada en GitHub el 04-01-2026. El operador mantenía el nombre de la técnica publicado ("DbgNexum") en las cadenas de depuración del implante sin cambios, y la máquina de estados interna es una coincidencia 1:1: la misma API de cebo, nombre de sección, cadena de gadgets y constantes. PHANTOMPULSE envuelve el núcleo x64 elevado con andamiaje operativo que el PoC no tiene: selección de proceso host (FindHostProcessEx), un respaldo que genera un nuevo SysWOW64\cmd.exe / rundll32.exe / notepad.exe, un bootstrap personalizado para carga de PE (para que pueda transportar EXE completos en lugar de shellcode en bruto) y una variante separada de arquitectura cruzada WoW64.

Para cargas útiles nativas x64, el implante pre-prepara el PE, el stub de arranque y la configuración del trampolín dentro de una sección de mapeo de archivos nombrada. El nombre de la sección es la cadena literal de dos bytes "MZ", el implante se conecta con DebugActiveProcess y crea un hilo remoto en FileTimeToSystemTime con un punto de interrupción hardware en DR0.

Cuando el cebo alcanza el punto de interrupción, una máquina de estados impulsa el objetivo a través de esta cadena de API:

  1. Redirigir RIP a DbgBreakPoint+1 con la bandera de trampa activada; la excepción resultante de un solo paso conecta el resto de la cadena.
  2. LocalAlloc(LMEM_ZEROINIT, 3), asigna el búfer de nombre de 3 bytes.
  3. memcpy(buf, kernel32_base, 2), copia "MZ" desde el encabezado DOS de kernel32.dllal búfer.
  4. memset(stack+40, 0, 8): cero un slot de stack arg.
  5. OpenFileMappingA(0x1F, FALSE, "MZ"), abre la sección preparada con acceso completo al mapeo de secciones.
  6. MapViewOfFile(...), mapearla en el objetivo.
  7. Redirígete RIP a mapped_base + 0x400, el stub bootstrap. Esta es la única etapa registrada directamente: DbgNexumLoop64: stage 6 -> stub at %llx, base=%llx. (El PoC redirige a mapped_base + 0 para el shellcode en bruto; PHANTOMPULSE agrega el desplazamiento +0x400 para aterrizar en su cargador PE personalizado.)

Cada transición intercepta el siguiente evento de depuración, restaura RSP, elimina la bandera de trampa, modifica RIP y el argumento se registra (RCX, RDX, R8, R9) para la siguiente llamada y continúa con el depurador. DR0 se reutiliza como punto de interrupción de hardware para ejecutar en cada dirección de retorno almacenada, por lo que no se necesitan parches en línea ni WriteProcessMemory contra el objetivo. Desde la perspectiva del núcleo, lo único que ocurría era un hilo dentro de kernel32.dll llamando a LocalAlloc, OpenFileMappingAy MapViewOfFile.

El camino de cross-architecture (PE32 desde un implante de 64 bits) es una variante solo PHANTOMPULSE que el PoC público no tiene. Toma un atajo: el implante recorre la PEB.Ldr del objetivo mediante NtReadVirtualMemory (usando ProcessWow64Information para elegir el diseño PEB de 32 o 64 bits) para encontrar una DLL con un punto de entrada llamable, y luego pre-mapea una sección que contiene un cargador de 32 bits y un trampolín en ambos procesos mediante NtCreateSection + NtMapViewOfSection. La redirección consta de solo dos etapas: cebo en RtlExitUserThread, luego un DbgBreakPointsaltos de un solo paso mediado RIP al trampolín. No hay cadena de API en este camino porque la sección ya está mapeada en ambos lados, así que no se necesitan OpenFileMappingAMapViewOfFile .

La selección de hosts cross-arch pasa por FindHostProcessEx, que excluye procesos críticos del sistema (csrss.exe, lsass.exe, smss.exe, winlogon.exe, services.exe, wininit.exe, svchost.exe, MsMpEng.exe) y vuelve a generar un nuevo SysWOW64\cmd.exe / rundll32.exe / notepad.exe si no se encuentra ningún host utilizable de WoW64.

ManualMap: mapador PE completo

ManualMap gestiona cargas útiles DLL con una implementación completa de mapeo manual de PE:

  1. Valida la firma MZ/PE; rechaza PE32 en la ruta de host x64 (registro de depuración: "PE32 DLL in x64 host is impossible")

  2. Asigna SizeOfImage en el objetivo mediante NtAllocateVirtualMemory

  3. Copia cabeceras y secciones a un búfer de estacionamiento local

  4. Aplica reubicaciones de base (IMAGE_REL_BASED_DIR64, IMAGE_REL_BASED_HIGHLOW)

  5. Resuelve importaciones mediante LoadLibraryA + GetProcAddress

  6. Borra cabeceras PE (ceros SizeOfHeaders bytes)

  7. Escribe la imagen por etapas en la asignación remota

  8. Protección de memoria por sección de conjuntos

  9. Construye un trampolín de 137 bytes en una asignación remota separada de 0x2000 bytes (configurada a PAGE_EXECUTE_READ):

El resumen completo del shellcode del trampolín contiene los bytes completos.

10. Secuestra un hilo mediante suspend / get-context / set-context

Escalada de privilegios

El comando elevate es un bypass UAC mediante la técnicaschuac (IElevatedFactoryServer::ServerCreateElevatedObject(CLSID_TaskScheduler)), publicada como número UACME #129 por zcgonvh, actualmente bajo el ID 74.

Mecanismo

MaintenanceUI.dllEl CMaintenanceUIVirtualFactory de 's (CLSID {A6BFEA43-501F-456F-A845-983D3AD7B8F0}) está registrado con una clave de registro Elevation , por lo que el sistema operativo entrega a los llamantes no administradores una instancia elevada. Su interfaz IElevatedFactoryServer expone ServerCreateElevatedObject(rclsid, riid, ppv), que el servidor elevado emplea para instanciar cualquier otro CLSID bajo su contexto elevado. PHANTOMPULSE lo alimenta CLSID_TaskScheduler, recibe un ITaskServiceelevado , y emplea ese servicio para registrar una tarea HighestAvailable-RunLevel que resetear el implante.

El mando elevate C2

Dentro ProcessCommands, el manejador de elevación:

  1. Construye la acción de tarea. El comando está <system_dir>\rundll32.exe con argumentos \"<deployed_dll>\",DllRegisterServer. El identificador de usuario COMPUTERNAME\USERNAME de GetEnvironmentVariableW. ! [Llamada de comando de elevación][/assets/imágenes/blockchain-c2-phantompulse-rat-sinkhole/image17.png]
  2. Escribe el marcador de .elevate como un solo byte ("1", 0x31), no como parámetros codificados. La escritura pasa por NtCreateFile + NtWriteFile para saltar los ganchos en modo usuario. El marcador es solo una señal de presencia; Los parámetros de elevación viajan dentro de la definición de tarea que el implante está a punto de registrar.
  3. Descifra por XOR el Moniker de Elevación COM en tiempo de ejecución, 66 bytes de .rdata se xredan contra 0xE95CA237semilla , que decodifica a Elevation:Administrator!new:{A6BFEA43-501F-456F-A845-983D3AD7B8F0}.
  4. Llama CoGetObject(moniker, &BIND_OPTS3{dwClassContext=CLSCTX_LOCAL_SERVER}, IID_IElevatedFactoryServer, &factory) para obtener un IElevatedFactoryServer*elevado, luego factory->ServerCreateElevatedObject(CLSID_TaskScheduler, IID_ITaskService, &elevatedTaskService) para obtener un ITaskService* elevado que herede esa elevación.
  5. Registra una tarea transitoria DotNetSvcElevateTask en HighestAvailable RunLevel con la acción rundll32 arriba.
  6. Elimina cualquier tarea persistente no elevada preexistente para que la persistencia antigua de baja IL no pueda acelerar el resetear elevado.
  7. Llama ITaskService::Run a la tarea transitoria y la elimina inmediatamente después. Libera el mutex de instancia única y sale del implante de IL medio.

Reintento de respaldo mediante proxy rundll32

Si falla la cadena CoGetObject / RegisterTask , se activa un camino de respaldo. Un camino de inicio separado genera un rundll32.exe "<deployed_dll>",DllRegisterServer nuevo directamente a través de CreateProcessW, con reintentos (">>> .elevate redirect attempt %d"). Dentro de ese rundll32, el implante detecta isProxy=1, elevated=0 y vuelve a intentar la secuencia de schuac con tres variantes de registro (ELEVATED+INTERACTIVE+user, ELEVATED+INTERACTIVE, INTERACTIVE). En caso de éxito, la tarea elevada se activa y el relanzamiento elevado toman el control. Al agotar, el proxy registra ">>> Phase 1: all registration methods failed, cleaning marker" y sale.

Relanzamiento elevado de rundll32

Cuando la tarea transitoria se activa, svchost.exe (Schedule) se lanza rundll32.exe "<deployed_dll>",DllRegisterServer bajo un token de alta IL nuevo. La exportación DllRegisterServer del implante se realiza como entrada; al arrancar ve el marcador de .elevate y un token de alta inversión y enruta hacia la ruta elevada:

  • Lee y elimina el marcador de .elevate .
  • Reinstala la persistencia bajo contexto elevado, incluyendo la tarea de arranque DotNetSvcCoreTask bajo \Microsoft\Windows\NetFramework\, registrada con INTERACTIVE_TOKEN + BootTrigger, lo que requiere que el administrador se registre.
  • Continúa con la operación normal del implante como un servicio de alta IL.

Enrutamiento por estado de marcador

En cada startup, MainEntryLogic llama a GetFileAttributesW en el camino .elevate . Si vuelve INVALID_FILE_ATTRIBUTES, el implante se salta toda la lógica de elevación y arranca normalmente. Si el marcador existe, el implante recopila dos hechos más: si el proceso actual es un proxy de rundll32.exe/regsvr32.exe , y si el token está elevado, entonces rutas sobre la combinación:

MarcadorProxyElevadoQué sucede
ausenteN/AN/AArranque normal, sin enrutamiento especial
PresenteNoNoInvoca proxy rundll32 para volver a intentar la cadena de Schuac, y luego sal
PresenteNoreintentar a Schuac dentro del rundll32; Al agotar, eliminar marcador y salida
Presente*marcador de eliminación, reinstala la persistencia con la tarea de arranque, continúa con alta IL

El contenido del archivo de marcadores nunca se lee, su sola presencia impulsa el enrutamiento.

Exploring Windows UAC Bypasses de Elastic Security Labs cubre directamente los patrones de detección para la clase IElevatedFactoryServer de bypass.

Persistencia

Tres tareas programadas

PHANTOMPULSE instala tres tareas programadas a través de la interfaz COM ITaskService , cada una ejecutando rundll32.exe "<stub_dll>",DllRegisterServer:

TareaDisparadorIntervaloRunLevel
DotNetSvcUpdateTaskInicio de sesión del usuario + Tiempo3 minEstándar
DotNetSvcCoreTaskArranque + Tiempo15 minMás AltoDisponible + Oculto
DotNetSvcUserTaskInicio de sesión de usuarioEn el inicio de sesiónEstándar

La tarea de arranque está bajo \Microsoft\Windows\NetFramework\ para integrar con tareas legítimas de mantenimiento de .NET. Su cuerpo completo en XML está incrustado en .rdata, no se construye en tiempo de ejecución. Las firmas en este blob literal son duraderas en todas las builds.

La instalación prueba múltiples variantes de RegisterTask de respaldo por tarea, combinando tipos de inicio de sesión INTERACTIVE_TOKEN y S4U con diferentes formatos de identificadores de usuario (COMPUTER\User, SID, nombre de usuario corto). El orden exacto de los intentos depende del contexto de privilegio del implante (SISTEMA, administrador o usuario estándar); los fallos pasan a la siguiente variante hasta que uno tiene éxito.

Caída de DLL de esbozo

El agente desplegado vive cifrado y comprimido dentro de la propia .rdatadel EXE. FilelessPersist lo descifra bajo demanda y escribe el resultado en disco mediante (NtCreateFile + NtWriteFile).

El descifrado consta de dos etapas:

  1. Decodifica XOR un blob contra una clave giratoria de 16 bytes en (decoded[i] = blob[i] ^ key[i & 0xF], con decoded[0] = 0x1E como primer byte codificado en fija que activa el encabezado de bloque LZNT1).
  2. RtlDecompressBuffer(COMPRESSION_FORMAT_LZNT1, ...) en el búfer decodificado, que es una DLL (svcagent.dll).

Rutas de caída en orden de prioridad, fallando por errores de acceso:

  1. %ProgramData%\AssetMon\svcagent.dll (primaria)
  2. %APPDATA%\AssetMon\svcagent.dll
  3. %TEMP%\svcagent.dll
  4. Una copia redundante %ProgramData% "sleeper" en un camino separado

Los analistas pueden reproducir la DLL desplegada sin conexión leyendo las dos regiones anteriores del EXE, aplicando el bucle XOR y enviando el resultado a RtlDecompressBuffer (o a cualquier implementación de LZNT1), como se ve en la captura de pantalla de CyberChef a continuación.

Migración de carga lateral DLL

Un bloque dentro de SetupRegistryPE (registrado con los prefijos de cadena de depuración MigrateSideload / MigrateLegacySideloads ) enumera los procesos en ejecución y sus directorios ejecutables, buscando diagcore.dll. Cuando se encuentra, sobreescribir el archivo con el stub actual a través de CopyFileW.

Autorreparación

La auto-reparación se ejecuta en iteraciones 2 del bucle C2 y cada décima iteración posterior, bloqueada con una bandera de diferido-persistencia que queda limpia. El orden de comprobación:

  1. Primero comprobación de persistencia del registro. CheckRegistryPersistence Corre en la parte superior de la manzana. Si informa de que está mal, el implante vuelve a ejecutar inmediatamente FilelessPersist (vuelve a descifrar y volver a colocar el DLL del stub) y InstallPersistence (vuelve a registrar los disparos de tarea).
  2. Verificación de tareas. SelfHealCheckTasks luego verifica las tres tareas de persistencia (DotNetSvcUpdateTask, DotNetSvcCoreTask, DotNetSvcUserTask) y reinstala las que falten. La comprobación de tareas de arranque está bloqueada en contexto SYSTEM o admin; Los que no tienen privilegios se lo saltan.
  3. Actualización del inventario AV. DetectInstalledAV se ejecuta al final para actualizar la lista de productos antivirus visible por el operador.

La barrera de privilegios es importante para el desahucio. Desde un contexto no elevado, la comprobación de la tarea de arranque se oye, por lo que la tarea de arranque no se inspecciona durante la limpieza. La expulsión total requiere eliminar las tres tareas más el artefacto del registro en una sola ventana desde un contexto elevado.

Más allá de la auto-curación basada en iteraciones, el implante lleva un mecanismo de persistencia diferida activado por una sola bandera. En las trayectorias de éxito de latido en C2Loop_Main, una vez que el contador de éxito de latidos supera a uno con la bandera colocada, el implante se repite FilelessPersist + InstallPersistence y elimina la bandera. Esto le da a PHANTOMPULSE un segundo camino de persistencia-reparación que se activa en un disparador diferente al de la auto-curación basada en iteraciones.

Colección

Keylogger en línea con monitorización en portapapeles

El keylogger funciona en línea en el bucle C2 sin hilo dedicado. Resuelve APIs de user32.dll en tiempo de ejecución:

APIObjetivo
GetAsyncKeyStateEstado clave de las encuestas
GetForegroundWindowDetección activa de ventanas
GetWindowTextACaptura de título de ventana
MapVirtualKeyA / ToUnicodeTraducción de claves
GetClipboardSequenceNumberDetección de cambios en la tabla
OpenClipboard / GetClipboardDataLectura en portapapeles (CF_UNICODETEXT)

El archivo de registro está cifrado por XOR con la semilla 0xE95CA237 . Las subidas solo envían el delta para evitar la retransmisión.

Captura de pantalla

Las capturas de pantalla emplean APIs GDI resueltas por hash. Si el ancho del escritorio supera 960 px, la imagen se reduce antes de subirla. El BMP en bruto está integrado en memoria y se sube con Content-Type: image/bmp. Activado bajo demanda por el comando screenshot C2 (hash 0x9A37F083).

Reconocimiento de sistemas

Datos de reconocimiento que recopila el implante:

DatosFuente
CPURegistro: ProcessorNameString
GPUAdaptador de pantalla del registro DriverDesc (filtra "Microsoft Basic")
RAMGlobalMemoryStatusEx
OSRtlGetVersion con mapeo de compilación a versión (Win7 a Win11, Server 2008 a Server 2025)
Nombre de usuarioGetUserNameW con respaldo a LookupAccountSidW desde explorer.exe token
PrivilegioTipo de elevación de la ficha: user, admin, admin_nouac, system
AVDetectInstalledAV compara procesos en ejecución con una lista fija de ~25–30 nombres de proveedores de antivirus
AplicacionesDetectInstalledApps Consulta una lista seleccionada de 19 nombres de aplicaciones dirigidas
Estado del firewallLas lecturas SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy\{Domain,Standard,Public}Profile para registrar el estado habilitado por perfil
ServiciosRecuento de servicios en funcionamiento mediante enumeración de servicios
ID de máquinaDJB2(nombre del módulo) ^ volumen serial
IP públicaCadena HTTPS multi-API

La lista de detección AV es inusualmente amplia, abarcando productos estándar occidentales para consumidores como Defender, Norton, McAfee, Avast, AVG, Avira, Bitdefender, ESET, F-Secure, G Data, Kaspersky, Panda, Sophos, Trend Micro, VIPRE, Webroot, ZoneAlarm, Comodo, junto con proveedores EDR (CrowdStrike, SentinelOne, Cylance, Malwarebytes, HitmanPro). El implante también realiza sondeos para AhnLab V3 (surcoreano), Qihoo 360 / 360 Total Security y Tencent QQPC (chino), y K7 Computing (indio). La inclusión de la AV asiática es poco común entre los ladrones de mercancías dirigidos a Occidente y es coherente con un implante diseñado para víctimas en múltiples mercados regionales.

El implante también busca una lista seleccionada de 19 aplicaciones de alto valor por nombre y señala coincidencias en el latido (App detection: found %d apps):

CategoríaObjetivos
Billeteras de criptomonedasledger, trezor, bitcoin-core, electrum, exodus, atomic, guarda
Mensajerostelegram, discord, signal, viber, slack, whatsapp
Clientes de correothunderbird, outlook
Aplicación 2FAauthy
Transferencia de archivos / SSHfilezilla, winscp
Juegossteam

La función de detección (DetectInstalledApps) no escanea el registro ni enumera procesos. Expande tres raíces de variables de entorno (%LOCALAPPDATA%, %APPDATA%, %ProgramFiles(x86)%), concatena un sufijo de ruta relativa UTF-16 codificado de forma fija por aplicación (por ejemplo, \Telegram Desktop\, \Authy Desktop\, \Ledger Live\, \@trezor\trezor-suite\, \Steam\steam.exe), y llama a GetFileAttributesW en cada camino. Un retorno sin error significa que la app está instalada y el nombre se registra en el buffer de resultados de latido.

PHANTOMPULSE en sí no extrae datos de ninguno de estos. La lista es reconocimiento de objetivos para tareas posteriores. El operador ve en el instante qué aplicaciones de alto valor tiene una víctima determinada y decide qué carga útil especializada enviar a continuación mediante inject o drop.

No se identificó ninguna funcionalidad de cartera, navegador, mensajería o robo de credenciales en la muestra analizada; La lista de puntería es puramente una comprobación de presencia que alimenta el árbol de decisiones del operador.

Desinstalar

Una limpieza en 6 pasos, activada por el comando uninstall , por "status":"deleted" en una respuesta de latido o por una bandera de apagada en el registro:

PasoAcción
1/6Escribe la bandera de apagada en HKCU + HKLM, procesa el anfitrión de apagar
2/6Elimina todas las tareas 3 programadas mediante COM + CreateProcessW de respaldo
3/6Eliminar registro heredado: valor NTLoad, COM secuestra claves, imprimir teclas del monitor
4/6Eliminar DLLs de stub, registros de durmiente, blob de PE de registro, directorios de ProgramData
5/6Eliminar la ruta de instalación y la ruta propia desde el disco
6/6Terminar healthmon.exe residual y cualquier instancia rundll32.exe que aloje svcagent.dll

El paso 3 revela las técnicas heredadas de persistencia: lógica de limpieza para secuestros de COM y teclas de monitor de impresión que esta compilación nunca instala.

Atribución

Las elecciones de técnica, segmentación e infraestructura de PHANTOMPULSE se alinean con los clústeres de intrusiones cripto-targeting alineados con la RPDC que incluyen Lazarus, BlueNoroff, UNC5342 (Contagious Interview) y APT38. Múltiples dimensiones independientes coinciden con los reportes recientes sobre esos grupos.

Señales alineadas con la información de la RPDC:

  • El C2 resuelto por blockchain mediante campos de input de transacción coincide con el patrón de dead-drop-resolver que Mandiant atribui a UNC5342 (Entrevista Contagiosa) en DPRK adopta EtherHiding. Las especificaciones de PHANTOMPULSE (XOR por byte de cartera, Blockscout multi-cadena) no son una huella 1:1, pero la clase de técnica ahora está etiquetada con DPRK.
  • El conjunto de enumeración de carteras criptográficas de escritorio (ledger, trezor, bitcoin-core, electrum, exodus, atomic, guarda) se ajusta estrechamente a la lista de segmentación RustDoor / Koi Stealer para macOS de la Unidad 42, que está atribuida a la RPDC.
  • Los implantes multiplataforma de Windows + macOS para el mismo perfil de víctima (la publicación anterior REF6598 documentó un hermano macOS con C2 a 0x666[.]info y un respaldo de Telegram a t[.]me/ax03bot) es una firma BlueNoroff.
  • La segmentación por Telegram y mensajería es específicamente una especialidad de BlueNoroff según la cobertura de Arctic Wolf BlueNoroff.

Búsqueda de nuevos dominios C2 a través de la firma de texto plano conocido de la cartera resolver

El esquema XOR empleado por el resolver blockchain filtra una firma estable de 2 bytes que los defensores pueden buscar contra toda la cadena, no solo contra una cartera.

Se combinan dos hechos: cada URL C2 comienza con ht (de http:// o https://), y la clave XOR es la dirección ASCII de la cartera textualmente, por lo que sus dos primeros bytes clave son siempre los caracteres literales 0 y x. Hacer XOR ht contra 0x produce \x58 \x0c. Cada campo de input cifrado producido por un resolvedor tipo PHANTOMPULSE, en cualquier cadena firmado por cualquier monedero relacionado, comienza con los cuatro caracteres hexadecimales 580c.

Esto convierte la búsqueda de monitorizar una cartera en barrier la cadena en busca de la firma. Los datos públicos de transacciones de Ethereum, Base y Optimism pueden consultar a través de BigQuery, Dune o nodos de archivo completos. Una consulta contra el conjunto público de datos de transacciones de Ethereum para valores de input que comienzan por 0x580c, con alcance a una ventana reciente de marca de tiempo de bloque, pone a la luz monederos resolvedores previamente desconocidos usados por la misma base de código. Cada coincidencia se valida decodificando con la dirección ASCII de la cartera del emisor como clave: una URL C2 real comienza con http tras la decodificación. La siguiente receta de CyberChef puede usar para descifrar la URL de C2.

SELECT 
    block_timestamp AS block_time, 
    from_address AS `from`, 
    to_address AS `to`, 
    input AS data
FROM `bigquery-public-data.crypto_ethereum.transactions`
WHERE block_timestamp >= '2026-04-01 00:00:00'
  AND input LIKE '0x580c%'
ORDER BY block_timestamp DESC
LIMIT 10000;

CyberChef puede descifrar los datos de entrada para revelar el dominio, como se muestra en la captura de pantalla a continuación.

Conclusión

PHANTOMPULSE está diseñado a partir de componentes publicados: pisoteamiento de módulos, máquinas de estados de depuración API, mapeo manual, bypass AMSI/WLDP/ETW con punto de interrupción por hardware, persistencia de tareas programadas y C2 de blockchain. La combinación y el endurecimiento que lo une apuntan a una base de código madura en desarrollo activo. Las señales duraderas son de carácter conductual y están cubiertas por las protecciones conductuales de Elastic para REF6598.

PHANTOMPULSE y MITRE ATT&CK

Elastic usa el framework MITRE ATT&CK para documentar tácticas, técnicas y procedimientos comunes que las amenazas persistentes avanzadas emplean contra las redes empresariales.

Táctica

Las tácticas representan el porqué de una técnica o subtécnica. Es el objetivo táctico del adversario: la razón para realizar una acción.

Técnicas

Las técnicas representan cómo un adversario logra un objetivo táctico mediante la realización de una acción.

Remediación

YARA

Elastic Security creó reglas YARA para identificar esta actividad.

Observaciones

ObservableTipoNombreReferencia
33dacf9f854f636216e5062ca252df8e5bed652efd78b86512f5b868b11ee70fSHA-256RATONA PULSO FANTASMAFinal payload
70bbb38b70fd836d66e8166ec27be9aa8535b3876596fc80c45e3de4ce327980SHA-256syncobs.exeCargador PHANTOMPULL
def66275fa3baffb16e6e4ae0297861d9790ae7161fbc271a2ba05d121f13c70SHA-256Haz la balizaGTESTIC_WIN registro
panel.fefea22134[.]netdominioPanel C2PHANTOMPULSE con respaldo codificado directamente
fea22134[.]netdominioC2 domainCifrado en binario
195.3.222[.]251IPv4-ADDRServidor de etapasEntrega PowerShell/cargador
0xc117688c530b660e15085bF3A2B664117d8672aACartera de criptomonedasCartera Blockchain C2ETH/Base/Optimismo
0x38796B8479fDAE0A72e5E7e326c87a637D0Cbc0ECartera de criptomonedasCartera de financiaciónFinanciación de la resolución C2
eth.blockscout[.]comdominioProveedor de blockchainResolución de URL C2
base.blockscout[.]comdominioProveedor de blockchainResolución de URL C2
optimism.blockscout[.]comdominioProveedor de blockchainResolución de URL C2
hVNBUORXNiFLhYYhmutexInstancia únicaDescifrado por XOR
svcagent.dllNombre del archivoDLL de esbozoCarga útil de persistencia
AssetMonDirectorioDirectorio de DLL de incompleto%ProgramData% o%APPDATA%
healthmon.exeNombre del archivoDropperNombre original del ejecutable
diagcore.dllNombre del archivoDLL de carga lateral heredadaMigrado por MigrateSideload
.elevateNombre del archivoMarcador de elevaciónRutas para el relanzamiento elevado
DotNetSvcUpdateTaskTarea programadaPersistencia primariaIntervalo de 3 minutos
DotNetSvcCoreTaskTarea programadaPersistencia del SISTEMA15 minutos, oculto
DotNetSvcUserTaskTarea programadaPersistencia del usuarioDisparador de inicio de sesión
EdgeWebViewUpdateTaskTarea programadaTarea de legadoLimpiado durante la desinstalación
\Microsoft\Windows\NetFramework\DotNetSvcCoreTaskTask-uriRuta de arranque de tareaTarea programada oculta
Elevation:Administrator!new:{A6BFEA43-501F-456F-A845-983D3AD7B8F0}com-monikerCircunvalación UACServicio ITaskElevado
0x666[.]infodominiomacOS C2Dropper de macOS
t[.]me/ax03botURLRespaldo de TelegramDead-drop en macOS C2
thoroughly-publisher-troy-clara[.]trycloudflare[.]comdominioPrior C2Túnel Cloudflare

Referencias

Reportes y kits de herramientas previos referenciados en este análisis: