Skip to content

gdut/stackplz

 
 

Repository files navigation

stackplz

stackplz是一款基于eBPF的堆栈追踪工具,本项目主要参考以下项目和文章,致谢

特性:

  • 对原进程影响极小
  • 详细的堆栈信息

要求

  • 手机有root权限
  • 内核大于等于4.14,可使用uname -r查看自己手机的内核信息
  • Android 11以及之后的系统版本
  • 仅支持对64位库进行hook

使用

从release下载预编译好的二进制文件即可,或者自行编译,产物在bin目录下

  1. 推送到手机的/data/local/tmp目录下,添加可执行权限即可
adb push stackplz /data/local/tmp
adb shell
su
chmod +x /data/local/tmp/stackplz
  1. 第一次使用时需要释放库文件,请使用下面的命令
/data/local/tmp/stackplz stack --prepare

  1. 参考下列命令示例进行hook

追踪系统调用时的堆栈,以及寄存器信息,支持按pid过滤

./stackplz --name com.lemon.lv --pid 11267 syscall --nr 63 --regs --stack

通过指定uid,对/apex/com.android.runtime/lib64/bionic/libc.soopen函数进行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 表示是否禁用hook
    • configs 目标库的多个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/ebpfmanagercilium/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

Q & A

  1. 使用时手机卡住并重启怎么办?

经过分析,出现这种情况是因为bpf_perf_event_output参数三使用的是BPF_F_CURRENT_CPU导致

借助vmlinux-to-elf把boot.img转换成ELF文件,通过对比分析

发现出现崩溃的内核走到了brk 1指令,但是这个分支本不该存在,详细分析过程后续会单独出一篇文章

对于此种情况,建议升级系统到Android 12版本一般可以避免

(或者尝试自己编译下内核?)

  1. preload_libs里面的库怎么编译的?

参见:unwinddaemon

  1. perf event ring buffer full, dropped 9 samples

有待优化,目前建议是不输出堆栈,或者减少hook点

  1. 通过符号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

NEXT

后续功能开发:

  • 更合理的获取maps的方案,缓存机制,有变化时再获取
  • 提供选项区分hook类型,而不是拆成两个子命令,简化代码
  • 为高版本内核提供读取数据内存并输出hex、字符串参数等功能
  • 批量hook使用新的配置文件,更细化控制
  • 为特定syscall的参数提供过滤功能,当然这是高版本内核才有的
  • pid、tid等选项的黑名单+白名单过滤支持

性价比真机推荐Redmi Note 11T Pro(理由:价格亲民、内核开源、内核版本5.10.66、可解锁或临时root):

Releases

No releases published

Packages

No packages published

Languages

  • Go 87.8%
  • C 9.6%
  • Makefile 2.0%
  • Shell 0.6%