ebpf常见挂载点
基本上所有设备都能用的常见挂载点,目前考虑6.1内核,可惜的是pixel 6 的6.1内核还不支持fentry/fexit
未写完,在写了
Kprobe
Kprobe是比较常见的附加到内核函数的方法,kprobe/kretprobe分别负责在进入前和返回前hook内核函数
用法是使用SEC(kprobe/name)
,直接使用内核函数的签名即可,比如SEC(kprobe/kernel_clone)
,这个签名可以在/proc/kallsym
中获取
获取调用参数有两种方案,最基础的方法是使用struct pt_regs *ctx
作为参数,这个ctx结构体是具有架构依赖的,然后使用PT_REGS_PARM*(ctx)
宏读取参数,arm64架构中这个可以填1~8
1 |
|
这就是一个比较简单的探测kernel_clone这个函数的kprobe探针,使用PT_REGS_PARM1
读取第一个参数(每个具体参数要查阅内核源代码中的定义),使用bpf_probe_read_kernel
读取这个指针指向的参数列表结构体(直接解引用会被检查器拒绝加载,因为这是不安全的行为),然后根据flag这个掩码参数简单区分下进程和线程,通过bpf_get_current_pid_tgid
获取调用clone的进程pid,使用printk
输出到trace_pipe中
可以看到捕捉到了adb服务的活动,和新创建的shell的活动
除了直接声明函数还可以使用BPF_KPROBE
这个宏,上述的代码可以写成如下等价形式
1 |
|
这个宏的第一个参数是声明的函数名,之后接受最多五个参数,作为前5个传参寄存器中读取的参数(PT_REGS_PARM1~5),写起来会比第一种写法简洁一点,基本所有探针类型都有类似的宏
以及对于这些宏,触发探针时的上下文环境会被以*ctx
保存,可以通过ctx访问上下文环境,所以不要在函数中再次使用ctx作为变量名
另外还有一种libbpf提供的语法糖ksyscall
,因为linux syscall函数的命名通常遵循一定标准,ksyscall可以根据部分提供的函数名自动选择对应的函数附加(比如ksyscall/openat就会选择一种openat的实现),不过由于syscall通常由多种实现所以这种方法很容易漏掉调用,如果只是想
Uprobe
Uprobe是用来hook用户态函数的探针,原理是把目标地址的指令替换成int3(其他架构上就是对应的中断指令)跳到内核态执行hook逻辑(类似条件断点脚本),但是和调试器相比隐蔽性更强,基本上对应用侧只有扫描自身指令是否被修改这一种检测手法
Tracepoint
Syscall
如果内核开启了CONFIG_FTRACE_SYSCALLS的话就可以使用,使用方法是SEC(tp/syscall/name)
宏,大部分手机的原厂镜像应该是不支持的
fentry/fexit
如果内核支持fentry可以使用,和kprobe语法类似,SEC(fentry/name)
,与kprobe不同的是fentry/fexit对每个内核函数提供了参数结构体,可以直接使用Args->fieldname
的方法访问参数