摘要

這份報告提供了對 VoidLink 的全面技術分析,這是一個複雜的 Linux 惡意軟體框架,其特點在於結合了傳統可載入核心模組 (Loadable Kernel Modules, LKM) 與延伸的 Berkeley Packet Filter (eBPF) 程式。VoidLink 代表了 rootkit 設計的重大演進,超越了簡單的 syscall 表格劫持,採用了多世代隱藏技術,其中包含 ftrace 為基礎的攔截、Netlink 緩衝區操作,以及延遲初始化。該研究剖析了其核心組件的運作邏輯,分析了顯示 AI 協助工作流程的演進式開發模式(Iterative development pattern),並評估了其針對現代端點偵測與回應 (Endpoint Detection and Response, EDR) 系統的規避策略。

VoidLink Rootkit 如何同時使用 eBPF 與 ftrace 繞過 Linux 核心防禦 | 資訊安全新聞

1. 簡介

隨著模組化、多元件框架的出現,Linux 核心層級威脅的格局發生了模式轉變(Paradigm shift)。於 2026 年初首次被識別的 VoidLink,即是將多種持久性與隱藏機制整合到一個統一系統中的典型範例。與依賴單一攔截點的傳統 rootkit 不同,VoidLink 採用分層方式:LKM 處理深層核心操作,而 eBPF 程式則管理特定針對使用者空間的隱藏任務 [1] 。這種混合模型解決了單一技術的局限性,例如深層核心 hook 的不穩定性,或傳統利用 LKM 的網路隱藏的可見性問題。

2. 架構概述:混合模型

VoidLink 的架構分為兩個主要功能層:LKM 骨幹與 eBPF 隱藏夥伴。LKM 元件通常偽裝成名稱如 vl_stealth amd_mem_encrypt ,作為處理程序隱藏、檔案過濾以及透過 ICMP 隱蔽通道進行命令與控制 (C2) 通訊的主要引擎。eBPF 元件則專注於一項關鍵任務:對現代的診斷工具(如 ss )隱藏網路連線,這些工具會繞過傳統利用 /proc 的監控機制 [1]

graph TD UserSpace[User Space Application] -->|Syscall| KernelEntry[Kernel Entry Point] KernelEntry -->|ftrace Hook| LKM[VoidLink LKM] LKM -->|Filter| VFS[Virtual File System] LKM -->|Hide| ProcFS["/proc File System"] UserSpace_ss[ss Utility] -->|Netlink Query| Netlink[Netlink Socket Interface] Netlink -->|kprobe/kretprobe| eBPF[VoidLink eBPF Program] eBPF -->|Buffer Manipulation| UserSpace_ss LKM ---|Coordination| eBPF

圖 1:VoidLink Rootkit 的混合架構

3. LKM 元件的技術分析

LKM 元件在現代核心 (5.x 與 6.x) 中使用 ftrace 進行函式 hook,這種方法比直接修補 syscall 表格更穩定且更難偵測。透過註冊一個 ftrace_ops 結構,rootkit 可以在目標核心函式進入點攔截執行。

3.1. 程序與檔案隱藏邏輯

VoidLink 透過 hook __x64_sys_getdents64 來隱藏程序。當請求目錄列表時(例如被 ls ps 請求),被 hook 的函式會過濾掉與 rootkit 隱藏的 PID 或檔案名稱相符的項目。以下程式碼片段說明了用於解析 kallsyms_lookup_name 函式的邏輯,這對於在新版核心中找到未匯出的核心符號至關重要 [2]

  1. /*
  2. * Resolve kallsyms_lookup_name using kprobes to bypass symbol export restrictions.
  3. * This is a common technique in modern rootkits to find internal kernel functions.
  4. */
  5. void *find_kallsyms_lookup_name(void) {
  6. struct kprobe kp = {
  7. .symbol_name = "kallsyms_lookup_name", // Target symbol
  8. };
  9. void *addr;
  10. if (register_kprobe(&kp) < 0) {
  11. return NULL; // Failed to register probe
  12. }
  13. addr = kp.addr; // Extract the resolved address from the kprobe structure
  14. unregister_kprobe(&kp); // Clean up the probe
  15. return addr;
  16. }

3.2. 繞過記憶體保護

為了在唯讀的核心記憶體中套用 hook,VoidLink 必須暫時停用 CR0 暫存器中的寫入保護 (WP) 位元。這允許 rootkit 修改可執行程式碼或 jump table [2]

  1. /*
  2. * Temporarily disable kernel write protection by clearing the 16th bit of CR0.
  3. * This allows the rootkit to patch the kernel's executable code segments.
  4. */
  5. static inline void disable_write_protection(void) {
  6. unsigned long cr0 = read_cr0(); // Read current control register 0
  7. clear_bit(16, &cr0); // Clear the WP (Write Protect) bit
  8. // Use inline assembly to force the write, ensuring the change is applied immediately
  9. asm volatile("mov %0, %%cr0" : "+r"(cr0) : : "memory");
  10. }

4. eBPF 創新:隱蔽的網路隱藏

VoidLink 最具創新性的方面是使用 eBPF 向 ss 工具隱藏網路連線。與從 /proc/net/tcp 讀取的 netstat 不同, ss 使用 Netlink socket。VoidLink 的 eBPF 程式 hook __sys_recvmsg 來攔截使用者空間記憶體中的 Netlink 回應緩衝區。

4.1. Netlink 緩衝區操作

eBPF 程式不會從 Netlink 訊息鏈中移除項目(這可能導致核心不穩定),而是使用「swallowing」技術。它識別對應於隱藏 port 的 nlmsghdr 結構,並擴展 前一個 訊息的 nlmsg_len 以包含隱藏的項目。然後 ss 解析器會將隱藏的項目視為填充資料而忽略它 [1]

  1. /*
  2. * Simplified eBPF logic for swallowing Netlink messages.
  3. * It uses bpf_probe_write_user to modify the userspace buffer directly.
  4. */
  5. SEC("kretprobe/__sys_recvmsg")
  6. int kretprobe_sys_recvmsg(struct pt_regs *ctx) {
  7. // ... (context retrieval and buffer validation)
  8. if (is_hidden_port(current_entry->port)) {
  9. // Absorb the current entry into the previous one by updating nlmsg_len
  10. uint32_t new_len = prev_entry->nlmsg_len + current_entry->nlmsg_len;
  11. bpf_probe_write_user(&prev_entry->nlmsg_len, &new_len, sizeof(new_len));
  12. }
  13. return 0;
  14. }

5. 規避與持久性策略

VoidLink 採用多種進階規避技術來繞過 EDR 與靜態分析。這些技術包括字串碎片化以擊敗 YARA 特徵碼,以及延遲初始化以避開同步模組載入檢查 [3]

5.1. 字串碎片化與混淆

為了避免被靜態特徵掃描器偵測,VoidLink 將敏感字串(如 MODULE_LICENSE )碎片化。透過將 "GPL" 分解為 "G"、"P"、"L",連續的字串永遠不會出現在二進位檔的資料區段中 [3]

  1. /*
  2. * Obfuscated MODULE_LICENSE to evade YARA rules targeting continuous strings.
  3. * The compiler concatenates these, but they don't exist as a single entity in the binary.
  4. */
  5. MODULE_LICENSE("G" "P" "L");
  6. MODULE_AUTHOR("A" "M" "D");
  7. MODULE_DESCRIPTION("A" "M" "D" " " "M" "e" "m" "o" "r" "y" " " "E" "n" "c" "r" "y" "p" "t" "i" "o" "n");

5.2. 透過 memfd_create 實現僅記憶體載入

rootkit 的載入器避免了將最終的 .ko 檔案寫入磁碟。相反地,它在記憶體中重構模組,使用 memfd_create 建立一個匿名檔案描述子,並透過 finit_module 載入它。這確保了沒有惡意 artifact 遺留在實體儲存裝置上供鑑識分析 [3]

graph TD Disk[Encoded Fragments on Disk] -->|XOR Decode| Memory[Decoded Module in Memory] Memory -->|memfd_create| FD[Anonymous File Descriptor] FD -->|finit_module| Kernel[Kernel Module Loaded] subgraph "                        EDR Bypass" Memory FD end

圖 2:僅記憶體載入流程

6. Hooking 技術的比較分析

VoidLink 在四個世代中的演進反映了 rootkit 開發者與作業系統維護者之間更廣泛的「核心介面戰爭」。下表比較了 VoidLink 及類似現代 rootkit 所採用的不同 hooking 模型。

技術 機制 目標核心 隱蔽等級
Syscall Table Patching 直接修改 sys_call_table 舊版 (例如 CentOS 7) 低 (容易被 KPP 偵測)
ftrace Hooking 透過 ftrace_ops 進行動態 instrumentation 現代 (5.x, 6.x) 中 (穩定,但在 ftrace 日誌中可見)
eBPF Injection 附加到 kprobes/tracepoints 現代 (5.x 以上) 高 (對傳統 LKM 掃描器不可見)
Inline Patching 直接修改函式機器碼 最新 (6.9 以上) 非常高 (繞過利用 switch 的分配器)

7. 結論

VoidLink 代表了傳統核心漏洞利用與現代可觀測性功能的複雜融合。其混合型 LKM-eBPF 架構使其能夠在不同的核心版本中保持高度的隱蔽性,同時為模組化的 C2 操作提供了一個強健的平台。AI 輔助開發的跡象表明,建立如此複雜的核心級威脅的門檻正在降低,使得能夠快速演化並適應防禦措施。未來的防禦策略必須超越簡單的系統呼叫稽核,並納入深層 Kernel Runtime Security Instrumentation (KRSI),以偵測此類混合框架引入的細微行為異常。