SeeFlowerX / stackplz

基于eBPF的堆栈追踪工具
Apache License 2.0
856 stars 171 forks source link

stackplz

wbstack/watch breakpoint stack/stackplz plus

stackplz是一款基于eBPF的堆栈追踪工具,目前仅适用于Android平台

特性:

要求:

不仅仅是真机,这些环境下也可以使用:

使用

从Releases或者Github Action下载最新预编译好的二进制文件即可

  1. 推送到手机的/data/local/tmp目录下,添加可执行权限即可
adb push stackplz /data/local/tmp
adb shell
su
chmod +x /data/local/tmp/stackplz

注意:v3.0.1之前,使用不同版本时,需要释放库文件,请使用下面的命令

cd /data/local/tmp && ./stackplz --prepare

2. 选项说明

stackplz的所有可用选项,可以通过./stackplz --help查看

2.1 用于对目标进程/线程进行过滤的选项

注意:如果存在多个目标,使用逗号隔开;--no-xxx意为黑名单

选项 黑名单选项 说明
-n/--name APP包名,分组名(root/system/shell/app/iso)
-u/--uid --no-uid 目标uid
-p/--pid --no-pid 目标pid
-t/--tid --no-tid 目标tid
--tname --no-tname 目标线程名,注意最多16字节

2.2 syscall/uprobe hook选项

即syscall hook,后跟系统调用号对应的名字,或者分组,对应的黑名单选项--no-syscall

即uprobe hook,必须配合-l/--lib使用,具体用法参考后面的命令演示

2.3 硬件断点相关选项

选项 默认值 说明
--pid 目标进程pid,与--brk-lib搭配使用计算断点地址
--brk 要下断的地址
--brk-len 4 断点长度
--brk-lib 目标库,使用该选项时--brk为相对偏移
--brk-pid -1 目标进程pid,通常不建议设置该选项

2.4 发送信号选项

--kill SIGSTOP/SIGABRT/SIGTRAP/...,只能设置一个,效果是在命中hook时向目标进程发送信号

注意:对于syscall来说,发送信号的时机位于syscall执行完成之后,所以对于exit/exit_group等syscall可能无法实现预期效果

2.5 参数过滤选项

-f/--filter,该选项用于设定参数的过滤规则

规则 示例 说明
w/white w:/sbin/su 字符串白名单,过滤以/sbin/su开头的内容,最多256字节
b/black b:/sbin/su 字符串黑名单,过滤以/sbin/su开头的内容,最多256字节
bx/bufhex bx:73ea68 buffer数据白名单,过滤16进制以73ea68开头的内容,最多比较8字节
eq/equal eq:0x748a484d2c 寄存器值白名单,过滤寄存器值等于0x748a484d2c的内容

2.6 部分布尔类型选项

选项 说明
--auto 该选项需要配合--kill SIGSTOP使用,效果是自动恢复被挂起的进程
--btf 显式声明当前环境的内核开启了CONFIG_DEBUG_INFO_BTF
--color 该选项需要配合--dumphex使用,效果是在终端显示颜色
--dumphex 启用该选项后,对于buf类型数据将输出为hexdump,风格与CyberChef保持一致
--getoff 输出PC和LR的偏移信息,注意使用该选项会导致性能降低
--json 将日志输出为json格式
--jstack 配合--kill SIGSTOP使用,可对堆栈中的jar/vdex进行解析
--mstack 简易实现堆栈回溯,没有符号信息
--nocheck 禁用bpf特性检查,没有/proc/config.gz或者是其他路径时使用
--quiet 不在终端输出日志
--regs 输出全部寄存器
--showpc 输出堆栈原始PC值
--showtime 输出自开机以来的时间,单位ns
--showuid 输出记录的uid
--stack 输出堆栈

2.7 rpc选项

主要用于frida联动,远程下硬件断点

2.8 杂项选项

3. 命令演示

3.1 追踪syscall

./stackplz -n com.starbucks.cn --syscall connect,sendto,recvfrom -o tmp.log --dumphex

自定义syscall参数类型

受限于代码结构,暂时采取了一种迂回的方法

即,在uprobe的写法下,末尾加上s/ss,即可转为hook syscall,两个s表示syscall退出时也同样读取结构体的详细数据

常规类型末尾的x表示输出为hex

./stackplz -n com.termux -w writev[int,ptr,intx]s
./stackplz -n com.termux -w writev[ptrx,buf,ptrx]ss --dumphex --color

关于syscall名,请查阅Linux kernel syscall tables

3.2 追踪libc的open

注:默认设定的库是/apex/com.android.runtime/lib64/bionic/libc.so,要自定义请使用--lib指定

./stackplz -n com.starbucks.cn --point strstr[str,str] --point open[str,int] -o tmp.log

3.3 在命中uprobe hook时发送信号

有时候希望在经过特定点位的时候停止进程,以便于dump内存,那么可以使用--kill来发送信号,示例:

./stackplz -n com.sfx.ebpf --lib libnative-lib.so -w _Z5func1v --stack --kill SIGSTOP
./stackplz -n com.starbucks.cn --syscall exit --kill SIGSTOP --stack

如果要恢复进程运行,可以用下面这样的命令(另起一个shell,root下执行):

kill -SIGCONT 4326

v3.0.0版本起,可以在终端输入c后回车恢复进程运行

3.4 硬件断点示例如下,支持的断点类型:r,w,rw,x

pid + 绝对地址

./stackplz --pid `pidof com.sfx.ebpf` --brk 0x70ddfd63f0:x --stack

pid + 偏移 + 库文件

./stackplz --pid `pidof com.sfx.ebpf` --brk 0xf3a4:x --brk-lib libnative-lib.so --stack

对内核中的函数下硬件断点:

!!!注意,内核函数通常触发非常频繁,该操作可能导致设备重启,请谨慎使用,原因不明

echo 1 > /proc/sys/kernel/kptr_restrict
cat /proc/kallsyms  | grep "T sys_"
./stackplz --brk 0xffffff93c5beb634:x --pid `pidof com.sfx.ebpf` --stack
./stackplz --brk 0xffffffc0003654dc:x --pid `pidof com.sfx.ebpf` --regs

3.5 以寄存器的值作为大小读取数据、或者指定大小

./stackplz --name com.sfx.ebpf -w write[int,buf:x2,int]
./stackplz --name com.sfx.ebpf -w write[int,buf:32,int]
./stackplz --name com.sfx.ebpf -w write[int,buf:0x10,int]

进阶用法:

libc.so+0xA94E8处下断,读取x1int,读取sp+0x30-0x2cptr

./stackplz --name com.sfx.ebpf -w 0xA94E8[int:x1,ptr:sp+0x30-0x2c]

libc.so+0xA94E8处下断,读取x1int,读取sp+0x30-0x2cbuf,长度为8

./stackplz --name com.sfx.ebpf -w 0xA94E8[int:x1,buf:8:sp+0x30-0x2c]
.text:00000000000A94E4                 LDR             W1, [SP,#0x30+var_2C]
.text:00000000000A94E8                 MOV             W20, W0

按默认顺序读取,以及按指定寄存器读取,下面的示例中两个方式输出结果相反:

./stackplz --name com.sfx.ebpf -w 0xA94E8[int,int]
./stackplz --name com.sfx.ebpf -w 0xA94E8[int:x1,int:x0]

call_constructors处获取soinfo内容

# 打印名称和完整路径
./stackplz -n com.coolapk.market -l linker64 -w __dl__ZN6soinfo17call_constructorsEv[ptr,str.f0:x0+409,str:x0+448.] -f w:libjiagu
./stackplz -n com.coolapk.market -l linker64 -w __dl__ZN6soinfo17call_constructorsEv[ptr,std.f0:x0+408,std:x0+432] -f w:libjiagu
# 将 init_array_count_ 和 init_array_ 内容打印出来 
./stackplz -n com.coolapk.market -l linker64 -w __dl__ZN6soinfo17call_constructorsEv[ptr,std.f0:x0+408,*int:x0+160,ptr_arr:6:x0+152.] -f w:libjiagu --dumphex --color

偏移说明如下,这些偏移可以根据call_constructors get_realpath get_soname得到:

在指定偏移处做退出读取,退出偏移即RET指令的偏移,示例如下

./stackplz -n com.termux -w gettimeofday[timeval,timezone]0x4B320
./stackplz -n com.termux -w 0x9D150[int,buf:x2,int]0x9D164 --dumphex --color

3.6 按分组批量追踪进程

追踪全部APP类型的进程,但是排除一个特定的uid:

./stackplz -n app --no-uid 10084 --point open[str,int] -o tmp.log

同时追踪一个APP和(所有)isolated进程:

./stackplz -n com.starbucks.cn,iso --syscall openat -o tmp.log

可选的进程分组:root system shell app iso

3.7 按分组批量追踪syscall

./stackplz -n com.xingin.xhs -s %file,%net --no-syscall openat,recvfrom

可选的syscall分组如下:

具体分组情况请查看Parse_SyscallNames

3.8 应用过滤规则

黑白名单:

./stackplz -n com.starbucks.cn -s openat:f0.f1.f2 -f w:/system -f w:/dev -f b:/system/lib64 -o tmp.log

LR比较,需要提前计算用于比较的值:

./stackplz -n com.chinarainbow.tft -w memcpy[ptr,ptr,int,ptr.f0:lr] -f eq:0x748a484d2c --stack --kill SIGSTOP

引入buffer数据比较,bx/bufhex,可以进行最多8字节的比较

./stackplz -n com.netease.cloudmusic -w sendto[int,buf.f0:x2,int] -f bx:73ea68 -o tmp.log --dumphex --color --stack

3.9 尝试输出更详细的java堆栈

注意:--jstack必需搭配--kill SIGSTOP使用,应用被挂起后可按c回车恢复运行

./stackplz_arm64 -n com.wsy.crashcatcher -w raise --stack --jstack --showpc --kill SIGSTOP

使用提示:

注意,默认屏蔽下列线程,原因是它们属于渲染或后台相关的线程,会触发大量的syscall调用

如果有需求追踪下列线程,请添加--full-tname使用,或者手动修改DefaultThreadBlacklist函数

更多用法,请通过-h/--help查看:

编译

可参考workflow或查看编译文档

Q & A

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

参见:unwinddaemon,注意v3.0.3之后采用了新的编译方案

  1. perf event ring buffer full, dropped 9 samples

使用-b/-buffer设置每个CPU的缓冲区大小,默认为8M,如果出现数据丢失的情况,请适当增加这个值,直到不再出现数据丢失的情况

命令示意如下:

./stackplz -n com.starbucks.cn -b 32 --syscall all -o tmp.log

增大缓冲区大小也可能带来新的问题,比如分配失败,这个时候建议尽可能清理正在运行的进程

failed to create perf ring for CPU 0: can't mmap: cannot allocate memory

  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

文章

个人碎碎念太多,有关stackplz文章就不同步到本项目了,请移步博客查看:

之前针对syscall追踪并获取参数单独开了一个项目,整体结构更简单,没有interface,有兴趣请移步estrace

不过目前estrace的全部功能已经在stackplz中实现,不日将存档

Ref

本项目参考了以下项目和文章:


wbstack

./wbstack_arm64 -p `pidof com.sfx.ebpf` --brk 0x6dd9d563a4:x --stack
./wbstack_arm64 -p `pidof com.sfx.ebpf` --brk 0x6dd9d563a4:x -w 0x0[str,ptr,buf:32:x0] --color --dumphex
./wbstack_arm64 -p `pidof com.sfx.ebpf` --brk 0xF3A4:x --brk-lib libnative-lib.so -w 0x0[str,ptr,buf:32:x0] --color --dumphex --stack
./wbstack_arm64 -p `pidof com.sfx.ebpf` --brk 0xF3A4:x --brk-lib libnative-lib.so -w 0x0[str,ptr,buf:32:x0] --color --dumphex --stack --jstack --kill SIGSTOP