0x00 bpf
BPF 的全称是 Berkeley Packet Filter,是一个用于过滤(filter)网络报文(packet)的架构。(例如tcpdump),目前称为Cbpf(Classical bpf)。BPF 在数据包过滤上引入了两大革新:
- 一个新的虚拟机 (VM) 设计,可以有效地工作在基于寄存器结构的 CPU 之上;
- 应用程序使用缓存只复制与过滤数据包相关的数据,不会复制数据包的所有信息。这样可以最大程度地减少BPF 处理的数据;
由于这些巨大的改进,所有的 Unix 系统都选择采用 BPF 作为网络数据包过滤技术,直到今天,许多 Unix 内核的派生系统中(包括 Linux 内核)仍使用该实现。
0x01 ebpf
eBPF演进为一个通用执行引擎,可基于此开发性能分析工具、软件定义网络等诸多场景,原来的 BPF 就被称为经典 BPF,缩写cBPF(classic BPF),cBPF现在已经基本废弃。现在,Linux 内核只运行eBPF。
eBPF全称extended BPF,Linux Kernel 3.15 中引入的全新设计, 是对既有BPF架构进行了全面扩展,一方面,支持了更多领域的应用,比如:内核追踪(Kernel Tracing)、应用性能调优/监控、流控(Traffic Control)等;另一方面,在接口的设计以及易用性上,也有了较大的改进。
eBPF支持在用户态将C语言编写的一小段“内核代码”注入到内核中运行,注入时要先用llvm编译得到使用BPF指令集的 ELF 文件,然后从ELF文件中解析出可以注入内核的部分,最后用 bpf_load_program() 方法完成注入。 用户态程序和注入到内核中的程序通过共用一个位于内核的 eBPF MAP实现通信。为了防止注入的代码导致内核崩溃,eBPF 会对注入的代码进行严格检查,拒绝不合格的代码的注入。
- eBPF prog load的严格的verify机制
- eBPF访问内核资源需借助各种eBPF 的helper func,helper func函数能在最坏的情况下保证安全
- 现在,Linux 内核只运行 eBPF,内核会将加载的BPF字节码 透明地转换成 eBPF 再执行
eBPF程序执行过程
- 编译:将eBPF程序转成BPF bytecode
- 加载:特权进程通过pbf系统调用将BPF bytecode提交给内核(pbf系统在eBPF诞生后,成为了内核的一个顶级子系统)
- 验证:在执行前进行安全性校验,如无限循环、不能导致内核崩溃、可完成等,保证eBPF程序操作的安全性
- 内核态执行:通过kprobo、uprobe、perf_event等方式调用
*用户态程序与内核态程序交互*
BPF映射
ebpf的应用
- 动态追踪:bcc、bpftrace
- 观测监控:Pixie、Hubble、kubectl-trace
- 网络:Cilium、Katran
- 安全:Falco、Tracee
0x02 libbpf-bootstrap
libbpf-bootstrap就是这样一个 BPF 游乐场,它已经尽可能地为初学者配置好了环境,帮助他们可以直接步入到 BPF 程序的书写。它综合了 BPF 社区多年来的最佳实践,并且提供了一个现代化的、便捷的工作流。libbpf-bootstrap 依赖于 libbpf 并且使用了一个很简单的 Makefile。对于需要更高级设置的用户,它也是一个好的起点。即使这个 Makefile不会被直接使用到,也可以很轻易地迁移到别的构建系统上。
libbpf-bootstrap 为生成基于 libbpf 的 bpf 程序提供了模板,开发者可以很方便的使用该模板生成自定义的 bpf 程序。这里说的模板有两层含义:
- 源码中使用 libbpf 接口的使用模板;
- 源码文件命名的模板;
安装:
https://mp.weixin.qq.com/s/nJtvgtXuSGzGQHIEA-R9Pw
基于 libbpf-bootstrap 编写自己的bpf文件,并使用 uprobe 跟踪应用程序中的函数:
https://zhuanlan.zhihu.com/p/467647354
libbpf-bootstrap 为编写 bpf 程序提供了模板——源文件命名规则和接口的使用方法。这里以uprobe跟踪应用程序中的函数为例进行展开
一些函数定义:https://github.com/libbpf/libbpf/blob/master/src/libbpf.h
模板
源文件名称
libbpf-bootstrap 中的 Makefile 和 CMakeList.txt 规定了源文件名的规则:
- 生成bpf字节码的 bpf 文件以 .bpf.c 结尾;
- 加载bpf字节码的 c 文件以 .c 结尾;
- 上述两个类型文件名的前缀必须相同。
鉴于 libbpf-bootstrap 中已经提供了 uprobe 的例子,我们这里以 selfuprobe 进行命名—— selfuprobe.bpf.c 和 selfuprobe.c
接口用法
bpftool会根据bpf字节码文件(xxx.bpf.o)生成对应的 skeleton 文件——xxx.skel.h。这个文件中包含了关键的函数和结构体。比如,这个文件中包含了xxx.bpf.o文件的内容。还有,xxx_bpf__open_and_load()函数,该函数用于加载bpf字节码到内核中。
具体用法请查看以下 selfuprobe的实现。
selfuprobe目标及代码
目标:跟踪 /home/syj/test 程序中的 foo(int a, intb) 函数,将该函数的执行时间显示到终端。
example/c/selfuprobe.bpf.c
#include <linux/bpf.h>
#include <linux/ptrace.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
char LICENSE[] SEC("license") = "GPL"; //bpf程序必须遵守GPL协议
unsigned long long func_entry_time = 0;
unsigned long long func_exit_time = 0;
SEC("uprobe/func-exec-time")
int BPF_KPROBE(uprobe)
{
func_entry_time = bpf_ktime_get_ns();
return 0;
}
SEC("uretprobe/func-exec-time")//SEC(uprobe/func-exec-time) 和 SEC(uretprobe/func-exec-time) 。其中的 uprobe/和 uretprobe/ 是关键,libbpf 根据这两个关键字确定在此段中定义的函数为 uprobe/uretprobe 相关;
int BPF_KRETPROBE(uretprobe)
{
func_exit_time = bpf_ktime_get_ns();
bpf_printk("function execute time:%lu\n", func_exit_time - func_entry_time);
return 0;
}
BPF_KPROBE(uprobe)
和 BPF_KRETPROBE(uretprobe)
分别定义了进入函数(uprobe)和退出函数(uretprobe)时执行的动作。括号中的名字会生成 struct bpf_program
和 struct bpf_link
类型的结构体成员名,在 selfuprobe.skel.h 文件中有体现
example/c/selfuprobe.c
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <sys/resource.h>
#include <bpf/libbpf.h>
#include "selfuprobe.skel.h"
static int libbpf_output(enum libbpf_print_level level, const char *format, va_list args)
{
return vfprintf(stderr, format, args);
}
static void bump_memlock_limit(void)
{
int32_t ret = 0;
struct rlimit rl = {
.rlim_cur = RLIM_INFINITY,
.rlim_max = RLIM_INFINITY,
};
ret = setrlimit(RLIMIT_MEMLOCK, &rl);
if (0 != ret)
{
printf("failed to increase RLIMIT_MEMLOCK limit\n");
exit(1);
}
}
int main(int argc, char *argv[])
{
int32_t ret = 0;
struct selfuprobe_bpf *skel = NULL;
long func_offset = 0x1149;
libbpf_set_print(libbpf_output);
//设置 libbpf 的日志输出函数,libbpf 执行过程中的日志会通过libbpf_output函数进行输出
bump_memlock_limit();
skel = selfuprobe_bpf__open_and_load();
//用于加载 bpf 字节码文件(selfuprobe.bpf.o)到内核中
if (NULL == skel)
{
printf("failed to open and load bpf skeleton\n");
return -1;
}
skel->links.uprobe = bpf_program__attach_uprobe(skel->progs.uprobe, false, -1, "/home/sdc/test", func_offset);
ret = libbpf_get_error(skel->links.uprobe);
if (0 != ret)
{
printf("failed to attach uprobe:%d\n", ret);
goto exit;
}
skel->links.uretprobe = bpf_program__attach_uprobe(skel->progs.uretprobe, true, -1, "/home/sdc/test", func_offset);
//bpf_program__attach_uprobe() 用于绑定 uprobe 和 uretprobe 追踪目标的信息。
//此例中,追踪的目标文件名称为 /home/sdc/test,偏移量为 0x1149
ret = libbpf_get_error(skel->links.uretprobe);
if (0 != ret)
{
printf("failed to attach uretprobe:%d\n", ret);
goto exit;
}
printf("Successfully started! Please run `sudo cat /sys/kernel/debug/tracing/trace_pipe` "
"to see output of the BPF programs.\n");
while (1)
{
printf("I am alive ...\n");
sleep(1);
}
return 0;
exit:
selfuprobe_bpf__destroy(skel);
return -1;
}
selfuprobe.skel.h
这个文件中包含了与自定义名称相关的一些函数,用于在 xxx.c 文件中进行调用
0x03 逆向基于libbpf-bootstrap编写的bpf文件
提取bpf字节码
fpbe_bpf *__cdecl fpbe_bpf__open()
{
return fpbe_bpf__open_opts(0LL);
}
fpbe_bpf *__cdecl fpbe_bpf__open_opts(const bpf_object_open_opts *opts)
{
fpbe_bpf *obj; // [rsp+18h] [rbp-8h]
obj = (fpbe_bpf *)calloc(1LL, 32LL);
if ( !obj )
return 0LL;
if ( !fpbe_bpf__create_skeleton(obj) && !bpf_object__open_skeleton(obj->skeleton, opts) )
return obj;
fpbe_bpf__destroy(obj);
return 0LL;
}
int __cdecl fpbe_bpf__create_skeleton(fpbe_bpf *obj)
{
bpf_object_skeleton *s; // [rsp+18h] [rbp-8h]
s = (bpf_object_skeleton *)calloc(1LL, 72LL);
if ( !s )
return -1;
obj->skeleton = s;
s->sz = 72LL;
s->name = "fpbe_bpf";
s->obj = &obj->obj;
s->prog_cnt = 1;
s->prog_skel_sz = 24;
s->progs = (bpf_prog_skeleton *)calloc(s->prog_cnt, s->prog_skel_sz);
if ( s->progs )
{
s->progs->name = "uprobe";
s->progs->prog = &obj->progs.uprobe;
s->progs->link = &obj->links.uprobe;
s->data_sz = 1648LL;
s->data = &bytecode;
return 0;
}
else
{
bpf_object__destroy_skeleton(s);
return -1;
}
}
反汇编bpf字节码
方法一:llvm-objdump
使用llvm-objdump -d xxx.bpf.o来得到IR
方法二:IDA Plugins ebpf_processor
https://github.com/cylance/eBPF_processor
ebpf.py放置到procs目录下即可
导入没报错就可以了
uprobe_func:0000000000000008 uprobe:
uprobe_func:0000000000000008 ldxdw r2, [r1+0x68]
uprobe_func:0000000000000010 lsh r2, 0x20
uprobe_func:0000000000000018 rsh r2, 0x20
uprobe_func:0000000000000020 ldxdw r3, [r1+0x70]
uprobe_func:0000000000000028 lsh r3, 0x20
uprobe_func:0000000000000030 rsh r3, 0x20
uprobe_func:0000000000000038 mov r4, r3
uprobe_func:0000000000000040 mul r4, 0x6DC0
uprobe_func:0000000000000048 mov r5, r2
uprobe_func:0000000000000050 mul r5, 0xFB88
uprobe_func:0000000000000058 add r5, r4
uprobe_func:0000000000000060 ldxdw r4, [r1+0x60]
uprobe_func:0000000000000068 lsh r4, 0x20
uprobe_func:0000000000000070 rsh r4, 0x20
uprobe_func:0000000000000078 mov r0, r4
uprobe_func:0000000000000080 mul r0, 0x71FB
uprobe_func:0000000000000088 add r5, r0
uprobe_func:0000000000000090 ldxdw r1, [r1+0x58]
uprobe_func:0000000000000098 mov r0, 0
uprobe_func:00000000000000A0 stxb [r10-8], r0
uprobe_func:00000000000000A8 stxdw [r10-0x10], r0
uprobe_func:00000000000000B0 stxdw [r10-0x18], r0
uprobe_func:00000000000000B8 lsh r1, 0x20
uprobe_func:00000000000000C0 rsh r1, 0x20
uprobe_func:00000000000000C8 mov r0, r1
uprobe_func:00000000000000D0 mul r0, 0xCC8E
uprobe_func:00000000000000D8 add r5, r0
uprobe_func:00000000000000E0 mov r6, 1
uprobe_func:00000000000000E8 lddw r0, 0xBE18A1735995
uprobe_func:00000000000000F8 jne r5, r0, LBB0_5
uprobe_func:0000000000000100 mov r5, r3
uprobe_func:0000000000000108 mul r5, 0xF1BF
uprobe_func:0000000000000110 mov r0, r2
uprobe_func:0000000000000118 mul r0, 0x6AE5
uprobe_func:0000000000000120 add r0, r5
uprobe_func:0000000000000128 mov r5, r4
uprobe_func:0000000000000130 mul r5, 0xADD3
uprobe_func:0000000000000138 add r0, r5
uprobe_func:0000000000000140 mov r5, r1
uprobe_func:0000000000000148 mul r5, 0x9284
uprobe_func:0000000000000150 add r0, r5
uprobe_func:0000000000000158 lddw r5, 0xA556E5540340
uprobe_func:0000000000000168 jne r0, r5, LBB0_5
uprobe_func:0000000000000170 mov r5, r3
uprobe_func:0000000000000178 mul r5, 0xDD85
uprobe_func:0000000000000180 mov r0, r2
uprobe_func:0000000000000188 mul r0, 0x8028
uprobe_func:0000000000000190 add r0, r5
uprobe_func:0000000000000198 mov r5, r4
uprobe_func:00000000000001A0 mul r5, 0x652D
uprobe_func:00000000000001A8 add r0, r5
uprobe_func:00000000000001B0 mov r5, r1
uprobe_func:00000000000001B8 mul r5, 0xE712
uprobe_func:00000000000001C0 add r0, r5
uprobe_func:00000000000001C8 lddw r5, 0xA6F374484DA3
uprobe_func:00000000000001D8 jne r0, r5, LBB0_5
uprobe_func:00000000000001E0 mov r5, r3
uprobe_func:00000000000001E8 mul r5, 0x822C
uprobe_func:00000000000001F0 mov r0, r2
uprobe_func:00000000000001F8 mul r0, 0xCA43
uprobe_func:0000000000000200 add r0, r5
uprobe_func:0000000000000208 mov r5, r4
uprobe_func:0000000000000210 mul r5, 0x7C8E
uprobe_func:0000000000000218 add r0, r5
uprobe_func:0000000000000220 mov r5, r1
uprobe_func:0000000000000228 mul r5, 0xF23A
uprobe_func:0000000000000230 add r0, r5
uprobe_func:0000000000000238 lddw r5, 0xB99C485A7277
uprobe_func:0000000000000248 jne r0, r5, LBB0_5
uprobe_func:0000000000000250 stxw [r10-0xC], r1
uprobe_func:0000000000000258 stxw [r10-0x10], r4
uprobe_func:0000000000000260 stxw [r10-0x14], r2
uprobe_func:0000000000000268 stxw [r10-0x18], r3
uprobe_func:0000000000000270 lddw r1, 0xA7D73257B465443
uprobe_func:0000000000000280 stxdw [r10-0x28], r1
uprobe_func:0000000000000288 lddw r1, 0x4648203A47414C46
uprobe_func:0000000000000298 stxdw [r10-0x30], r1
uprobe_func:00000000000002A0 lddw r1, 0x2052554F59202145
uprobe_func:00000000000002B0 stxdw [r10-0x38], r1
uprobe_func:00000000000002B8 lddw r1, 0x4E4F44204C4C4557
uprobe_func:00000000000002C8 stxdw [r10-0x40], r1
uprobe_func:00000000000002D0 mov r6, 0
uprobe_func:00000000000002D8 stxb [r10-0x20], r6
uprobe_func:00000000000002E0 mov r1, r10
uprobe_func:00000000000002E8 add r1, -0x40
uprobe_func:00000000000002F0 mov r3, r10
uprobe_func:00000000000002F8 add r3, -0x18
uprobe_func:0000000000000300 mov r2, 0x21
uprobe_func:0000000000000308 call 6
uprobe_func:0000000000000310
uprobe_func:0000000000000310 LBB0_5: ; CODE XREF: uprobe+F0↑j
uprobe_func:0000000000000310 ; uprobe+160↑j ...
uprobe_func:0000000000000310 mov r0, r6
uprobe_func:0000000000000318 ret
uprobe_func:0000000000000318 ; End of function uprobe
uprobe_func:0000000000000318
uprobe_func:0000000000000318 ; end of 'uprobe_func'
uprobe_func:0000000000000318
方法三:bpftool dump出ir和流程图
Usage: ./bpftool [OPTIONS] OBJECT { COMMAND | help }
./bpftool batch file FILE
./bpftool version
OBJECT := { prog | map | link | cgroup | perf | net | feature | btf | gen | struct_ops | iter }
OPTIONS := { {-j|--json} [{-p|--pretty}] | {-f|--bpffs} |
{-m|--mapcompat} | {-n|--nomount} }
libbpf-bootstrap/tools/bpftool
bpftool命令使用:https://blog.csdn.net/Longyu_wlz/article/details/109931993
首先运行并调试程序,将断点下在fpbe_bpf__open_and_load将字节码载入内核之后
然后执行./bpftool prog show
知道id为175
sudo ./bpftool prog dump xlated id 175
得到IR
这种IR可以通过重编译之后利用IDA来进行反编译
ir = """ 0: (79) r2 = *(u64 *)(r1 +104)
1: (67) r2 <<= 32
2: (77) r2 >>= 32
3: (79) r3 = *(u64 *)(r1 +112)
4: (67) r3 <<= 32
5: (77) r3 >>= 32
6: (bf) r4 = r3
7: (27) r4 *= 28096
8: (bf) r5 = r2
9: (27) r5 *= 64392
10: (0f) r5 += r4
...
"""
for line in ir.splitlines():
addr_end = line.find(":")
addr = line[2:addr_end]
print('_' + addr, end=': ')
ins_start = line.find(") ")
if line.find("goto") != -1:
arr = line.split(' ')
print(arr[4] + " (" + arr[5] + arr[6] + arr[7] + '){' + 'goto _' + str(eval(str(addr) + '+1+' + arr[9][3:])) + ';}')
else:
print(line[ins_start + 2:] + ';')
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
typedef uint64_t u64;
typedef uint8_t u8;
typedef uint32_t u32;
int main()
{
u64 flag[4] = {1,2,3,4};
u64 r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10;
_0: r2 = *(u64 *)flag[2];
_1: r2 <<= 32;
_2: r2 >>= 32;
_3: r3 = *(u64 *)flag[3];
_4: r3 <<= 32;
_5: r3 >>= 32;
_6: r4 = r3;
_7: r4 *= 28096;
_8: r5 = r2;
_9: r5 *= 64392;
_10: r5 += r4;
_11: r4 = *(u64 *)flag[1];
_12: r4 <<= 32;
_13: r4 >>= 32;
_14: r0 = r4;
_15: r0 *= 29179;
_16: r5 += r0;
_17: r1 = *(u64 *)flag[0];
_18: r0 = 0;
_19: *(u8 *)(r10 -8) = r0;
_20: *(u64 *)(r10 -16) = r0;
_21: *(u64 *)(r10 -24) = r0;
_22: r1 <<= 32;
_23: r1 >>= 32;
_24: r0 = r1;
_25: r0 *= 52366;
_26: r5 += r0;
_27: r6 = 1;
_28: r0 = 0xbe18a1735995;
_30: if (r5 != r0){goto _97;}
_31: r5 = r3;
_32: r5 *= 61887;
_33: r0 = r2;
_34: r0 *= 27365;
_35: r0 += r5;
_36: r5 = r4;
_37: r5 *= 44499;
_38: r0 += r5;
_39: r5 = r1;
_40: r5 *= 37508;
_41: r0 += r5;
_42: r5 = 0xa556e5540340;
_44: if (r0!=r5){goto _97;}
_45: r5 = r3;
_46: r5 *= 56709;
_47: r0 = r2;
_48: r0 *= 32808;
_49: r0 += r5;
_50: r5 = r4;
_51: r5 *= 25901;
_52: r0 += r5;
_53: r5 = r1;
_54: r5 *= 59154;
_55: r0 += r5;
_56: r5 = 0xa6f374484da3;
_58: if (r0!=r5){goto _97;}
_59: r5 = r3;
_60: r5 *= 33324;
_61: r0 = r2;
_62: r0 *= 51779;
_63: r0 += r5;
_64: r5 = r4;
_65: r5 *= 31886;
_66: r0 += r5;
_67: r5 = r1;
_68: r5 *= 62010;
_69: r0 += r5;
_70: r5 = 0xb99c485a7277;
_72: if (r0!=r5){goto _97;}
_73: *(u32 *)(r10 -12) = r1;
_74: *(u32 *)(r10 -16) = r4;
_75: *(u32 *)(r10 -20) = r2;
_76: *(u32 *)(r10 -24) = r3;
_77: r1 = 0xa7d73257b465443;
_79: *(u64 *)(r10 -40) = r1;
_80: r1 = 0x4648203a47414c46;
_82: *(u64 *)(r10 -48) = r1;
_83: r1 = 0x2052554f59202145;
_85: *(u64 *)(r10 -56) = r1;
_86: r1 = 0x4e4f44204c4c4557;
_88: *(u64 *)(r10 -64) = r1;
_89: r6 = 0;
_90: *(u8 *)(r10 -32) = r6;
_91: r1 = r10;
_92: r1 += -64;
_93: r3 = r10;
_94: r3 += -24;
_95: r2 = 33;
_96: printf((char*)r1, r3);
_97: r0 = r6;
_98: exit(0);
return 0;
}
得到流程图
sudo ./bpftool prog dump xlated id 175 visual &> output.out
dot -Tpng output.out -o visual-digraph.png
0x04 反编译bpf
ghidra9.2.0+ebpf-for-ghidra
https://github.com/Nalen98/eBPF-for-Ghidra