eBPF 开发入门一

admin 2023年11月20日21:30:54评论9 views字数 4356阅读14分31秒阅读模式

eBPF 在云原生中很重要,所以来学习一下,第一次接触该系列可能会很懵逼,即使是我已经从原项目中抛开次要提取出最主要的部分并做了详细的解释。可以多看几遍然后跑下代码和问 openai。

开发环境

Ubuntu20.04 64位

下载安装 eunomia-bpf 开发工具

# 下载 ecli 工具,用于运行 eBPF 程序wget https://aka.pw/bpf-ecli -O ecli && chmod +x ./ecli./ecli -h
# 下载编译器工具链,用于将 eBPF 内核代码编译为 config 文件或 WASM 模块wget https://github.com/eunomia-bpf/eunomia-bpf/releases/latest/download/ecc && chmod +x ./ecc./ecc -h
sudo apt install clang llvm


处理一下问题

vim ~/.bashrc  # 设置环境变量 BTF_FILE_PATH,将下行添加到文件的末尾。正在运行的内核的 vmlinux 文件,通常位于 /boot 目录下。  # 比如我的是 /boot/vmlinuz-5.4.0-70-generic  export BTF_FILE_PATH=/boot/vmlinuz-5.4.0-70-genericsource ~/.bashrc


代码案例

检测并打印从指定PID进入write系统调用的事件

这段程序是一个基于eBPF的跟踪程序,它捕获并记录从指定进程ID(PID)执行的 write 系统调用。这个程序被注册为 sys_enter_write 的一个跟踪点,当系统中的某个进程执行 write 系统调用时,它将被触发,并输出该进程的 PID。

# 使用 ecc 编译程序./ecc minimal.bpf.c# 使用 ecli 运行编译后的程序./ecli run package.json
# 运行上面命令后,可以通过查看 /sys/kernel/debug/tracing/trace_pipe 文件来查看 eBPF 程序的输出cat /sys/kernel/debug/tracing/trace_pipe | grep "BPF triggered sys_enter_write"
# 输出 sshd-17097 [000] .... 1256.254917: 0: BPF triggered sys_enter_write from PID 17097. sshd-17097 [000] .... 1256.254939: 0: BPF triggered sys_enter_write from PID 17097. grep-25513 [000] .... 1256.254960: 0: BPF triggered sys_enter_write from PID 25513. grep-25513 [000] .... 1256.254971: 0: BPF triggered sys_enter_write from PID 25513. grep-25513 [000] .... 1256.254977: 0: BPF triggered sys_enter_write from PID 25513. grep-25513 [000] .... 1256.254983: 0: BPF triggered sys_enter_write from PID 25513. grep-25513 [000] .... 1256.254990: 0: BPF triggered sys_enter_write from PID 25513. grep-25513 [000] .... 1256.254996: 0: BPF triggered sys_enter_write from PID 25513.


‍minimal.bpf.c

/* 定义许可证,通常使用 "Dual BSD/GPL" *//* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
/* 禁用全局数据 */#define BPF_NO_GLOBAL_DATA
/* eBPF的核心定义 */#include <linux/bpf.h>
/* BPF辅助函数和跟踪功能 */#include <bpf/bpf_helpers.h>#include <bpf/bpf_tracing.h>
typedef unsigned int u32;typedef int pid_t;const pid_t pid_filter = 0;char LICENSE[] SEC("license") = "Dual BSD/GPL";
/* 定义一个 handle_tp 函数并使用 SEC 宏把它附加到 sys_enter_write tracepoint(即在进入 write 系统调用时执行)。*/SEC("tp/syscalls/sys_enter_write")int handle_tp(void* ctx){ /* 获取当前进程的PID,eBPF中没有直接获取PID的方法,可以使用下方函数获取当前线程的PID和TID(线程ID),右移32位可以获得PID*/ pid_t pid = bpf_get_current_pid_tgid() >> 32; if (pid_filter && pid != pid_filter) return 0; /* 打印从特定PID进入 sys_enter_write 的消息。*/ bpf_printk("BPF triggered sys_enter_write from PID %d.n", pid); return 0;}


捕获进程打开文件的系统调用

当进程打开一个文件时,它会向内核发出 sys_openat 系统调用,并传递相关参数(例如文件路径、打开模式等)。内核会处理这个请求,并返回一个文件描述符(file descriptor),这个描述符将在后续的文件操作中用作引用。通过捕获 sys_openat 系统调用,我们可以了解进程在什么时候以及如何打开文件。

下面程序将捕获指定进程(或所有进程)的 sys_openat 系统调用,并在用户空间输出相关信息。

./ecc opensnoop.bpf.c./ecli run package.json
# 通过查看 /sys/kernel/debug/tracing/trace_pipe 文件来查看 eBPF 程序的输出cat /sys/kernel/debug/tracing/trace_pipe# 输出 <...>-165144 [000] .... 14738.287516: 0: Process ID: 165144 enter sys openat <...>-165145 [000] .... 14738.288076: 0: Process ID: 165145 enter sys openat <...>-165145 [000] .... 14738.288155: 0: Process ID: 165145 enter sys openat <...>-165145 [000] .... 14738.288357: 0: Process ID: 165145 enter sys openat <...>-165145 [000] .... 14738.288478: 0: Process ID: 165145 enter sys openat <...>-165145 [000] .... 14738.288564: 0: Process ID: 165145 enter sys openat <...>-165145 [000] .... 14738.288615: 0: Process ID: 165145 enter sys openat <...>-165145 [000] .... 14738.288668: 0: Process ID: 165145 enter sys openat <...>-165146 [000] .... 14738.289536: 0: Process ID: 165146 enter sys openat <...>-165146 [000] .... 14738.289623: 0: Process ID: 165146 enter sys openat

使用全局变量在 eBPF 中过滤进程 pid。全局变量在 eBPF 程序中充当一种数据共享机制,它们允许用户态程序与 eBPF 程序之间进行数据交互。这在过滤特定条件或修改 eBPF 程序行为时非常有用。这种设计使得用户态程序能够在运行时动态地控制 eBPF 程序的行为。下面全局变量 pid_target 用于过滤进程 PID。用户态程序可以设置此变量的值,以便在 eBPF 程序中只捕获与指定 PID 相关的 sys_openat 系统调用。

./ecli run package.json -- --pid_target 165146

opensnoop.bpf.c

/* 禁用全局数据 */#define BPF_NO_GLOBAL_DATA
/* 包含了内核数据结构的定义 */#include <vmlinux.h>
/* BPF辅助函数 */#include <bpf/bpf_helpers.h>
/* 用于过滤指定进程 ID。这里设为 0 表示捕获所有进程的 sys_openat 调用 */const volatile int pid_target = 0;
/* 使用 SEC 宏定义一个 eBPF 程序,关联到 tracepoint "tracepoint/syscalls/sys_enter_openat"。这个 tracepoint 会在进程发起 sys_openat 系统调用时触发。*/SEC( "tracepoint/syscalls/sys_enter_openat" )int tracepoint__syscalls__sys_enter_openat( struct trace_event_raw_sys_enter* ctx ) /* trace_event_raw_sys_enter 包含了关于系统调用的信息*/{ /* 获取当前进程的PID,eBPF中没有直接获取PID的方法,可以使用下方函数获取当前线程的PID和TID(线程ID),右移32位可以获得PID*/ u64 id = bpf_get_current_pid_tgid(); u32 pid = id >> 32;
if ( pid_target && pid_target != pid ) return(false); bpf_printk( "Process ID: %d enter sys openatn", pid ); return(0);}
/* 将程序许可证设置为 "GPL",这是运行 eBPF 程序的必要条件 */char LICENSE[] SEC( "license" ) = "GPL";


参考资料

https://github.com/eunomia-bpf/bpf-developer-tutoria


封面图

eBPF 开发入门一

图源:https://github.com/eunomia-bpf/bpf-developer-tutorial

原文始发于微信公众号(安全小将李坦然):eBPF 开发入门一

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年11月20日21:30:54
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   eBPF 开发入门一https://cn-sec.com/archives/2220576.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息