android ebpf中的CO-RE学习
CO-RE原理
因为不同的内核版本的系统内部结构体会有差异,例如struct user_arg_ptr
,当内核编译配置中存在CONFIG_COMPAT=y
的时候,会在native
成员之前增加一个布尔变量is_compat
,这样native
的偏移就发生的变化。
如果编写的ebpf
内核程序需要访问struct user_arg_ptr
类型的变量就需要考虑在不同内核版本上的兼容问题,即一次编译到处运行(CO-RE)。libbpf
提供CO-RE
的支持,libbpf程序编译的时候依赖特定内核的vmlinux.h
头文件,这个头文件中包含了内核中所有结构体信息,这些结构体的成员偏移都是固定的。vmlinux.h
头文件可以通过bpftool btf dump file /sys/kernel/btf/vmlinux format c
生成。
当编译ebpf
程序生成字节码的时候,libbpf
的CO-RE
接口函数借助clang
,通过__builtin_preserve_access_index
记录成员的偏移量。
当ebpf
字节码加载到内存之前,libbpf
会从运行系统的btf文件/sys/kernel/btf/vmlinux
中获取实际的内核结构体信息并进行解析,之后对ebpf
字节码中的结构体引用进行重定位,这样ebpf就可以获取内核结构体正确的偏移。只有系统内核编译的时候打开CONFIG_DEBUG_INFO_BTF=y
才会生成对应的btf
文件,否则就需要通过其他方法提取btf
文件并重编译libbpf
增加对自提取btf
文件的加载。
CO-RE使用
这里以bcc
中提供的libbpf-tools
为例,libbpf-tools
是bcc
为了支持CO-RE
依赖与libbpf
编写的一些和bcc tool
功能类似的小工具。其中execsnoop
通过SEC("tracepoint/syscalls/sys_entry_execve")
和SEC("tracepoint/syscalls/sys_entry_execveat")
对execve/execveat
系统调用进行hook,因为大多数android
设备都没有打开CONFIG_FTRACE_SYSCALLS=y
配置,所以并不支持syscalls
类型的tracepoint
。
这里利用kprobe/kretprobe
对原程序进行改写,从而能在未开启CONFIG_FTRACE_SYSCALLS=y
的设备上运行。这里hook的是do_execveat_common
,因为execve/execveat
系统调用最后都会调用此函数,并且此函数是导出的。这里注意是struct user_arg_ptr
的native
成员保存了argv
命令行参数指针,struct user_arg_ptr
类型变量作为参数传递给do_execveat_common
函数。
这里需要注意gcc/clang
编译器在编译时,如果函数调用传递的参数是一个结构体,其会将此结构体所有的成员作为单个的参数进行传递,而windows平台的MSVC
编译器则是将结构体先拷贝到目标函数栈空间中,然后将对应栈空间中结构体的地址作为参数进行传递。
因为我这里内核配置有CONFIG_COMPAT=y
,所以struct user_arg_ptr
类型的native
成员前有个布尔类型的成员is_compat
,那么对应的is_compat
就是do_execveat_common
函数的第三个参数,native
就是第四个参数(没配置CONFIG_COMPAT=y
就是第三个参数)。利用libbpf
的CO-RE支持函数bpf_core_field_exists
可以判断某个成员是否存在,从而判断是取第三个参数还是第四个参数。
最后通过bpf_prob_read_user
读取对应的命令行参数
do_execveat_common
函数的第二个参数是filename
类型的指针,其成员name
指向对应可执行文件的全路径。虽然filename
在各个linux
内核中的结构差异不大,但是为了安全最好所有的内核结构访问都使用libbpf
的CO-RE
支持函数。
如果访问filename
类型的name
成员不使用CO-RE
支持函数,那么就使用bpf_probe_read_kernel
,默认为name
在filename
结构中的偏移为0
如果使用libbpf
的CO-RE
支持函数bpf_core_read
访问
这里name
就是一个指针,所以可以直接使用bpf_core_read
,对于结构体多层嵌套的情况可以使用BPF_CORE_READ
访问
最后通过bpf_probe_read_kernel_str
从name
内核指针中读取可执行文件全路径
android平台运行的效果如下
代码已上传github:https://github.com/revercc/libbpf-bootstrap-for-android.git
参考:
https://nakryiko.com/posts/bpf-core-reference-guide/#bpf-core-read-str
https://mp.weixin.qq.com/s/zOgwwvVSMlEQzRXse3SBhw
https://github.com/iovisor/bcc.git