stackplz是一款基于eBPF的堆栈追踪工具,本项目主要参考以下项目和文章,致谢
特性:
- 对原进程影响极小
- 详细的堆栈信息
- 手机有root权限
- 内核大于等于4.14,可使用
uname -r
查看自己手机的内核信息 - Android 11以及之后的系统版本
- 仅支持对64位库进行hook
从release下载预编译好的二进制文件即可,或者自行编译,产物在bin
目录下
- 推送到手机的
/data/local/tmp
目录下,添加可执行权限即可
adb push stackplz /data/local/tmp
adb shell
su
chmod +x /data/local/tmp/stackplz
- 第一次使用时需要释放库文件,请使用下面的命令
/data/local/tmp/stackplz stack --prepare
- 参考下列命令示例进行hook
追踪系统调用时的堆栈,以及寄存器信息,支持按pid过滤
./stackplz --name com.lemon.lv --pid 11267 syscall --nr 63 --regs --stack
通过指定uid,对/apex/com.android.runtime/lib64/bionic/libc.so
的open
函数进行hook
./stackplz --uid 10245 stack --symbol open --stack --regs
通过指定包名,对libnative-lib.so
的_Z5func1v
符号进行hook
./stackplz --name com.sfx.ebpf stack --library libnative-lib.so --symbol _Z5func1v --stack --regs
通过--reg
指定寄存器,对跳转目标地址进行偏移计算,再也不担心找不到跳哪儿去了
--reg
选项需要搭配--regs
或者--stack
使用,后续进行优化
./stackplz --name com.xingin.xhs stack --library libtiny.so --offset 0x175248 --regs --reg x8
通过指定包名和配置文件进行批量hook
./stackplz --name com.sfx.ebpf stack --config config.json
配置文件示例如下
{
"library_dirs": [
"/apex/com.android.runtime/lib64"
],
"libs": [
{
"library": "bionic/libc.so",
"disable": false,
"configs": [
{
"stack": true,
"regs": true,
"symbols": ["open"],
"offsets": []
},
{
"stack": false,
"regs": true,
"symbols": ["read", "send", "recv"],
"offsets": []
}
]
},
{
"library": "libnative-lib.so",
"disable": false,
"configs": [
{
"stack": true,
"regs": true,
"symbols": ["_Z5func1v"],
"offsets": ["0xF37C"]
}
]
}
]
}
字段说明:
library_dirs
目标库的搜索路径,可以设置多个libs
目标多个库的hook配置library
库名、完整库路径或者与搜索路径拼接后存在的路径disable
表示是否禁用hookconfigs
目标库的多个hook点配置,按输出需要进行配置- 即输出堆栈与输出寄存器信息的组合,每一种组合都可以设定多个符号和多个偏移
注意事项:
- 必须提供包名或者目标的uid,二选一
- 默认hook的库是
/apex/com.android.runtime/lib64/bionic/libc.so
,可以只提供符号进行hook - hook目标加载的库时,默认在对应的库目录搜索,所以可以直接指定库名而不需要完整路径
- 例如
/data/app/~~t-iSPdaqQLZBOa9bm4keLA==/com.sfx.ebpf-C_ceI-EXetM4Ma7GVPORow==/lib/arm64
- 例如
- 如果要hook的库无法被自动检索到,请提供在内存中加载的完整路径
- 最准确的做法是当程序运行时,查看程序的
/proc/{pid}/maps
内容,这里的路径是啥就是啥
- 最准确的做法是当程序运行时,查看程序的
- 批量hook请记得把配置文件推送到程序运行的同一目录
查看更多帮助信息使用如下命令:
/data/local/tmp/stackplz -h
/data/local/tmp/stackplz stack -h
输出到日志文件添加-o/--out tmp.log
,只输出到日志,不输出到终端再加一个--quiet
即可
本项目依赖于ehids/ebpfmanager和cilium/ebpf,但是做出了一些修改
所以目前编译需要使用我修改过的版本,三个项目需要放在同一目录下
git clone https://github.com/SeeFlowerX/ebpf
git clone https://github.com/SeeFlowerX/ebpfmanager
然后是本项目的代码
git clone https://github.com/SeeFlowerX/stackplz
本项目在linux x86_64环境下编译,编译时先进入本项目根目录
准备必要的外部代码,记得挂全局代理或者使用proxychains
等工具
./build_env.sh
然后下载ndk并解压,我这里选的是android-ndk-r25b
,解压后修改build.sh
中的NDK_ROOT
路径
本项目还需要使用golang,版本要求为1.18
,建议通过snap安装,或者使用如下方法安装
wget "https://golang.org/dl/go1.18.7.linux-amd64.tar.gz"
tar -C /usr/local -xvf "go1.18.7.linux-amd64.tar.gz"
设置环境变量
nano ~/.bashrc
在末尾添加如下内容
export GOPATH=$HOME/go
export PATH=/usr/local/go/bin:$PATH:$GOPATH/bin
export GOPROXY=https://goproxy.cn,direct
export GO111MODULE=on
对单个项目来说,似乎要用下面的命令手动操作下,再重新用vscode打开才不会报错
go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.cn,direct
使环境变量立即生效
source ~/.bashrc
执行./build.sh
即可完成编译,产物在bin
目录下
将可执行文件推送到手机上后就可以开始使用了
adb push bin/stackplz /data/local/tmp
- 使用时手机卡住并重启怎么办?
经过分析,出现这种情况是因为bpf_perf_event_output
参数三使用的是BPF_F_CURRENT_CPU
导致
借助vmlinux-to-elf把boot.img转换成ELF文件,通过对比分析
发现出现崩溃的内核走到了brk 1
指令,但是这个分支本不该存在,详细分析过程后续会单独出一篇文章
对于此种情况,建议升级系统到Android 12版本一般可以避免
(或者尝试自己编译下内核?)
preload_libs
里面的库怎么编译的?
参见:unwinddaemon
- perf event ring buffer full, dropped 9 samples
有待优化,目前建议是不输出堆栈,或者减少hook点
- 通过符号hook确定调用了但是不输出信息?
某些符号存在多种实现(或者重定位?),这个时候需要指定具体使用的符号或者偏移
例如strchr
可能实际使用的是__strchr_aarch64
,这个时候应该指定__strchr_aarch64
而不是strchr
coral:/data/local/tmp # readelf -s /apex/com.android.runtime/lib64/bionic/libc.so | grep strchr
868: 00000000000b9f00 32 GNU_IFUNC GLOBAL DEFAULT 14 strchrnul
869: 00000000000b9ee0 32 GNU_IFUNC GLOBAL DEFAULT 14 strchr
1349: 000000000007bcf8 68 FUNC GLOBAL DEFAULT 14 __strchr_chk
689: 000000000004a8c0 132 FUNC LOCAL HIDDEN 14 __strchrnul_aarch64_mte
692: 000000000004a980 172 FUNC LOCAL HIDDEN 14 __strchrnul_aarch64
695: 000000000004aa40 160 FUNC LOCAL HIDDEN 14 __strchr_aarch64_mte
698: 000000000004ab00 204 FUNC LOCAL HIDDEN 14 __strchr_aarch64
5143: 00000000000b9ee0 32 FUNC LOCAL HIDDEN 14 strchr_resolver
5144: 00000000000b9f00 32 FUNC LOCAL HIDDEN 14 strchrnul_resolver
5550: 00000000000b9ee0 32 GNU_IFUNC GLOBAL DEFAULT 14 strchr
6253: 000000000007bcf8 68 FUNC GLOBAL DEFAULT 14 __strchr_chk
6853: 00000000000b9f00 32 GNU_IFUNC GLOBAL DEFAULT 14 strchrnul
如图,我们可以看到直接调用了__strchr_aarch64
而不是经过strchr
再去调用__strchr_aarch64
有关eBPF on Android系列可以加群交流
个人碎碎念太多,有关stackplz文章就不同步到本项目了,请移步博客查看:
针对syscall追踪并获取参数单独开了一个项目,整体结构更简单,没有interface,有兴趣请移步estrace
后续功能开发:
- 更合理的获取maps的方案,缓存机制,有变化时再获取
- 提供选项区分hook类型,而不是拆成两个子命令,简化代码
- 为高版本内核提供读取数据内存并输出hex、字符串参数等功能
- 批量hook使用新的配置文件,更细化控制
- 为特定syscall的参数提供过滤功能,当然这是高版本内核才有的
- pid、tid等选项的黑名单+白名单过滤支持
性价比真机推荐Redmi Note 11T Pro(理由:价格亲民、内核开源、内核版本5.10.66、可解锁或临时root):