写点什么

【Abyss】Android 平台 BPF 和 SECCOMP 的 SVC 指令拦截

作者:iofomo
  • 2025-01-23
    湖南
  • 本文字数:3376 字

    阅读完需:约 11 分钟

【Abyss】Android平台BPF和SECCOMP的SVC指令拦截

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

☞ Github: https://www.github.com/iofomo/abyss ☜ 


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


源码位置:https://github.com/iofomo/abyss/tree/main/svcer

01. 说明

Seccomp(Secure Computing Mode):


SeccompLinux 内核的一个安全特性,用于限制进程可以执行的系统调用。它通过过滤系统调用,防止恶意程序执行危险操作。Seccomp 通常与 BPF 结合使用,以实现更灵活的过滤规则。


BPF(Berkeley Packet Filter):


BPF 是一种内核技术,最初用于网络数据包过滤,但后来被扩展用于更广泛的用途,包括系统调用过滤。BPF 程序可以在内核中运行,用于检查和过滤系统调用。

02. 主要流程

首先,配置 BPF 规则,如下我们配置了目标系统调用号的拦截规则,不在这个名单内的就放过,这样可以实现仅拦截我们关心的系统调用(即函数),提升拦截效率和稳定性。


static void doInitSyscallNumberFilter(struct sock_filter* filter, unsigned short& i) {    // Load syscall number into accumulator    filter[i++] = BPF_STMT(BPF_LD | BPF_W | BPF_ABS, (offsetof(struct seccomp_data, nr)));    // config target syscall    // add more syscall here ...//    filter[i++] = BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_openat, 5, 0);//    filter[i++] = BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_getcwd, 4, 0);//    filter[i++] = BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_chdir, 3, 0);//    filter[i++] = BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_execve, 2, 0);    filter[i++] = BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_openat, 1, 0);
filter[i++] = BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW);}
复制代码


然后,我们需要过滤掉一些系统库和自身库,防止写入死循环。


  • 自身实现库的过滤【必须】

  • vdso 的过滤【必须】

  • linker 的过滤【可选,提效】

  • libc 的过滤【可选,提效】


通过解析进程 maps 中对应库地址区间,配置跳过此区间的系统调用规则。


static void doInitSyscallLibFilterByAddr(struct sock_filter* filter, unsigned short& i, const uintptr_t& start, const uintptr_t& end) {    // Load syscall lib into accumulator#if defined(__arm__)    filter[i++] = BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, instruction_pointer));    filter[i++] = BPF_JUMP(BPF_JMP | BPF_JGE | BPF_K, start, 0, 2);    filter[i++] = BPF_JUMP(BPF_JMP | BPF_JGE | BPF_K, end, 1, 0);    filter[i++] = BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW);#else // __aarch64__    filter[i++] = BPF_STMT(BPF_LD | BPF_W | BPF_ABS, (offsetof(struct seccomp_data, instruction_pointer) + 4));    filter[i++] = BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, (uint32_t)(start >> 32), 0, 4);    filter[i++] = BPF_STMT(BPF_LD | BPF_W | BPF_ABS, (offsetof(struct seccomp_data, instruction_pointer)));    filter[i++] = BPF_JUMP(BPF_JMP | BPF_JGE | BPF_K, (uint32_t)start, 0, 2);    filter[i++] = BPF_JUMP(BPF_JMP | BPF_JGE | BPF_K, (uint32_t)end, 1, 0);    filter[i++] = BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW);#endif}
复制代码


其次,应用以上配置。


struct sigaction act = { 0 };act.sa_flags = SA_SIGINFO | SA_NODEFER;act.sa_sigaction = handleSignalAction;struct sigaction old_sa = {};
ret = sigaction(SIGSYS, &act, &old_sa);if (0 != ret) { LOGSVCE("sigaction: %d, %d, %s", ret, errno, strerror(errno)) ::free(filter); __ASSERT(0) return -11;}
// Unmask SIGSYSsigset_t mask;if (sigemptyset(&mask) || sigaddset(&mask, SIGSYS) || sigprocmask(SIG_UNBLOCK, &mask, nullptr) ) { LOGSVCE("sigprocmask: %d, %d, %s", ret, errno, strerror(errno)) ::free(filter); __ASSERT(0) return -12;}
struct sock_fprog prog = { .len = filterCount, .filter = filter,};
// set to self processret = prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);if (0 != ret) { LOGSVCE("PR_SET_NO_NEW_PRIVS: %d, %d, %s", ret, errno, strerror(errno)) ::free(filter); __ASSERT(0) return -13;}
// set seccomp to kernelret = prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog);if (0 != ret) { LOGSVCE("PR_SET_SECCOMP: %d, %d, %s", ret, errno, strerror(errno)) ::free(filter); __ASSERT(0) return -14;}
复制代码


最后,实现拦截后的处理。


static void handleSignalAction(int signo, siginfo_t* info, void* context) {    if (!info || !context || signo != SIGSYS || info->si_code != SYS_SECCOMP) {        LOGSVCW("signal: signo=%d, code=%d, errno=%d, call_addr=%p, arch=0x%x, syscall=0x%x,%s",              info->si_signo, info->si_code, info->si_errno, info->si_call_addr, info->si_arch,              info->si_syscall, SvcerDumper::index2name(info->si_syscall)              )        return;    }
ucontext_t *uc = reinterpret_cast<ucontext_t *>(context); intptr_t rc = SvcerSyscall::Call(SECCOMP_SYSCALL(uc), SECCOMP_PARM1(uc), SECCOMP_PARM2(uc), SECCOMP_PARM3(uc), SECCOMP_PARM4(uc), SECCOMP_PARM5(uc), SECCOMP_PARM6(uc) ); SvcerSyscall::PutValueInUcontext(rc, uc);}
复制代码

03. 封装

为了使用方便,封装了一些基础系统调用的日志打印接口。


1)添加要拦截的系统调用号。(日常日志打印)


SvcerDumper::addDump(SVCER_SYSCALL_execve);SvcerDumper::addDump(SVCER_SYSCALL_execveat);SvcerDumper::addDump(SVCER_SYSCALL_open);SvcerDumper::addDump(SVCER_SYSCALL_openat);SvcerDumper::addAll();
SvcerHooker::init(ESvcerHookerMode_IgnoreAll, "libifmamts.so");
复制代码


2)注册要拦截的系统调用回调。


// 这里注册for (int i=SVCER_SYSCALL_None; i<SVCER_SYSCALL_Max; ++i) {    SvcerHooker::registerCallback((TSVCER_SYSCALL_Type)i, handleSvcerHookerCallback);}
// 这里实现
static void handleKonkerSvcerHookerCallback(int sn, SvcerHookerArgument* arg/*Not NULL*/) { switch (sn) { case __NR_statfs:// int statfs(const char* path, struct statfs* result); case __NR_truncate:// typedef int truncate(const char *filename, off_t len); case __NR_chdir:// int chdir(const char *path); { const char* pathname = (const char*)arg->getArgument1(); char memString[512]; if (memString == KonkerFixer::fixDataPath(pathname, memString)) { LOGSVCI("fixer, %s: %s", SvcerDumper::index2name(sn), __PRINTSTR(pathname)) arg->setArgument1((intptr_t)memString); } arg->doSyscall(); return; } default: LOGSVCI("ignore, %s", SvcerDumper::index2name(sn)) break; } arg->doSyscall();}
复制代码


3)初始化


// 设置要过滤的库和当前自身库名称SvcerHooker::init(ESvcerHookerMode_IgnoreVdso|ESvcerHookerMode_IgnoreLibc|ESvcerHookerMode_IgnoreLinker, "libdemo.so");
复制代码

04. 附

额外模块:


本框架实现了最基本的检测仿真,如通过 __NR_rt_sigaction__NR_prctl 获取配置时,会对返回值进行还原。


参考项目:


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


https://github.com/termux/proot


用户头像

iofomo

关注

数字锋芒(全网同名),开源,探索,分享 2023-12-04 加入

专注应用沙箱,虚拟化,云手机,逆向分析,数据采集,风控对抗,数据保护方向。

评论

发布
暂无评论
【Abyss】Android平台BPF和SECCOMP的SVC指令拦截_android_iofomo_InfoQ写作社区