分享一个Android通用svc跟踪以及hook方案
2022-4-7 17:59:0 Author: mp.weixin.qq.com(查看原文) 阅读量:27 收藏


本文为看雪论坛优秀文章
看雪论坛作者ID:阿碧

暂时只支持ARM64,ARM32的逻辑也是一样,有兴趣的大佬可以自行更改。

对 openat进行跟踪:

对 recvfrom进行跟踪:


在这里感谢珍惜大佬介绍的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

seccomp 是 Linux 内核提供的一种应用程序沙箱机制,主要通过限制进程的系统调用来完成部分沙箱隔离功能。seccomp-bpf 是 seccomp 的一个扩展,它可以通过配置来允许应用程序调用其他的系统调用。

基本原理

这是一个bpf规则: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, 0, 1),        BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_TRAP),        BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW),};

seccomp的具体用法可以参考「什么是seccomp」中的seccomp介绍文章。当返回规则设置为「SECCOMP_RET_TRAP」,目标系统调用时seccomp会产生一个SIGSYS系统信号并软中断,这时就可以通过捕获这个SIGSYS信号获得svc调用和打印具体参数。

如何脚本化安装seccomp规则

这里使用Frida的API「CModule」,CModule提供强大的动态编译功能可以让你在JS中写C。
frida文档中的示例
const cm = new CModule(`#include <stdio.h>void hello(void) {  printf("Hello World from CModule\\n");}`);const hello = new NativeFunction(cm.hello, 'void', []);hello();

如何捕获异常

使用Frida的API「Process.setExceptionHandler」即可捕获异常并在自己写的回调中进行数据处理。
数据处理的逻辑解释写在注释里。
// 异常处理    Process.setExceptionHandler(function (details) {        const current_off = details.context.pc - 4;        // 判断是否是seccomp导致的异常 读取opcode 010000d4 == svc 0        if (details.message == "system error" && details.type == "system" && hex(ptr(current_off).readByteArray(4)) == "010000d4") {            // 上锁避免多线程问题            lock(syscall_thread_ptr)            // 获取x8寄存器中的调用号            const nr = details.context.x8.toString(10);            let loginfo = "\n=================="            loginfo += `\nSVC[${syscalls[nr][1]}|${nr}] ==> PC:${addrToString(current_off)} P${Process.id}-T${Process.getCurrentThreadId()}`            // 构造线程syscall调用参数            const args = Memory.alloc(7 * 8)            args.writePointer(details.context.x8)            let args_reg_arr = {}            for (let index = 0; index < 6; index++) {                eval(`args.add(8 * (index + 1)).writePointer(details.context.x${index})`)                eval(`args_reg_arr["arg${index}"] = details.context.x${index}`)            }            // 获取手动堆栈信息            loginfo += "\n" + stacktrace(ptr(current_off), details.context.fp, details.context.sp).map(addrToString).join('\n')            // 打印传参            loginfo += "\nargs = " + JSON.stringify(args_reg_arr)            // 调用线程syscall 赋值x0寄存器            details.context.x0 = call_task(syscall_thread_ptr, args, 0)            loginfo += "\nret = " + details.context.x0.toString()            // 打印信息            call_thread_log(loginfo)            // 解锁            unlock(syscall_thread_ptr)            return true;        }        return false;    })


还有什么坑

1.syscall调用resume

问题描述:

根据Frida文档介绍「setExceptionHandler」捕获异常后只需要让回调返回true就会resume原本的线程,但是其只是跳过了svc指令继续执行,实际上并不会执行svc,这时候如果不执行syscall轻则导致APP数据异常,重则导致APP直接崩溃。所以在异常的回调中需要手动调用了syscall并赋值给x0。
但这时候会发生个新的问题,因为在主线程开启seccomp后,主线程和其后创建出来的线程都会被seccomp规则约束,在异常处理函数直接调用syscall同样会被seccomp约束再次抛出异常,就形成了”死锁“了。

如何解决:

可以注意到上面“死锁”部分描述,那我们在主线程被约束前,提前创建一个线程,这个线程就是不被约束的,同时受到线程池启发,我们让这个syscall线程循环接受任务,就能完成在一个没有约束的线程里进行syscall调用。

2.堆栈回溯

问题描述:

直接使用Frida的API「Thread.backtrace」很容易导致崩溃,原因可能是seccomp规则或者「prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)」导致的权限收紧和Frida实现堆栈回溯功能冲突。

如何解决:

手动实现堆栈回溯,原理是Arm64中每个函数都会在函数头部位置对x29、x30寄存器存入栈中,所以可以对x29不断读取往上回溯,最后得到完整的堆栈信息。
实现:
function stacktrace(pc, fp, sp) {    let n = 0, stack_arr = [], fp_c = fp;    stack_arr[n++] = pc;    const mem_region = call_thread_read_maps(sp);    while (n < MAX_STACK_TRACE_DEPTH) {        if (parseInt(fp_c.toString()) < parseInt(sp.toString()) || fp_c < mem_region.start || fp_c > mem_region.end) {            break        }        let next_fp = fp_c.readPointer()        let lr = fp_c.add(8).readPointer()        fp_c = next_fp        stack_arr[n++] = lr    }    return stack_arr;}


3.「Process.findModuleByAddress」「Process.enumerateModules」类的API导致崩溃或找不到Module信息

问题描述:

疑似同坑2的原因。

如何解决:

在CModule中手动实现了通过地址查soinfo信息「base, size, soname」等。

4.write调用约束下调用Frida的API「send」崩溃

问题描述:

同坑2的原因。

如何解决:

直接改用syscall线程使用「__android_print_log」打印信息。
在调用线程syscall前后可以更改传参、返回值、地址等更改,达到HOOK的效果。
求star
https://github.com/Abbbbbi/Frida-Seccomp
pip3 install fridapython3 multi_frida_seccomp.py

log信息可以在logcat过滤“seccomp”查看。

同时也自动保存到了「包名_pid_时间戳」文件夹内(支持多进程)。

看雪ID:阿碧

https://bbs.pediy.com/user-home-903162.htm

*本文由看雪论坛 阿碧 原创,转载请注明来自看雪社区

# 往期推荐

1.Windows API调用详解

2.漏洞考古:MS08-067详细分析

3.Android netlink&svc 获取 Mac方法深入分析

4.逆向角度看C++部分特性

5.CVE-2014-4113提权漏洞学习笔记

6.Go语言模糊测试工具:Go-Fuzz

球分享

球点赞

球在看

点击“阅读原文”,了解更多!


文章来源: http://mp.weixin.qq.com/s?__biz=MjM5NTc2MDYxMw==&mid=2458437006&idx=1&sn=3e8fdeae54f546b5e76b5a8c9df5c359&chksm=b18ff50486f87c12b5b8ee850f865d5d865aa45c94d522767e0bbe4754bd1a59010723385553#rd
如有侵权请联系:admin#unsafe.sh