- No LinkedIn, após oferecer uma vaga de engenheiro de software, foi enviado um repositório privado do GitHub disfarçado de tarefa legítima de React/Web3; ao ser executado, um malware em JavaScript escondido em
tailwind.config.jsroda no computador do desenvolvedor - O arquivo de configuração de 30.987 bytes escondia 27 KB de código ofuscado depois de milhares de espaços; ele baixava de um servidor remoto uma carga útil de segundo estágio criptografada com AES-256-CBC, salvava em
%TEMP%\packe então a executava comnode pack - Em uma VM isolada com Windows 11 ARM, observada por cerca de 10 minutos, a carga útil ativou quatro componentes: backdoor de controle remoto, ladrão de navegador e carteiras, scanner recursivo de arquivos e monitor de área de transferência
- Via Socket.IO, ela controlava terminal, SSH, tela, teclado e mouse, e coletava bancos de dados do Chromium, armazenamento de extensões de carteiras, chaves SSH, código-fonte, arquivos de configuração etc.; na porta de upload de arquivos, foram observadas 2.588 requisições
- Repositórios de tarefas enviados por desconhecidos devem ser tratados como código não confiável e inspecionados em uma VM ou contêiner descartável sem perfis reais de navegador, chaves SSH, credenciais de nuvem ou carteiras; se houver infecção, após bloquear a rede e preservar evidências, é preciso trocar também as informações secretas
Repositório disfarçado de tarefa normal de recrutamento Web3
- O usuário do LinkedIn Wayan Adrian ofereceu uma vaga de engenheiro de software na shrapnel.com e enviou como tarefa o repositório privado do GitHub
tech-active-workplace-frontend-main, da contayevhen-o - A plataforma de campanha de NFT chamada BLAIEXS parecia, por fora, um projeto React/Web3 comum
- O frontend em React oferecia marca, painel administrativo, gerenciamento de campanhas, fluxo de criação de NFTs etc.
- A API em Express tratava autenticação, dados de dashboard, verificação KYB, criação e resgate de NFTs, upload de arquivos e alguns endpoints stub
- O script de seed criava contas de demonstração de superadmin, marca e consumidor, a campanha
Demo NFT Drope o NFTDemo Collectible, com 100 unidades disponíveis
- O escopo real da tarefa era uma simples funcionalidade de exibição de rede da MetaMask
- Implementar em
src/utils/ethereum.jsa função assíncronagetChainId()para chamareth_chainIde retornar o valor hexadecimal parseado ounull - Implementar no mesmo arquivo
getNetworkLabel(chainId)para encontrar, no objetoNETWORKSexistente, um nome de rede legível - Modificar
src/components/Wallet/ConnectWalletButton.jspara chamar as duas funções após a conexão da carteira
- Implementar em
- Após concluir a implementação e executar o servidor de desenvolvimento, ele demorou muito para iniciar; percebendo algo estranho, o usuário desconectou o cabo de internet
Código executável escondido em tailwind.config.js
- No
ls -la,tailwind.config.jstinha 30.987 bytes, mas no editor apareciam apenas 97 linhas; o mesmo tamanho foi confirmado comwc -c - Por volta da linha 95, havia um grande blob de JavaScript difícil de ler escondido depois de milhares de espaços
- O código usava técnicas comuns de ofuscação de JavaScript
- Armazenar strings importantes em um grande array
- Rotacionar o array até que o checksum batesse
- Esconder o acesso às strings atrás de uma função decodificadora
- Substituir nomes claros por índices numéricos e chamadas de wrappers
- Um dos dois decodificadores recuperava strings em formato Base64, e o outro aplicava uma chave por string e uma cifra de fluxo semelhante a RC4; antes de os índices do decodificador baterem, o array de strings era rotacionado
- Foi possível desofuscar sem executar o malware, na seguinte ordem
- Extrair o array de strings
- Reproduzir a rotação do array até alcançar o checksum esperado
- Reimplementar as funções decodificadoras
- Substituir chamadas como
b(0x214)ec(0x2f1, "key")pelas strings reais - Simplificar objetos wrapper e funções auxiliares para revelar a intenção de execução
Funcionamento do dropper de primeiro estágio
- O código desofuscado carregava
child_process,os,fs,pathecrypto, e instalavaaxiosesocket.io-clientno diretório temporário - Handlers de
uncaughtExceptioneunhandledRejectionem nível de processo eram configurados para ignorar erros - O fluxo de execução da carga útil remota era o seguinte
- O UID era
59e605dd78fb2aafccd1b622f06a00ca - Buscava dados em
http://45.146.252.17/api/service/59e605dd78fb2aafccd1b622f06a00ca - Passava o UID e a string
saltparacrypto.scryptSyncpara derivar uma chave de 32 bytes - Separava a resposta no formato
base64_iv:base64_ciphertexte descriptografava comaes-256-cbc - Gravava o texto claro no arquivo
packdo diretório temporário - Executava com
child_process.execSync("node pack", { windowsHide: true, cwd: os.tmpdir() })
- O UID era
- Em vez de executar funcionalidades próprias, tratava-se de um dropper de malware que baixava e executava outro software malicioso
- O código baixado não tinha verificação de assinatura, lista de hashes permitidos, fixação de origem, restrição de caminho nem guardas contra execução, permitindo que o endpoint remoto executasse código com as permissões da conta do sistema operacional que carregou a configuração do Tailwind
Análise dinâmica em VM isolada
- Para não expor o sistema host, foi configurada no UTM uma VM descartável com Windows 11 ARM
- Memória
4096 MiB - Disco
64 GiB - Rede
shared/NAT - Pastas compartilhadas desativadas
- Memória
- Foram instaladas ferramentas para coletar o comportamento do malware de vários pontos de vista
- Wireshark: coleta de pacotes e tráfego de C2/exfiltração
- Sysinternals Sysmon: telemetria persistente de eventos do Windows
- Procmon: coleta de processos em execução e atividades de registro e sistema de arquivos
- Para verificar os alvos de coleta do malware, foram inseridos dados-isca
- Par de chaves SSH
- Repositório privado do GitHub
- Carteira de criptomoedas
- Dados de navegador
- Outros arquivos que um infostealer poderia mirar
- Na VM,
yarn startfoi executado e, após cerca de 10 minutos de observação, foram obtidosrun1-full.pcapng,run1-sysmon.evtxestage2-pack.bin
Fluxo de infecção identificado na rede
- As requisições HTTP para
45.146.252.17tinham papéis separados por porta- Porta
80: obtenção da carga útil de primeiro estágio, registro do host e envio de logs - Porta
7641: backdoor de comando e controle via Socket.IO - Porta
7646: 2.588 uploads de arquivos - Porta
7649: 3 uploads maiores, porém em menor quantidade, de dados de navegador
- Porta
- A primeira requisição foi
GET /api/service/59e605dd78fb2aafccd1b622f06a00ca, e a resposta estava no formatobase64_iv:base64_ciphertext, comContent-Length: 150897 tailwind.config.jsdescriptografava a resposta com AES-256-CBC, escrevia o texto claro em%TEMP%\packe o executava comnode pack- As características da carga útil de segundo estágio obtida eram as seguintes
- SHA-256:
68a64d8c015c06fd70bcb8c5878c1e430da827dd00b62f8e6ef69e76bb94de5b - Tamanho:
113136bytes - Formato: JavaScript ASCII composto por uma única linha
- SHA-256:
- O
packdescriptografado também estava ofuscado novamente, mas usava as mesmas técnicas do primeiro estágio e pôde ser desofuscado pelo mesmo procedimento
Payload de segundo estágio composto por quatro programas
packnão era um script único, mas uma pequena árvore de processos que executava os quatro programas a seguir- Gravava e executava o backdoor de comando e controle
scdataem um diretório temporário - Gravava e executava o ladrão de navegador e carteiras
ldataem um diretório temporário - Executava um scanner recursivo de arquivos diretamente na memória com
node -e - Executava um monitor de área de transferência diretamente na memória com
node -e
- Gravava e executava o backdoor de comando e controle
- As principais configurações eram o UID
59e605dd78fb2aafccd1b622f06a00ca, a chave de usuário308, o host45.146.252.17, a porta Socket.IO7641, a porta de chave7648, a porta de upload de navegador7649e a porta de upload de arquivos7646
scdata: backdoor interativo de controle remoto
scdataé gravado como arquivo em%TEMP%e então executado comnpm i axios socket.io-client ... && node scdata, permanecendo como um processo filho de longa duração- Após a execução, configura a si mesmo para parecer um processo normal
- Título do processo:
vhost.ctl - Arquivo de indicação de instância única:
%TEMP%\.npm\vhost.ctl - Registro do host:
http://45.146.252.17/api/service/… - Envio de logs:
http://45.146.252.17/api/service/makelog - C2 via Socket.IO:
http://45.146.252.17:7641
- Título do processo:
- Também instala pacotes adicionais necessários para as funções de controle remoto
socket.io-client,ssh2,node-pty: funções semelhantes a terminal e SSHsharp,screenshot-desktop,clipboardy,@nut-tree-fork/nut-js: capturas de tela, área de transferência, movimentação/clique/rolagem do mouse e entrada de teclado
- Os eventos Socket.IO mostram diretamente o escopo do controle remoto
- Terminal:
start-terminal,terminal-input,terminal-resize,stop-terminal,command - Verificação e captura do host:
whour,capture - Controle de entrada:
mouseMove,mouseClick,mouseScroll,keyTap,keyCombo - Área de transferência:
copyText,pasteText - SSH e encerramento:
start_ssh,ssh_input,kill
- Terminal:
- No PCAP, foram confirmados o polling e o handshake WebSocket na porta
7641, além do primeiro evento do servidor,whour; no Sysmon, foram capturados em sequêncianode.exe scdata, uma consulta de informações do sistema via PowerShell e a instalação de dois grupos de pacotes npm - Vai além do simples roubo de informações e também oferece ao atacante funcionalidades de shell e controle do desktop
ldata: ladrão de dados de navegadores e carteiras
ldataé gravado em%TEMP%e executado comnpm i axios && node ldata; o título do processo énpm-cache- Coleta perfis de navegador e armazenamentos de extensões de carteira e os envia para
http://45.146.252.17:7649/upload; o status do LevelDB é verificado em/cldbs - Os navegadores-alvo são definidos de forma ampla por sistema operacional
- Windows: Chrome, Edge, Brave, LT Browser
- macOS: Chrome, Brave, Opera, LT Browser, Edge e o chaveiro local de login
- Linux: pastas de perfil correspondentes da família Chromium
- Nos diretórios
DefaultouProfile*, faz upload dos seguintes bancos de dados do ChromiumLogin DataLogin Data For AccountWeb Data
- Em seguida, percorre
Local Extension Settings/<extension-id>; entre os IDs de extensões de carteira hardcoded está também o da MetaMask,nkbihfbeogaeaoehlefnkodbefgpgknn - Para os caminhos das primeiras 8 extensões de carteira, copia o diretório LevelDB para uma pasta temporária e faz upload dos arquivos individualmente; com base na resposta
cldbsdo servidor, decide se concluiu ou se deve tentar novamente - Os três uploads confirmados na porta
7649eram os seguintes bancos de dadosLogin_Data.sqlite: 51.200 bytesLogin_Data_For_Account.sqlite: 51.200 bytesWeb_Data.sqlite: 262.144 bytes
- Os dados do experimento não continham senhas salvas nem linhas de cartão, mas incluíam 6 valores de preenchimento automático e metadados do navegador
- Navegadores baseados em Chrome criptografam localmente alguns campos, portanto nem sempre é possível recuperar segredos em texto claro apenas com os bancos de dados
- Ainda assim, quando combinados com segredos do sistema operacional, cookies, armazenamentos de extensões e um navegador desbloqueado, eles se tornam parte de um pipeline de roubo de credenciais
- Depois de fazer upload dos dados de navegador e dos LevelDB necessários, ou quando o servidor os marca como concluídos, o processo termina sem aguardar comandos adicionais do operador
Scanner recursivo de arquivos e monitor de área de transferência
- O scanner recursivo de arquivos é executado com
node -e, sem criar um arquivo separado, e começa a varredura pelo diretório home do usuário- No Windows, enumera as letras de unidade com
Get-CimInstance Win32_LogicalDiske também verifica a raiz de cada unidade - Os padrões de arquivos coletados incluem
*.env*,*.md,*.pem,*.ini,*.secret,*.json,*.js,*.ts,*.csv,*.txt,*.doc,*.docx,*.pdf,*.xlsx,.zsh_history,.bash_history ~/.ssh,~/.aws,~/.azure,~/.config,~/.foundrysão automaticamente tratados como pastas de interesse- Também procura nomes como
metamask,bitcoin,btc,solana,secret phrase,private key - Os resultados são enviados para
http://45.146.252.17:7646/upload
- No Windows, enumera as letras de unidade com
- No ambiente-isca, arquivos como
id_ed25519,id_ed25519.pub,History.txt,seed.js,password.js,tokens.js,ConnectWalletButton.js,ExportWallet.js,RegisterWallet.js,tailwind.config.js,aptos-cli.jsonforam de fato enviados - Se
ldatase encarrega de navegadores e armazenamentos de carteiras, o scanner de arquivos coleta amplamente chaves SSH, configurações, código-fonte, segredos locais, históricos de shell e arquivos de projeto - O monitor de área de transferência também é executado com
node -e; o título do processo énpm-compiler.log- Após aguardar alguns segundos, lê a área de transferência repetidamente
- No macOS usa
pbpaste; no Windows usapowershell -NoProfile -NonInteractive Get-Clipboard - Valores alterados são enviados para
http://45.146.252.17/api/service/makelog
- Mesmo sem interpretar o formato, ele pode capturar diretamente senhas, frases de recuperação, chaves privadas, tokens de API, códigos de uso único, URLs do GitHub, endereços de carteira e segredos de implantação que o usuário copiar
Princípios a seguir antes de executar uma tarefa de recrutamento
- Um repositório de tarefa enviado por uma pessoa desconhecida deve ser tratado como código executável não confiável até que sua segurança seja confirmada
- Antes de
yarn install,npm install,yarn buildouyarn start, é preciso revisar os seguintes itenspackage.json- scripts de ciclo de vida
- arquivos de configuração
- hooks de dependência evidentes
- Neste caso, o malware estava escondido em
tailwind.config.js, um arquivo que as pessoas tendem a deixar passar; arquivos de configuração, dependências e ferramentas de build também podem ser executados como código - Projetos de entrevista devem ser executados em uma VM ou contêiner descartável sem segredos reais montados
- perfil pessoal do navegador
- gerenciador de senhas
- chaves SSH
- carteiras de criptomoedas
- tokens do GitHub
- credenciais de nuvem
- sessões bancárias
- conta principal de e-mail
- Para tarefas que exigem uma conta ou carteira, use uma nova identidade de teste sem fundos e que não seja reutilizada em outros serviços
- Se for uma tarefa Web3, use apenas testnet e, mesmo que pareça inofensivo, não conecte sua carteira real a um projeto desconhecido
Indicadores para verificar se houve infecção
- No macOS e no Linux, verifique primeiro os seguintes itens
- Processos em execução relacionados a
node,pack,scdata,ldata,npm-compiler,vhost.ctl - Conexões com
45.146.252.17ou com as portas7641,7646,7649 pack,scdata,ldata,vhost.ctlem diretórios temporários, Downloads, Desktop, Documents e Library- Arquivo marcador
*/.npm/vhost.ctl - Strings como IP, UID,
/api/service/makelog,node scdata,node ldata,node packdentro do projeto baixado
- Processos em execução relacionados a
- No Windows, verifique os seguintes itens
- Entre os processos
node,npm,cmdepowershell, aqueles cuja linha de comando contémpack,scdata,ldata,node -e, endereço IP ouGet-Clipboard - Conexões TCP abertas com
45.146.252.17ou com as portas remotas7641,7646,7649 pack,scdata,ldata,vhost.ctl,.npm\vhost.ctldentro de%TEMP%- Strings C2 conhecidas dentro do diretório onde o repositório da entrevista foi clonado
- Entre os processos
- Se for encontrado qualquer um destes itens — processos Node ativos, conexões com esse IP ou artefatos
pack,scdataouldata— o sistema deve ser considerado exposto - Esses indicadores são verificações iniciais específicas das amostras analisadas e não constituem um procedimento forense completo
Limpeza do sistema infectado e rotação de segredos
- Antes de tudo, desconecte o computador da rede e não continue investigando, depurando nem copiando novos segredos no ambiente infectado
- Se forem necessárias evidências, preserve os seguintes materiais antes da remoção
- Lista de processos
- Conexões de rede
- Arquivos suspeitos
- Histórico do navegador
- Repositório malicioso
- Depois, encerre processos relacionados a
node pack,node scdata,node ldata,node -e,npm-compiler,vhost.ctle removapack,scdata,ldata,.npm/vhost.ctldos diretórios temporários - Se for uma VM descartável e não houver necessidade de preservar trabalhos legítimos em Node, é possível usar
pkill nodeno macOS/Linux ou forçar o encerramento de todos os processosnodeno Windows - Exclua o repositório malicioso e
node_modules, mas, se for necessária análise adicional, preserve uma cópia ZIP offline - Apenas excluir os arquivos não é suficiente; é preciso rotacionar ou revogar as seguintes informações secretas
- Chaves SSH
- Tokens do GitHub e do npm
- Chaves de nuvem e chaves de API
- Segredos de deploy
- Senhas salvas no navegador
- Sessões de login ativas
- Se houver possibilidade de que uma seed de carteira ou chave privada tenha passado pelo sistema infectado, crie uma nova carteira em um dispositivo limpo e transfira os fundos
Ataque que explora as fronteiras de confiança das ferramentas de desenvolvimento
- Produtos antivírus tradicionais não detectaram esse repositório, e agentes populares de codificação por IA usados para resolver a tarefa também não identificaram o código malicioso escondido no projeto
- Como o Tailwind avalia arquivos de configuração JavaScript como módulos Node, a IIFE da linha 95 é executada no momento em que ferramentas de desenvolvimento, scripts de build, integrações de editores, a CLI do Tailwind, bundlers ou tarefas de CI carregam a configuração
- A configuração legítima do Tailwind ia até a linha 94; depois dela havia cerca de 27 KB de código ofuscado adicionados
- O payload baixado é executado com os mesmos privilégios do sistema operacional do usuário que carregou a configuração
- Pode acessar arquivos locais, perfis de navegador, credenciais salvas, dados de extensões de carteira, chaves SSH, variáveis de ambiente, tokens de pacotes, credenciais de nuvem, código-fonte e área de transferência
- Se for executado em CI, segredos de deploy, artefatos de build, credenciais de registro e tokens de acesso à produção também podem ser expostos
- A correção necessária é remover completamente o blob JavaScript ofuscado de
tailwind.config.js
Ainda não há comentários.