BPF - 使用Rust为安卓arm64构建BPF

这里使用的是rust的aya框架

最近出现了一个使用BPF实现的外挂样本!

准备环境

rustup install stable
rustup toolchain install nightly --component rust-src
rustup target add aarch64-unknown-linux-musl

安装 Rust 工具链后,还必须安装。链接器依赖于 LLVM,如果您在 Linux x86_64 系统上bpf-linker运行,则可以针对 Rust 工具链附带的版本进行构建(默认且推荐,直接使用rust自带的LLVM,而不是使用外部的):

cargo install bpf-linker

在基于 Debian 的发行版上,需要安装llvm-19-devlibclang-19-devlibpolly-19-dev包(如果使用 LLVM 19)。

如果在任何其他架构上运行macos 或 linux,则需要先安装最新稳定版本的 LLVM(例如,使用 brew install llvm),然后使用以下命令安装链接器:

LLVM_SYS_180_PREFIX=$(brew --prefix llvm) cargo install \
    --no-default-features bpf-linker

手动写aya的项目太麻烦了,需要 cargo-generate去生成一个模板,安装命令:

cargo install cargo-generate

最后,为了生成内核数据结构的绑定,您必须安装 ,可以从您的发行版安装,也可以从源代码bpftool构建 。

https://aya-rs.dev/book/start/development/#prerequisites

输入以下命令创建一个示例的项目:

cargo generate https://github.com/aya-rs/aya-template

关于项目类型,如果你是一点基础都没有的,你可能需要看一下:https://aya-rs.dev/book/programs/

eBPF 程序约束

eBPF 程序将运行在 eBPF 虚拟机中,这是一个非常小,非常不实用的运行时环境:

  • 堆栈只有 512 字节

  • 无法访问堆空间,而必须将数据写入映射

在 Rust 中限制如下:

  • 几乎无法标准库。可以使用core其他库(例如字符串的contains用不了)

  • core::fmt用不了,并且依赖它的特征也不能被使用,例如DisplayDebug

  • 由于没有堆,不能使用alloccollections

  • 不能这样panic,因为 eBPF VM 不支持堆栈展开和abort指令

  • 没有main功能

除此之外,我们编写的很多代码都是unsafe,需要使用helper 包下的函数去进行一些额外操作。

安卓平台特化

创建完模板项目,会有一个README.md,这里会说怎么在本地机器上面运行,但是本文目的是运行到安卓arm64平台,所以说大概看看就好了,没什么用(((

首先在生成出来的项目根目录创建一个.cargo文件夹,里面放一个config.toml,内容如下

[target.aarch64-unknown-linux-musl]
linker = "ld.lld"

既然用了lld,所以说要安装一下,这里展示的是fedora平台的,如果是apt,把dnf换成apt就好了:

sudo dnf install lld

这里需要注意的是aya非常的阴间,他设定平台用的是环境变量,而不是指定feature,需要设置环境变量:CARGO_CFG_BPF_TARGET_ARCH=aarch64

如果你是RustRover,会更加麻烦,需要去到设置设置环境变量,非常的不优雅!!!

构建命令是,构建成功后会生成一个可执行文件,直接运行就好了

cargo build --target aarch64-unknown-linux-musl --release

后记

let program: &mut KProbe = ebpf.program_mut("insmod_tracer_arm64_sys_finit_module").unwrap().try_into()?;
program.attach("__arm64_sys_finit_module", 0)?;

最开始我比较好奇program_mut("insmod_tracer_arm64_sys_finit_module") 里面这坨字符串是什么,但是文档没有,AI也不会(GPT-5),后面我改改,又看了他的宏实现才发现,这个字符串,其实是方法名称

#[kprobe(function = "__arm64_sys_finit_module")]
pub fn insmod_tracer_arm64_sys_finit_module(ctx: ProbeContext) -> u32 {
    info!(&ctx, "kprobe called __arm64_sys_finit_module: pid = {}", ctx.pid());

    0
}

然后attach里面是你要附加的方法,我这里例子里面是__arm64_sys_finit_module

读取用户空间字符串

let mut buf = [0u8; 256];
let my_str = unsafe {
    core::str::from_utf8_unchecked(bpf_probe_read_user_str_bytes(arg1 as *const u8, &mut buf).map_err(|e| 1u32)?)
};