“A9 Team 甲方攻防团队,成员来自某证券、微步、青藤、长亭、安全狗等公司。成员能力涉及安全运营、威胁情报、攻防对抗、渗透测试、数据安全、安全产品开发等领域,持续分享安全运营和攻防的思考和实践。”
01
—
简介
eBPF(extended Berkeley Packet Filter)是一种在内核中运行用户定义程序的技术。它用于安全高效地扩展内核的功能,而无需更改内核源代码或加载内核模块。它起源于`BPF`(Berkeley Packet Filter),一个为网络数据包捕获提供高性能的过滤机制。`eBPF`通过扩展`BPF`的功能,使其能够在内核中执行更多类型的程序,提供了更强大的性能监控和安全功能。
我们编写好的`eBPF`程序在执行的时候首先会在用户态通过`clang`/`LLVM`编译器编译成字节码,然后加载到内核空间,进入内核态后会经过验证器进行一系列的验证(进程权限,安全性),接着经过`JIT`编译器编译成机器码指令集加载到指定事件的触发勾子(系统调用,网络事件)上等待触发。
用户空间和内核空间的映射通过哈希表(键值对)共享数据并保持状态。它们是通过带有`BPF_MAP_CREATE`参数的`bpf_cmd`系统调用来创建的,和Linux世界中的其他东西一样,它们是通过文件描述符来寻址。
如何编写eBPF程序?
from __future__ import print_function
from bcc import BPF
import argparse
from time import strftime
# 参数相关
examples = """examples:
./filelife # trace lifecycle of file(create->remove)
./filelife -p 181 # only trace PID 181
"""
parser = argparse.ArgumentParser(
description="Trace lifecycle of file",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=examples)
parser.add_argument("-p", "--pid",
help="trace this PID only")
parser.add_argument("--ebpf", action="store_true",
help=argparse.SUPPRESS)
args = parser.parse_args()
debug = 0
# 定义BPF程序
bpf_text = """
#include <uapi/linux/ptrace.h>
#include <linux/fs.h>
#include <linux/sched.h>
struct data_t {
u32 pid;
u64 delta;
char comm[TASK_COMM_LEN];
char fname[DNAME_INLINE_LEN];
};
BPF_HASH(birth, struct dentry *);
BPF_PERF_OUTPUT(events);
static int probe_dentry(struct pt_regs *ctx, struct dentry *dentry)
{
u32 pid = bpf_get_current_pid_tgid() >> 32;
FILTER
u64 ts = bpf_ktime_get_ns();
birth.update(&dentry, &ts);
return 0;
}
// trace file creation time
TRACE_CREATE_FUNC
{
return probe_dentry(ctx, dentry);
};
// trace file security_inode_create time
int trace_security_inode_create(struct pt_regs *ctx, struct inode *dir,
struct dentry *dentry)
{
return probe_dentry(ctx, dentry);
};
// trace file open time
int trace_open(struct pt_regs *ctx, struct path *path, struct file *file)
{
struct dentry *dentry = path->dentry;
if (!(file->f_mode & FMODE_CREATED)) {
return 0;
}
return probe_dentry(ctx, dentry);
};
// trace file deletion and output details
TRACE_UNLINK_FUNC
{
struct data_t data = {};
u32 pid = bpf_get_current_pid_tgid() >> 32;
FILTER
u64 *tsp, delta;
tsp = birth.lookup(&dentry);
if (tsp == 0) {
return 0; // missed create
}
delta = (bpf_ktime_get_ns() - *tsp) / 1000000;
birth.delete(&dentry);
struct qstr d_name = dentry->d_name;
if (d_name.len == 0)
return 0;
if (bpf_get_current_comm(&data.comm, sizeof(data.comm)) == 0) {
data.pid = pid;
data.delta = delta;
bpf_probe_read_kernel(&data.fname, sizeof(data.fname), d_name.name);
}
events.perf_submit(ctx, &data, sizeof(data));
return 0;
}
"""
trace_create_text_old="""
int trace_create(struct pt_regs *ctx, struct inode *dir, struct dentry *dentry)
"""
trace_create_text_new="""
int trace_create(struct pt_regs *ctx, struct user_namespace *mnt_userns,
struct inode *dir, struct dentry *dentry)
"""
trace_unlink_text_old="""
int trace_unlink(struct pt_regs *ctx, struct inode *dir, struct dentry *dentry)
"""
trace_unlink_text_new="""
int trace_unlink(struct pt_regs *ctx, struct user_namespace *mnt_userns,
struct inode *dir, struct dentry *dentry)
"""
if args.pid:
bpf_text = bpf_text.replace('FILTER',
'if (pid != %s) { return 0; }' % args.pid)
else:
bpf_text = bpf_text.replace('FILTER', '')
if debug or args.ebpf:
print(bpf_text)
if args.ebpf:
exit()
if BPF.kernel_struct_has_field(b'renamedata', b'old_mnt_userns') == 1:
bpf_text = bpf_text.replace('TRACE_CREATE_FUNC', trace_create_text_new)
bpf_text = bpf_text.replace('TRACE_UNLINK_FUNC', trace_unlink_text_new)
else:
bpf_text = bpf_text.replace('TRACE_CREATE_FUNC', trace_create_text_old)
bpf_text = bpf_text.replace('TRACE_UNLINK_FUNC', trace_unlink_text_old)
# 实例化BPF对象
b = BPF(text=bpf_text)
b.attach_kprobe(event="vfs_create", fn_name="trace_create")
# 版本比较新的内核版本不会掉用 fire vfs_create,二是调用 vfs_open instead:
b.attach_kprobe(event="vfs_open", fn_name="trace_open")
if BPF.get_kprobe_functions(b"security_inode_create"):
b.attach_kprobe(event="security_inode_create", fn_name="trace_security_inode_create")
b.attach_kprobe(event="vfs_unlink", fn_name="trace_unlink")
# 打印结果头信息
print("%-8s %-7s %-16s %-7s %s" % ("TIME", "PID", "COMM", "AGE(s)", "FILE"))
# 进程事件
def print_event(data):
event = b["events"].event(data)
print("%-8s %-7d %-16s %-7.2f %s" % (strftime("%H:%M:%S"), event.pid,
event.comm.decode('utf-8', 'replace'), float(event.delta) / 1000,
event.fname.decode('utf-8', 'replace')))
if __name__ == "__main__":
b["events"].open_perf_buffer(print_event)
while True:
try:
b.perf_buffer_poll()
except KeyboardInterrupt:
exit()
04
—
[参考]
1. https://www.infoq.com/articles/gentle-linux-ebpf-introduction/
2. https://ebpf.io/what-is-ebpf/
3. https://coolshell.cn/articles/22320.html