eBPF 隐形杀手:LinkPro 木马深度剖析

本文深入分析了一种名为 LinkPro 的先进 Linux 根工具包。该木马利用 eBPF 技术实现隐蔽性和远程激活能力,包含两个核心模块:一个用于隐藏自身痕迹,另一个则在收到特定“魔法数据包”后激活 C2 通道。

LinkPro: eBPF 根工具包分析

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

引言

eBPF(扩展的伯克利包过滤器)是一种在 Linux 中被广泛采用的技术,因其众多的用例(可观测性、安全性、网络等)以及在内核上下文中运行同时又能从用户空间进行编排的能力而备受青睐。威胁行为者越来越多地滥用它来创建复杂的后门并规避传统的系统监控工具。

诸如 BPFDoor、Symbiote 和 J-magic 等恶意软件展示了 eBPF 在创建被动后门方面的有效性,这些后门能够监控网络流量并在收到特定的“魔法数据包”时激活。此外,更复杂的开源工具,如 ebpfkit(概念验证)和 eBPFeXPLOIT,以及用 Golang 开发的编排器,充当了根工具包的角色,其功能范围涵盖建立秘密命令与控制(C2)通道、进程隐藏和容器逃逸技术。

最近在调查一个被入侵的 AWS 托管基础设施时,Synacktiv CSIRT 确定了一个相对复杂的感染链,导致在 GNU/Linux 系统上安装了一个隐蔽的后门。该后门依赖于安装两个 eBPF 模块:一个用于隐藏自身,另一个在收到“魔法数据包”时被远程激活。

感染链

取证分析确定,一个暴露在互联网上的存在漏洞的 Jenkins 服务器(CVE-2024-23897)是入侵的源头。该服务器随后成为威胁行为者的初始接入点,进而转移到托管在几个 Amazon EKS(Elastic Kubernetes Service,标准模式)集群上的集成和部署管道。

从 Jenkins 服务器,威胁行为者在几个 Kubernetes 集群上部署了一个名为 kvlnt/vv 的恶意 Docker 镜像(在作者提醒后被 Docker Hub 支持团队删除)。该 Docker 镜像基于 Kali Linux,并增加了两个附加层。

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

  • /app/start.sh:一个 bash 脚本,作为 Docker 镜像的入口点。其目的是启动 ssh 服务,执行 /app/app 后门程序和 /app/link 程序。
  • /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 是一个已有记录的 backdoor,特别被 UNC5174 使用。其源代码大约一年前已无法在 GitHub 上获取。然而,最近的一个版本 4.9.3 及其(破解的)许可证可供下载,使得各种行为者都可以使用 vShell。

然而,关于 vGet 没有开源发布,它是用 Rust 开发的并经过剥离。此恶意代码在执行开始时创建了一个从 /dev/null/tmp/.del 的符号链接,然后下载 vShell 负载。vShell 在执行期间,当打开终端(在操作员请求下)时,会初始化环境变量 HISTFILE=/tmp/.del。目的是确保命令历史不会写入文件(例如 .bash_history)。因此,这两个程序之间可能存在联系,并且 vGet 可能是专门为了在内存中直接执行 vShell 而开发的,不在磁盘上留下痕迹。

关于 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(在一些出版物中观察到用于投放 vShell),但目的相同。解密过程分两步进行。以下是 Synacktiv CSIRT 分析的样本摘录:

  • 第 1 步:解密第一个 shellcode,直接执行。
  • 第 2 步:shellcode 解密并将其嵌入的 ELF vShell 后门加载到其内存中。

最后,最终负载是一个未被记录的、Synacktiv CSIRT 命名为 LinkPro 的后门,它利用了 eBPF 技术,由于其隐蔽性、持久性和内部网络横向移动能力,可以被称为根工具包。

LinkPro 根工具包

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

LinkPro 根工具包样本

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

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

LinkPro 嵌入式 ELF 二进制文件

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

下面详细介绍不同的 ELF 模块。然而,LinkPro 从未使用内核模块(没有实现加载它的函数)。

配置与通信

根据其定义的配置,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

  • 反向连接模式对应于被动模式,后门监听来自 C2 的命令。在此模式下,安装了两种类型的 eBPF 程序:eXpress Data Path (XDP) 和 Traffic Control (TC),目的是仅在收到特定 TCP 数据包时才激活 C2 通信通道。
  • 正向连接模式对应于主动模式,后门主动与其 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 中的值。

正向(主动)模式下有五种可能的通信协议:

  • HTTP
  • WebSocket
  • UDP (原始)
  • TCP (原始)
  • DNS (直接/隧道)

对于反向(被动)模式,只使用 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. 如果使用反向模式,安装 “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.sochmod 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” 错误。
  • getdentsgetdents64getdents 系统调用用于列出目录中的文件。在这种情况下,libld.so 执行 getdents,然后检查是否存在以下情况:
    • 包含关键字 .tmp~data(Linkpro 后门)、libld.sosshidsld.so.preload 的文件名。
    • 其命令行包含关键字 .tmp~data 的进程目录(在 /proc/ 下,即 PID)。 如果找到,dirent 条目将被下一个条目覆盖。
  • 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 钩子返回空结果。

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

“Hide” eBPF 模块

LinkPro “Hide” eBPF 模块样本

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

“Hide” 模块由多个 Tracepoint 和 Kretprobe 类型的 eBPF 程序组成。

Tracepoint 类型的 eBPF 程序是附加到 Linux 内核定义的静态跟踪点的程序。它们被放置在内核代码中的特定位置,例如系统调用、内存分配、任务调度等。特别是,系统调用的跟踪点位于入口(tracepoint/syscalls/sys_enter_syscall)或出口(tracepoint/syscalls/sys_exit_syscall)。

Kprobes(内核探针)允许将 eBPF 程序附加到内核中几乎任何函数的入口点。Kretprobes 则在函数返回时触发。这允许拦截和修改系统调用的结果。

LinkPro 根工具包安装这些 eBPF 程序,并利用它们的能力来隐藏其进程和网络活动。

“Hide” 模块安装

首先,LinkPro 使用 ebpf-go 模块将嵌入的 “Hide” ELF 模块解析为特定对象(CollectionSpec)。可以在内存中找到加载的 Hide 模块的不同 eBPF 对象,即映射和程序。映射是可以在程序之间共享的数据结构。

LinkPro 使用当前 LinkPro 的 PID(进程 ID)以及命令行上指定的 PID 列表(-pid 参数)更新 pids_to_hide_map 映射。

LinkPro 更新 main_ebpf_progs 映射,添加 LinkPro 加载的 eBPF 程序的文件描述符(“Hide” 和 “Knock” 模块)。

然后,LinkPro 创建 BPF 链接,将 eBPF 程序附加到内核中的检测点。LinkPro 后门加载的 “Hide” eBPF 模块向 getdentsgetdents64 系统调用添加钩子,目标与 libld.so 共享库相同,即隐藏 LinkPro 特定的文件。此外,钩子被放置在 sys_bpf 系统调用的入口和出口处,以隐藏其 eBPF 映射和程序。以下是实现的钩子:

  • 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.so 类似。拦截 getdents 系统调用(sys_exit_getdents)的结果。eBPF 程序设置过滤器以隐藏:

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

getdents 系统调用返回一个 dirent 结构体的链表,其中每个结构体代表一个文件或目录,并包含其名称、inode 和条目大小等信息,这些信息允许计算下一个元素的位置。与 libld.sogetdents 上的钩子不同,dirent 条目不会被下一条记录直接覆盖。

为了隐藏名为 .tmp~data.resolveld 的文件,修改 dirent 结构链表的方式如下:

  1. eBPF 修改前:链表是一系列记录。每个 dirent 通过其自身的长度(d_reclen)隐式指向下一个。
  2. eBPF 修改后:eBPF 程序检测到 .tmp~data.resolveld。然后,它修改前一条记录(文件 A)的长度(d_reclen),将 .tmp~data.resolveld 的长度加进去。

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

handle_ebpf

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

handlerBpfEnter 跟踪点将 BPF_PROG_GET_NEXT_IDBPF_MAP_GET_NEXT_IDBPF_LINK_GET_NEXT_ID 类型的 BPF 命令保存在一个映射中。sys_bpf 系统调用执行后,触发 handlerBpfExit 钩子。

检索关联命令的结构,现在包含系统调用的结果,即下一个程序、映射或链接的 ID。 如果 cmd_info.attr_pr.next_id 的值等于 main_ebpfs_progs 映射中存在的某个对象的 ID,那么 sys_bpf 的返回值将被替换为 -ENOENT(没有这样的文件或目录)。

需要注意的是,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 根工具包

“Knock” 模块包含两个由 LinkPro 加载的 eBPF 程序。

第一个称为 xdp_ingress,属于 XDP(eXpress Data Path)类型。 XDP 提供了一种通过 eBPF 程序处理网络数据包的机制。它位于处理链的非常早期,在驱动程序级别,在经典的 Linux 网络堆栈之前。XDP eBPF 程序使用返回码(例如 XDP_PASSXDP_DROPXDP_REDIRECT)来决定 Linux 内核应对网络数据包采取的操作。

第二个称为 tc_egress,属于 TC(Traffic Control)类型。 tc 是由 iproute2 包引入的工具,允许控制接口上的传入(ingress)和传出(egress)网络流量。可以将 BPF 程序附加到不同的 TC 控制点,例如在数据包发送之前过滤某些数据包。TC 位于驱动程序和网络堆栈之间,即 XDP 的下游。XDP 程序只能附加到传入流量,不能附加到传出流量,这在这种情况下证明了使用 TC 的合理性。

“Knock” 模块安装

安装 xdp_ingresstc_egress 程序需要几个步骤:

  1. 检测用于与互联网通信的网络接口(例如 eth0)。
  2. 在 BPF FS 中创建一个 fire 目录。路径:/sys/fs/bpf/fire。BPF FS 是一个伪虚拟文件系统(仅驻留在内存中),允许存储 BPF 程序和映射,以及固定的对象(确保通过 BPF FS 中的伪文件保持对这些对象的引用以确保其持久性)。
  3. 将 “Knock” 模块加载到内存中(CollectionSpec 对象)。
  4. 使用 LinkPro 配置中存在的 reverse_port 属性的值更新 conf_map BPF 映射:在此上下文中为端口 2233。
  5. 安装 xdp_ingress 程序:
    • 分离已链接到网络接口的任何 XDP 程序:ip link set dev eth0 xdp off
    • 通过创建 BPF 链接将 xdp_ingress 程序附加到网络接口
  6. 安装 tc_egress 程序:
    • tc_egress 程序固定到 /sys/fs/bpf/fire/tc_egress。这意味着它已经被另一个进程(LinkPro)加载到内存中,并固定在 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

xdp_ingress

xdp_ingress eBPF 程序监听附加网络接口上的传入流量(提醒:被 LinkPro 识别为具有互联网访问权限)。该程序监控是否收到魔法数据包。

此魔法数据包必须具有以下特征:一个 TCP 协议数据包,类型为 SYN,其窗口大小值 tcp_header->windows_size 为 54321。 如果验证了这样的数据包,xdp_ingress 程序会将一个键保存到 knock_map 映射中,键为数据包的源 IP,其关联的过期时间(一小时)作为值,表示开放状态。 此外,程序在 rev_port 映射中保存以下键/值对:键:rev_key = { in_port, sip, sport }(sip = 源 IP,sport = 源端口),值:dport(目标端口)。in_port 等于存储在 conf_map 中的值,即 2233。 最后,xdp_ingress 程序返回 XDP_DROP 代码,指示 Linux 内核立即丢弃魔法数据包。程序已针对此特定源 IP 地址转换为“开放”状态。

开放状态xdp_ingress 程序监控是否收到 TCP 数据包,其源 IP 地址与已注册在 knock_map 中的地址相同,且在收到魔法数据包后的一小时窗口内。 在这种情况下,如果目标端口尚未对应于 in_port(2233)的值,那么 xdp_ingress 会修改传入数据包的 TCP 报头,将目标端口值替换为 in_port。此外,为了防止数据包在下游被内核丢弃,TCP 校验和 tcp_header->check_sum 也会被重新计算并修改在 TCP 报头中。最后,xdp_ingress 返回 XDP_PASS 代码,将数据包传递给网络堆栈的其余部分。

最后,如果使用目标端口 9999,程序会显示额外的内核调试消息。

tc_egress

tc_egress eBPF 程序监听附加网络接口上的传出流量。该程序监控是否发送源端口为 in_port(2233)的 TCP 数据包。

如果收到这样的数据包,程序检查 rev_port 映射中是否存在键 rev_key = { in_port, dip, dport }(dip = 目标 IP),该键先前由 xdp_ingress 保存。 如果找到,则修改传出数据包的 TCP 报头,以恢复传入数据包的原始目标端口(该端口已被 xdp_ingress 替换),在传出数据包的源端口级别。校验和也会被重新计算。最后,在所有情况下,数据包继续其处理(返回 TC_ACT_OK 代码)。

因此,LinkPro 的目标是在收到初始“魔法数据包”时激活命令接收状态。一旦收到魔法数据包,操作员就有一个一小时的时间窗口(稍后可以重新激活)向任意目标端口发送命令。xdp_ingress 程序的作用是修改传入 TCP 数据包的报头,将原始目标端口替换为 LinkPro 的监听端口,在此上下文中为 2233。 最后,当 LinkPro 响应操作员的命令时,tc_egress 程序的作用是修改传出数据包,将源端口(2233)替换为原始端口。此操作的目的是允许操作员通过任何前端防火墙允许的端口来激活 LinkPro 的命令接收。这也使得前端防火墙日志与被入侵主机的网络活动之间的关联更加复杂。例如:操作员将其命令发送到被入侵 Web 服务器的端口 443/https,而实际上数据包在服务器内部被转发到端口 2233。

持久化

为了在主机上持久化,LinkPro 将自己“伪装”成 systemd-resolved 服务(名称解析服务)。

  1. 通过执行命令挂载具有读写权限的根分区 /mount -o remount,rw /
  2. 将其自身的可执行文件复制到 /usr/lib/.system/.tmp~data.resolveld
  3. /etc/systemd/system/systemd-resolveld.service 中添加一个 systemd 单元文件:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
[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=true
  1. /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
  2. 启用 systemd-resolveld 服务在系统启动时启动:systemctl enable systemd-resolveld

自删除

在中断时(SIGHUPSIGINTSIGTERM 信号),LinkPro 卸载其程序:

  1. Knock 模块
    • 通过执行 tc 命令删除 tc_egress eBPF 链接:tc filter del dev eth0 egresseth0
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计