BruceFan's Blog

Stay hungry, stay foolish

0%

eBPF Uprobe Hook应用程序

一、uprobe介绍

作用层次:uprobe本身是基于Linux内核的机制,它工作在内核层(Kernel Space),由内核子系统提供支持。然而,uprobe的探测目标是用户态进程(User Space Process),它可以监视应用程序中函数的调用或指令的执行。
主要功能:允许用户在用户空间程序的特定位置(如函数入口)插入探测点,以监测和分析程序的行为:

  • 性能分析: 使用uprobe可以监视特定函数的调用频率、执行时间等,从而进行性能分析。
  • 程序调试: 可以用于监测程序的运行状态,帮助发现潜在的问题。
  • 安全监控: 通过监测某些关键函数的调用,可以检测恶意行为。

因此,uprobe是一种内核级的用户态探测工具。它本质上运行在内核层,但其监控和分析的目标是用户态代码。

uprobe hook应用程序

1.这里先写一个Demo应用程序,然后编写hook这个Demo的eBPF uprobe程序
calc.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
#include <unistd.h>
int myadd(int a, int b) {
return a+b;
}
int mymul(int a, int b) {
return a*b;
}
int main() {
int x, y;
for (x = 2, y = 1;;x++, y++) {
printf("x + y = %d\n", myadd(x, y));
printf("x * y = %d\n", mymul(x, y));
sleep(1);
}
return 0;
}

2.uprobe用户态代码
uprobe_bin.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
int main(int argc, char *argv[]) {
struct uprobe_bin_bpf *skel;
int err = 0;
// define var uprobe_opts, type is bpf_uprobe_opts
LIBBPF_OPTS(bpf_uprobe_opts, uprobe_opts);
skel = uprobe_bin_bpf__open_and_load();
if (!skel) {
fprintf(stderr, "Failed to open and load BPF skeleton\n");
return 1;
}

//uprobe_opts.func_name = "myadd"; // used when binary has symbols
uprobe_opts.retprobe = false;
skel->links.uprobe_add = bpf_program__attach_uprobe_opts(skel->progs.uprobe_add,
-1, "/home/fanrong/Computer/Cpp/calc",
0x1169, // offset for function, 0 when func_name knows
&uprobe_opts);
if (!skel->links.uprobe_add) {
fprintf(stderr, "Failed to attach uprobe: %d\n", -errno);
goto cleanup;
}
printf("Uprobe attached. Monitoring function add\n");
while (1) {
fprintf(stderr, ".");
sleep(1);
}
cleanup:
uprobe_bin_bpf__destroy(skel);
return -err;
}

bpf_program__attach_uprobe_opts()介绍:

1
2
3
4
LIBBPF_API struct bpf_link *
bpf_program__attach_uprobe_opts(const struct bpf_program *prog, pid_t pid,
const char *binary_path, size_t func_offset,
const struct bpf_uprobe_opts *opts);

是libbpf中定义的函数,主要作用是将BPF代码attach到指定应用程序:

  • prog:要attach的BPF代码
  • pid:uprobe要attach的目标进程id,0表示程序本身进程,-1表示任意进程
  • binary_path:要attach到的应用程序路径
  • func_offset:应用程序中的目标函数
  • opts:相关配置选项,如当应用程序中包含符号表,func_name可以设置为目标函数,func_offset可以设为0;retprobe可以标识attach到应用程序开头还是返回的位置

3.uprobe内核态代码
uprobe_bin.bpf.c

1
2
3
4
5
6
7
8
9
10
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>

char LICENSE[] SEC("license") = "GPL";
SEC("uprobe")
int BPF_UPROBE(uprobe_add, int a, int b) {
bpf_printk("add ENTRY: a = %d, b = %d", a, b);
return 0;
}

BPF_UPROBE和BPF_KPROBE一样是用来简化BPF代码的宏,主要作用是将struct pt_regs解析为应用程序的参数,如果不使用这个宏,可以将上述BPF代码写为:

1
2
3
4
5
6
7
SEC("uprobe")
int uprobe_add(struct pt_regs *ctx) {
int a = PT_REGS_PARM1(ctx);
int b = PT_REGS_PARM2(ctx);
bpf_printk("add ENTRY: a = %d, b = %d", a, b);
return 0;
}

在Android上使用uprobe需要用这种写法,用BPF_UPROBE宏会报错,具体原因还没有分析。
SEC宏中也可以直接指定要attach的目标函数,这样在uprobe用户态代码中直接调用uprobe_bin_bpf__attach(skel)即可

1
SEC("uprobe//home/fanrong/Computer/Cpp/calc:myadd")

在高版本内核(我这里是6.8)中,基本没有办法修改参数和返回值,bpf_probe_write_user()被lockdown了,报错unknown func;bpf_override_return()只能在kprobe中使用;PT_REGS_RC()会报错invalid bpf_context access。

uprobe hook so库

和hook应用程序基本一样,将bpf_program__attach_uprobe_opts()中的binary_path改为so的路径即可。

参考
https://github.com/libbpf/libbpf/blob/master/src/libbpf.h