Salim Bitam

PHANTOMPULSE: anatomia de um blockchain C2 RAT vulnerável a sequestro

A Elastic Security Labs apresenta uma análise detalhada de engenharia reversa do PHANTOMPULSE, o RAT de longa duração distribuído às vítimas do setor de criptomoedas por meio do conjunto de intrusão REF6598.

A cobertura anterior do Elastic Security Labs sobre o REF6598 documentou um conjunto de intrusões cuja cadeia de ferramentas do Windows foi obtida por meio de abuso de plugin do Obsidian, escalada por meio de um carregador PE em memória (PHANTOMPULL) e finalizada com um RAT (PHANTOMPULSE). Essa postagem focava na entrega. Esta postagem analisa o estágio final: PHANTOMPULSE, um implante que envia três técnicas de injeção de processo, resolve seu C2 por meio de entradas de transação Ethereum/Base/Optimism e contorna o UAC por meio da técnica pública schuac . A análise revela um canal C2 de blockchain vulnerável a ataques de sumidouro, uma primitiva unificada de ponto de interrupção de hardware que desativa AMSI / WLDP / ETW e impressões digitais generalizadas de desenvolvimento assistido por IA nas strings de depuração do implante.

Principais conclusões

  • O PHANTOMPULSE implementa três técnicas de injeção adaptadas de provas de conceito (PoCs) de segurança ofensiva recentes e públicas.
  • AMSI, WLDP e ETW são ignorados por meio de um único primitivo HWBP compartilhado.
  • O resolvedor C2 da blockchain não possui verificação de remetente, permitindo que um defensor substitua a URL C2 de cada implante enviando uma única transação.
  • Fortes indicadores de desenvolvimento assistido por IA presentes no cenário binário

Uma nota sobre desenvolvimento assistido por IA

PHANTOMPULSE apresenta fortes indícios de assistência de codificação por IA, visíveis em todas as linhas de depuração.

Os sinais mais claros:

  • Numeração estruturada de etapas em registros operacionais: [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)....
  • Rastreamento da função ENTER/DONE: "[HEIS] encrypt_text_only ENTER" / "[HEIS] encrypt_text_only DONE", "KeylogResolveAPIs: ENTER". O estilo de diagnóstico que os LLMs utilizam por padrão ao gerar novas funções.
  • Diagnóstico detalhado: "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". Saída autoexplicativa, incomumente verbosa para um malware.
  • Travessões em strings C: "elevate: FAIL — no deployed DLL path", ">>> .elevate: NOT proxy — spawning trusted host to handle elevation".

Cadeia de execução

MainEntryLogic é a função de orquestração que executa a sequência completa de inicialização antes de entrar no loop 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

Ao iniciar, o implante DJB2 utiliza o hash do nome de usuário e do nome do computador, consultando-os em uma tabela pré-computada. Uma correspondência encerra o processo. A força bruta da tabela contra listas de palavras anti-sandbox públicas recuperou 20 das 61 entradas: WDAGUtilityAccount (Windows Defender Application Guard), vários DESKTOP-XXXXXXX nomes de VM padrão e as personas Joe Sandbox (abby, patex, george, john, lisa, frank, RDhJ0CNFevzX).

Defense Evasion

Chamadas de sistema diretas e wrappers de API

PHANTOMPULSE resolve funções ntdll percorrendo PEB→Ldr com hashes DJB2, extrai os Números de Serviço do Sistema (SSNs) do prólogo de cada função NT e constrói stubs de chamadas de sistema privadas. Esses fragmentos são envolvidos por elementos auxiliares de nível superior, utilizados em todo o restante do implante:

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

O restante do implante chama esses wrappers em vez de exportações kernel32/ntdll , frustrando os hooks ntdll do modo de usuário (substituições IAT, desvios embutidos ou patches trampoline) que os produtos EDR injetam na superfície da API documentada.

Uma única função auxiliar direciona cada gravação em disco através de NtCreateFile + NtWriteFile diretamente, com exclusão e nova tentativa em erros de acesso.

Ofuscação de strings e configurações

O PHANTOMPULSE utiliza quatro camadas XOR para diferentes artefatos:

o queChaveOnde a chave mora
URL de fallback do C2, mutex, nomes de arquivos drop-path16 bytes: F7 7C 8E 40 DF C1 7B E5 E7 4D 86 79 D5 B3 53 41Incorporado em .rdata
Nomes de host de provedores de blockchain (UTF-16 LE)8 bytes: 5A 3C 7E 1D 9F 2B 4E 8AIncorporado em .rdata
MONITOR DE ELEVAÇÃO COM, carga útil do arquivo keylog0xE95CA237, calculado em tempo de execução para manter a constante fora de .rdataCalculado, não armazenado
URL C2 extraída da transação blockchain inputO próprio endereço da carteira do resolvedorReutilizado da chave de pesquisa pública

Bypass AMSI, WLDP e ETW através de pontos de interrupção de hardware

O PHANTOMPULSE desativa o AMSI, a verificação de confiança de código da Política de Bloqueio do Windows e a telemetria ETW por meio de uma única primitiva compartilhada: um ponto de interrupção de hardware inserido em cada entrada da API, interceptado por um manipulador de exceção vetorizado que simula o valor de retorno sem a necessidade de aplicação de patches em linha.

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

O mecanismo, passo a passo:

  1. O implante resolve o problema da API alvo. AMSI e WLDP passam por pesquisa de exportação baseada em LoadLibraryA + hash; ETW usa uma caminhada PEB→Ldr, já que ntdll já está carregado.
  2. O descritor HWBP (endereço da API de destino, modo, valor de retorno falsificado) é gravado em um dos quatro slots de 40 bytes em uma tabela de slots global.
  3. Uma thread auxiliar suspende a thread alvo, chama NtGetContextThread / NtSetContextThread para escrever DR0–DR3 + DR7 e então retoma. (Se o manipulador de exceções vetorizado do implante já estiver instalado, um STATUS_BREAKPOINT em processo é gerado em vez disso, permitindo que o VEH leia a tabela de slots e programe os DRs sem uma thread auxiliar.)
  4. Quando a API protegida é chamada, a CPU gera Debug Exception na primeira instrução da função.
  5. O manipulador de exceção vetorial do implante intercepta o Debug Exception, percorre sua tabela de 4 slots para encontrar o endereço de disparo e modifica o contexto da thread: CONTEXT.Rax é definido para o valor de retorno falsificado por slot, CONTEXT.Rip é redirecionado para um thunk "skip" pré-armazenado que retorna ao chamador.
  6. O manipulador retorna EXCEPTION_CONTINUE_EXECUTION. O chamador vê o RAX falsificado como se a API tivesse sido executada.

O despachante atende a dois caminhos. Um manipulador (VEH_Dispatcher) processa tanto as chamadas RaiseException(STATUS_BREAKPOINT) do próprio implante (usadas para inicializar e reprogramar os registros DR da tabela de slots) quanto as exceções STATUS_SINGLE_STEP que são disparadas quando uma API protegida é chamada. O código de exceção controla o desvio: STATUS_BREAKPOINT aciona a programação DR, STATUS_SINGLE_STEP aciona a falsificação.

O manipulador também não está registrado diretamente. AddVectoredExceptionHandler recebe um pequeno thunk JMP alocado em tempo de execução em uma nova página MEM_PRIVATE (VirtualAlloc + VirtualProtect para PAGE_EXECUTE_READ). O thunk é um salto indireto JMP [RIP-relative] (opcode de 6 bytes FF 25 00 00 00 00) seguido em linha pelo endereço do dispatcher. Como nenhum byte é gravado em AmsiScanBuffer, WldpQueryDynamicCodeTrust ou EtwEventWrite, a detecção baseada em assinatura que procura por patches de prólogo ignora isso completamente.

Variante de construção: subsistemas ativos e inativos

Vários subsistemas existem no binário como código ou strings, mas não estão ativos nesta versão. Esta é uma versão simplificada de um código-fonte maior.

  • NTDLL unhooking: As strings de depuração para um subsistema de unhooking estão em .rdata (UnhookNtdll: ntdll base = %p, applied %d relocation fixups to .text), mas nada as referencia. Morto nesta variante.
  • Carregador de blobs PE residente no registro: versões anteriores armazenavam o PE do próximo estágio dentro do registro. Esta versão não faz isso, mas a rotina de desinstalação ainda limpa o registro legado.
  • Persistência de sequestro de COM: nunca instalada por esta versão. A lógica de limpeza permanece na rotina de desinstalação.
  • Persistência do monitor de impressão: mesmo padrão do sequestro de COM; caminho de instalação ausente, caminho de desinstalação mantido.

Os três últimos (carregador de blobs de registro, sequestro de COM, monitor de impressão) mostram o padrão oposto: lógica de limpeza sem lógica de instalação, mantida para compatibilidade com versões anteriores.

Recursoconstrução de cargas úteisEvidência
Chamadas de sistema diretas (extração de SSN)AtivoExtração do número de segurança social (SSN) + geração de stub confirmadas
Bypass AMSI / WLDP / ETW HWBPAtivoDR0/DR1/DR2 via primitiva de thread auxiliar compartilhada
Injeção de processo de três viasAtivoPhantomInject, DbgNexum, ManualMap são todos funcionais
Resolução Blockchain C2AtivoTrês fornecedores da Blockscout foram consultados.
Desconexão do NTDLLCódigo mortoStrings presentes, sem referências de código
Criptografia HEISDesabilitadoCriptografia/descriptografia de código stub
Carregador de blobs PE residente no registroSomente legadoA limpeza só foi realizada durante a desinstalação.
persistência de sequestro de COMSomente legadoLimpo durante a desinstalação, nunca instalado.
Persistência do monitor de impressãoSomente legadoLimpo durante a desinstalação, nunca instalado.
Cordas de iscaAtivo4 strings de isca não referenciadas em .rdata

Comando e controle

Resolução Blockchain C2

O PHANTOMPULSE descentraliza a busca de C2 por meio de três provedores Blockscout:

  • eth.blockscout[.]com (Ethereum L1)
  • base.blockscout[.]com (Base L2)
  • optimism.blockscout[.]com (Otimismo Nível 2)

O endereço da carteira 0xc117688c530b660e15085bF3A2B664117d8672aA é descriptografado por XOR a partir do armazenamento com uma chave de 16 bytes. Para cada provedor, o implante emite um HTTPS GET (porta 443, erros de certificado SSL ignorados), obtém o campo input da transação mais recente, decodifica-o em hexadecimal, descriptografa XOR com os bytes do endereço da carteira como chave e valida se o resultado começa com http. Em caso de falha total, ele recorre ao URL codificado https://panel.fefea22134[.]net.

O resolvedor não verifica o remetente da transação. Ele apenas verifica se o último input decodificado começa com http. Qualquer pessoa pode enviar uma transação para essa carteira com seu próprio URL codificado em XOR sob os bytes da carteira, e cada instância PHANTOMPULSE dessa campanha que consultar posteriormente será resolvida para esse URL. Para os responsáveis pela segurança da rede, esta é uma via alternativa viável ao custo de uma única transação.

Pontos finais e batimento cardíaco

Cinco caminhos de API são construídos em tempo de execução e reencriptados na memória com uma chave por sessão:

CaminhoMétodoTipo de conteúdoObjetivo
/v1/telemetry/reportPUBLICARapplication/jsonBatimento cardíaco com telemetria completa do sistema
/v1/telemetry/tasks/<machine_id>getBusca de comando
/v1/telemetry/upload/PUBLICARimage/bmpCaptura de tela / upload de arquivo
/v1/telemetry/resultPUBLICARapplication/jsonentrega de resultados de comando
/v1/telemetry/keylog/PUBLICARtext/plainUpload de dados do registro de teclas

O comando `heartbeat` envia um perfil completo do sistema em formato 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>"
}

Dois campos de resposta são analisados: "status":"deleted" aciona a desinstalação completa; "ip":"<value>" preenche o cache de IP público, mas somente se a descoberta for local (ipif[.]org). / icanhazip[.]com/ checkip.amazonaws[.]com) ainda não o preencheu.

Cadência e resiliência do loop

  • Sono: aleatório uniforme em [20, 40] segundos
  • Autocura: executada na iteração 2 e, em seguida, a cada 10 iterações.
  • Verificação de saúde: primeira chamada após o implante entrar em funcionamento e, em seguida, a cada 5 iterações subsequentes. Preenche uma estrutura de informações do sistema local (CPU%, RAM, versão do SO, tempo de atividade, nome do computador).
  • Limiar de falhas: 10 falhas consecutivas de pulsação acionam uma reinicialização automática para recuperação SSL/TLS travada.
  • Re-resolução: em caso de falha, a resolução da blockchain é executada; se a URL resolvida for alterada, o contador de falhas é reiniciado.
  • IP público: api4.ipify[.]orgipv4.icanhazip[.]comcheckip.amazonaws[.]com
  • Verificação de conectividade: sondas microsoft[.]com, google[.]com, cloudflare[.]com, github[.]com

despacho de comando

O despachante de comandos encaminha comandos pelo hash DJB2. Oito comandos no total:

HashComandoComportamento
0x04CF1142injectInjetar shellcode/DLL/EXE. Rotas por tipo: shellcode → PhantomInject; DLL → ManualMap; EXE → DbgNexum. Os bypasses AMSI e WLDP HWBP são instalados na primeira chamada inject (o ETW HWBP já está instalado a partir de EvasionInit).
0x7C95D91AdropCopie o arquivo para o disco e execute-o. Suporta payloads DLL, EXE, shellcode (injeção APC) e MSI.
0x9A37F083screenshotCaptura GDI, redução para 960px de largura e upload como BMP.
0x08DEDEF0keylogInicie ou pare o keylogger embutido.
0x4EE251FFuninstallLimpeza e encerramento em 6 etapas.
0x65CCC50BelevateO bypass UAC via técnica schuac (IElevatedFactoryServer::ServerCreateElevatedObject(CLSID_TaskScheduler)); registra uma tarefa transitória elevada que relança o implante.
0xB3B5B880downgradeSISTEMA → transição para administrador com privilégios elevados.
0x20CE3BC8(auto-reinicialização)auto-terminação em cascata: NtTerminateProcess(-1, 0) chamada de sistema direta primeiro; se isso não for resolvido, recorre a ExitProcess(0). A persistência reinicia o implante na próxima execução da tarefa agendada. Operacionalmente equivalente a uma reinicialização suave.

O oitavo manipulador não possui nenhum registro de depuração com seu nome. Ele se encerra automaticamente; a tarefa agendada reinicia o implante no próximo ciclo de execução. A ausência de estrutura de código no estilo LLM (strings de depuração) faz deste um dos poucos manipuladores no binário que parece ter sido adicionado pelo autor humano em vez de gerado pelo LLM.

Técnicas de injeção

O PHANTOMPULSE utiliza três técnicas de injeção, uma para cada tipo de carga útil. O comando C2 inject encaminha shellcode para PhantomInject, DLLs para ManualMap e EXEs para DbgNexum.

Os bypasses de ponto de interrupção de hardware AMSI/WLDP são instalados na primeira chamada inject , antes de qualquer execução do injetor.

Payload Type (Tipo de carga)InjetorEstratégia
ShellcodePhantomInjectMódulo stopping em dbghelp.dll via SEC_IMAGE
EXEDbgNexumMáquina de estados da API de depuração
DLLMapa manualMapeamento manual completo de PE

PhantomInject: módulo sobrescrevendo dbghelp.dll

O método de eliminação de módulos evita a alocação de MEM_PRIVATE mapeando uma DLL legítima do Windows como SEC_IMAGE e sobrescrevendo .text:

  1. Adquire SeDebugPrivilege (via OpenProcessToken / LookupPrivilegeValueW / AdjustTokenPrivileges), então percorre o snapshot do processo para um dos sete candidatos de processo host (correspondência sem distinção entre maiúsculas e minúsculas). Tentados em ordem de prioridade: sihost.exe, taskhostw.exe, backgroundTaskHost.exe, RuntimeBroker.exe, dllhost.exe, ctfmon.exe, explorer.exe.

  2. Abre dbghelp.dll através de NtOpenFile, cria a seção SEC_IMAGE , mapeia para o destino através de NtMapViewOfSection

  3. Analisa a cópia local para obter o RVA e o tamanho de .text , e então a libera.

  4. Seleciona e suspende uma thread, captura o contexto.

  5. Constrói um trampolim de 82 bytes para salvar, chamar e restaurar.

  6. Escreve shellcode + trampoline em .text da DLL mapeada.

  7. Proteção Flips para PAGE_EXECUTE_READ

  8. Reaponta RIP para o trampolim, retoma a discussão.

Para um analisador de memória, o resultado parece um thread sendo executado dentro de um dbghelp.dll legítimo, uma região de imagem com suporte de arquivo com o caminho de arquivo, nome da seção e hash da primeira página corretos.

DbgNexum: API de depuração como controlador de execução

O DbgNexum lida com payloads EXE. Em vez de escrever código executável diretamente no alvo, ele usa a API de depuração do Windows para controlar a execução, uma exceção por vez: uma cadeia ROP cujos gadgets são APIs completas do Windows no alvo.

A técnica não é original do PHANTOMPULSE. É uma cópia literal de dis0rder0x00/DbgNexum, uma prova de conceito pública publicada no GitHub em 2026-01-04. O operador manteve o nome da técnica publicada ("DbgNexum") nas strings de depuração do implante inalterado, e a máquina de estado interna é uma correspondência 1:1: a mesma API de isca, nome da seção, cadeia de gadgets e constantes. PHANTOMPULSE envolve o núcleo x64 elevado com andaimes operacionais que o PoC não possui: seleção de processo host (FindHostProcessEx), um fallback que gera um novo SysWOW64\cmd.exe / rundll32.exe / notepad.exe, um bootstrap de carregamento PE personalizado (para que possa carregar EXEs completos em vez de shellcode bruto) e uma variante WoW64 multiarquitetura separada.

Para payloads x64 nativos, o implante pré-configura o PE, o stub de bootstrap e a configuração do trampoline dentro de uma seção de mapeamento de arquivo nomeada. O nome da seção é a string literal de dois bytes "MZ", o implante se conecta com DebugActiveProcess e cria uma thread remota em FileTimeToSystemTime com um ponto de interrupção de hardware em DR0.

Quando a isca atinge o ponto de interrupção, uma máquina de estados conduz o alvo através desta cadeia de API:

  1. Redirecionar RIP para DbgBreakPoint+1 com o sinalizador de armadilha definido; a exceção resultante de etapa única faz a ponte para o resto da cadeia.
  2. LocalAlloc(LMEM_ZEROINIT, 3), alocar o buffer de nome de 3 bytes.
  3. memcpy(buf, kernel32_base, 2), copie "MZ" do cabeçalho DOS de kernel32.dll para o buffer.
  4. memset(stack+40, 0, 8): zerar um argumento de pilha.
  5. OpenFileMappingA(0x1F, FALSE, "MZ")Abra a seção preparada com acesso completo ao mapeamento da seção.
  6. MapViewOfFile(...), mapeie-o para o destino.
  7. Redirecionar RIP para mapped_base + 0x400, o stub de bootstrap. Esta é a única etapa registrada diretamente: DbgNexumLoop64: stage 6 -> stub at %llx, base=%llx. (O PoC redireciona para mapped_base + 0 para shellcode bruto; PHANTOMPULSE adiciona o deslocamento +0x400 para chegar ao seu carregador PE personalizado.)

Cada transição intercepta o próximo evento de depuração, restaura RSP, limpa o sinalizador de trap, modifica RIP e os registros de argumento (RCX, RDX, R8, R9) para a próxima chamada e continua o depurado. DR0 é reutilizado como um ponto de interrupção de hardware de execução em cada endereço de retorno salvo, portanto, não são necessários patches embutidos ou WriteProcessMemory contra o alvo. Do ponto de vista do kernel, tudo o que aconteceu foi uma thread dentro de kernel32.dll chamando LocalAlloc, OpenFileMappingA e MapViewOfFile.

O caminho entre arquiteturas (PE32 a partir de um implante de 64 bits) é uma variante exclusiva do PHANTOMPULSE que o PoC público não possui. Ele usa um atalho: o implante percorre o PEB.Ldr do alvo via NtReadVirtualMemory (usando ProcessWow64Information para escolher o layout PEB de 32 bits ou 64 bits) para encontrar uma DLL com um ponto de entrada chamável, então pré-mapeia uma seção contendo um stub de carregador de 32 bits e trampolim em ambos os processos via NtCreateSection + NtMapViewOfSection. O redirecionamento consiste em apenas duas etapas: isca em RtlExitUserThread, então um único passo mediado por DbgBreakPointsalta para RIP no trampolim. Não há cadeia de API neste caminho porque a seção já está mapeada em ambos os lados, então OpenFileMappingA/MapViewOfFile não são necessários.

A seleção de host cross-arch passa por FindHostProcessEx, que exclui processos críticos do sistema (csrss.exe, lsass.exe, smss.exe, winlogon.exe, services.exe, wininit.exe, svchost.exe, MsMpEng.exe) e recorre à criação de um novo SysWOW64\cmd.exe / rundll32.exe / notepad.exe se nenhum host WoW64 utilizável for encontrado.

ManualMap: mapeador PE completo

O ManualMap lida com payloads de DLL com uma implementação completa de mapeamento manual de PE:

  1. Valida a assinatura MZ/PE; rejeita PE32 no caminho do host x64 (log de depuração: "PE32 DLL in x64 host is impossible")

  2. Aloca SizeOfImage no alvo através de NtAllocateVirtualMemory

  3. Copia cabeçalhos e seções para um buffer de preparação local.

  4. Aplica realocações de base (IMAGE_REL_BASED_DIR64, IMAGE_REL_BASED_HIGHLOW)

  5. Resolve importações via LoadLibraryA + GetProcAddress

  6. Apaga os cabeçalhos PE (zeros SizeOfHeaders bytes)

  7. Grava a imagem preparada na alocação remota.

  8. Conjuntos de proteção de memória por seção

  9. Constrói um trampolim de 137 bytes em uma alocação remota separada de 0x2000 bytes (definida como PAGE_EXECUTE_READ):

O arquivo gist completo do shellcode do trampolim contém todos os bytes.

10. Sequestra uma thread através de suspend / get-context / set-context

Escalação de privilégios

O comando elevate é um bypass do UAC através da técnicaschuac (IElevatedFactoryServer::ServerCreateElevatedObject(CLSID_TaskScheduler)), publicado como UACME issue #129 por zcgonvh, atualmente sob o ID 74.

Mecanismo

MaintenanceUI.dll's CMaintenanceUIVirtualFactory (CLSID {A6BFEA43-501F-456F-A845-983D3AD7B8F0}) está registrado com uma chave de registro Elevation , então o SO fornece aos chamadores não administradores uma instância elevada. Sua interface IElevatedFactoryServer expõe ServerCreateElevatedObject(rclsid, riid, ppv), que o servidor elevado usa para instanciar qualquer outro CLSID sob seu contexto elevado. PHANTOMPULSE alimenta-o CLSID_TaskScheduler, recebe de volta um ITaskService elevado e usa esse serviço para registrar uma tarefa HighestAvailable-RunLevel que reinicia o implante.

O comando elevate C2

Dentro de ProcessCommands, o manipulador de elevação:

  1. Constrói a ação da tarefa. O comando é <system_dir>\rundll32.exe com argumentos \"<deployed_dll>\",DllRegisterServer. O identificador do usuário é COMPUTERNAME\USERNAME de GetEnvironmentVariableW. ![Chamada de comando Elevate][/assets/images/blockchain-c2-phantompulse-rat-sinkhole/image17.png]
  2. Escreve o marcador .elevate como um único byte ("1", 0x31), não parâmetros codificados. A escrita passa por NtCreateFile + NtWriteFile para contornar os ganchos do modo de usuário. O marcador é apenas um indicador de presença; os parâmetros de elevação se movem dentro da definição da tarefa que o implante está prestes a registrar.
  3. XOR descriptografa o COM Elevation Moniker em tempo de execução, 66 bytes de .rdata xorados contra a semente 0xE95CA237, que decodifica para Elevation:Administrator!new:{A6BFEA43-501F-456F-A845-983D3AD7B8F0}.
  4. Chama CoGetObject(moniker, &BIND_OPTS3{dwClassContext=CLSCTX_LOCAL_SERVER}, IID_IElevatedFactoryServer, &factory) para obter um IElevatedFactoryServer* elevado, depois factory->ServerCreateElevatedObject(CLSID_TaskScheduler, IID_ITaskService, &elevatedTaskService) para obter um ITaskService* elevado que herda a elevação.
  5. Registra uma tarefa transitória DotNetSvcElevateTask em HighestAvailable RunLevel com a ação rundll32 acima.
  6. Exclui quaisquer tarefas persistentes não elevadas preexistentes para que a antiga persistência de baixo nível de instrução não entre em conflito com a reinicialização elevada.
  7. Chama ITaskService::Run na tarefa transitória e a exclui imediatamente depois. Libera o mutex de instância única e sai do implante medium-IL.

Tentativa de fallback via proxy rundll32

Se a cadeia CoGetObject / RegisterTask falhar, um caminho alternativo entra em ação. Um caminho de inicialização separado gera um novo rundll32.exe "<deployed_dll>",DllRegisterServer diretamente via CreateProcessW, com novas tentativas (">>> .elevate redirect attempt %d"). Dentro desse rundll32 o implante detecta isProxy=1, elevated=0 e tenta novamente a sequência schuac com três variantes de registro (ELEVATED+INTERACTIVE+user, ELEVATED+INTERACTIVE, INTERACTIVE). Em caso de sucesso, a tarefa de nível superior é acionada e o relançamento de nível superior entra em ação. Ao esgotar, o proxy registra ">>> Phase 1: all registration methods failed, cleaning marker" e sai.

Reinício do rundll32 com privilégios elevados

Quando a tarefa transitória é acionada, svchost.exe (Schedule) inicia rundll32.exe "<deployed_dll>",DllRegisterServer sob um novo token de alto IL. A exportação DllRegisterServer do implante é executada como a entrada; na inicialização, ela vê o marcador .elevate e um token de alto nível de instrução (IL) e direciona para o caminho elevado:

  • Lê e apaga o marcador .elevate .
  • Reinstala a persistência em contexto elevado, incluindo a tarefa de inicialização DotNetSvcCoreTask em \Microsoft\Windows\NetFramework\, registrada com INTERACTIVE_TOKEN + BootTrigger, que requer registro de administrador.
  • Continua a operação normal do implante como um serviço de alta IL.

Roteamento de estado de marcador

Em cada inicialização, MainEntryLogic chama GetFileAttributesW no caminho .elevate . Se retornar INVALID_FILE_ATTRIBUTES, o implante ignora toda a lógica de elevação e inicia normalmente. Se o marcador existir, o implante coleta mais dois fatos: se o processo atual é um proxy rundll32.exe/regsvr32.exe e se o token está elevado, e então roteia com base na combinação:

MarcadorProxyElevadoO que acontece
ausenteN/DN/DInicialização normal, sem roteamento especial.
PresenteNãoNãoExecute o proxy rundll32 para tentar novamente a cadeia schuac e, em seguida, saia.
PresenteSimNãoTente novamente o schuac dentro do rundll32; ao esgotar, exclua o marcador e saia.
Presente*SimExcluir marcador, reinstalar persistência com a tarefa de inicialização, continuar como high-IL

O conteúdo do arquivo marcador nunca é lido; sua mera presença determina o roteamento.

O guia " Explorando as formas de contornar o UAC do Windows " da Elastic Security Labs aborda diretamente os padrões de detecção para a classe de bypass IElevatedFactoryServer .

Persistência

Três tarefas agendadas

PHANTOMPULSE instala três tarefas agendadas através da interface COM ITaskService , cada uma executando rundll32.exe "<stub_dll>",DllRegisterServer:

TarefaAcionarIntervaloNível de execução
DotNetSvcUpdateTaskLogin do usuário + Hora3 minStandard
DotNetSvcCoreTaskInicialização + Tempo15 minutosMais alto disponível + oculto
DotNetSvcUserTaskLogin do usuárioAo iniciar sessãoStandard

A tarefa de inicialização reside em \Microsoft\Windows\NetFramework\ para se misturar com as tarefas legítimas de manutenção do .NET. Seu corpo XML completo está incorporado em .rdata, não construído em tempo de execução. As assinaturas nessa estrutura amorfo são duráveis em diferentes versões.

A instalação tenta várias variantes de fallback RegisterTask por tarefa, combinando tipos de logon INTERACTIVE_TOKEN e S4U com diferentes formatos de identificador de usuário (COMPUTER\User, SID, nome de usuário curto). A ordem exata das tentativas depende do contexto de privilégios do implante (SYSTEM, administrador ou usuário padrão); as falhas passam para a próxima variante até que uma seja bem-sucedida.

DLL stub drop

O agente implantado reside criptografado e compactado dentro do próprio .rdatado EXE. FilelessPersist descriptografa-o sob demanda e grava o resultado no disco via (NtCreateFile + NtWriteFile).

A decriptografia consiste em duas etapas:

  1. Decodificar XOR de um blob em relação a uma chave rotativa de 16 bytes em (decoded[i] = blob[i] ^ key[i & 0xF], com decoded[0] = 0x1E como um primeiro byte codificado que inicia o cabeçalho do bloco LZNT1).
  2. RtlDecompressBuffer(COMPRESSION_FORMAT_LZNT1, ...) no buffer decodificado, que é uma DLL (svcagent.dll).

Descartar caminhos por ordem de prioridade, prosseguindo em caso de erros de acesso:

  1. %ProgramData%\AssetMon\svcagent.dll (primário)
  2. %APPDATA%\AssetMon\svcagent.dll
  3. %TEMP%\svcagent.dll
  4. Uma cópia redundante %ProgramData% "adormecida" em um caminho separado.

Os analistas podem reproduzir a DLL implantada offline lendo as duas regiões acima do EXE, aplicando o loop XOR e alimentando o resultado para RtlDecompressBuffer (ou qualquer implementação de LZNT1), como visto na captura de tela do CyberChef abaixo.

migração de carregamento lateral de DLL

Um bloco dentro de SetupRegistryPE (registrado com os prefixos de string de depuração MigrateSideload / MigrateLegacySideloads ) enumera os processos em execução e seus diretórios executáveis, procurando por diagcore.dll. Quando encontrado, ele sobrescreve o arquivo com o stub atual via CopyFileW.

Autocura

A autorrecuperação é executada na iteração 2 do loop C2 e a cada 10 iterações subsequentes, dependendo de um sinalizador de persistência adiada ser limpo. O pedido de verificação:

  1. Primeiro, verifique a persistência no registro. CheckRegistryPersistence corre no topo do quarteirão. Se reportar problemas, o implante executa imediatamente FilelessPersist (descriptografa e descarta novamente a DLL stub) e InstallPersistence (registra novamente os gatilhos da tarefa).
  2. Verificação de tarefas. SelfHealCheckTasks em seguida verifica as três tarefas de persistência (DotNetSvcUpdateTask, DotNetSvcCoreTask, DotNetSvcUserTask) e reinstala quaisquer que estejam faltando. A verificação da tarefa de inicialização é controlada no contexto SYSTEM ou de administrador; chamadores sem privilégios a ignoram.
  3. Atualização do inventário de AV. DetectInstalledAV Executa no final para atualizar a lista de produtos AV visível ao operador.

A questão do privilégio é relevante para o despejo. Em um contexto sem privilégios elevados, a verificação da tarefa de inicialização é ignorada, portanto, a tarefa de inicialização não é inspecionada durante a limpeza. A remoção completa exige a exclusão de todas as três tarefas, além do artefato de registro, em uma única janela a partir de um contexto com privilégios elevados.

Além da autorreparação baseada em iterações, o implante possui um mecanismo de persistência diferida acionado por um único indicador. Nos caminhos de sucesso de batimento cardíaco em C2Loop_Main, assim que o contador de sucesso de batimento cardíaco exceder um com o sinalizador definido, o implante executa novamente FilelessPersist + InstallPersistence e limpa o sinalizador. Isso confere ao PHANTOMPULSE um segundo caminho de reparo de persistência que é acionado por um gatilho diferente da autorreparação baseada em iteração.

Coleta

Keylogger embutido com monitoramento da área de transferência

O keylogger é executado diretamente no loop C2, sem uma thread dedicada. Ele resolve APIs de user32.dll em tempo de execução:

APIObjetivo
GetAsyncKeyStateEstado-chave das pesquisas
GetForegroundWindowDetecção ativa de janelas
GetWindowTextACaptura do título da janela
MapVirtualKeyA / ToUnicodeTradução principal
GetClipboardSequenceNumberdetecção de alterações na área de transferência
OpenClipboard / GetClipboardDataLeitura da área de transferência (CF_UNICODETEXT)

O arquivo de log é criptografado com XOR usando a semente 0xE95CA237 . Os uploads enviam apenas a variação (delta) para evitar retransmissões.

Captura de tela

As capturas de tela utilizam APIs GDI resolvidas por hash. Se a largura da área de trabalho exceder 960 px, a imagem será redimensionada antes do upload. O BMP bruto é construído na memória e carregado com Content-Type: image/bmp. Acionado sob demanda pelo comando C2 screenshot (hash 0x9A37F083).

Reconhecimento do sistema

Dados de reconhecimento coletados pelo implante:

DadosFonte
CPURegistro: ProcessorNameString
GPUAdaptador de vídeo do registro DriverDesc (filtra "Microsoft Basic")
BATERGlobalMemoryStatusEx
SORtlGetVersion com mapeamento de compilação para versão (Win7 a Win11, Server 2008 a Server 2025)
Nome de usuárioGetUserNameW com fallback para LookupAccountSidW a partir do token explorer.exe
PrivilégioTipo de elevação do token: user, admin, admin_nouac, system
AVDetectInstalledAV compara os processos em execução com uma lista fixa de aproximadamente 25 a 30 nomes de processos de fornecedores de antivírus.
AplicativosDetectInstalledApps Verifica uma lista selecionada de 19 aplicativos-alvo.
Estado do firewallSYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy\{Domain,Standard,Public}Profile para registrar o estado ativado por perfil.
ServiçosContagem de serviços em execução por meio da enumeração de serviços
ID da máquinaDJB2 (nome do módulo) ^ número de série do volume
IP públicoCadeia HTTPS com múltiplas APIs

A lista de detecção de antivírus é excepcionalmente ampla, abrangendo produtos antivírus padrão para o consumidor ocidental, como Defender, Norton, McAfee, Avast, AVG, Avira, Bitdefender, ESET, F-Secure, G Data, Kaspersky, Panda, Sophos, Trend Micro, VIPRE, Webroot, ZoneAlarm, Comodo, além de fornecedores de EDR (CrowdStrike, SentinelOne, Cylance, Malwarebytes, HitmanPro). O implante também busca informações sobre AhnLab V3 (coreano do sul), Qihoo 360 / 360 Total Security e Tencent QQPC (chineses) e K7 Computing (indiano). A inclusão do AV asiático é incomum para ladrões de mercadorias que têm como alvo o Ocidente e é consistente com um implante projetado para vítimas em vários mercados regionais.

O implante também verifica uma lista selecionada de 19 aplicativos de alto valor por nome e sinaliza as correspondências no heartbeat (App detection: found %d apps):

CategoriaAlvos
Carteiras de criptomoedasledger, trezor, bitcoin-core, electrum, exodus, atomic, guarda
Mensageirostelegram, discord, signal, viber, slack, whatsapp
Clientes de e-mailthunderbird, outlook
Aplicativo 2FAauthy
Transferência de arquivos / SSHfilezilla, winscp
Jogossteam

A função de detecção (DetectInstalledApps) não examina o registro nem enumera processos. Expande três raízes de variáveis de ambiente (%LOCALAPPDATA%, %APPDATA%, %ProgramFiles(x86)%), concatena um sufixo de caminho relativo UTF-16 codificado por aplicativo (por exemplo \Telegram Desktop\, \Authy Desktop\, \Ledger Live\, \@trezor\trezor-suite\, \Steam\steam.exe), e chama GetFileAttributesW em cada caminho. Um retorno sem erros significa que o aplicativo está instalado e o nome foi registrado no buffer de resultados do heartbeat.

O próprio PHANTOMPULSE não extrai dados de nenhuma dessas fontes. A lista serve para reconhecimento de alvos para tarefas subsequentes. O operador vê no sinal de pulsação quais aplicações de alto valor uma determinada vítima possui e decide qual payload especializado enviar em seguida via inject ou drop.

Nenhuma funcionalidade de roubo de carteira, navegador, mensageiro ou credenciais foi identificada na amostra analisada; a lista de alvos é puramente uma verificação de presença que alimenta a árvore de decisão do operador.

Desinstalar

Uma limpeza em 6 etapas, acionada pelo comando uninstall , por "status":"deleted" em uma resposta de pulsação ou por um sinalizador de encerramento no registro:

EtapaAção
1/6Escreva a flag de encerramento (kill flag) em HKCU + HKLM e encerre o processo do host.
2/6Remover todas as 3 tarefas agendadas via COM + CreateProcessW fallback
3/6Remover registros legados: valor NTLoad, chaves de sequestro COM, chaves de monitor de impressão
4/6Exclua DLLs de stub, logs de processos inativos, blobs PE do registro e diretórios ProgramData.
5/6Exclua o caminho de instalação e o caminho próprio do disco.
6/6Encerrar instâncias residuais healthmon.exe e quaisquer instâncias rundll32.exe que hospedam svcagent.dll

A etapa 3 revela as técnicas de persistência legadas: lógica de limpeza para sequestro de COM e teclas de monitor de impressão que esta versão nunca instala.

Atribuição

As técnicas, os alvos e as escolhas de infraestrutura do PHANTOMPULSE estão alinhados com os grupos de intrusão com foco em criptografia alinhados à Coreia do Norte , que incluem Lazarus, BlueNoroff, UNC5342 (Entrevista Contagiosa) e APT38. Diversas dimensões independentes coincidem com os recentes relatórios públicos sobre esses grupos.

Sinais que corroboram os relatórios da Coreia do Norte:

  • O C2 resolvido por blockchain via campos de transação input corresponde ao padrão dead-drop-resolver que a Mandiant atribui a UNC5342 (Entrevista Contagiosa) em DPRK Adota EtherHiding. As especificidades do PHANTOMPULSE (XOR de bytes de carteira, Blockscout multicadeia) não são uma impressão digital 1:1, mas a classe da técnica agora está marcada como DPRK.
  • O conjunto de enumeração de carteiras criptográficas para desktop (ledger, trezor, bitcoin-core, electrum, exodus, atomic, guarda) corresponde quase exatamente à lista de alvos do RustDoor / Koi Stealer para macOS da Unit 42, que é atribuída à Coreia do Norte.
  • Implantes multiplataforma Windows + macOS para o mesmo perfil de vítima (a postagem anterior REF6598 documentou um irmão macOS com C2 em 0x666[.]info e um fallback Telegram em t[.]me/ax03bot) é uma assinatura BlueNoroff.
  • O direcionamento de mensagens no Telegram e em outros aplicativos de mensagens é uma especialidade da BlueNoroff, conforme cobertura da Arctic Wolf BlueNoroff.

Busca por novos domínios C2 através da assinatura de texto plano conhecida da carteira do resolvedor.

O esquema XOR usado pelo resolvedor de blockchain vaza uma assinatura estável de 2 bytes que os defensores podem usar para rastrear toda a cadeia, não apenas uma carteira específica.

Dois fatos se combinam: cada URL C2 começa com ht (de http:// ou https://), e a chave XOR é o endereço ASCII da carteira literalmente, então seus dois primeiros bytes de chave são sempre os caracteres literais 0 e x. A operação XOR de ht contra 0x resulta em \x58 \x0c. Cada campo criptografado input produzido por um resolvedor estilo PHANTOMPULSE, em qualquer cadeia, assinado por qualquer carteira relacionada, começa com os quatro caracteres hexadecimais 580c.

Isso transforma a busca, que antes se concentrava no monitoramento de uma única carteira, em uma varredura completa da cadeia em busca da assinatura. Os dados de transações públicas do Ethereum, Base e Optimism podem ser consultados via BigQuery, Dune ou nós de arquivo completo. Uma consulta ao conjunto de dados de transações públicas do Ethereum para valores input começando com 0x580c, com escopo em uma janela de carimbo de data/hora de bloco recente, revela carteiras de resolução previamente desconhecidas usadas pela mesma base de código. Cada correspondência é validada pela decodificação com o endereço ASCII da carteira do remetente como chave: um URL C2 real começa com http após a decodificação. A seguinte receita do CyberChef pode ser usada para descriptografar a URL do 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;

O CyberChef consegue descriptografar os dados de entrada para revelar o domínio, como mostrado na captura de tela abaixo.

Conclusão

O PHANTOMPULSE foi projetado a partir de componentes publicados: manipulação de módulos, máquinas de estado da API de depuração, mapeamento manual, bypass de pontos de interrupção de hardware AMSI/WLDP/ETW, persistência de tarefas agendadas e C2 em blockchain. A combinação e o fortalecimento que a une apontam para uma base de código madura em desenvolvimento ativo. Os sinais duráveis são comportamentais e estão cobertos pelas proteções comportamentais da Elastic para REF6598.

PHANTOMPULSE e MITRE ATT&CK

A Elastic usa a estrutura MITRE ATT&CK para documentar táticas, técnicas e procedimentos comuns que ameaças persistentes avançadas usam contra redes corporativas.

Táticas

A tática representa o propósito de uma técnica ou subtécnica. Trata-se do objetivo tático do adversário: a razão para realizar uma ação.

Técnicas

Técnicas representam como um adversário atinge um objetivo tático executando uma ação.

Remediação

YARA

O Elastic Security criou regras YARA para identificar essa atividade.

Observações

ObservávelTipoNomeReferência
33dacf9f854f636216e5062ca252df8e5bed652efd78b86512f5b868b11ee70fSHA-256RATO DE PULSO FANTASMAFinal payload
70bbb38b70fd836d66e8166ec27be9aa8535b3876596fc80c45e3de4ce327980SHA-256syncobs.execarregador PHANTOMPULL
def66275fa3baffb16e6e4ae0297861d9790ae7161fbc271a2ba05d121f13c70SHA-256Sinalizador de proximidadeCheck-in GTESTIC_WIN
panel.fefea22134[.]netDomínioPainel C2PHANTOMPULSE fallback codificado
fea22134[.]netDomínioC2 domainCriptografado em binário
195.3.222[.]251endereço-ipv4Servidor de testeEntrega do PowerShell/carregador
0xc117688c530b660e15085bF3A2B664117d8672aAcarteira de criptomoedasCarteira Blockchain C2ETH/Base/Otimismo
0x38796B8479fDAE0A72e5E7e326c87a637D0Cbc0Ecarteira de criptomoedasCarteira de financiamentoFinanciamento da resolução C2
eth.blockscout[.]comDomínioProvedor de BlockchainResolução de URL C2
base.blockscout[.]comDomínioProvedor de BlockchainResolução de URL C2
optimism.blockscout[.]comDomínioProvedor de BlockchainResolução de URL C2
hVNBUORXNiFLhYYhmutexInstância únicadescriptografado por XOR
svcagent.dllnome-do-arquivoDLL de stubCarga útil de persistência
AssetMondiretórioDiretório de DLLs stub%ProgramData% ou %APPDATA%
healthmon.exenome-do-arquivoConta-gotasNome original do executável
diagcore.dllnome-do-arquivoDLL de carregamento lateral legadaMigrado por MigrateSideload
.elevatenome-do-arquivoMarcador de elevaçãoRotas para o relançamento elevado
DotNetSvcUpdateTasktarefa agendadaPersistência primáriaIntervalo de 3 minutos
DotNetSvcCoreTasktarefa agendadapersistência do sistema15 minutos, escondido
DotNetSvcUserTasktarefa agendadaPersistência do usuárioGatilho de login
EdgeWebViewUpdateTasktarefa agendadaTarefa legadaLimpo durante a desinstalação
\Microsoft\Windows\NetFramework\DotNetSvcCoreTaskURI da tarefaCaminho da tarefa de inicializaçãoTarefa agendada oculta
Elevation:Administrator!new:{A6BFEA43-501F-456F-A845-983D3AD7B8F0}apelido comumDesvio do UACServiço de Tarefas de TI Elevado
0x666[.]infoDomíniomacOS C2Contador de arquivos macOS
t[.]me/ax03botURLRecurso alternativo do TelegrammacOS C2 dead drop
thoroughly-publisher-troy-clara[.]trycloudflare[.]comDomínioAnterior C2Túnel Cloudflare

Referências

Relatórios e ferramentas anteriores mencionados nesta análise:

Compartilhe este artigo