linux跟踪技术之ebpf

打印 上一主题 下一主题

主题 895|帖子 895|积分 2685

ebpf简介

eBPF是一项革命性的技术,起源于 Linux 内核,可以在操作系统内核等特权上下文中运行沙盒程序。它可以安全有效地扩展内核的功能,而无需更改内核源代码或加载内核模块。 比如,使用ebpf可以追踪任何内核导出函数的参数,返回值,以实现kernel hook 的效果;通过ebpf还可以在网络封包到达内核协议栈之前就进行处理,这可以实现流量控制,甚至隐蔽通信。
ebpf追踪

ebpf本质上只是运行在linux 内核中的虚拟机,要发挥其强大的能力还是要跟linux kernel 自带的追踪功能搭配:

  • kprobe
  • uprobe
  • tracepoint
  • USDT
通常可以通过以下三种工具使用ebpf:

  • bcc
  • libbpf
  • bpftrace
bcc

BCC 是一个用于创建高效内核跟踪和操作程序的工具包,包括几个有用的工具和示例。它利用扩展的 BPF(Berkeley Packet Filters),正式名称为 eBPF,这是 Linux 3.15 中首次添加的新功能。BCC 使用的大部分内容都需要 Linux 4.1 及更高版本。
源码安装bcc v0.25.0

首先clone bcc 源码仓库
git clone https://github.com/iovisor/bcc.git git checkout v0.25.0 git submodule init git submodule update
bcc 从v0.10.0开始使用libbpf 并通过submodule 的形式加入源码树,所以这里需要更新并拉取子模块
安装依赖
apt install flex bison libdebuginfod-dev libclang-14-dev
编译bcc
mkdir build && cd build cmake -DCMAKE_BUILD_TYPE=Release .. make -j #n取决于机器的cpu核心数
编译安装完成后,在python3中就能使用bcc模块了 安装bcc时会在/usr/share/bcc目录下安装bcc自带的示例脚本和工具脚本,以及manual 文档 可以直接使用man -M /usr/share/bcc/man 来查询
使用python + bcc 跟踪内核函数

bcc 自带的工具execsnoop可以跟踪execv系统调用,其源代码如下:
  1. #!/usr/bin/python
  2. # @lint-avoid-python-3-compatibility-imports
  3. #
  4. # execsnoop Trace new processes via exec() syscalls.
  5. #           For Linux, uses BCC, eBPF. Embedded C.
  6. #
  7. # USAGE: execsnoop [-h] [-T] [-t] [-x] [-q] [-n NAME] [-l LINE]
  8. #                  [--max-args MAX_ARGS]
  9. #
  10. # This currently will print up to a maximum of 19 arguments, plus the process
  11. # name, so 20 fields in total (MAXARG).
  12. #
  13. # This won't catch all new processes: an application may fork() but not exec().
  14. #
  15. # Copyright 2016 Netflix, Inc.
  16. # Licensed under the Apache License, Version 2.0 (the "License")
  17. #
  18. # 07-Feb-2016   Brendan Gregg   Created this.
  19. from __future__ import print_function
  20. from bcc import BPF
  21. from bcc.containers import filter_by_containers
  22. from bcc.utils import ArgString, printb
  23. import bcc.utils as utils
  24. import argparse
  25. import re
  26. import time
  27. import pwd
  28. from collections import defaultdict
  29. from time import strftime
  30. def parse_uid(user):
  31.     try:
  32.         result = int(user)
  33.     except ValueError:
  34.         try:
  35.             user_info = pwd.getpwnam(user)
  36.         except KeyError:
  37.             raise argparse.ArgumentTypeError(
  38.                 "{0!r} is not valid UID or user entry".format(user))
  39.         else:
  40.             return user_info.pw_uid
  41.     else:
  42.         # Maybe validate if UID < 0 ?
  43.         return result
  44. # arguments
  45. examples = """examples:
  46.     ./execsnoop           # trace all exec() syscalls
  47.     ./execsnoop -x        # include failed exec()s
  48.     ./execsnoop -T        # include time (HH:MM:SS)
  49.     ./execsnoop -U        # include UID
  50.     ./execsnoop -u 1000   # only trace UID 1000
  51.     ./execsnoop -u user   # get user UID and trace only them
  52.     ./execsnoop -t        # include timestamps
  53.     ./execsnoop -q        # add "quotemarks" around arguments
  54.     ./execsnoop -n main   # only print command lines containing "main"
  55.     ./execsnoop -l tpkg   # only print command where arguments contains "tpkg"
  56.     ./execsnoop --cgroupmap mappath  # only trace cgroups in this BPF map
  57.     ./execsnoop --mntnsmap mappath   # only trace mount namespaces in the map
  58. """
  59. parser = argparse.ArgumentParser(
  60.     description="Trace exec() syscalls",
  61.     formatter_class=argparse.RawDescriptionHelpFormatter,
  62.     epilog=examples)
  63. parser.add_argument("-T", "--time", action="store_true",
  64.     help="include time column on output (HH:MM:SS)")
  65. parser.add_argument("-t", "--timestamp", action="store_true",
  66.     help="include timestamp on output")
  67. parser.add_argument("-x", "--fails", action="store_true",
  68.     help="include failed exec()s")
  69. parser.add_argument("--cgroupmap",
  70.     help="trace cgroups in this BPF map only")
  71. parser.add_argument("--mntnsmap",
  72.     help="trace mount namespaces in this BPF map only")
  73. parser.add_argument("-u", "--uid", type=parse_uid, metavar='USER',
  74.     help="trace this UID only")
  75. parser.add_argument("-q", "--quote", action="store_true",
  76.     help="Add quotemarks (") around arguments."
  77.     )
  78. parser.add_argument("-n", "--name",
  79.     type=ArgString,
  80.     help="only print commands matching this name (regex), any arg")
  81. parser.add_argument("-l", "--line",
  82.     type=ArgString,
  83.     help="only print commands where arg contains this line (regex)")
  84. parser.add_argument("-U", "--print-uid", action="store_true",
  85.     help="print UID column")
  86. parser.add_argument("--max-args", default="20",
  87.     help="maximum number of arguments parsed and displayed, defaults to 20")
  88. parser.add_argument("--ebpf", action="store_true",
  89.     help=argparse.SUPPRESS)
  90. args = parser.parse_args()
  91. # define BPF program
  92. bpf_text = """
  93. #include <uapi/linux/ptrace.h>
  94. #include <linux/sched.h>
  95. #include <linux/fs.h>
  96. #define ARGSIZE  128
  97. enum event_type {
  98.     EVENT_ARG,
  99.     EVENT_RET,
  100. };
  101. struct data_t {
  102.     u32 pid;  // PID as in the userspace term (i.e. task->tgid in kernel)
  103.     u32 ppid; // Parent PID as in the userspace term (i.e task->real_parent->tgid in kernel)
  104.     u32 uid;
  105.     char comm[TASK_COMM_LEN];
  106.     enum event_type type;
  107.     char argv[ARGSIZE];
  108.     int retval;
  109. };
  110. BPF_PERF_OUTPUT(events);
  111. static int __submit_arg(struct pt_regs *ctx, void *ptr, struct data_t *data)
  112. {
  113.     bpf_probe_read_user(data->argv, sizeof(data->argv), ptr);
  114.     events.perf_submit(ctx, data, sizeof(struct data_t));
  115.     return 1;
  116. }
  117. static int submit_arg(struct pt_regs *ctx, void *ptr, struct data_t *data)
  118. {
  119.     const char *argp = NULL;
  120.     bpf_probe_read_user(&argp, sizeof(argp), ptr);
  121.     if (argp) {
  122.         return __submit_arg(ctx, (void *)(argp), data);
  123.     }
  124.     return 0;
  125. }
  126. int syscall__execve(struct pt_regs *ctx,
  127.     const char __user *filename,
  128.     const char __user *const __user *__argv,
  129.     const char __user *const __user *__envp)
  130. {
  131.     u32 uid = bpf_get_current_uid_gid() & 0xffffffff;
  132.     UID_FILTER
  133.     if (container_should_be_filtered()) {
  134.         return 0;
  135.     }
  136.     // create data here and pass to submit_arg to save stack space (#555)
  137.     struct data_t data = {};
  138.     struct task_struct *task;
  139.     data.pid = bpf_get_current_pid_tgid() >> 32;
  140.     task = (struct task_struct *)bpf_get_current_task();
  141.     // Some kernels, like Ubuntu 4.13.0-generic, return 0
  142.     // as the real_parent->tgid.
  143.     // We use the get_ppid function as a fallback in those cases. (#1883)
  144.     data.ppid = task->real_parent->tgid;
  145.     bpf_get_current_comm(&data.comm, sizeof(data.comm));
  146.     data.type = EVENT_ARG;
  147.     __submit_arg(ctx, (void *)filename, &data);
  148.     // skip first arg, as we submitted filename
  149.     #pragma unroll
  150.     for (int i = 1; i < MAXARG; i++) {
  151.         if (submit_arg(ctx, (void *)&__argv[i], &data) == 0)
  152.              goto out;
  153.     }
  154.     // handle truncated argument list
  155.     char ellipsis[] = "...";
  156.     __submit_arg(ctx, (void *)ellipsis, &data);
  157. out:
  158.     return 0;
  159. }
  160. int do_ret_sys_execve(struct pt_regs *ctx)
  161. {
  162.     if (container_should_be_filtered()) {
  163.         return 0;
  164.     }
  165.     struct data_t data = {};
  166.     struct task_struct *task;
  167.     u32 uid = bpf_get_current_uid_gid() & 0xffffffff;
  168.     UID_FILTER
  169.     data.pid = bpf_get_current_pid_tgid() >> 32;
  170.     data.uid = uid;
  171.     task = (struct task_struct *)bpf_get_current_task();
  172.     // Some kernels, like Ubuntu 4.13.0-generic, return 0
  173.     // as the real_parent->tgid.
  174.     // We use the get_ppid function as a fallback in those cases. (#1883)
  175.     data.ppid = task->real_parent->tgid;
  176.     bpf_get_current_comm(&data.comm, sizeof(data.comm));
  177.     data.type = EVENT_RET;
  178.     data.retval = PT_REGS_RC(ctx);
  179.     events.perf_submit(ctx, &data, sizeof(data));
  180.     return 0;
  181. }
  182. """
  183. bpf_text = bpf_text.replace("MAXARG", args.max_args)
  184. if args.uid:
  185.     bpf_text = bpf_text.replace('UID_FILTER',
  186.         'if (uid != %s) { return 0; }' % args.uid)
  187. else:
  188.     bpf_text = bpf_text.replace('UID_FILTER', '')
  189. bpf_text = filter_by_containers(args) + bpf_text
  190. if args.ebpf:
  191.     print(bpf_text)
  192.     exit()
  193. # initialize BPF
  194. b = BPF(text=bpf_text)
  195. execve_fnname = b.get_syscall_fnname("execve")
  196. b.attach_kprobe(event=execve_fnname, fn_name="syscall__execve")
  197. b.attach_kretprobe(event=execve_fnname, fn_name="do_ret_sys_execve")
  198. # header
  199. if args.time:
  200.     print("%-9s" % ("TIME"), end="")
  201. if args.timestamp:
  202.     print("%-8s" % ("TIME(s)"), end="")
  203. if args.print_uid:
  204.     print("%-6s" % ("UID"), end="")
  205. print("%-16s %-7s %-7s %3s %s" % ("PCOMM", "PID", "PPID", "RET", "ARGS"))
  206. class EventType(object):
  207.     EVENT_ARG = 0
  208.     EVENT_RET = 1
  209. start_ts = time.time()
  210. argv = defaultdict(list)
  211. # This is best-effort PPID matching. Short-lived processes may exit
  212. # before we get a chance to read the PPID.
  213. # This is a fallback for when fetching the PPID from task->real_parent->tgip
  214. # returns 0, which happens in some kernel versions.
  215. def get_ppid(pid):
  216.     try:
  217.         with open("/proc/%d/status" % pid) as status:
  218.             for line in status:
  219.                 if line.startswith("PPid:"):
  220.                     return int(line.split()[1])
  221.     except IOError:
  222.         pass
  223.     return 0
  224. # process event
  225. def print_event(cpu, data, size):
  226.     event = b["events"].event(data)
  227.     skip = False
  228.     if event.type == EventType.EVENT_ARG:
  229.         argv[event.pid].append(event.argv)
  230.     elif event.type == EventType.EVENT_RET:
  231.         if event.retval != 0 and not args.fails:
  232.             skip = True
  233.         if args.name and not re.search(bytes(args.name), event.comm):
  234.             skip = True
  235.         if args.line and not re.search(bytes(args.line),
  236.                                        b' '.join(argv[event.pid])):
  237.             skip = True
  238.         if args.quote:
  239.             argv[event.pid] = [
  240.                 b""" + arg.replace(b""", b"\\"") + b"""
  241.                 for arg in argv[event.pid]
  242.             ]
  243.         if not skip:
  244.             if args.time:
  245.                 printb(b"%-9s" % strftime("%H:%M:%S").encode('ascii'), nl="")
  246.             if args.timestamp:
  247.                 printb(b"%-8.3f" % (time.time() - start_ts), nl="")
  248.             if args.print_uid:
  249.                 printb(b"%-6d" % event.uid, nl="")
  250.             ppid = event.ppid if event.ppid > 0 else get_ppid(event.pid)
  251.             ppid = b"%d" % ppid if ppid > 0 else b"?"
  252.             argv_text = b' '.join(argv[event.pid]).replace(b'\n', b'\\n')
  253.             printb(b"%-16s %-7d %-7s %3d %s" % (event.comm, event.pid,
  254.                    ppid, event.retval, argv_text))
  255.         try:
  256.             del(argv[event.pid])
  257.         except Exception:
  258.             pass
  259. # loop with callback to print_event
  260. b["events"].open_perf_buffer(print_event)
  261. while 1:
  262.     try:
  263.         b.perf_buffer_poll()
  264.     except KeyboardInterrupt:
  265.         exit()
复制代码
此工具使用kprobe和kretprobe跟踪execv系统调用的进入和退出事件,并将进程名,进程参数,pid,ppid以及返回代码输出到终端。
【----帮助网安学习,以下所有学习资料免费领!加vx:yj009991,备注 “博客园” 获取!】
 ① 网安学习成长路径思维导图
 ② 60+网安经典常用工具包
 ③ 100+SRC漏洞分析报告
 ④ 150+网安攻防实战技术电子书
 ⑤ 最权威CISSP 认证考试指南+题库
 ⑥ 超1800页CTF实战技巧手册
 ⑦ 最新网安大厂面试题合集(含答案)
 ⑧ APP客户端安全检测指南(安卓+IOS)
使用python + bcc 跟踪用户函数

bcc中使用uprobe跟踪glibc malloc 函数的工具,并统计malloc 内存的总量。
  1. #!/usr/bin/python
  2. #
  3. # mallocstacks  Trace malloc() calls in a process and print the full
  4. #               stack trace for all callsites.
  5. #               For Linux, uses BCC, eBPF. Embedded C.
  6. #
  7. # This script is a basic example of the new Linux 4.6+ BPF_STACK_TRACE
  8. # table API.
  9. #
  10. # Copyright 2016 GitHub, Inc.
  11. # Licensed under the Apache License, Version 2.0 (the "License")
  12. from __future__ import print_function
  13. from bcc import BPF
  14. from bcc.utils import printb
  15. from time import sleep
  16. import sys
  17. if len(sys.argv) < 2:
  18.     print("USAGE: mallocstacks PID [NUM_STACKS=1024]")
  19.     exit()
  20. pid = int(sys.argv[1])
  21. if len(sys.argv) == 3:
  22.     try:
  23.         assert int(sys.argv[2]) > 0, ""
  24.     except (ValueError, AssertionError) as e:
  25.         print("USAGE: mallocstacks PID [NUM_STACKS=1024]")
  26.         print("NUM_STACKS must be a non-zero, positive integer")
  27.         exit()
  28.     stacks = sys.argv[2]
  29. else:
  30.     stacks = "1024"
  31. # load BPF program
  32. b = BPF(text="""
  33. #include <uapi/linux/ptrace.h>
  34. BPF_HASH(calls, int);
  35. BPF_STACK_TRACE(stack_traces, """ + stacks + """);
  36. int alloc_enter(struct pt_regs *ctx, size_t size) {
  37.     int key = stack_traces.get_stackid(ctx, BPF_F_USER_STACK);
  38.     if (key < 0)
  39.         return 0;
  40.     // could also use `calls.increment(key, size);`
  41.     u64 zero = 0, *val;
  42.     val = calls.lookup_or_try_init(&key, &zero);
  43.     if (val) {
  44.       (*val) += size;
  45.     }
  46.     return 0;
  47. };
  48. """)
  49. b.attach_uprobe(name="c", sym="malloc", fn_name="alloc_enter", pid=pid)
  50. print("Attaching to malloc in pid %d, Ctrl+C to quit." % pid)
  51. # sleep until Ctrl-C
  52. try:
  53.     sleep(99999999)
  54. except KeyboardInterrupt:
  55.     pass
  56. calls = b.get_table("calls")
  57. stack_traces = b.get_table("stack_traces")
  58. for k, v in reversed(sorted(calls.items(), key=lambda c: c[1].value)):
  59.     print("%d bytes allocated at:" % v.value)
  60.     if k.value > 0 :
  61.         for addr in stack_traces.walk(k.value):
  62.             printb(b"\t%s" % b.sym(addr, pid, show_offset=True))
复制代码
libbpf

libbpf是linux 源码树中的ebpf 开发包。同时在github上也有独立的代码仓库。 这里推荐使用libbpf-bootstrap这个项目
libbpf-bootstrap

libbpf-bootstrap是使用 libbpf 和 BPF CO-RE 进行 BPF 应用程序开发的脚手架项目 首先克隆libbpf-bootstrap仓库
git clone https://github.com/libbpf/libbpf-bootstrap.git
然后同步子模块
cd libbpf-bootstrap git submodule init git submodule update
注意,子模块中包含bpftool,bpftool中还有子模块需要同步 在bpftool目录下重复以上步骤
libbpf-bootstrap中包含以下目录

这里进入example/c中,这里包含一些示例工具 直接make编译 等编译完成后,在此目录下会生成可执行文件
[img=720,94.41717791411043]https://picx.zhimg.com/80/v2-98841a76cbc74809a1d1822e6d81c066_1440w.png[/img]
先运行一下bootstrap,这里要用root权限运行
[img=720,107.95425667090215]https://picx.zhimg.com/80/v2-f2129cc3b9f3a6c014d8c57f151cbd25_1440w.png[/img]
bootstrap程序会追踪所有的exec和exit系统调用,每次程序运行时,bootstrap就会输出运行程序的信息。

再看看minimal,这是一个最小ebpf程序。
[img=720,374.9501661129568]https://pica.zhimg.com/80/v2-5cf07b280280f54ebfab4732e7d855bc_1440w.png[/img]
运行后输出大量信息,最后有提示让我们运行sudo cat /sys/kernel/debug/tracing/trace_pipe来查看输出 运行这个命令
[img=720,306.5711361310133]https://pic1.zhimg.com/80/v2-6a82826aed423c7bd7baf97371a0e54d_1440w.png[/img]
minimal 会追踪所有的write系统调用,并打印出调用write的进程的pid 这里看到pid为11494,ps 查询一下这个进程,发现就是minimal
[img=720,73.96039603960396]https://picx.zhimg.com/80/v2-c0aa80fd9a786c06a739fa44cb5b9aa6_1440w.png[/img]
来看看minimal的源码,这个程序主要有两个C文件组成,minimal.c和minimal.bpf.c前者为此程序的源码,后者为插入内核虚拟机的ebpf代码。
  1. // SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
  2. /* Copyright (c) 2020 Facebook */
  3. #include <stdio.h>
  4. #include <unistd.h>
  5. #include <sys/resource.h>
  6. #include <bpf/libbpf.h>
  7. #include "minimal.skel.h"
  8. static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args)
  9. {
  10.     return vfprintf(stderr, format, args);
  11. }
  12. int main(int argc, char **argv)
  13. {
  14.     struct minimal_bpf *skel;
  15.     int err;
  16.     libbpf_set_strict_mode(LIBBPF_STRICT_ALL);
  17.     /* Set up libbpf errors and debug info callback */
  18.     libbpf_set_print(libbpf_print_fn);
  19.     /* Open BPF application */
  20.     skel = minimal_bpf__open();
  21.     if (!skel) {
  22.         fprintf(stderr, "Failed to open BPF skeleton\n");
  23.         return 1;
  24.     }
  25.     /* ensure BPF program only handles write() syscalls from our process */
  26.     skel->bss->my_pid = getpid();
  27.     /* Load & verify BPF programs */
  28.     err = minimal_bpf__load(skel);
  29.     if (err) {
  30.         fprintf(stderr, "Failed to load and verify BPF skeleton\n");
  31.         goto cleanup;
  32.     }
  33.     /* Attach tracepoint handler */
  34.     err = minimal_bpf__attach(skel);
  35.     if (err) {
  36.         fprintf(stderr, "Failed to attach BPF skeleton\n");
  37.         goto cleanup;
  38.     }
  39.     printf("Successfully started! Please run `sudo cat /sys/kernel/debug/tracing/trace_pipe` "
  40.            "to see output of the BPF programs.\n");
  41.     for (;;) {
  42.         /* trigger our BPF program */
  43.         fprintf(stderr, ".");
  44.         sleep(1);
  45.     }
  46.     cleanup
  47.     minimal_bpf__destroy(skel);
  48.     return -err;
  49. }
复制代码
首先看一下minimal.c的内容,在main函数中首先调用了libbpf_set_strict_mode(LIBBPF_STRICT_ALL);设置为libbpf v1.0模式。此模式下错误代码直接通过函数返回值传递,不再需要检查errno。 之后调用libbpf_set_print(libbpf_print_fn);将程序中一个自定义输出函数设置为调试输出的回调函数,即运行minimal的这些输出全都时通过libbpf_print_fn输出的。
[img=720,225.03896103896105]https://picx.zhimg.com/80/v2-96f62f1b99401b2e197e758292b6012e_1440w.png[/img]
然后在minimal.c:24调用生成的minimal.skel.h中的预定义函数minimal_bpfopen打开bpf程序,这里返回一个minimal_bpf类型的对象(c中使用结构体模拟对象)。 在31行将minimal_bpf对象的bss子对象的my_pid属性设置为当前进程pid 这里minimal_bpf对象和bss都由minimal.bpf.c代码编译而来。minimal.bpf.c经过clang 编译连接,生成minimal.bpf.o,这是一个elf文件,其中包含bss段,这个段内通常储存着minimal.bpf.c中所有经过初始化的变量。 skel->bss->my_pid = getpid();就是直接将minimal.bpf.o中的my_pid设置为minimal进程的pid。 之后在34行调用minimal_bpfload(skel);加载并验证ebpf程序。 41行调用minimal_bpfattach(skel);使ebpf程序附加到bpf源码中声明的跟踪点上。 此时ebpf程序已经开始运行了。ebpf中通过bpf_printk输出的内容会写入linux debugFS中的trace_pipe中。可以使用sudo cat /sys/kernel/debug/tracing/trace_pipe输出到终端里。 之后minimal程序会进入一个死循环,以维持ebpf程序的运行。当用户按下发送SIGINT信号后就会调用minimal_bpfdestroy(skel);卸载内核中的ebpf程序,之后退出。
接下来看minimal.bpf.c 这是ebpf程序的源码,是要加载到内核中的ebpf虚拟机中运行的,由于在运行在内核中,具有得天独厚的地理位置,可以访问系统中所有资源,再配合上众多的tracepoint,就可以发挥出强大的追踪能力。 下面是minimal.bpf.c的源码
  1. // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
  2. /* Copyright (c) 2020 Facebook */
  3. #include <linux/bpf.h>
  4. #include <bpf/bpf_helpers.h>
  5. char LICENSE[] SEC("license") = "Dual BSD/GPL";
  6. int my_pid = 0;
  7. SEC("tp/syscalls/sys_enter_write")
  8. int handle_tp(void *ctx)
  9. {
  10.     int pid = bpf_get_current_pid_tgid() >> 32;
  11.     if (pid != my_pid)
  12.         return 0;
  13.     bpf_printk("BPF triggered from PID %d.\n", pid);
  14.     return 0;
  15. }
复制代码
minimal.bpf.c会被clang 编译器编译为ebpf字节码,然后通过bpftool将其转换为minimal.skel.h头文件,以供minimal.c使用。 此代码中定义并初始化了一个全局变量my_pid,经过编译连接后此变量会进入elf文件的bss段中。 然后,代码中定义了一个函数int handle_tp(void *ctx),此函数中通过调用bpf_get_current_pid_tgid() >> 32获取到调用此函数的进程pid

然后比较pid与my_pid的值,如果相同则调用bpf_printk输出"BPF triggered from PID %d\n” 这里由于handle_tp函数是通过SEC宏附加在write系统调用上,所以在调用write()时,handle_tp也会被调用,从而实现追踪系统调用的功能。 SEC宏在bpf程序中处于非常重要的地位。可以参考此文档 SEC宏可以指定ebpf函数附加的点,包括系统调用,静态tracepoint,动态的kprobe和uprobe,以及USDT等等。 Libbpf 期望 BPF 程序使用SEC()宏注释,其中传入的字符串参数SEC()确定 BPF 程序类型和可选的附加附加参数,例如 kprobe 程序要附加的内核函数名称或 cgroup 程序的挂钩类型。该SEC()定义最终被记录为 ELF section name。
通过llvm-objdump 可以看到编译后的epbf程序文件包含一个以追踪点命名的section

ebpf字节码dump

ebpf程序可以使用llvm-objdump -d dump 出ebpf字节码

bpftrace

bpftrace 提供了一种类似awk 的脚本语言,通过编写脚本,配合bpftrace支持的追踪点,可以实现非常强大的追踪功能
安装
sudo apt-get update sudo apt-get install -y \ bison \ cmake \ flex \ g++ \ git \ libelf-dev \ zlib1g-dev \ libfl-dev \ systemtap-sdt-dev \ binutils-dev \ libcereal-dev \ llvm-12-dev \ llvm-12-runtime \ libclang-12-dev \ clang-12 \ libpcap-dev \ libgtest-dev \ libgmock-dev \ asciidoctor git clone https://github.com/iovisor/bpftrace mkdir bpftrace/build; cd bpftrace/build; ../build-libs.sh cmake -DCMAKE_BUILD_TYPE=Release .. make -j8 sudo make install
bpftrace命令行参数
  1. # bpftrace
  2. USAGE:
  3.     bpftrace [options] filename
  4.     bpftrace [options] -e 'program'
  5. OPTIONS:
  6.     -B MODE        output buffering mode ('line', 'full', or 'none')
  7.     -d             debug info dry run
  8.     -dd            verbose debug info dry run
  9.     -e 'program'   execute this program
  10.     -h             show this help message
  11.     -I DIR         add the specified DIR to the search path for include files.
  12.     --include FILE adds an implicit #include which is read before the source file is preprocessed.
  13.     -l [search]    list probes
  14.     -p PID         enable USDT probes on PID
  15.     -c 'CMD'       run CMD and enable USDT probes on resulting process
  16.     -q             keep messages quiet
  17.     -v             verbose messages
  18.     -k             emit a warning when a bpf helper returns an error (except read functions)
  19.     -kk            check all bpf helper functions
  20.     --version      bpftrace version
  21. ENVIRONMENT:
  22.     BPFTRACE_STRLEN             [default: 64] bytes on BPF stack per str()
  23.     BPFTRACE_NO_CPP_DEMANGLE    [default: 0] disable C++ symbol demangling
  24.     BPFTRACE_MAP_KEYS_MAX       [default: 4096] max keys in a map
  25.     BPFTRACE_MAX_PROBES         [default: 512] max number of probes bpftrace can attach to
  26.     BPFTRACE_MAX_BPF_PROGS      [default: 512] max number of generated BPF programs
  27.     BPFTRACE_CACHE_USER_SYMBOLS [default: auto] enable user symbol cache
  28.     BPFTRACE_VMLINUX            [default: none] vmlinux path used for kernel symbol resolution
  29.     BPFTRACE_BTF                [default: none] BTF file
  30. EXAMPLES:
  31. bpftrace -l '*sleep*'
  32.     list probes containing "sleep"
  33. bpftrace -e 'kprobe:do_nanosleep { printf("PID %d sleeping...\n", pid); }'
  34.     trace processes calling sleep
  35. bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @[comm] = count(); }'
  36.     count syscalls by process name
复制代码
bpftrace程序语法规则

bpftrace语法由以下一个或多个action block结构组成,且语法关键字与c语言类似
  1. probe[,probe]
  2. /predicate/ {
  3.   action
  4. }
复制代码

  • probe:探针,可以使用bpftrace -l 来查看支持的所有tracepoint和kprobe探针
  • Predicate(可选):在 / / 中指定 action 执行的条件。如果为True,就执行 action
  • action:在事件触发时运行的程序,每行语句必须以 ; 结尾,并且用{}包起来
  • //:单行注释
  • /**/:多行注释
  • ->:访问c结构体成员,例如:bpftrace -e 'tracepoint:syscalls:sys_enter_openat { printf("%s %s\n", comm, str(args->filename)); }'
  • struct:结构声明,在bpftrace脚本中可以定义自己的结构
 
bpftrace 单行指令

bpftrace -e 选项可以指定运行一个单行程序 1、追踪openat系统调用
bpftrace -e 'tracepoint:syscalls:sys_enter_openat { printf("%s %s\n", comm, str(args->filename)); }'
2、系统调用计数
bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @[comm] = count(); }'
3、计算每秒发生的系统调用数量
bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @ = count(); } interval:s:1 { print(@); clear(@); }'
bpftrace脚本文件

还可以将bpftrace程序作为一个脚本文件,并且使用shebang#!/usr/local/bin/bpftrace可以使其独立运行 例如:
  1. 1 #!/usr/local/bin/bpftrace
  2. 2
  3. 3 tracepoint:syscalls:sys_enter_nanosleep
  4. 4 {
  5. 5   printf("%s is sleeping.\n", comm);
  6. 6 }
复制代码
bpftrace探针类型

bpftrace支持以下类型的探针:

  • kprobe- 内核函数启动
  • kretprobe- 内核函数返回
  • uprobe- 用户级功能启动
  • uretprobe- 用户级函数返回
  • tracepoint- 内核静态跟踪点
  • usdt- 用户级静态跟踪点
  • profile- 定时采样
  • interval- 定时输出
  • software- 内核软件事件
  • hardware- 处理器级事件
更多靶场实验练习、网安学习资料,请点击这里>>
 


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

大号在练葵花宝典

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表