Rust + Ebpf + Android(WIP)

什么原神大集合(bushi)
Rust + Ebpf + Android(WIP)

使用的框架是aya

内容参考

安装 toolchain

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup toolchain install nightly --component rust-src
rustup default nightly
rustup target add aarch64-unknown-linux-musl
sudo apt install lld
cargo install bpf-linker
sudo apt install libssl-dev pkg-config
cargo install cargo-generate

得按照这个来安装才好使
它使用了 musl 更加方便,因为不需要配置 ndk 了,这里我也不使用 Android toolchain 了,这样更加方便一些

创建一个 EBPF 项目

如果需要这样编译的话,首先内核得是 5.10+

更早的内核一般来说不支持,需要手动开启选项然后重新编译内核,而且可能会有奇怪的 bug

对于 5.10+ 内核是默认开启的

创建使用

cargo generate https://github.com/aya-rs/aya-template
## 或者用那个作者的,不过那个作者的比较旧,有些 Module 名字变了
git clone https://github.com/fanrong1992/Rust_Android_eBPF.git

编译

cargo xtask build --arch aarch64-unknown-linux-musl --release

代码编写

对于 vscode 的话,跨平台编写会有些奇怪的问题,对于 #![no_std]会报一个 no crate for test 的错误。

需要在.vscode 目录下的 settings.json 里面

{
  "rust-analyzer.cargo.extraArgs": [
    "--target",
    "aarch64-unknown-linux-musl"
  ],
  "rust-analyzer.linkedProjects": ["Cargo.toml"],
}

bpf 参数问题

网上给的教程说要不使用 vmlinux 来写要不就是查看 /sys/kernel/debug/tracing/events

raven:/sys/kernel/tracing # cat events/binder/binder_transaction/format
name: binder_transaction
ID: 871
format:
        field:unsigned short common_type;       offset:0;       size:2; signed:0;
        field:unsigned char common_flags;       offset:2;       size:1; signed:0;
        field:unsigned char common_preempt_count;       offset:3;       size:1; signed:0;
        field:int common_pid;   offset:4;       size:4; signed:1;

        field:int debug_id;     offset:8;       size:4; signed:1;
        field:int target_node;  offset:12;      size:4; signed:1;
        field:int to_proc;      offset:16;      size:4; signed:1;
        field:int to_thread;    offset:20;      size:4; signed:1;
        field:int reply;        offset:24;      size:4; signed:1;
        field:unsigned int code;        offset:28;      size:4; signed:0;
        field:unsigned int flags;       offset:32;      size:4; signed:0;

print fmt: "transaction=%d dest_node=%d dest_proc=%d dest_thread=%d reply=%d flags=0x%x code=0x%x", REC->debug_id, REC->target_node, REC->to_proc, REC->to_thread, REC->reply, REC->flags, REC->code

但是实验发现好像我的 Pixel 没有这个文件夹,但是系统内核的 tracepoint 是打开的
(/sys/kernel/kheaders.tar.gz 存了 header,/proc/config.gz 存了编译选项)

解决办法就是手动挂载

mount -t debugfs none /sys/kernel/debug
mount -t tracefs none /sys/kernel/debug

之后我们还要有方便的编程环境,需要一个 rust vmlinux
首先下载bpftool

然后 dump vmlinux

./bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h

不过这样得到的是 c 语言的结构体,如果要使用 rust 版的 aya-ebpf 是需要手动改的,非常麻烦。这里参照 官方教程 使用 aya-tool 来生成一个 rust 版的 binding

首先需要 termux,因为我们要编译还需要使用 cargo

pkg update
pkg upgrade
pkg install rust libllvm
cargo install bindgen-cli
cargo install --git https://github.com/aya-rs/aya -- aya-tool

这样就安装完成了,但是需要 dump 的话我们需要有 root 权限,但是直接 su 的话会破坏环境变量

这里使用另一种方法, 直接 adb 执行

su
cd /data/data/com.termux/files/home
export SHELL=/data/data/com.termux/files/usr/bin/bash
export COLORTERM=truecolor
export HISTCONTROL=ignoreboth
export PREFIX=/data/data/com.termux/files/usr
export TERMUX_IS_DEBUGGABLE_BUILD=1
export TERMUX_MAIN_PACKAGE_FORMAT=debian
export PWD=/data/data/com.termux/files/home
export TERMUX_VERSION=0.118.0
export EXTERNAL_STORAGE=/sdcard
export LD_PRELOAD=/data/data/com.termux/files/usr/lib/libtermux-exec.so
export HOME=/data/data/com.termux/files/home
export LANG=en_US.UTF-8
export TERMUX_APK_RELEASE=GITHUB
export TMPDIR=/data/data/com.termux/files/usr/tmp
export TERM=xterm-256color
export SHLVL=2
export PATH=/data/data/com.termux/files/usr/bin:$PATH
export PATH=/data/data/com.termux/files/home/.cargo/bin:$PATH

这样就可以直接在 root adb shell 里面接管整个 termux 的环境了

然后下载 bpftool,并将其移到 /data/data/com.termux/files/usr/bin,就可以开始 generate 了
要注意的是,对于 tracepoint 来说,要用的是 trace_event_raw_xxxx 型的结构体
例如

aya-tool generate trace_event_raw_binder_ioctl > /sdcard/vmlinux.rs
adb pull /sdcard/vmlinux.rs .

这样做的好处是生成的结构体用的是 aya-ebpf-cty。因为写 ebpf 程序只能调用 kernel helper,所以不能链接 std 库,程序最开始会使用 #[no_std]宏来保证没有 std,但是这样的话很多数据结构就都不存在了。cty 就是 std type 的替代品,还是很方便的

不过由于 lib 是自动生成的,需要禁用下 warning。在 vmlinux.rs 最上面加一行

#![allow(warnings)]

然后就可以直接使用 bindings 了。
不过还要注意一点,如果要转换的话需要

ctx.read_at::<trace_event_raw_binder_ioctl>(0).unwrap() ;

RUST_LOG=INFO

如果要读取一个结构体,由于 bpf 程序运行在内核里面,需要 helper 函数来将数据复制过来。
具体是 bpf_probe_read_user, 注意必须要 user!先声明一个指针指向需要的数据结构,然后调用这个函数就可以了

如果想要获得 regs,需要先获得 task,从 task 获得 regs

正文完
 0
评论(没有评论)