首页
社区
课程
招聘
【Abyss】Android平台应用级系统调用拦截框架
发表于: 2024-9-19 09:06 3135

【Abyss】Android平台应用级系统调用拦截框架

2024-9-19 09:06
3135

Android平台从上到下,无需ROOT/解锁/刷机,应用级拦截框架的最后一环 —— SVC系统调用拦截。

☞ Github ☜ 

由于我们虚拟化产品的需求,需要支持在普通的Android手机运行。我们需要搭建覆盖应用从上到下各层的应用级拦截框架,而Abyss作为系统SVC指令的调用拦截,是我们最底层的终极方案。

01. 说明

tracee:ptrace附加的进程,通常为目标应用进程。

tracer: 用来ptrace其他进程的进程,在该进程里处理系统调用。

本框架利用AndroidProvider组件启动拦截处理的服务进程,进程启动后创建独立的一个线程循环处理所有拦截的系统调用回调。由于本工程只是演示方案的可行性并打印日志,所以业务逻辑处理比较简单,可以根据需要的自行扩展。

若要接入具体业务,可能需要改成多线程的方式进行处理,提升稳定性。不过我们实测多线切换也有一定损耗,性能提升有限,但确实稳定性有提升,防止某个处理耗时导致应用所有进程阻塞。

02. 处理流程

应用进程tracee被附加流程如下:

tracer过程如下:

说明: 使用fork()的目的是为了让工作线程去附加。ptrace有严格的限制,只有执行附加attach的线程才有权限操作对应tracee的寄存器。

03. 系统调用处理

03.01 忽略库机制

由于业务的需要,为了提升性能,我们需要忽略某些库中的系统调用,如:libc.so

find_libc_exec_maps()中找到libc.so可执行代码在maps中的内存地址区间,需要处理的系统调用:

1
2
3
4
5
6
7
8
9
//enable_syscall_filtering()   
FilteredSysnum internal_sysnums[] = {
    { PR_ptrace,        FILTER_SYSEXIT },
    { PR_wait4,     FILTER_SYSEXIT },
    { PR_waitpid,       FILTER_SYSEXIT },
    { PR_execve,        FILTER_SYSEXIT },
    { PR_execveat,      FILTER_SYSEXIT },
    {PR_readlinkat,   FILTER_SYSEXIT}, //暂时没有处理
};

set_seccomp_filters针对不同的arch,设置系统调用的ebpf。不同架构的ebpf语句会填充到一起,ebpf的组成伪代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
for (每一种架构) {
    start_arch_section;
    for (每一个当前架构下的系统调用)
        add_trace_syscall;
   end_arch_section;
}
finalize_program_filter;
 
start_arch_section;// 架构相关处理的ebpf,包括libc筛选的语句
add_trace_syscall;// 增加匹配要处理系统调用的ebpf语句
end_arch_section;// 尾部的ebpf语句(语句含义:匹配到系统调用则返回)
finalize_program_filter;// 最后面的ebpf语句,杀死其他异常情况下的线程

最终,调用如下语句,设置ebpf

1
status = prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &program);

03.02 PR_ptrace

因为一个tracee只能有一个tracer,所以需要处理该系统调用,在应用本身使用了ptrace的时候进行仿真。

系统调用进入前,将系统调用替换为PR_void,不做真正的ptrace,后续仿真。

退出系统调用后,针对ptrace的仿真。针对请求是PTRACE_ATTACHPTRACE_TRACEME等做各种不同的处理。同时也处理PTRACE_SYSCALLPTRACE_CONTPTRACE_SETOPTIONSPTRACE_GETEVENTMSG等各种ptrace操作。

ptrace有各种各样的请求,完整的处理逻辑比较复杂(我们还在消化中)。

03.03 PR_wait4、PR_waitpid

配合PR_ptrace使用,如果当前的tracee不是一个tracer,则不处理直接透传给系统。或者wait的第一个参数不为-1,则去集合里找看等待的这个线程是否存在并且是否是当前处理线程的tracee,如果不是,则不处理直接透传给系统。

处理的逻辑如下:

系统调用进入前,将系统调用替换为PR_void,不实际传给内核。

退出系统调用后,仿真tracerwait的处理逻辑。主要为基于当前处理的这个tracer(代码里定义为ptracer),去遍历它的tracee,看是否有事件需要被处理,如有,则填充好寄存器,唤醒当前正在被处理的这个tracer

03.04 PR_execve、PR_execveat

主要是在USE_LOADER_EXE开启时,将native程序替换为使用一个固定的loader来加载程序。

03.05 拦截日志

E INTERCEPT/SYS: vpid 2: got event 7057f
E INTERCEPT: vpid 2,secomp_enabled 0,
E INTERCEPT/SYS: (null) info: vpid 2: sysenter start: openat(0xffffff9c, 0xb4000073c72fcd60, 0x0, 0x0, 0xb4000073c72fcd88, 0xb4000073c72fcde8) = 0xffffff9c [0x7367d45e80, 0]
E INTERCEPT/SYS: vpid 2: open path:/system/fonts/NotoSansMalayalamUI-VF.ttf
E INTERCEPT/SYS: syscall_number:216
E INTERCEPT/SYS: vpid 2,openat: /system/fonts/NotoSansMalayalamUI-VF.ttf
E INTERCEPT/SYS: (null) info: vpid 2: sysenter end: openat(0xffffff9c, 0xb4000073c72fcd60, 0x0, 0x0, 0xb4000073c72fcd88, 0xb4000073c72fcde8) = 0xffffff9c [0x7367d45e80, 0]
E INTERCEPT/SYS: vpid 2: open path:/system/fonts/NotoSansMalayalamUI-VF.ttf
E INTERCEPT/SYS: (null) info: vpid 2: restarted using 7, signal 0, tracee pid 32222,app_pid 32162

E/INTERCEPT/SYS: (null) info: vpid 3: sysenter start: close(0x90, 0x0, 0x7492d0d088, 0x6, 0x73b7b82860, 0x73b7b82880) = 0x90 [0x73633faae0, 0]
E/INTERCEPT/SYS: syscall_number:41
E/INTERCEPT/SYSW: noting to do,sn:41
E/INTERCEPT/SYS: (null) info: vpid 3: sysenter end: close(0x90, 0x0, 0x7492d0d088, 0x6, 0x73b7b82860, 0x73b7b82880) = 0x90 [0x73633faae0, 0]
E/INTERCEPT/SYS: (null) info: vpid 3: restarted using 7, signal 0, tracee pid 32223,app_pid 32162
E/INTERCEPT/SYS: vpid 3: got event 7057f

04. 附

额外模块:

由于本框架会在原应用中增加一个处理进程,并且会trace到应用进程中,因此在实际使用时,还需要对新增进程和trace痕迹进行隐藏,防止与应用检测模块冲突,支持完整的应用自身trace调用的仿真。

这是附加的应用对抗模块,后面会作为单独文章分享给大家。

参考项目:

https://github.com/proot-me/proot

https://github.com/termux/proot


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 3
支持
分享
最新回复 (7)
雪    币: 10
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
2
感谢分享
2024-9-19 09:14
0
雪    币: 707
活跃值: (1441)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
感谢分享
2024-9-19 10:04
0
雪    币: 29
活跃值: (5647)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
楼主,seccomp + bpf了解一下
2024-9-19 10:39
0
雪    币: 220
活跃值: (636)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
不吃早饭 楼主,seccomp + bpf了解一下
嗯呢,seccomp + BPF + sigsys,我们后面也会开源出来,分享给大家
2024-9-19 11:35
0
雪    币: 1379
活跃值: (2796)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
感谢分享
2024-9-26 09:52
0
雪    币: 223
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
7


大佬请教一下,在可执行文件可以正常拦截,但是在安卓会卡死

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <sys/ptrace.h>
#include <sys/syscall.h>
#include <sys/prctl.h>
#include <linux/filter.h>
#include <linux/seccomp.h>
#include <linux/prctl.h>
#include <elf.h>
bool peek_memory(int pid_, void *addr, unsigned long words[], size_t size) {
    for (auto i = 0; i < size; i++) {
        words[i] = ptrace(PTRACE_PEEKDATA, pid_, ((unsigned long*) addr) + i, 0);
    }
    return true;
}
bool poke_memory(int pid_, void *addr, const unsigned long words[], size_t size) {
    for (auto i = 0; i < size; i++) {
        if (ptrace(PTRACE_POKEDATA, pid_, ((unsigned long*) addr) + i, words[i]) == -1) {
            perror("pokedata");
            return false;
        }
    }
    return true;
}
void install_seccomp_filter(){
 struct sock_filter filter[] = {
  BPF_STMT(BPF_LD | BPF_W | BPF_ABS, (offsetof(struct seccomp_data, nr))),
  BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_openat, 1, 0),
  BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
  BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_TRACE),
 };
 struct sock_fprog prog = {
  .len = (unsigned short)(sizeof(filter) / sizeof(filter[0])),
  .filter = filter,
 };
 prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
 prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog);
}
void process_signal(int pid){
 int status;
 while (true){
  struct user_regs_struct regs;
  struct iovec io;
  io.iov_base = &regs;
  io.iov_len = sizeof(regs);
  ptrace(PTRACE_CONT, pid, 0, 0);
  waitpid(pid, &status, 0);
  ptrace(PTRACE_GETREGSET, pid, (void *)NT_PRSTATUS, &io);
  // printf("nr : %llu \n",regs.regs[8]);
  if (status >> 8 == (SIGTRAP | (PTRACE_EVENT_SECCOMP << 8))){
   switch (regs.regs[8]){
   case __NR_openat:
    unsigned long origPath[4096];
    peek_memory(pid, (void *)regs.regs[1], origPath, 4096);
    printf("openat : %s\n", (char *)origPath);
                break;
            default:
             break;
   }
  }
  if (WIFEXITED(status)){
   break;
  }
  
 }
}
void startSvcHook(){
 int status;
 int parent = getpid();
 int child;
 if ((child = fork()) == 0){
  if (ptrace(PTRACE_ATTACH, parent, 0, 0) < 0){
   perror("ptrace(PTRACE_ATTACH)");
   exit(1);
  }
  
  waitpid(parent, &status, 0);
  
  const unsigned long default_ptrace_options = (
         PTRACE_O_TRACESYSGOOD|
          PTRACE_O_TRACEFORK |
         PTRACE_O_TRACEVFORK |
         PTRACE_O_TRACEVFORKDONE |
         PTRACE_O_TRACEEXEC |
         PTRACE_O_TRACECLONE |
         PTRACE_O_TRACEEXIT);
  if (ptrace(PTRACE_SETOPTIONS, parent, 0, default_ptrace_options | PTRACE_O_TRACESECCOMP) < 0){
   perror("ptrace(PTRACE_SETOPTIONS)");
   exit(1);
  }
        if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP){
         process_signal(parent);
        }
        
        exit(0);
 } else {
  install_seccomp_filter();
 }
}
int main(){
 startSvcHook();
 int fd = syscall(__NR_openat, AT_FDCWD, "/sdcard/1.txt", O_RDONLY);
    char *const argv[] = { "ls", "-l", NULL };
    if (execve("/bin/ls", argv, NULL) == -1) {
        perror("execve failed");
        exit(0);
    }
}


最后于 2024-9-27 23:32 被mb_uaoofzgb编辑 ,原因:
2024-9-27 20:33
0
雪    币: 220
活跃值: (636)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
mb_uaoofzgb 大佬请教一下,在可执行文件可以正常拦截,但是在安卓会卡死#include&nbsp;&lt;stdio.h&gt; #include&nbsp;&lt;std ...
抱歉,暂看不出来啥问题,卡死是进入递归了吗?如果是,那可能需要配置BPF过滤掉自身库
2024-10-2 21:14
0
游客
登录 | 注册 方可回帖
返回
//