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

查看 23|回复 2
作者:iofomo   

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

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


400.png (118.34 KB, 下载次数: 0)
下载附件
2025-1-23 13:46 上传

由于我们虚拟化产品的需求,需要支持在普通的Android手机运行。我们需要搭建覆盖应用从上到下各层的应用级拦截框架,而Abyss作为系统SVC指令的调用拦截,是我们最底层的终极方案。
源码位置:https://github.com/iofomo/abyss/tree/main/svcer
01.  说明
Seccomp(Secure Computing Mode):
Seccomp 是 Linux 内核的一个安全特性,用于限制进程可以执行的系统调用。它通过过滤系统调用,防止恶意程序执行危险操作。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 SIGSYS
    sigset_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 process
    ret = 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 kernel
    ret = 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(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; igetArgument1();
            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

    系统, 规则

  • wosn   

    感谢分享
    回不去的时光   

    楼主,我想请教下,hook svc 拦截openat重定向base.apk 经常碰到2种情况,不知能否不吝赐教
    1. U3d游戏会卡资源资源解压失败
    2. Bad syscall 31信号量错误
    相信很多人都会遇到这2个情况,不知道能不能有人出来解答下
    您需要登录后才可以回帖 登录 | 立即注册

    返回顶部