首页
社区
课程
招聘
[原创]【Abyss】Android平台BPF和SECCOMP的SVC指令拦截
发表于: 2025-1-23 13:38 14627

[原创]【Abyss】Android平台BPF和SECCOMP的SVC指令拦截

2025-1-23 13:38
14627

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

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

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

源码位置:596K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6A6L8$3k6G2L8h3!0Q4x3V1k6S2j5Y4W2K6M7#2)9J5c8Y4c8J5k6h3g2Q4x3V1k6E0j5h3W2F1i4K6u0r3M7%4k6U0k6i4t1`.

Seccomp(Secure Computing Mode):

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

BPF(Berkeley Packet Filter):

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

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

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

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

其次,应用以上配置。

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

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

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

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

3)初始化

额外模块:

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

参考项目:

62aK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6H3M7X3!0G2N6q4)9J5k6r3#2W2i4K6u0r3M7s2u0G2L8%4b7`.

02aK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6@1k6i4u0E0N6i4S2Q4x3V1k6H3M7X3!0G2N6l9`.`.

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);
}
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);
}
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
}
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;
}
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))

[注意]看雪招聘,专注安全领域的专业人才平台!

收藏
免费 6
支持
分享
最新回复 (12)
雪    币: 105
活跃值: (4895)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
66666666666
2025-1-24 12:05
0
雪    币: 2533
活跃值: (2630)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
3

这个项目如何 导入自己的项目中进行测试  ? 大佬 有 api使用的说明不?  (未看真切 答案在 开头    把源码 直接复制到自己的项目中就行了 文章 中也说明的很清楚  我看的是大佬之前的 文章  没发现 这个篇是新的 )

最后于 2025-2-5 19:09 被逆天而行编辑 ,原因:
2025-2-5 19:03
0
雪    币: 2533
活跃值: (2630)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
4
大佬 为什么 你给的demo 有日志显示  而我导入的源码 进的项目 却提示 内核不支持
2025-2-5 20:19
0
雪    币: 231
活跃值: (731)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
逆天而行 这个项目如何 导入自己的项目中进行测试  ? 大佬 有 api使用的说明不?&nbs ...
你可以直接将svcer模块源码导入到你工程就可以
2025-2-5 20:23
0
雪    币: 231
活跃值: (731)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
逆天而行 大佬 为什么 你给的demo 有日志显示 而我导入的源码 进的项目 却提示 内核不支持 [em_010]
你的内核版本多少?这个是有版本要求的,你可以看初始化函数有内核版本检测,基本上最近几年的系统都应该支持的
2025-2-5 20:24
0
雪    币: 2533
活跃值: (2630)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
7
Tiony.bo 你的内核版本多少?这个是有版本要求的,你可以看初始化函数有内核版本检测,基本上最近几年的系统都应该支持的

我使用的piexl 5  内核版本 是 4.19 观察 大佬的源码 发现最低 需要 5.9    我观察源码,发现原理 与这篇文章 相似 基于seccomp+sigaction的Android通用svc hook方案 - 软件逆向板块 - 逆想技术论坛 - Powered by Discuz! 我尝试编写类似的代码 结合大佬项目  发现大佬对 多架构做了 支持 我拿来直接用   没做什么 线程判断   做的app内 全局 hook   就能够实现 我的需求 对文件进行 io重定向

最后于 2025-2-6 13:02 被逆天而行编辑 ,原因:
2025-2-6 13:01
0
雪    币: 2533
活跃值: (2630)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
8
还有一点点 的疑惑  大佬 对  __NR_rt_sigaction 和 __NR_prctl   的仿真 我确实 看的不大懂   大佬有相关的文章 吗  ?  我确实 没找到 相关资料
2025-2-6 13:06
0
雪    币: 231
活跃值: (731)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
逆天而行 Tiony.bo 你的内核版本多少?这个是有版本要求的,你可以看初始化函数有内核版本检测,基本上最近几年的系统都应该支持的 我使 ...
这个要做好检测和考虑处理好进程fork的问题
3天前
0
雪    币: 231
活跃值: (731)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
逆天而行 还有一点点 的疑惑 大佬 对 __NR_rt_sigaction 和 __NR_prctl 的仿真 我确实 看的不大懂 大佬有相关的文章 吗 ? 我确实 没找到 相关资料
这个是为了解决应用的普通检测逻辑不出错,挺简单的实现,实际上各大加固和安全模块检测更为深度,这里只是给出实现思路,所谓商业化还需要花精力实现这部分
3天前
0
雪    币: 205
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
11
小白问下这个东西需要手机root吗
1天前
0
雪    币: 231
活跃值: (731)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
yazigegeda 小白问下这个东西需要手机root吗
不需要,应用级的
9小时前
0
雪    币: 24
活跃值: (1659)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
这个其实会遇到点问题,就是有的App也会注册 SIGSYS的信号监听,不知道 up 有没有遇到过相似的~
4小时前
0
游客
登录 | 注册 方可回帖
返回