eBPF介绍
eBPF(extended Berkeley Packet Filter)是一种内核技术,它允许开发人员在不修改内核代码的情况下运行特定的功能。在出厂版本高于Android12的内核上,谷歌都要求加入eBPF作为安卓内核原生的一部分,因此eBPF具有开销低、无感知等等优点。除了固定的tracepoint之外,还提供kprobe追踪内核指令、uprobe追踪用户态程序,追踪非常灵活全面。
想要在安卓上开发自定义的eBPF程序,除了自己写源码手动编译并整合进系统外,也可以选择BCC或者bpftrace这样的现成工具。但是这两个工具都需要系统具有root权限,同时也不利于我们对eBPF的实现有一个较为深入的认识,因此本文将采取手动编译的方式使用eBPF。
Kernel-side与User-side程序
根据安卓官方的介绍,在 Android 启动期间,系统会加载位于 /system/etc/bpf/ 的所有 eBPF 程序。这些程序是 Android 构建系统根据 C 程序构建而成的二进制对象,并附带了 Android 源代码树中的 Android.bp 文件。构建系统将生成的对象存储在 /system/etc/bpf 中,这些对象将成为系统映像的一部分。
因此,使用eBPF需要分为两步:
- 编写内核态的eBPF程序,编译后储存在/system/etc/bpf/目录
- 编写用户态的eBPF程序,访问bpfmap以读取内核态程序的输出
具体如何编写网上已经有充足的教程,本文不再赘述,这里推荐一篇我自己参考的文章:https://pshocker.github.io/2022/06/18/Android-eBPF%E7%9B%91%E6%8E%A7%E6%89%80%E6%9C%89%E7%B3%BB%E7%BB%9F%E8%B0%83%E7%94%A8/
非特权使用eBPF
内核态程序本身就具有root权限;而用户态程序想要读取bpfmap,就必须具有root权限。接下来是本文的主要内容:如何在一台不进行root的手机上使用eBPF。
最初我进行的尝试是先使用magisk获取临时root,进行必要配置之后还原成没有root的镜像。主要思路是赋予用户态程序的可执行文件suid权限,在运行时将自己变成root,并且也确实能够将自己的euid甚至ruid改为root,但是读取bpfmap时仍提示权限不足。翻阅相关资料之后发现,除了权限之外,安卓系统还有一套capabilities的访问控制机制,其中就包括控制bpfmap读取权限的CAP_BPF。而运行程序时,会将调用者和被调用文件的capabilities做一个与操作,也就是只取两者较低的权限,因此这个方法无法达到我们想要的效果。
失败之后,决定另辟蹊径,思考如何在非root手机上获取真正的root。在这里我主要受到了magisk获取root权限的方式的启发:与传统linux的su不同,magisk 在开机后会启动一个高权限 root 用户的进程 magiskd,调用 su 会通过 socket 连接到 magiskd,然后在 magiskd 进程中执行命令。那么我们也可以对系统进行类似的修改,启动一个真正的root进程,并与其通信以完成对bpfmap的读取。
由于我们可以对系统进行修改,获取特权进程并非难事:init.rc中规定了系统启动时会触发的一系列服务,此时使用的权限就是root权限,我们可以将我们的程序注入system/core/rootdir/init.rc:
1 | #定义service |
这样,系统便会在启动后调用我们存放在/system/bin/myroot.sh的脚本,其内容如下:
1 | #!/bin/sh |
这样,该进程便会陷入死循环,等待本地23334端口的连接,执行用户态bpf程序/system/bin/bpf_cli并传出程序的输出。
配置完成后,我们便可以通过一个普通进程连接23334端口,从而以root权限执行bpf用户态程序,实现在非root的环境里使用eBPF。值得一提的是,要使该进程能够在启用SELinux的环境下执行相应功能,我们还需要对sepolicy进行修改,主要流程大致如下:
0. 在system/sepolicy/private/file_contexts里加入对相关文件的记录:
1 | /system/bin/myroot.sh u:object_r:myroot_exec:s0 |
创建system/sepolicy/private/myroot.te,写入如下内容:
1 | type myroot, domain, coredomain; |
(注意此处要同时修改system/sepolicy/prebuilts/api/32.0/private/file_contexts和system/sepolicy/prebuilts/api/32.0/private/myroot.te,否则无法通过编译)
- 将SELinux设置为permissive
- 执行程序,在dmesg里寻找所有相关的avc denied记录
- 将记录输入audit2allow,将输出增添至myroot.te
- 重复2-3直到不再有新的报错,将将SELinux设置为enforcing
参考文章
- 安卓官方eBPF介绍(https://source.android.com/docs/core/architecture/kernel/bpf?hl=zh-cn)
- 编写并编译eBPF程序(https://pshocker.github.io/2022/06/18/Android-eBPF%E7%9B%91%E6%8E%A7%E6%89%80%E6%9C%89%E7%B3%BB%E7%BB%9F%E8%B0%83%E7%94%A8/)
- 新增具有root权限的开机服务(http://www.shibaking.com/blog/2020-03-16-Android8.1%E4%B8%8B%E6%96%B0%E5%A2%9E%E5%BC%80%E6%9C%BA%E6%9C%8D%E5%8A%A1.html)
- 修改sepolicy(https://www.codeleading.com/article/90661709951/)