之前通过修改内核插桩并编写内核模块的方式hookdo_sys_open
函数(这种方式有点像tracepoint,都属于静态探测),这种方式优点是可以hook内核中的任意函数,但是需要编译内核和驱动模块较为麻烦。
eBPF
相当于在内核中定义了一个虚拟机,能够加载eBPF
字节码并依赖kprobe
,uprobe
,tracepoint
实现内核层以及用户层函数的hook。每次更新hook代码不需要重新编译内核,只需要编写新的eBPF程序生成新的eBPF
字节码。缺点是eBPF
程序有很多限制,包括堆栈大小限制,内核函数调用限制(只能通过BPF Helper
函数调用部分内核函数)等。同时在android
平台上的开发编译和部署也是不太成熟,eBPF
程序开发和部署相关参考https://github.com/eunomia-bpf/bpf-developer-tutorial/blob/main/src/0-introduce/README.md
目前最常用的的eBPF
开发就是使用BCC
,BCC
全称为BPF Compiler Collection
,该项目是一个python库,包含了完整的编写、编译、和加载BPF程序的工具链。可以使用c
编写内核部分代码并使用python
加载和解析输出,环境搭建参考https://bbs.kanxue.com/thread-275176.htm#msg_header_h1_5,https://cloud.tencent.com/developer/article/2312646
BCC
官方开发教程https://github.com/iovisor/bcc/blob/master/docs/reference_guide.md#2-attach_kretprobe,https://github.com/iovisor/bcc/blob/master/docs/tutorial_bcc_python_developer.md,官方也提供了很多小工具作为例子。
参考官方文档编写一个文件重定位的BCC
脚本,使用的pixel6内核是Linux 5.10
,查看内核代码中open
,openat
和openat2
系统调用最后的实现都是do_sys_openat2
。对do_sys_openat2
函数进行hook并修改参数即可实现文件重定位。
BCC
脚本如下:
import argparse
from bcc import BPF
# define BPF program
prog = """
#include <uapi/linux/ptrace.h>
#include <linux/sched.h>
struct openat_entry_info {
u32 pid;
u64 time;
char filename[NAME_MAX];
char comm[TASK_COMM_LEN];
};
struct openat_filter_info {
int fd;
u32 pid;
u64 time;
char filename[NAME_MAX];
char comm[TASK_COMM_LEN];
};
//key : pid_tid
BPF_HASH(entry_info, u64, struct openat_entry_info);
int pre_openat(struct pt_regs *ctx, int dfd, const char __user *filename, struct open_how *how) {
struct openat_entry_info _info = {};
u64 pid_tid = bpf_get_current_pid_tgid();
_info.pid = pid_tid >> 32;
_info.time = bpf_ktime_get_ns();
if(bpf_get_current_comm(&_info.comm, sizeof(_info.comm)) != 0){
return 0;
}
if(bpf_probe_read(&_info.filename, sizeof(_info.filename), (void*)PT_REGS_PARM2(ctx)) < 0){
return 0;
}
entry_info.update(&pid_tid, &_info);
size_t filename_len;
for (filename_len = 0; filename_len < NAME_MAX; filename_len++) {
if (_info.filename[filename_len] == '\\0') {
break;
}
}
size_t num = 0;
char hide_file[] = "HIDE_FILE_NAME";
if(strlen(hide_file) == filename_len){
for(int i = 0; i < filename_len; i++){
if(hide_file[i] == _info.filename[i]){
num++;
}
else{
break;
}
}
if(num == strlen(hide_file)){
char relocate_file[] = "RELOCATE_FILE_NAME";
bpf_probe_write_user((void*)filename, relocate_file, sizeof(relocate_file));
bpf_trace_printk("Hello, World : %s!\\n", relocate_file);
}
}
return 0;
}
BPF_PERF_OUTPUT(openat_filter_events);
int post_openat(struct pt_regs *ctx) {
struct openat_filter_info filter_info = {};
u64 pid_tid = bpf_get_current_pid_tgid();
struct openat_entry_info* p_info;
p_info = entry_info.lookup(&pid_tid);
if(p_info == 0){
return 0;
}
filter_info.fd = PT_REGS_RC(ctx);
filter_info.pid = p_info->pid;
filter_info.time = p_info->time;
bpf_probe_read_kernel(&filter_info.comm, sizeof(filter_info.comm), p_info->comm);
bpf_probe_read_kernel(&filter_info.filename, sizeof(filter_info.filename), p_info->filename);
openat_filter_events.perf_submit(ctx, &filter_info, sizeof(filter_info));
entry_info.delete(&pid_tid);
return 0;
}
"""
# Get args
parser = argparse.ArgumentParser(description="file relocate by bcc")
parser.add_argument("--hide", type=str)
parser.add_argument("--relocate", type=str)
args = parser.parse_args()
prog = prog.replace("HIDE_FILE_NAME", args.hide)
prog = prog.replace("RELOCATE_FILE_NAME", args.relocate)
# load BPF program
b = BPF(text=prog)
b.attach_kprobe(event="do_sys_openat2", fn_name="pre_openat")
b.attach_kretprobe(event="do_sys_openat2", fn_name="post_openat")
# read events
def print_openat_filter(cpu, data, size):
event = b["openat_filter_events"].event(data)
print("%-10d %-32s %-6d %-32s" % (event.pid, event.comm, event.fd, event.filename))
b["openat_filter_events"].open_perf_buffer(print_openat_filter)
while True:
try:
b.perf_buffer_poll()
except KeyboardInterrupt:
exit()
在手机的BCC
环境中运行脚本python relocate_file.py --hide /data/local/tmp/as --relocate /tmp/234234234
,将/data/local/tmp/as
文件重定位到一个不存在的文件/tmp/234234234