首页
社区
课程
招聘
[原创]基于seccomp+sigaction的Android通用svc hook方案
发表于: 2023-6-9 22:10 26796

[原创]基于seccomp+sigaction的Android通用svc hook方案

2023-6-9 22:10
26796

众所周知,目前各大APP的安全模块几乎都会使用自实现的libc函数,如open,read等函数,通过自实现svc方式来实现系统调用。因此我们如果想要hook系统调用,只能通过扫描厂商自实现的代码段,定位svc指令所在地址,再通过inline hook方式来进行hook操作,但是这种方式需要涉及内存修改,很容易被检测到内存篡改行为。

本文将利用seccomp方式来监听系统调用,以达到劫持svc调用的目的。

Seccomp是一个Linux内核安全模块,它可以使进程限制可以进行的系统调用数量,从而提高进程的安全性和可靠性。Seccomp提供了一种轻量级的进程隔离方式,可以在限制进程的能力的同时,不会影响操作系统的整体功能,是现代容器和虚拟化技术中广泛使用的安全保障机制之一。

Seccomp的主要工作流程是通过在进程中使用prctl()系统调用来指定一个过滤规则集,该规则集称为“过滤器”,它定义了该进程允许使用的系统调用类型和参数。当进程调用系统调用时,过滤器会拦截该调用并进行验证,以判断其是否符合规则集中指定的条件。如果系统调用不符合规则,Seccomp将拒绝该操作,并终止进程。

Seccomp的过滤器有两种类型:全局过滤器和线程过滤器。全局过滤器是在prctl()系统调用时设置的,它会对整个进程使用的所有线程都生效。而线程过滤器是在线程创建时设置的,只会对该线程生效。通常情况下,Seccomp的使用者只需要使用全局过滤器即可,因为它可以在进程创建时就应用到所有的线程中。

过滤器的具体设置可以使用在C语言中定义的BPF程序宏来完成,也可以使用SECCOMP_MODE_FILTER模式下的seccomp()库函数来设置。BPF程序宏和seccomp()函数都需要将过滤器规则集以二进制方式指定,并将其传递给prctl()系统调用来启用过滤器。下面是一个简单的示例,演示如何使用BPF程序宏来在全局范围内应用过滤器:

此例中,过滤器仅允许进程调用getpid()系统调用。如果进程尝试调用其他系统调用,Seccomp将拒绝该操作并终止进程。这里使用BPF程序宏来定义过滤器,当然也可以使用seccomp()库函数来编写等效的过滤器规则,例如通过添加seccomp_rule_add()库函数,来定义更复杂的规则集匹配条件等。

当前网上能搜到的基于seccomp实现svc hook的文章大致有两类:

本文的通用hook svc方法与第二种类似,主要是捕获SECCOMP_RET_TRAP信号,来实现的hook操作。

seccomp的filter中发出SECCOMP_RET_TRAP信号来触发中断,代码如下。

seccomp_data是一个结构体类型,它在使用Seccomp系统调用过滤机制时作为参数传递给BPF程序来指定系统调用。它的定义包含在linux/seccomp.h头文件中,通常定义如下:

其中,各字段表示的含义如下:

使用Seccomp过滤器功能时,可以使用seccomp_data结构体的这些成员来访问和操作该进程的系统调用,对系统调用进行过滤或修改,来完成对特定系统调用的限制。

seccomp发出SECCOMP_RET_TRAP信号时,会引发程序阻塞机制,此时系统会产生一个SIGSYS信号,并使原程序处于临时阻塞状态。因此我们可以使用sigaction来注册一个对SIGSYS信号进行处理的handler,再将处理完的结果返回出去,从而达到hook效果。代码如下:

上面例子中我们想要对openat的svc进行hook操作,那么我们就需要再sig_handler函数里面对修改过的参数再调用一次openat函数,再将返回值给到主进程,让进程继续执行。

但是这样就会有个问题,就是我们的sig_handler里面再调用openat函数会再次经过seccomp的filter,并且会再次触发一次SECCOMP_RET_TRAP信号,从而又一次进入sig_handler。。。造成死循环问题。

因此我们需要优化filter内容,让其能够知道哪些svc调用是从sig_handler调用过来,哪些svc调用是主进程自身调用。有多种不同的思路,包括:

sig_handle中处理逻辑如下:

有问题欢迎大家私聊或者下方评论。


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

最后于 2023-6-13 10:51 被一只流浪狗编辑 ,原因: 事例代码更正。
收藏
免费 16
支持
分享
最新回复 (25)
雪    币: 71
活跃值: (1414)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
有很多不错的svc hook开源项目
2023-6-10 16:15
0
雪    币: 5
活跃值: (1080)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
能否开源提供完整代码给小白学习下
2023-6-10 21:29
0
雪    币: 3573
活跃值: (31026)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
感谢分享
2023-6-10 22:05
0
雪    币: 2442
活跃值: (10708)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
恋一世的爱 能否开源提供完整代码给小白学习下
我也是想问有没有一个完整的demo来跟着复习下
2023-6-12 10:16
0
雪    币: 355
活跃值: (652)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
6
你瞒我瞒 我也是想问有没有一个完整的demo来跟着复习下[em_85]
等过两天我再整理好发上来。
2023-6-12 11:47
0
雪    币: 764
活跃值: (230)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
要什么完整demo,论坛里看完相关文章真的写不出来吗.想要源码白嫖还是学习目的
2023-6-12 13:47
0
雪    币: 19
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
8
你好,代码用在安卓上switch case命中就卡主了。。是什么情况啊
2023-6-13 10:40
0
雪    币: 355
活跃值: (652)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
9
jilay 你好,代码用在安卓上switch case命中就卡主了。。是什么情况啊
“BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, args[1]))”改成“BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, args[2]))”,试试。
2023-6-13 10:44
0
雪    币: 19
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
10
一只流浪狗 “BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, args[1]))”改成“BPF_STMT(BPF_LD | BPF ...
可以了。。
2023-6-13 12:23
0
雪    币: 202
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
11
在miui上跑过嘛,大佬。我在demo上跑正常hook,注入以后就出问题了
2023-6-25 15:05
0
雪    币: 5
活跃值: (1080)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12

测试__NR_kill  __NR_openat都正常,__NR_exit会卡死

最后于 2023-7-2 13:49 被恋一世的爱编辑 ,原因:
2023-7-1 01:54
0
雪    币: 5
活跃值: (1080)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
测试__NR_kill  __NR_openat都正常,__NR_exit会卡死
2023-7-2 13:49
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
14
大佬,按照你的教程来,写好demo,拦截的是  __NR_openat  注入游戏(游戏没检测)会崩溃,报错:Process 8911 exited due to signal 31 (Bad system call),可以帮忙看下吗,游戏链接可以在apkpure或者APKCombo搜索Animal Revolt Battle Simulator(版本3.1.1_arm64)https://apkcombo.com/zh/animal-revolt-battle-simulator/com.vdimensionltd.animalrevoltbattlesimulator/download/apk
2023-7-14 23:59
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
15
SharkFall 在miui上跑过嘛,大佬。我在demo上跑正常hook,注入以后就出问题了
兄弟,你是拦截哪个NR,出现的问题是什么呢
2023-7-15 00:01
0
雪    币: 116
活跃值: (1012)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
16
一只流浪狗 等过两天我再整理好发上来。

大佬 64位的信号处理怎么写呀

最后于 2023-9-5 21:16 被万里星河编辑 ,原因:
2023-9-5 21:15
0
雪    币: 202
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
17
mb_kgsesxqm 兄弟,你是拦截哪个NR,出现的问题是什么呢
忘了,哈哈哈,不过解决了,现在是32位跑不进hook
2024-2-8 11:15
0
雪    币: 20
活跃值: (993)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
18

请问有尝试hook __NR_clone 的吗 会出现无限重启的情况

最后于 2024-2-11 13:20 被肉蚌葱鸡编辑 ,原因: 回答错误
2024-2-9 09:56
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
19
尝试hook openat注入后有些为什么跑不进hook
2024-2-12 09:14
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
20
mb_gipfprwu 尝试hook openat注入后有些为什么跑不进hook

其他线程的似乎没被应用到

最后于 2024-3-1 08:02 被mb_gipfprwu编辑 ,原因:
2024-3-1 08:01
0
雪    币: 220
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
21
请教大神__NR_openat 的实现该怎么写呢,我写的始终不对。
2024-4-26 15:57
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
22
SharkFall 忘了,哈哈哈,不过解决了,现在是32位跑不进hook
怎么解决的呢  我也出现这个问题了
2024-5-30 15:21
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
23
#define _GNU_SOURCE
#include <jni.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <malloc.h>
#include <unistd.h>
#include <linux/filter.h>
#include <linux/seccomp.h>
#include <sys/prctl.h>
#include <signal.h>
#include <string.h>
#include <xhook/xhook.h>
#include <xhook/xh_log.h>
#include <sys/syscall.h>
#include <errno.h>

#define SyscallCode 123456

const char *apkPath__;
const char *repPath__;

static void signal_handler(int signum, siginfo_t *siginfo, void *context);

void registerSignalHandler() {
    // Register signal handler
    struct sigaction sig;
    sigset_t sigset;
    sigfillset(&sigset);
    sig.sa_mask = sigset;
    sig.sa_flags = SA_SIGINFO;
    sig.sa_sigaction = signal_handler;
    if (sigaction(SIGSYS, &sig, NULL) == -1) {
        perror("sigaction");
        //exit(EXIT_FAILURE);
    }

    XH_LOG_ERROR("Finished registering signal handler");
}

void installFilterForE() {
    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, 0, 3),
            BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(struct seccomp_data, args[5])),
            BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, SyscallCode, 1, 0),
            BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_TRAP),
            BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW),
    };

    struct sock_fprog prog = {
            .len = (unsigned short) (sizeof(filter) / sizeof(filter[0])),
            .filter = filter,
    };

    if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1) {
        perror("prctl(PR_SET_NO_NEW_PRIVS)");
        //exit(EXIT_FAILURE);
    }

    if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog) == -1) {
        perror("prctl(PR_SET_SECCOMP)");
        //exit(EXIT_FAILURE);
    }

    XH_LOG_ERROR("Set new filter end");
}

#if defined(__aarch64__)
static void signal_handler(int signum, siginfo_t *siginfo, void *context) {
    ucontext_t *uc = static_cast<ucontext_t *>(context);
    struct sigcontext *sigc = &uc->uc_mcontext;

    int code = sigc->regs[8];

    if (code == __NR_openat) {
        char *fileName = reinterpret_cast<char *>(sigc->regs[1]);

        if (strcmp(fileName, apkPath__) == 0) {
            sigc->regs[0] = AT_FDCWD;  // dirfdNum = AT_FDCWD;
            sigc->regs[1] = reinterpret_cast<long>(repPath__);  // fileName = repPath__
            sigc->regs[2] = O_RDONLY;  // openFlags = O_RDONLY;
            sigc->regs[3] = S_IRUSR;  // permission: read/write/execute

            XH_LOG_ERROR("Redirect apk: %s", repPath__);
        }

         sigc->regs[0] = syscall(code, sigc->regs[0], sigc->regs[1], sigc->regs[2],
                                      sigc->regs[3], sigc->regs[4], SyscallCode);
          
    }
}
#else
static void signal_handler(int signum, siginfo_t *siginfo, void *context) {
    ucontext_t *uc = static_cast<ucontext_t *>(context);
    struct sigcontext *sigc = &uc->uc_mcontext;

    int code = sigc->arm_r7;

    if (code == __NR_openat) {
        char *fileName = reinterpret_cast<char *>(sigc->arm_r1);

        if (strcmp(fileName, apkPath__) == 0) {
            sigc->arm_r0 = AT_FDCWD;  // dirfdNum = AT_FDCWD;
            sigc->arm_r1 = reinterpret_cast<long>(repPath__);  // fileName = repPath__
            sigc->arm_r2 = O_RDWR | O_CREAT;  // openFlags = O_RDONLY;
            sigc->arm_r3 = S_IRUSR | S_IWUSR | S_IXUSR;  // permission: read/write/execute
        }

        sigc->arm_r0 = syscall(code, sigc->arm_r0, sigc->arm_r1, sigc->arm_r2,
                                      sigc->arm_r3, sigc->arm_r4, SyscallCode);
    }
}
#endif

void startSvcHook() {
    registerSignalHandler();
    installFilterForE();
}

extern "C"
JNIEXPORT void JNICALL
Java_bin_mt_signature_KillerApplication_hookApkPath(JNIEnv *env, __attribute__((unused)) jclass clazz, jstring apkPath, jstring repPath) {
    apkPath__ = env->GetStringUTFChars(apkPath, nullptr);
    repPath__ = env->GetStringUTFChars(repPath, nullptr);
    startSvcHook();
}

有人能帮我修改这个代码吗,因为当我使用这个代码登录游戏时它会崩溃
2024-6-9 15:57
0
雪    币: 11
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
24
LKNB888 #define _GNU_SOURCE #include #include #include #include #include #include #include #i ...
这段代码好像根本用不起
2024-6-17 19:15
0
雪    币: 71
活跃值: (1414)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
25
想要学习seccomp bpf最好去看看proot的源码。非常不错
2024-6-18 21:12
0
游客
登录 | 注册 方可回帖
返回
//