这篇文章上次修改于 665 天前,可能其部分内容已经发生变化,如有疑问可询问作者。

前言

Android从9.0版本开始全面支持eBPF(extended Berkeley Packet Filters), 其主要用在流量统计上, 也可以用来监控CPU/IO/内存等模块的状态.简单来说, eBPF可以与内核的kprobe/tracepoints/skfilter等模块相结合, 将eBPF的函数hook到内核事件从而监控相应的系统状态。

在之前,逆向分析过程中为了追踪so的系统调用,通常是结合frida做的

基于frida,各路大佬给出了多种方案,在此列举一二

另外补充一个非frida的方案

frida很方便,但是随着对抗日益升级,使用frida的前提是先过frida检测,检测手段不断在升级,搞不定岂不是很难受,这个时候就要换个赛道

  • 改内核?水平不够
  • 自编译ROM?太复杂
  • 使用沙盒APP?难以贴近真机环境

而eBPF可以以不侵入的方式去记录程序的一举一动,对于APP来说是完全无感的

对于逆向工作者来说,eBPF有个【缺陷】,即无法更改其中的逻辑,只能记录信息

不过查询资料,应该是可以配合SECCOMP来实现修改

更正】实际上是存在bpf_probe_write_user这样的函数,可以修改用户态数据;不过没有修改内核态数据的函数

说了这么多,其实就一句话,系统自带有追踪系统调用的功能,正是基于eBPF提供的

环境

  • Android 11
  • Pixel 4XL
  • root权限

步骤

下面命令中的路径在不同系统可能不同,即有的系统是第一个,有的是第二个,有的两个都有

  • /sys/kernel/tracing
  • /sys/kernel/debug/tracing

而两个都有的话,其实是一样的,比如本文使用的测试环境就是两个都有

进入shell环境,并切换到root身份

adb shell
su

先查看是否支持syscall调用的追踪

coral:/ # zcat /proc/config.gz | grep TRACEPOINT
CONFIG_TRACEPOINTS=y
CONFIG_HAVE_SYSCALL_TRACEPOINTS=y
# CONFIG_TRACEPOINT_BENCHMARK is not set

如果出现CONFIG_HAVE_SYSCALL_TRACEPOINTS=y那么就说明支持

执行下面两个命令,对系统调用之前的状态进行追踪

echo 1 > /sys/kernel/debug/tracing/events/raw_syscalls/sys_enter/enable
echo 1 > /sys/kernel/debug/tracing/tracing_on

注意,如果不想追踪了,记得将配置改回去,毕竟会带来一些性能消耗...

echo 0 > /sys/kernel/debug/tracing/events/raw_syscalls/sys_enter/enable
echo 0 > /sys/kernel/debug/tracing/tracing_on

相关的输出会保存到/sys/kernel/debug/tracing/trace,而默认有一个格式,通过下面的命令可以看到格式

coral:/ # head /sys/kernel/debug/tracing/trace
# tracer: nop
#
# entries-in-buffer/entries-written: 265418/5871240   #P:8
#
#                              _-----=> irqs-off
#                             / _----=> need-resched
#                            | / _---=> hardirq/softirq
#                            || / _--=> preempt-depth
#                            ||| /     delay
#           TASK-PID   CPU#  ||||    TIMESTAMP  FUNCTION

也就是说会打印PID信息,那么可以过滤下pid,减少输出

日志信息实时输出到/sys/kernel/debug/tracing/trace_pipe

那么查看指定pid的系统调用人命令如下

cat /sys/kernel/debug/tracing/trace_pipe | grep 23841

效果是这样的

其中NR后面的数字就是系统调用号,至于后面几个...暂时没有搞清楚

这个输出格式可以通过下面的命令查看

coral:/ # cat /sys/kernel/debug/tracing/events/raw_syscalls/sys_enter/format
name: sys_enter
ID: 18
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:long id;  offset:8;       size:8; signed:1;
        field:unsigned long args[6];    offset:16;      size:48;        signed:0;

print fmt: "NR %ld (%lx, %lx, %lx, %lx, %lx, %lx)", REC->id, REC->args[0], REC->args[1], REC->args[2], REC->args[3], REC->args[4], REC->args[5]

系统提供的比较简单,我们当然可以自行编写eBPF程序,然后解析参数,具体打印出调用的详细情况

关于这一点,后面会单独写一篇文章


除了sys_enter,还有sys_exit,是系统调用结束后的状态

同样也可以通过上面方式开启追踪

如果相同时开启,那么执行下面的命令即可

echo 1 > /sys/kernel/debug/tracing/events/raw_syscalls/enable

这个时候你会发现,其实在/sys/kernel/debug/tracing/events下面还有许多类型的事件,它们都可以通过向对应的enable管道写入配置,开启tracing_on,实现对应信息的追踪

/sys/kernel/debug/tracing/events下面有许多文件夹,每个文件夹下可能还有文件夹

如果向更高层级的文件夹的enable写入1,那么文件夹下的子文件夹的对应事件也会被开启记录

/sys/kernel/debug/tracing/events/enable则是最顶层的控制开关,如果向其中写入1,那么将记录所有内置的事件,这个时候输出会非常多

参考