什么是ebpf
eBPF 全称 extended Berkeley Packet Filter,中文意思是 扩展的伯克利包过滤器。一般来说,要向内核添加新功能,需要修改内核源代码或者编写 内核模块 来实现。而 eBPF 允许程序在不修改内核源代码,或添加额外的内核模块情况下运行
用途
ebpf由于直接与内核交互,所以更为底层,可以用来绕过一些安全组件的监控功能。同时可以做到如你看不见的后门(通过hook read的syscall,篡改返回结果)等等更有意思的场景。个人看来,ebpf在未来会得到更广泛的应用,同时也是以后rootkit的首选
简单使用
今天这个demo是个入门,原理就是hook syscall,判断是否是execve,然后获取对应的pid和执行文件,然后返回并输出
python
整体代码还是很简单的,注释里面也比较详细了
from bcc import BPFbpf_text = """
#include <linux/binfmts.h>
struct data_t {int pid;char comm[TASK_COMM_LEN];char filename[256];
};BPF_PERF_OUTPUT(myevents);//注册eventsTRACEPOINT_PROBE(raw_syscalls, sys_enter) {if (args->id != __NR_execve)return 0;struct data_t data = {};data.pid = bpf_get_current_pid_tgid() >> 32;//获取pidbpf_get_current_comm(&data.comm, sizeof(data.comm));//获取当前进程的命令名称bpf_probe_read_user(&data.filename, sizeof(data.filename), (void *)args->args[0]);//用户空间读取execve的第一个参数,通常是要执行的文件路径myevents.perf_submit(args, &data, sizeof(data));// 将data结构体的内容发送到用户空间的events性能事件输出return 0;
}
"""b = BPF(text=bpf_text)#创建了一个BPF对象b,它用于编译和加载前文中定义的eBPF程序def print_event(cpu, data, size):#它是一个回调函数,用于处理从eBPF程序发送到用户空间的事件。函数的参数包括:cpu:捕获事件的CPU编号。,data:一个包含eBPF事件数据的字节流。,size:数据的大小。event = b["myevents"].event(data)#将data字节流转换成一个Python对象,这个对象包含了与eBPF程序中定义的数据结构匹配的字段print("New process created, PID: %d, Command: %s, Path: %s" % (event.pid, event.comm.decode('utf-8', 'replace'), event.filename.decode('utf-8', 'replace')))b["myevents"].open_perf_buffer(print_event)#这行代码将print_event回调函数附加到名为myevents的eBPF性能事件输出上。当myevents输出中有新的事件时,print_event函数将被调用。print("Listening for sys_execve calls... Press Ctrl+C to exit.")
try:while True:b.perf_buffer_poll()#用于轮询性能事件缓冲区,检查是否有任何新的事件到达。如果有,它将调用open_perf_buffer登记的回调函数来处理事件。
except KeyboardInterrupt:pass
运行效果
go
go代码更为复杂一点,但大体逻辑上类似
package mainimport ("bytes""encoding/binary""fmt""os""os/signal""github.com/iovisor/gobpf/bcc"
)const bpfProgram = `
#include <linux/sched.h>struct data_t {u32 pid;char comm[TASK_COMM_LEN];char filename[256];
};BPF_PERF_OUTPUT(myevents);TRACEPOINT_PROBE(raw_syscalls, sys_enter) {if (args->id != __NR_execve) return 0;struct data_t data = {};data.pid = bpf_get_current_pid_tgid() >> 32;bpf_get_current_comm(&data.comm, sizeof(data.comm));bpf_probe_read_user(&data.filename, sizeof(data.filename), (void *)args->args[0]);myevents.perf_submit(args, &data, sizeof(data));return 0;
}`type execveEvent struct {Pid uint32Comm [16]byteFilename [256]byte
}func main() {//创建了一个新的 eBPF 模块,并加载了 eBPF 程序。defer module.Close() 确保在程序结束时清理和关闭 eBPF 模块module := bcc.NewModule(bpfProgram, []string{})defer module.Close()//加载 eBPF 程序中定义的跟踪点tracepoint, err := module.LoadTracepoint("tracepoint__raw_syscalls__sys_enter")if err != nil {fmt.Fprintf(os.Stderr, "Failed to load tracepoint: %s\n", err)os.Exit(1)}//将跟踪点附加到系统调用的入口点err = module.AttachTracepoint("raw_syscalls:sys_enter", tracepoint)if err != nil {fmt.Fprintf(os.Stderr, "Failed to attach tracepoint: %s\n", err)os.Exit(1)}//创建一个新的性能事件表来接收 eBPF 程序发送的事件,并创建一个 Go 通道来接收这些事件table := bcc.NewTable(module.TableId("myevents"), module)channel := make(chan []byte)perfMap, err := bcc.InitPerfMap(table, channel, nil)if err != nil {fmt.Fprintf(os.Stderr, "Failed to init perf map: %s\n", err)os.Exit(1)}go func() {for {data := <-channelvar event execveEventerr := binary.Read(bytes.NewBuffer(data), binary.LittleEndian, &event)if err != nil {fmt.Fprintf(os.Stderr, "Failed to decode received data: %s\n", err)continue}filename := string(bytes.Split(event.Filename[:], []byte("\x00"))[0])fmt.Printf("execve called by PID %d (%s): %s\n", event.Pid, event.Comm, filename)}}()//启动性能事件表来接收事件,并确保在程序结束时停止它perfMap.Start()defer perfMap.Stop()fmt.Println("Waiting for execve events... Press Ctrl-C to exit.")signals := make(chan os.Signal, 1)signal.Notify(signals, os.Interrupt)<-signals
}
效果
但是这两个都有一个共性的问题,python虽然可以是pyinstaller去编译成可执行文件,但和go编译的结果都是动态链接的,而往往目标机器上可能没有对应的库,所以我们要转化为静态链接,目前这两个demo我尝试了一下是无法变成静态链接的,如果有思路也可以进一步交流