什么是ebpf
eBPF 全称 extended Berkeley Packet Filter,中文意思是 扩展的伯克利包过滤器。一般来说,要向内核添加新功能,需要修改内核源代码或者编写 内核模块 来实现。而 eBPF 允许程序在不修改内核源代码,或添加额外的内核模块情况下运行
用途
ebpf由于直接与内核交互,所以更为底层,可以用来绕过一些安全组件的监控功能。同时可以做到如你看不见的后门(通过hook read的syscall,篡改返回结果)等等更有意思的场景。个人看来,ebpf在未来会得到更广泛的应用,同时也是以后rootkit的首选
简单使用
今天这个demo是个入门,原理就是hook syscall,判断是否是execve,然后获取对应的pid和执行文件,然后返回并输出
python
整体代码还是很简单的,注释里面也比较详细了
from bcc import BPF
bpf_text = """
#include <linux/binfmts.h>
struct data_t {
int pid;
char comm[TASK_COMM_LEN];
char filename[256];
};
BPF_PERF_OUTPUT(myevents);//注册events
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;//获取pid
bpf_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 main
import (
"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 uint32
Comm [16]byte
Filename [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 := <-channel
var event execveEvent
err := 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我尝试了一下是无法变成静态链接的,如果有思路也可以进一步交流