Loading... # 前言 > “某数字壳又双叒叕更新了” 众所周知,插件党想要开心,那安全厂就不开心,于是插件党就得想办法开心—— 然而国内这内卷的现状,许多旧的过签方式一出来就被修掉了,于是又得开始开发新的魔法。常见的过签方法两种,要么欺骗目标拿到的包文件是“正确的”,要么日掉系统调用狸猫换太子。前者过于依赖壳内部实现,后者很容易被检测。因此亟需发明一种新的魔法。 # Magic Time ## 背景 各大厂商都在积极绞杀重打包方案,一堆用户大叫某易云、某安和某乎在 LSPatched 之后打开闪退,太极等框架已经放弃签名对抗。 一开始我想使用 fork + ptrace 监听 syscall,但由于 fork 后 ART 尸体之类的问题,ptrace 会让某些 app 一直等待 gc 而卡死(说的就是你,X易云)。在无意之中看到了[这篇文章](https://bbs.pediy.com/thread-271815.htm),于是产生了用 seccomp 日 syscall 的想法,鉴于著名的 vmos 就是采用的 seccomp,应该具有不错的兼容性。 ## 施工 ### 启用 seccomp [参考文章](http://pollux.cc/2019/09/22/seccomp%E6%B2%99%E7%AE%B1%E6%9C%BA%E5%88%B6%20&%202019ByteCTF%20VIP/#%E9%80%9A%E8%BF%87%E4%BD%BF%E7%94%A8%E8%AF%A5%E5%BA%93%E7%9A%84%E5%87%BD%E6%95%B0%E5%AE%9E%E7%8E%B0%E7%A6%81%E7%94%A8execve%E7%B3%BB%E7%BB%9F%E8%B0%83%E7%94%A8) ### syscall 拦截与恢复 要达到沙盒的效果,除了拦截系统调用外,还需要对其进行监控和修改。而 sighandler 本身仍然受 seccomp 约束,因此如果在 handler 里直接进行系统调用则会再次触发 `SIGSYS`,导致程序 abort。 我们希望实现的是,在 handler 内能够自由进行 syscall,而拦截 handler 外的调用。一种方法如看雪那篇文章,将我们的 syscall 转发到一个自由线程处理,但是这么做存在一些不足: 1. 部分线程相关调用,如 `pthread_sigprocmask`、`getpid` 无法或者很难在 worker 线程中处理 2. 线程转发可能存在一些很难发现的异步问题,并且某些情况下 `errno` 会被强制设置成 `EPERM`,原因未知 因此,我们需要自己的事情自己做,通过修改规则放行属于自己的 syscall。幸运的是,BPF Filter 具有这样的能力。 借鉴谷歌 [linux-syscall-support](https://chromium.googlesource.com/linux-syscall-support/) 的处理方案,内嵌汇编设置 `Trampoline`,并在 BPF 规则中放行,实现 syscall wrapper。(问题来了,为什么谷歌只做了 x86 的......) #### Wrapper ```cpp extern "C" void Trampoline(); __attribute__((naked, noinline)) long DoSyscall(long nr, ...) { #if defined(__i386__) asm( "pushl %ebp\n\t" "pushl %edi\n\t" "pushl %esi\n\t" "pushl %ebx\n\t" "movl 44(%esp), %ebp\n\t" "movl 40(%esp), %edi\n\t" "movl 36(%esp), %esi\n\t" "movl 32(%esp), %edx\n\t" "movl 28(%esp), %ecx\n\t" "movl 24(%esp), %ebx\n\t" "movl 20(%esp), %eax\n\t" "int $0x80\n\t" "Trampoline:\n\t" "popl %ebx\n\t" "popl %esi\n\t" "popl %edi\n\t" "popl %ebp\n\t" "ret\n\t" ); #elif defined(__x86_64__) ... } ``` #### BPF Filter ```cpp auto trampoline = (uintptr_t) Trampoline; struct sock_filter filter[] = { BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, nr)), #if defined(__i386__) || defined(__arm__) BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, nr, 0, 2), BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, instruction_pointer)), BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, trampoline, 0, 1), #else BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, nr, 0, 4), BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, instruction_pointer)), BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, static_cast<uint32_t>(trampoline), 0, 3), BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, instruction_pointer) + sizeof(uint32_t)), BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, static_cast<uint32_t>(trampoline >> 32), 0, 1), #endif BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW), BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_TRAP) }; ``` 由此,我们便可以在 handler 中自由使用 syscall,不再被自己的 seccomp 困住。 ### 信号处理 等等,这就完了?当然没那么简单。真正的应用极有可能设置自己的各种 sighandler,而这无意间就会把我们的 handler 给覆盖,然后一旦 app 的 handler 使用了 syscall,`Fatal error: 31 (Bad system call)` 就会炸掉整个 app。 因此,我们还需要特殊处理各种涉及信号的系统调用。 #### SIGSYS 启用 seccomp 后,在发生系统调用时,如果满足 BPF 规则则会触发 `SIGSYS`,从而转入自己设置的 handler 中。 常规情况下,由于隐式阻塞机制,在 sighandler 中再次触发相同信号会被阻塞,直到处理结束后再次进入 handler;但 seccomp 是一个特例:在 seccomp handler 中再次触发 `SIGSYS`,会导致程序直接 abort。因此,必须避免 handler 中出现 `SIGSYS`。 [seccomp(2) - Linux manual page](https://man7.org/linux/man-pages/man2/seccomp.2.html) > The process terminates as though killed by a SIGSYS signal. Even if a signal handler has been registered for SIGSYS, the handler will be ignored in this case and the process always terminates. To a parent process that is waiting on this process (using waitpid(2) or similar), the returned wstatus will indicate that its child was terminated as though by a SIGSYS signal. #### SIGSEGV bionic(?) 默认的 `SIGSEGV` 处理器会遮蔽 `SIGSYS` 信号,从而使 seccomp handler 失效导致 abort。因此,我们需要代理系统的 handler。(虽然发生 `SIGSEGV` 本身也会导致程序死掉,但我们需要栈展开信息和 tombstone 以定位问题所在) ```cpp sigemptyset64(&sa.sa_mask); sigaction64(SIGSYS, &sa, nullptr); syscall(SYS_rt_sigaction, SIGSEGV, nullptr, &sa, 8); if (sigismember64(&sa.sa_mask, SIGSYS)) { LOGD("Sigsys is masked"); sigdelset64(&sa.sa_mask, SIGSYS); } else { LOGD("Sigsys is not masked"); } syscall(SYS_rt_sigaction, SIGSEGV, &sa, nullptr, 8); ``` #### sigaction 用户程序使用 `rt_sigaction` 系统调用设置 sighandler。我们需要拦截涉及到 `SIGSYS` 的行为,防止 seccomp handler 失效。 ```cpp thread_local struct sigaction kSavedAction; inline void RegisterSigactionInterceptor() { RegisterSyscallHandler(SYS_rt_sigaction, [](long* regs) { LOGD("SigactionInterceptor"); auto* new_action = (const struct sigaction*) regs[REG_ARG1]; auto* old_action = (struct sigaction*) regs[REG_ARG2]; if (regs[REG_ARG0] != SIGSYS) { regs[REG_RET] = DoSyscall(SYS_rt_sigaction, SIGSYS, new_action, old_action, regs[REG_ARG3]); return; } LOGI("Intercept SIGSYS action"); if (old_action != nullptr) { *old_action = kSavedAction; } if (new_action != nullptr) { kSavedAction = *new_action; } }); } ``` #### sigprocmask 用户程序使用 `rt_sigaction` 系统调用设置 signal mask。同样,我们也需要阻止 `SIGSYS` 被 mask,同时还不能让用户程序发现不对劲的地方。 ```cpp thread_local bool kSigsysMasked; inline void RegisterSigprocmaskInterceptor() { RegisterSyscallHandler(SYS_rt_sigprocmask, [](long* regs) { LOGD("SigprocmaskInterceptor"); auto* new_set = (sigset64_t*) regs[REG_ARG1]; auto* old_set = (sigset64_t*) regs[REG_ARG2]; auto newSigsysMasked = kSigsysMasked; if (new_set && sigismember64(new_set, SIGSYS)) { newSigsysMasked = regs[REG_ARG0] != SIG_UNBLOCK; sigdelset64(new_set, SIGSYS); regs[REG_RET] = DoSyscall(SYS_rt_sigprocmask, regs[REG_ARG0], new_set, old_set, regs[REG_ARG3]); sigaddset64(new_set, SIGSYS); } else { regs[REG_RET] = DoSyscall(SYS_rt_sigprocmask, regs[REG_ARG0], new_set, old_set, regs[REG_ARG3]); } if (kSigsysMasked) { sigaddset64(old_set, SIGSYS); } kSigsysMasked = newSigsysMasked; }); } ``` #### seccomp handler 当然,我们的 handler 中也需要特殊处理。 ```cpp void SyscallHandler(int /*unused*/, siginfo_t* info, void* ucontext) { ... if (info->si_code != 1) { LOGD("SIGSYS: si_code = %d saved_handler = %p", info->si_code, kSavedAction.sa_handler); if (kSigsysMasked) return; if (kSavedAction.sa_handler == nullptr) exit(1); if (kSavedAction.sa_flags == SA_SIGINFO) { kSavedAction.sa_sigaction(info->si_signo, info, ucontext); } else { kSavedAction.sa_handler(info->si_signo); } LOGD("Exit SyscallHandler %d", info->si_syscall); return; } ... } ``` #### 未完待续—— 最后修改:2022 年 04 月 27 日 © 允许规范转载 赞 0 如果觉得我的文章对你有用,请随意赞赏
1 条评论
牛蛙