内容简介:前置文章:eBPF简介、安装和简单示例:http://kerneltravel.net/blog/2020/ebpf_ljr_no1/
前置文章:
eBPF简介、安装和简单示例:
http://kerneltravel.net/blog/2020/ebpf_ljr_no1/
获取内核网络中的SOCKET信息:
http://kerneltravel.net/blog/2020/ebpf_ljr_no2/
程序采集系统相关参数时,通常有两种方式。一种是程序主动去轮询,检查系统变化,即 poll 模型;另一种是系统主动通知程序,即 push 模型。使用 poll 模型还是 push 模型取决于具体的问题。通常情况下,如果事件频率相对于事件处理时间来说比较低,那 push 模型比较合适;如果事件频率很高,就采用 pull 模型。例如,通常的网络驱动会等待网卡事件,而 dpdk 这样的框架会主动 poll 网卡, 以获得最高的吞吐性能和最低的延迟。理想情况下,我们需要一个通用的方式处理事件。
Linux 4.4 以上内核基于 eBPF 可以将任何内核函数调用转换成可带任何数据的用户空间事件。
本文将使用 bcc工具抓取内核网络中的数据,包括抓取 backlog 信息、 IP 地址、端口号和网络命名空间信息等。修改上一篇文章的代码并使用bpf_probe_read读取到相应变量的地址,使用 perf 使得 bpf_trace_printk 带三个以上参数,获取IP信息,端口、backlog 信息和网络命名空间。
上回,我们说到 bpf_trace_printk
带的参数太多了,会出现 error: <unknown>:0:0: in function kprobe__inet_listen i32 (%struct.pt_regs*): too many args to 0x55a83e8f8320: i64 = Constant<6>
这样的错误,这是 BPF 的限制。解决这个问题的办法就是使用 perf,它支持传递任意大小的结构体到用户空间。
我们对比原来的代码进行修改,原代码如下:
from bcc import BPF # Hello BPF Program bpf_text = """ #include <net/inet_sock.h> #include <bcc/proto.h> #include <net/sock.h> // 1. Attach kprobe to "inet_listen" int kprobe__inet_listen(struct pt_regs *ctx, struct socket *sock, int backlog) { // cast types. Intermediate cast not needed, kept for readability struct sock *sk = sock->sk; struct inet_sock *inet = (struct inet_sock *)sk; // Create an populate the variable u32 netns = 0; // Read the netns inode number, like /proc does netns = sk->__sk_common.skc_net.net->ns.inum; bpf_trace_printk("Listening on %x %d with %d pending connections in container %d \\n", inet->inet_rcv_saddr, inet->inet_sport, backlog, netns); return 0; }; """ # 2. Build and Inject program b = BPF(text=bpf_text) # 3. Print debug output while True: print b.trace_readline()
运行时会出现如下错误:
要使用 perf,我们需要:
-
定义一个结构体
-
声明一个事件
-
推送(push)事件
-
Python 端再定义一遍这个事件(将来这一步就不需要了)
-
消费并格式化输出事件
为了使内核检测器验证这个程序的内存访问是合法的,我们让内存访问变得更加显式,使用受信任的 bpf_probe_read
函数,可以用它读取任何内存地址。
为了使得程序可以正常运行, bpf_trace_printk
先使用三个参数,只获取IP信息,端口和 backlog 信息,将程序改为:
from bcc import BPF # BPF Program bpf_text = """ #include <net/sock.h> #include <net/inet_sock.h> #include <bcc/proto.h> // Send an event for each IPv4 listen with PID, bound address and port int kprobe__inet_listen(struct pt_regs *ctx, struct socket *sock, int backlog) { // Cast types. Intermediate cast not needed, kept for readability struct sock *sk = sock->sk; struct inet_sock *inet = inet_sk(sk); // Working values. You *need* to initialize them to give them "life" on the stack and use them afterward u32 laddr = 0; u16 lport = 0; // Pull in details. As 'inet_sk' is internally a type cast, we need to use 'bpf_probe_read' // read: load into 'laddr' 'sizeof(laddr)' bytes from address 'inet->inet_rcv_saddr' bpf_probe_read(&laddr, sizeof(laddr), &(inet->inet_rcv_saddr)); bpf_probe_read(&lport, sizeof(lport), &(inet->inet_sport)); // Push event bpf_trace_printk("Listening on %x %d with %d pending connections\\n", ntohl(laddr), ntohs(lport), backlog); return 0; }; """ # Build and Inject BPF b = BPF(text=bpf_text) # Print debug output while True: print b.trace_readline()
运行程序,在另一终端使用nc小 工具 建立单连接,结果如下:
可以看到,我们已经成功地使用 bpf_probe_read
读取到了相应变量的地址,获取了IP信息,端口和 backlog 信息。接下来我们使用perf,修改程序,使得 bpf_trace_printk
带的参数超过三个。
在bcc程序的c程序中加入以下代码:
struct listen_evt_t { u64 laddr; u64 lport; u64 netns; u64 backlog; }; BPF_PERF_OUTPUT(listen_evt);
在 kprobe__inet_listen
函数中使用以下代码代替 bpf_trace_printk
:
struct listen_evt_t evt = { .laddr = ntohl(laddr), .lport = ntohs(lport), .netns = netns, .backlog = backlog, }; listen_evt.perf_submit(ctx, &evt, sizeof(evt));
在 python 中加入以下代码:
# We need ctypes to parse the event structure import ctypes # Declare data format class ListenEvt(ctypes.Structure): _fields_ = [ ("laddr", ctypes.c_ulonglong), ("lport", ctypes.c_ulonglong), ("netns", ctypes.c_ulonglong), ("backlog", ctypes.c_ulonglong), ] # Declare event printer def print_event(cpu, data, size): event = ctypes.cast(data, ctypes.POINTER(ListenEvt)).contents print("Listening on %x %d with %d about %d" % ( event.laddr, event.lport, event.backlog, event.netns, ))
使用以下代码代替python中的循环输出:
b["listen_evt"].open_perf_buffer(print_event) while True: b.kprobe_poll()
最终修改后的代码如下:
from bcc import BPF # We need ctypes to parse the event structure import ctypes # Declare data format class ListenEvt(ctypes.Structure): _fields_ = [ ("laddr", ctypes.c_ulonglong), ("lport", ctypes.c_ulonglong), ("netns", ctypes.c_ulonglong), ("backlog", ctypes.c_ulonglong), ] # Declare event printer def print_event(cpu, data, size): event = ctypes.cast(data, ctypes.POINTER(ListenEvt)).contents print("Listening on %x %d with %d about process %d" % ( event.laddr, event.lport, event.backlog, event.netns, )) # BPF Program bpf_text = """ #include <net/sock.h> #include <net/inet_sock.h> #include <bcc/proto.h> struct listen_evt_t { u64 laddr; u64 lport; u64 netns; u64 backlog; }; BPF_PERF_OUTPUT(listen_evt); // Send an event for each IPv4 listen with PID, bound address and port int kprobe__inet_listen(struct pt_regs *ctx, struct socket *sock, int backlog) { // Cast types. Intermediate cast not needed, kept for readability struct sock *sk = sock->sk; struct inet_sock *inet = inet_sk(sk); // Working values. You *need* to initialize them to give them "life" on the stack and use them afterward u32 laddr = 0; u16 lport = 0; // Create an populate the variable u32 netns = 0; // Read the netns inode number, like /proc does netns = sk->__sk_common.skc_net.net->ns.inum; // Pull in details. As 'inet_sk' is internally a type cast, we need to use 'bpf_probe_read' // read: load into 'laddr' 'sizeof(laddr)' bytes from address 'inet->inet_rcv_saddr' bpf_probe_read(&laddr, sizeof(laddr), &(inet->inet_rcv_saddr)); bpf_probe_read(&lport, sizeof(lport), &(inet->inet_sport)); // Push event struct listen_evt_t evt = { .laddr = ntohl(laddr), .lport = ntohs(lport), .netns = netns, .backlog = backlog, }; listen_evt.perf_submit(ctx, &evt, sizeof(evt)); //bpf_trace_printk("Listening on %x %d with %d pending connections\\n", ntohl(laddr), ntohs(lport), backlog); return 0; }; """ # Build and Inject BPF b = BPF(text=bpf_text) # Print debug output b["listen_evt"].open_perf_buffer(print_event) while True: b.kprobe_poll()
运行程序,在另一终端使用nc小工具建立单连接:
程序结果如下:
可以看到,我们已经成功地使用 bpf_probe_read
读取到了相应变量的地址,使用perf使得 bpf_trace_printk
带了四个参数,获取了IP信息,端口、backlog 信息和网络命名空间。使用了 eBPF 将内核的函数调用转换成事件触发的方式。
参考链接:
https://blog.yadutaf.fr/2016/03/30/turn-any-syscall-into-event-introducing-ebpf-kernel-probes/
以上所述就是小编给大家介绍的《eBPF内核探测将任意系统调用转换成事件》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- [译] eBPF内核探测:如何将任意系统调用转换成事件
- 渗透&&探测 (之UDP探测篇)
- 渗透测试之子域名探测指南
- 使用 JavaScript 探测网络状态
- WebP 可用性探测
- XSS WAF规则探测与绕过
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。