Analistas de malware frequentemente se deparam com a realidade de que, por trás de nomes chamativos como ShadowSomething RAT/Loader/Stealer, muitas vezes se escondem técnicas familiares: um loader, persistência no sistema, um canal de comunicação com o C2 e ofuscação superficial. No entanto, ocasionalmente, surgem amostras que realmente surpreendem. Em uma investigação recente, encontramos um desses casos: o PhantomRShell, um backdoor empregado pelo grupo PhantomCore (Head Mare), que tem como alvo empresas na Rússia e Bielorrússia.
A principal característica do PhantomRShell reside em como ele mascara sua presença no sistema. O malware utiliza um desmontador (disassembler) integrado para interceptar chamadas de sistema e ocultar seus arquivos do usuário e de ferramentas de defesa. Em termos simples: o arquivo está presente no disco, mas para as ferramentas de análise, ele parece não existir. Neste artigo, vamos detalhar a arquitetura do PhantomRShell, desde a cadeia de infecção até o mecanismo de ocultação que o torna significativamente mais interessante do que a maioria de seus 'colegas de profissão'. A amostra analisada apresenta semelhanças com o que foi descrito por colegas da Positive Technologies. Dando o devido crédito ao trabalho deles, aprofundaremos a análise deste malware, com foco na mecânica interna do interceptador de API, métodos de inicialização não óbvios e contorno de defesas.
O arquivo original, nomeado 'Задание_на_оценку_N_2046_от_05_августа_2025_года.zip', é, à primeira vista, um arquivo ZIP comum. No entanto, ao abri-lo em um editor HEX, revela-se uma assinatura completamente diferente: um cabeçalho MZ/PE. Trata-se de um arquivo poliglota de arquivamento, contendo dois cabeçalhos externos: um de DLL e outro de PDF. Ao descompactar o arquivo, obtemos um terceiro componente: um atalho LNK. É a partir deste atalho que a cadeia de infecção se inicia.
O arquivo que se disfarça como um documento PDF é, na verdade, um atalho do Windows (LNK). É ele que inicia a cadeia de infecção. Ao ser aberto, o atalho executa um script PowerShell que prepara o sistema para o carregamento da biblioteca maliciosa. O primeiro estágio envolve a persistência no sistema através de COM Hijacking. O script cria uma chave no registro em HKCU:\Software\Classes\CLSID{c53e07ec-25f3-4093-aa39-fc67ea22e99d}\InprocServer32. Ao acessar um objeto COM, a chave original localizada em HKEY_CLASSES_ROOT tem prioridade menor do que a mesma chave criada em HKEY_CURRENT_USER. O valor padrão na nova chave contém o caminho para o futuro módulo malicioso. Consequentemente, em vez da biblioteca legítima %WinDir%\System32\Windows.StateRepositoryPS.dll, processos do sistema carregarão a biblioteca do atacante: %ProgramData%\winnt64_.dll. Essa abordagem resolve várias tarefas simultaneamente: primeiro, o código malicioso começa a ser executado no contexto de processos confiáveis, como explorer.exe, o que reduz a probabilidade de detecção. Segundo, garante a persistência, pois a substituição é salva no registro e sobrevive a reinicializações do sistema. Por fim, a execução em um processo confiável permite contornar parcialmente mecanismos de defesa como UAC, AppLocker e WDAC.
Após estabelecer a persistência, o script prossegue para extrair a carga útil do arquivo poliglota original. Ele procura sequencialmente o arquivo poliglota em vários diretórios: o diretório atual, o perfil do usuário (%USERPROFILE%) e o diretório temporário (%TEMP%). Ao encontrar o arquivo apropriado, o script começa a lê-lo não como um arquivo, mas como um fluxo binário. Inicialmente, os primeiros 16 bytes são ignorados. Esta é uma tentativa simples, mas eficaz, de contornar a análise de assinatura, pois muitas regras YARA analisam o cabeçalho pelos primeiros bytes. Para arquivos PE, isso se manifesta como: uint16(0) == 0x5A4D and uint32(uint32(0x3C)) == 0x00004550. Em seguida, um bloco de 642.064 bytes é lido e salvo em %ProgramData%\winnt64_.dll. É este arquivo que posteriormente será carregado via COM Hijacking. Um bloco de dados subsequente de 225.723 bytes é extraído como um documento PDF e salvo em C:\sponge-bob\exe-zip-injector\Задание_на_оценку_N_2046_от_05_августа_2025_года.pdf. Neste ponto, o malware conclui a parte técnica do carregamento e passa a mascarar sua atividade. O arquivo PDF extraído é aberto pelos meios padrão do sistema e apresenta um documento inofensivo – um anexo a um contrato. Este é um truque típico para desviar a atenção: o usuário recebe o arquivo esperado e não suspeita que a carga útil principal já foi implantada no sistema. Uma nuance importante identificada durante a análise é que o script PowerShell original extraído do arquivo LNK contém um caminho absoluto explicitamente definido para a isca em PDF. No caso investigado, este diretório não existia no nó da vítima, portanto, a tentativa de abrir o arquivo isca falhou. No entanto, isso não afetou as etapas cruciais da infecção. Aparentemente, os atacantes simplesmente corrigiram a configuração de rede do arquivo malicioso original. Assim, o arquivo LNK atua como um loader completo. Ele não apenas inicia a execução do código, mas também garante sua entrega oculta, persistência e mascaramento parcial, estabelecendo a base para o trabalho posterior da DLL.
A biblioteca maliciosa, %ProgramData%\winnt64_.dll, é o componente principal deste ataque. Durante a análise, foi descoberto que ela contém um módulo de interceptação de API do sistema. Este módulo é o Mhook. Quando DllMain é chamado no contexto de anexação a qualquer processo (fdwReason = DLL_PROCESS_ATTACH), a função QueueUserWorkItem é invocada, permitindo a execução da função de interceptação após a liberação do thread. O hook é armazenado não como um 'único endereço', mas como uma estrutura trampoline. O algoritmo de instalação pode ser entendido como uma cadeia de várias fases. Primeiro, o mhook tenta alcançar o início 'real' da função, pulando os stubs típicos. Isso inclui o prólogo hotpatch, o stack frame colapsado e cadeias de saltos incondicionais, incluindo o import thunk. Isso é feito pela função SkipJumps, para que a biblioteca não coloque um hook em um wrapper intermediário em vez do código real. Em seguida, vem o mais importante: a análise do código de máquina instrução por instrução, e não byte a byte. Para isso, é utilizada a biblioteca disasm-lib. A função DisassembleAndSkip lê instruções a partir do início da função alvo, soma seus comprimentos até atingir o mínimo para o patch e para se encontrar um ret, um salto condicional ou uma chamada. Em x64, ela também fixa instruções com endereçamento RIP-relative, pois seus deslocamentos precisarão ser recalculados após a transferência para a estrutura do hook. Isso é uma proteção contra o 'corte' de uma instrução ao meio, o que tornaria um memcpy simples do prólogo perigoso. O Mhook tenta alocar memória próxima à função original, pois para um salto curto comum é usado jmp rel32 (código de operação E9), que tem um alcance limitado de endereço. A função EmitJump escolhe a opção mais curta: se o destino estiver perto o suficiente, ela escreve um E9 rel32 de 5 bytes; se estiver longe, ela escreve um salto absoluto indireto usando RIP-relative, que em x64 ocupa mais bytes, mas não depende da proximidade dos endereços. Portanto, o bloco trampoline é posicionado o mais próximo possível da função alvo. A alocação de memória também não é aleatória. A função BlockAlloc percorre regiões de memória livres através de VirtualQuery, procura um intervalo MEM_FREE adequado e aloca uma página de memória separada (ou 4096 bytes) com acesso RWX. Em seguida, este bloco é dividido em um conjunto de estruturas MHOOKS_TRAMPOLINE, que entram na lista livre e são usadas para hooks futuros. Para evitar condições de corrida (race condition), o Mhook pausa temporariamente outros threads do processo. Usando SuspendOtherThreads, o mhook aumenta a prioridade do thread atual, tira um snapshot dos processos/threads através de ZwQuerySystemInformation, percorre todos os threads do processo atual e tenta suspendê-los um por um. A função SuspendOneThread verifica o ponteiro de instrução do thread através de GetThreadContext. Se ele aponta para uma área que será sobrescrita, o thread é temporariamente retomado e tenta ser suspenso novamente. Somente após isso, o thread é considerado congelado com segurança. Isso é feito justamente para não sobrescrever o código sob um thread em execução. Após isso, a biblioteca pode aplicar o patch na entrada da função com segurança. Primeiro, as N instruções inteiras originais são copiadas para o trampoline, um salto de volta para original + overwritten_len é adicionado ao final delas, e a entrada original é sobrescrita com um salto para a função de hook. Se havia instruções com endereçamento RIP-relative em x64, a função FixupIPRelativeAddressing corrige seu deslocamento, levando em conta a diferença entre o endereço no trampoline e o endereço original. Em resumo, o Mhook implementa não apenas a substituição dos primeiros 5 bytes, mas um esquema completo de inline hooking: encontra o EP real, analisa corretamente as instruções, aloca um trampoline perto da função, copia e, se necessário, corrige o prólogo transferido, insere um jmp para o hook e, em seguida, pausa temporariamente o ambiente de threads durante a gravação.
O que é interceptado? No nosso caso, a função NtQueryDirectoryFile é interceptada. Esta é uma função NTAPI que lista o conteúdo de um diretório e retorna informações sobre arquivos e diretórios. Ela retorna um buffer com uma lista de arquivos, onde cada elemento é uma estrutura FILE_ID_BOTH_DIR_INFORMATION. Na verdade, é uma lista simplesmente encadeada clássica, onde o ponteiro para o próximo elemento é armazenado no campo NextEntryOffset. De forma análoga a uma lista encadeada, um método pop() é implementado aqui, que remove um elemento da lista se a string 'winnt64_.dll' for encontrada no campo FileName. Vamos ver um exemplo visual do funcionamento desse mecanismo de ocultação. A biblioteca é injetada no processo explorer.exe e deve estar localizada no diretório %ProgramData%. No entanto, no cmd.exe, a biblioteca não é injetada, portanto, ao digitar o comando dir neste processo, o arquivo será exibido. Também vale ressaltar que, caso o arquivo a ser ocultado esteja no início da lista, o elemento da estrutura FILE_ID_BOTH_DIR_INFORMATION não será removido. Em vez disso, o nome do arquivo e seu comprimento serão simplesmente 'zerados', o que pode ser motivo suficiente para detectar atividade maliciosa.
Antes que o malware inicie a comunicação de rede, ocorre outra etapa importante que pode ser facilmente perdida em uma análise superficial. Trata-se do mecanismo de armazenamento oculto e descriptografia 'preguiçosa' da configuração. Curiosamente, a função de descriptografia não é chamada diretamente do código da carga útil. Em vez disso, ela é colocada na seção .CRT$XCU, que é usada pelo runtime do Visual C++ para registrar inicializadores personalizados. Durante a inicialização do processo, tais funções são chamadas automaticamente via _initterm, antes mesmo da transferência de controle para a lógica principal da DLL. É assim que isso se parece: a função dllmain_crt_process_attach chama initterm(__xc_a, __xc_z). O linker combina os deslocamentos de .CRT$XCA – .CRT$XCZ em um único array e chama initterm. Isso significa que a descriptografia da configuração ocorre implicitamente na fase de inicialização do runtime e não está vinculada a um local específico no código. Isso pode ser confuso, pois as strings já estão descriptografadas, mas não há uma chamada explícita para a função. Outro ponto importante: onde exatamente o resultado é armazenado. Em vez de variáveis globais ou buffers estáticos, o malware utiliza TLS (Thread Local Storage). O acesso aos dados é feito através do ThreadLocalStoragePointer da estrutura TEB (Thread Environment Block), o que permite armazenar a configuração separadamente para cada thread. Na primeira chamada, a função C2Decrypt verifica um flag de inicialização dentro da estrutura TLS personalizada. Se os dados ainda não estiverem preparados, os valores criptografados são gravados lá. Após isso, ocorre a descriptografia byte a byte usando XOR. Após a descriptografia, o valor é copiado para um buffer separado, e os dados originais no TLS são marcados como já processados. A descriptografia repetida não ocorre. Essa abordagem oferece várias vantagens: ausência de IOCs estáticos no arquivo binário; complexidade na análise estática; vinculação da configuração ao contexto do thread; ponto de inicialização não óbvio através do CRT. Como resultado, mesmo com uma análise completa do código, pode não ser imediatamente óbvio de onde vêm as strings usadas na comunicação de rede. Após a configuração ser preparada, o malware prossegue para a inicialização da comunicação de rede.
Antes de passar para a carga útil, vale a pena destacar algumas ações preparatórias que ocorrem na função 0x1800110E0. Apesar da abundância de ofuscação 'lixo', ocupando quase 300 linhas de pseudocódigo, a preparação inclui apenas 4 ações: 1. Geração de um ID único: ocorre através da inicialização do COM GUID. Este GUID será posteriormente usado para identificar o nó infectado. 2. Coleta de informações do sistema: ou obtenção do nome do computador e do domínio usando GetComputerNameW e GetComputerNameExW. As informações também serão usadas na interação com o C2. 3. Preparação do diretório de trabalho: usando CreateDirectoryW, um diretório de trabalho %ProgramData%\IntelHVD é criado, onde os arquivos instalados pelo servidor de controle serão salvos. 4. Lançamento de um thread separado para interação com o C2: a criação do thread é realizada usando beginthreadex, no qual o código da carga útil será executado. Após a conclusão de todas as etapas preparatórias – desde a inicialização da configuração até o lançamento do thread de trabalho – o malware prossegue para a lógica principal de interação com o servidor de controle. Aproximadamente assim pode ser descrita a função que contém a lógica de interação com o C2. No sample do grupo PhantomCore, notei dois padrões chave: Código 'lixo', ocupando cerca de 90% da listagem. Ofuscação via RUNTIME. Novas versões do IDA Pro geralmente lidam bem com a ofuscação RUNTIME, mas a combinação desses dois métodos pode causar dificuldades para a ferramenta. Corrigimos manualmente, definindo o deslocamento necessário como QWORD e alterando seu tipo para const __int64. Após isso, o descompilador exibirá as funções corretamente. Após tornar a função legível, vemos um esquema de controle de C2 bastante familiar para backdoors. A troca com o servidor é construída em um modelo pull clássico. Primeiro, a amostra envia uma requisição GET para o endereço http://31.56.206[.]116/poll, passando nos parâmetros id (GUID gerado anteriormente), hostname e domain. Os valores são empacotados em uma string no formato id=
Conclusão: PhantomRShell é uma ferramenta bem elaborada que emprega métodos de mascaramento não convencionais. Os atacantes demonstraram como técnicas normalmente usadas para pesquisa podem ser voltadas contra o próprio sistema. Ao mesmo tempo, sua componente de rede, pelo contrário, mimetiza o tráfego web comum, o que dificulta a detecção por fluxos de rede. Esse desequilíbrio é típico de ataques direcionados modernos. Abaixo estão os indicadores de comprometimento que ajudarão a reconhecer a presença deste backdoor na rede corporativa:
| Tipo de Indicador | Indicador |
|---|---|
| Rede | 31.56.206[.]116:80 |
| Arquivos | %ProgramData%\winnt64_.dll |
| Задание_на_оценку_N_2046_от_05_августа_2025_года.pdf | |
| Задание_на_оценку_N_2046_от_05_августа_2025_года.pdf.lnk | |
| %USERPROFILE%\Задание_на_оценку_N_2046_от_05_августа_2025_года.zip | |
| %TEMP%\Задание_на_оценку_N_2046_от_05_августа_2025_года.zip | |
| %ProgramData%\IntelHVD | |
| Registro | HKCU:\Software\Classes\CLSID{c53e07ec-25f3-4093-aa39-fc67ea22e99d}\InprocServer32 |








