iOS 安全攻防之进程调试检测以及反调试

2017/07/10 blog

当一个应用程序被调试时,内核会自动设置一个进程状态标志表示该进程正在被调试。应用程序可以把检测标志位作为一个安全机制,如果这个标志位被设置,应用程序可以知道它是由调试器启动的,或者调试器之后将会附加到该进程。这样,一旦改程序知道自己被调试时,就可以做很多事情,比如说程序会清除数据,汇报并反馈程序状态,位置,或者自杀。当然,也可以用于保护用户数据,帮助恢复,迷惑攻击者。

进程调试检测
static int check_debugger() __attribute__((always_inline));
int check_debugger() {
    size_t size = sizeof(struct kinfo_proc);
    struct kinfo_proc info;
    int ret = 0,name[4];
    memset(&info, 0, sizeof(struct kinfo_proc));
    name[0] = CTL_KERN;
    name[1] = KERN_PROC;
    name[2] = KERN_PROC_PID;
    name[3] = getpid();
    if (ret == (sysctl(name, 4, &info, &size, NULL, 0))) {
        return ret;
    }
    return (info.kp_proc.p_flag & P_TRACED) ? 1:0;
}

attribute((always_inline)) 强制编译成内联模式,防止静态分析

而static 会让二进制没有清晰的符号表,这样也可以防止静态分析

当应用程序被调试时,内核会对进程设置P_TRACED 标志,这些标志位信息可以在/usr/include/sys/proc.h 文件中找到。

在现实的环境中,这个函数检测到该进程正在被调试时,可以立即退出,或者销毁机密文件,或者立刻销毁机密文件秘钥,并且立即切断网络。如果在企业环境中,可以立刻向服务端报告改程序正在被调试,甚至可以立刻上传包含设备的位置信息和其他一些有用的信息。

阻挡调试器

在Linux系统中,进程状态有TASK_RUNNING,TASK_INTERRUPTIBLE,TASK_STOPPED,还有TASK_TRACED。 ptrace 从名字上看是用于集成跟踪的,它提供了父进程可以观察和控制紫禁城执行的能力,病允许父进程检查和替换紫禁城的内核镜像(包括寄存器的值)其基本原理是: 当使用了ptrace跟踪后,所有发送给被跟踪的子进程的信号(除了SIGKILL),都会被转发给父进程,而子进程则会被阻塞,这时子进程的状态就会被 系统标注为TASK_TRACED。而父进程收到信号后,就可以对停止下来的子进程进行检查和修改,然后让子进程继续运行。 最开始,为了方便程序员的开发和调试,从Unix的早期版本开始就提供了一种对运行中的进程进行跟踪和控制的手段,那就是系统调用ptrace()。 通过ptrace可以对另一个进程实现调试跟踪,同时ptrace还提供了一个非常有用的参数那就是PT_DENY_ATTACH,这个参数用来告诉系统,阻止调试器依附。

所以最常用的反调试方案就是通过调用ptrace来实现反调试。

#ifndef PT_DENY_ATTACH
//表示一个进程现在正在被跟踪且不能被其他追踪器追踪,如果使用此参数,其他参数将会被忽略。如果进程正在被追踪,它将会退出ENOTSUP的退出状态,否则,它会设置一个标志禁用其他跟踪器,父进程的其他跟踪器设有这个标志的进程收到一个由父进程发出的段错误。
//这就是为什么我们在调试其他APP时,总会报这个错误:
//# debugserver *:1234 -x auto /var/mobile/Containers/Bundle/Application/4C85E022-A029-4D4D-A6A1-7FE078652845/AlipayWallet.app/AlipayWallet
debugserver-@(#)PROGRAM:debugserver PROJECT:debugserver-340.3.124
for arm64.
Segmentation fault: 11

    #define PT_DENY_ATTACH 31
#endif

typedef int (*ptrace_ptr_t)(int _request, pid_t _pid, caddr_t _addr, int _data);

ptrace(PT_DENY_ATTACH, 0, 0, 0);

void *handle = dlopen(0, RTLD_GLOBAL | RTLD_NOW);
ptrace_ptr_t ptrace_ptr = (ptrace_ptr_t)dlsym(handle, "ptrace");
ptrace_ptr(PT_DENY_ATTACH, 0, 0, 0);

这样看来,程序似乎具备了一些反调试的能力。但是这种方法并不能完全保证程序不被调试。对于一个熟练的攻击者来说。可以通过给ptrace 下断点,通过改变ptrace参数或者修改ptrace 返回值来绕过检测。比如,本人在调试某个APP 的时候,APP 就有ptrace 反调试,但是通过修改ptrace 参数,很轻松就绕过去。

当然除了上述的这种反调试的方法,还可以通过优化标记、去除符号等方法是汇编复杂化。

资料参考:

[1]:Hacking and Securing iOS Applications

[2]:iOS 应用安全攻防实战

Search

    Post Directory