首页
社区
课程
招聘
[原创]通过xnuspy hook 内核ptrace 函数绕过反调试
发表于: 2024-6-5 21:40 8746

[原创]通过xnuspy hook 内核ptrace 函数绕过反调试

2024-6-5 21:40
8746

昨天实践了修改kernproc 结构体绕过反调试.今天突然想到是不是直接内核hook 更方便点儿

image

让我们详细分析 struct ptrace_args 在内存中的布局。在 64 位系统上,考虑到内存对齐和每个成员的大小,下面是详细的内存布局。

假设 pid_t 是 4 字节,user_addr_t 是 8 字节。在 64 位系统上,指针类型通常是 8 字节并且需要对齐到 8 字节边界。

首先,结构体定义如下:

int req:

pid_t pid:

user_addr_t addr:

int data:

由于数据对齐要求,编译器可能会在结构体的末尾添加填充以确保结构体大小是对齐的。因此,整个结构体的大小是 24 字节(4 + 4 + 8 + 4 + 4(填充))。

这就是 struct ptrace_args 在内存中的布局。

完整代码在ptrace_hook

int
ptrace(struct proc *p, struct ptrace_args *uap, int32_t *retval)
{
    struct proc     *t; /* target process */
    task_t          task;
    thread_t        th_act;
    struct uthread  *ut;
    int tr_sigexc = 0;
    int error = 0;
    int stopped = 0;
 
    AUDIT_ARG(cmd, uap->req);
    AUDIT_ARG(pid, uap->pid);
    AUDIT_ARG(addr, uap->addr);
    AUDIT_ARG(value32, uap->data);
 
    if (uap->req == PT_DENY_ATTACH) {
#if (DEVELOPMENT || DEBUG) && !defined(XNU_TARGET_OS_OSX)
        if (PE_i_can_has_debugger(NULL)) {
            return 0;
        }
#endif
        proc_lock(p);
        if (ISSET(p->p_lflag, P_LTRACED)) {
            proc_unlock(p);
            KERNEL_DEBUG_CONSTANT(BSDDBG_CODE(DBG_BSD_PROC, BSD_PROC_FRCEXIT) | DBG_FUNC_NONE,
                p->p_pid, W_EXITCODE(ENOTSUP, 0), 4, 0, 0);
            exit1(p, W_EXITCODE(ENOTSUP, 0), retval);
 
            thread_exception_return();
            /* NOTREACHED */
        }
        SET(p->p_lflag, P_LNOATTACH);
        proc_unlock(p);
 
        return 0;
    }
 
    if (uap->req == PT_FORCEQUOTA) {
        if (kauth_cred_issuser(kauth_cred_get())) {
            t = current_proc();
            OSBitOrAtomic(P_FORCEQUOTA, &t->p_flag);
            return 0;
        } else {
            return EPERM;
        }
    }
 
    /*
     *  Intercept and deal with "please trace me" request.
     */
    if (uap->req == PT_TRACE_ME) {
retry_trace_me: ;
        proc_t pproc = proc_parent(p);
        if (pproc == NULL) {
            return EINVAL;
        }
#if CONFIG_MACF
        /*
         * NB: Cannot call kauth_authorize_process(..., KAUTH_PROCESS_CANTRACE, ...)
         *     since that assumes the process being checked is the current process
         *     when, in this case, it is the current process's parent.
         *     Most of the other checks in cantrace() don't apply either.
         */
        struct proc_ident p_ident = proc_ident(p);
        struct proc_ident pproc_ident = proc_ident(pproc);
        kauth_cred_t pproc_cred = kauth_cred_proc_ref(pproc);
 
        proc_rele(pproc);
        error = mac_proc_check_debug(&pproc_ident, pproc_cred, &p_ident);
        kauth_cred_unref(&pproc_cred);
 
        if (error != 0) {
            return error;
        }
        if (proc_find_ident(&pproc_ident) == PROC_NULL) {
            return ESRCH;
        }
#endif
        proc_lock(p);
        /* Make sure the process wasn't re-parented. */
        if (p->p_ppid != pproc->p_pid) {
            proc_unlock(p);
            proc_rele(pproc);
            goto retry_trace_me;
        }
        SET(p->p_lflag, P_LTRACED);
        /* Non-attached case, our tracer is our parent. */
        p->p_oppid = p->p_ppid;
        proc_unlock(p);
        /* Child and parent will have to be able to run modified code. */
        cs_allow_invalid(p);
        cs_allow_invalid(pproc);
        proc_rele(pproc);
 
        return error;
    }
    if (uap->req == PT_SIGEXC) {
        proc_lock(p);
        if (ISSET(p->p_lflag, P_LTRACED)) {
            SET(p->p_lflag, P_LSIGEXC);
            proc_unlock(p);
            return 0;
        } else {
            proc_unlock(p);
            return EINVAL;
        }
    }
 
    /*
     * We do not want ptrace to do anything with kernel or launchd
     */
    if (uap->pid < 2) {
        return EPERM;
    }
 
    /*
     *  Locate victim, and make sure it is traceable.
     */
    if ((t = proc_find(uap->pid)) == NULL) {
        return ESRCH;
    }
 
    AUDIT_ARG(process, t);
 
    task = t->task;
    if (uap->req == PT_ATTACHEXC) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
        uap->req = PT_ATTACH;
        tr_sigexc = 1;
    }
    if (uap->req == PT_ATTACH) {
#pragma clang diagnostic pop
        int             err;
 
#if !defined(XNU_TARGET_OS_OSX)
        if (tr_sigexc == 0) {
            error = ENOTSUP;
            goto out;
        }
#endif
 
        if (kauth_authorize_process(proc_ucred(p), KAUTH_PROCESS_CANTRACE,
            t, (uintptr_t)&err, 0, 0) == 0) {
            /* it's OK to attach */
            proc_lock(t);
            SET(t->p_lflag, P_LTRACED);
            if (tr_sigexc) {
                SET(t->p_lflag, P_LSIGEXC);
            }
 
            t->p_oppid = t->p_ppid;
            /* Check whether child and parent are allowed to run modified
             * code (they'll have to) */
            proc_unlock(t);
            cs_allow_invalid(t);
            cs_allow_invalid(p);
            if (t->p_pptr != p) {
                proc_reparentlocked(t, p, 1, 0);
            }
 
            proc_lock(t);
            if (get_task_userstop(task) > 0) {
                stopped = 1;
            }
            t->p_xstat = 0;
            proc_unlock(t);
            psignal(t, SIGSTOP);
            /*
             * If the process was stopped, wake up and run through
             * issignal() again to properly connect to the tracing
             * process.
             */
            if (stopped) {
                task_resume(task);
            }
            error = 0;
            goto out;
        } else {
            error = err;
            if (error == ESRCH) {
                /*
                 * The target 't' is not valid anymore as it
                 * could not be found after the MAC check.
                 */
                return error;
            }
            /* not allowed to attach, proper error code returned by kauth_authorize_process */
            if (ISSET(t->p_lflag, P_LNOATTACH)) {
                psignal(p, SIGSEGV);
            }
            goto out;
        }
    }
 
    /*
     * You can't do what you want to the process if:
     *  (1) It's not being traced at all,
     */
    proc_lock(t);
    if (!ISSET(t->p_lflag, P_LTRACED)) {
        proc_unlock(t);
        error = EPERM;
        goto out;
    }
 
    /*
     *  (2) it's not being traced by _you_, or
     */
    if (t->p_pptr != p) {
        proc_unlock(t);
        error = EBUSY;
        goto out;
    }
 
    /*
     *  (3) it's not currently stopped.
     */
    if (t->p_stat != SSTOP) {
        proc_unlock(t);
        error = EBUSY;
        goto out;
    }
 
    /*
     *  Mach version of ptrace executes request directly here,
     *  thus simplifying the interaction of ptrace and signals.
     */
    /* proc lock is held here */
    switch (uap->req) {
    case PT_DETACH:
        if (t->p_oppid != t->p_ppid) {
            struct proc *pp;
 
            proc_unlock(t);
            pp = proc_find(t->p_oppid);
            if (pp != PROC_NULL) {
                proc_reparentlocked(t, pp, 1, 0);
                proc_rele(pp);
            } else {
                /* original parent exited while traced */
                proc_list_lock();
                t->p_listflag |= P_LIST_DEADPARENT;
                proc_list_unlock();
                proc_reparentlocked(t, initproc, 1, 0);
            }
            proc_lock(t);
        }
 
        t->p_oppid = 0;
        CLR(t->p_lflag, P_LTRACED);
        CLR(t->p_lflag, P_LSIGEXC);
        proc_unlock(t);
        goto resume;
 
    case PT_KILL:
        /*
         *  Tell child process to kill itself after it
         *  is resumed by adding NSIG to p_cursig. [see issig]
         */
        proc_unlock(t);
#if CONFIG_MACF
        error = mac_proc_check_signal(p, t, SIGKILL);
        if (0 != error) {
            goto resume;
        }
#endif
        psignal(t, SIGKILL);
        goto resume;
 
    case PT_STEP:                   /* single step the child */
    case PT_CONTINUE:               /* continue the child */
        proc_unlock(t);
        th_act = (thread_t)get_firstthread(task);
        if (th_act == THREAD_NULL) {
            error = EINVAL;
            goto out;
        }
 
        /* force use of Mach SPIs (and task_for_pid security checks) to adjust PC */
        if (uap->addr != (user_addr_t)1) {
            error = ENOTSUP;
            goto out;
        }
 
        if ((unsigned)uap->data >= NSIG) {
            error = EINVAL;
            goto out;
        }
 
        if (uap->data != 0) {
#if CONFIG_MACF
            error = mac_proc_check_signal(p, t, uap->data);
            if (0 != error) {
                goto out;
            }
#endif
            psignal(t, uap->data);
        }
 
        if (uap->req == PT_STEP) {
            /*
             * set trace bit
             * we use sending SIGSTOP as a comparable security check.
             */
#if CONFIG_MACF
            error = mac_proc_check_signal(p, t, SIGSTOP);
            if (0 != error) {
                goto out;
            }
#endif
            if (thread_setsinglestep(th_act, 1) != KERN_SUCCESS) {
                error = ENOTSUP;
                goto out;
            }
        } else {
            /*
             * clear trace bit if on
             * we use sending SIGCONT as a comparable security check.
             */
#if CONFIG_MACF
            error = mac_proc_check_signal(p, t, SIGCONT);
            if (0 != error) {
                goto out;
            }
#endif
            if (thread_setsinglestep(th_act, 0) != KERN_SUCCESS) {
                error = ENOTSUP;
                goto out;
            }
        }
resume:
        proc_lock(t);
        t->p_xstat = uap->data;
        t->p_stat = SRUN;
        if (t->sigwait) {
            wakeup((caddr_t)&(t->sigwait));
            proc_unlock(t);
            if ((t->p_lflag & P_LSIGEXC) == 0) {
                task_resume(task);
            }
        } else {
            proc_unlock(t);
        }
 
        break;
 
    case PT_THUPDATE:  {
        proc_unlock(t);
        if ((unsigned)uap->data >= NSIG) {
            error = EINVAL;
            goto out;
        }
        th_act = port_name_to_thread(CAST_MACH_PORT_TO_NAME(uap->addr),
            PORT_TO_THREAD_NONE);
        if (th_act == THREAD_NULL) {
            error = ESRCH;
            goto out;
        }
        ut = (uthread_t)get_bsdthread_info(th_act);
        if (uap->data) {
            ut->uu_siglist |= sigmask(uap->data);
        }
        proc_lock(t);
        t->p_xstat = uap->data;
        t->p_stat = SRUN;
        proc_unlock(t);
        thread_deallocate(th_act);
        error = 0;
    }
    break;
    default:
        proc_unlock(t);
        error = EINVAL;
        goto out;
    }
 
    error = 0;
out:
    proc_rele(t);
    return error;
}
int
ptrace(struct proc *p, struct ptrace_args *uap, int32_t *retval)
{
    struct proc     *t; /* target process */
    task_t          task;
    thread_t        th_act;
    struct uthread  *ut;
    int tr_sigexc = 0;
    int error = 0;
    int stopped = 0;
 
    AUDIT_ARG(cmd, uap->req);
    AUDIT_ARG(pid, uap->pid);
    AUDIT_ARG(addr, uap->addr);
    AUDIT_ARG(value32, uap->data);
 
    if (uap->req == PT_DENY_ATTACH) {
#if (DEVELOPMENT || DEBUG) && !defined(XNU_TARGET_OS_OSX)
        if (PE_i_can_has_debugger(NULL)) {
            return 0;
        }
#endif
        proc_lock(p);
        if (ISSET(p->p_lflag, P_LTRACED)) {
            proc_unlock(p);
            KERNEL_DEBUG_CONSTANT(BSDDBG_CODE(DBG_BSD_PROC, BSD_PROC_FRCEXIT) | DBG_FUNC_NONE,
                p->p_pid, W_EXITCODE(ENOTSUP, 0), 4, 0, 0);
            exit1(p, W_EXITCODE(ENOTSUP, 0), retval);
 
            thread_exception_return();
            /* NOTREACHED */
        }
        SET(p->p_lflag, P_LNOATTACH);
        proc_unlock(p);
 
        return 0;
    }
 
    if (uap->req == PT_FORCEQUOTA) {
        if (kauth_cred_issuser(kauth_cred_get())) {
            t = current_proc();
            OSBitOrAtomic(P_FORCEQUOTA, &t->p_flag);
            return 0;
        } else {
            return EPERM;
        }
    }
 
    /*
     *  Intercept and deal with "please trace me" request.
     */
    if (uap->req == PT_TRACE_ME) {
retry_trace_me: ;
        proc_t pproc = proc_parent(p);
        if (pproc == NULL) {
            return EINVAL;
        }
#if CONFIG_MACF
        /*
         * NB: Cannot call kauth_authorize_process(..., KAUTH_PROCESS_CANTRACE, ...)
         *     since that assumes the process being checked is the current process
         *     when, in this case, it is the current process's parent.
         *     Most of the other checks in cantrace() don't apply either.
         */
        struct proc_ident p_ident = proc_ident(p);
        struct proc_ident pproc_ident = proc_ident(pproc);
        kauth_cred_t pproc_cred = kauth_cred_proc_ref(pproc);
 
        proc_rele(pproc);
        error = mac_proc_check_debug(&pproc_ident, pproc_cred, &p_ident);
        kauth_cred_unref(&pproc_cred);
 
        if (error != 0) {
            return error;
        }
        if (proc_find_ident(&pproc_ident) == PROC_NULL) {
            return ESRCH;
        }
#endif
        proc_lock(p);
        /* Make sure the process wasn't re-parented. */
        if (p->p_ppid != pproc->p_pid) {
            proc_unlock(p);
            proc_rele(pproc);
            goto retry_trace_me;
        }
        SET(p->p_lflag, P_LTRACED);
        /* Non-attached case, our tracer is our parent. */
        p->p_oppid = p->p_ppid;
        proc_unlock(p);
        /* Child and parent will have to be able to run modified code. */
        cs_allow_invalid(p);
        cs_allow_invalid(pproc);
        proc_rele(pproc);
 
        return error;
    }
    if (uap->req == PT_SIGEXC) {
        proc_lock(p);
        if (ISSET(p->p_lflag, P_LTRACED)) {
            SET(p->p_lflag, P_LSIGEXC);
            proc_unlock(p);
            return 0;
        } else {
            proc_unlock(p);
            return EINVAL;
        }
    }
 
    /*
     * We do not want ptrace to do anything with kernel or launchd
     */
    if (uap->pid < 2) {
        return EPERM;
    }
 
    /*
     *  Locate victim, and make sure it is traceable.
     */
    if ((t = proc_find(uap->pid)) == NULL) {
        return ESRCH;
    }
 
    AUDIT_ARG(process, t);
 
    task = t->task;
    if (uap->req == PT_ATTACHEXC) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
        uap->req = PT_ATTACH;
        tr_sigexc = 1;
    }
    if (uap->req == PT_ATTACH) {
#pragma clang diagnostic pop
        int             err;
 
#if !defined(XNU_TARGET_OS_OSX)
        if (tr_sigexc == 0) {
            error = ENOTSUP;
            goto out;
        }
#endif
 
        if (kauth_authorize_process(proc_ucred(p), KAUTH_PROCESS_CANTRACE,
            t, (uintptr_t)&err, 0, 0) == 0) {
            /* it's OK to attach */
            proc_lock(t);
            SET(t->p_lflag, P_LTRACED);
            if (tr_sigexc) {
                SET(t->p_lflag, P_LSIGEXC);
            }
 
            t->p_oppid = t->p_ppid;
            /* Check whether child and parent are allowed to run modified
             * code (they'll have to) */
            proc_unlock(t);
            cs_allow_invalid(t);
            cs_allow_invalid(p);
            if (t->p_pptr != p) {
                proc_reparentlocked(t, p, 1, 0);
            }
 
            proc_lock(t);
            if (get_task_userstop(task) > 0) {
                stopped = 1;
            }
            t->p_xstat = 0;
            proc_unlock(t);
            psignal(t, SIGSTOP);
            /*
             * If the process was stopped, wake up and run through
             * issignal() again to properly connect to the tracing
             * process.
             */
            if (stopped) {
                task_resume(task);
            }
            error = 0;
            goto out;
        } else {
            error = err;
            if (error == ESRCH) {
                /*
                 * The target 't' is not valid anymore as it
                 * could not be found after the MAC check.
                 */
                return error;
            }
            /* not allowed to attach, proper error code returned by kauth_authorize_process */
            if (ISSET(t->p_lflag, P_LNOATTACH)) {
                psignal(p, SIGSEGV);
            }
            goto out;
        }
    }
 
    /*
     * You can't do what you want to the process if:
     *  (1) It's not being traced at all,
     */
    proc_lock(t);
    if (!ISSET(t->p_lflag, P_LTRACED)) {
        proc_unlock(t);
        error = EPERM;
        goto out;
    }
 
    /*
     *  (2) it's not being traced by _you_, or
     */
    if (t->p_pptr != p) {
        proc_unlock(t);
        error = EBUSY;
        goto out;
    }
 
    /*
     *  (3) it's not currently stopped.
     */
    if (t->p_stat != SSTOP) {
        proc_unlock(t);
        error = EBUSY;
        goto out;
    }
 
    /*
     *  Mach version of ptrace executes request directly here,
     *  thus simplifying the interaction of ptrace and signals.
     */
    /* proc lock is held here */
    switch (uap->req) {
    case PT_DETACH:
        if (t->p_oppid != t->p_ppid) {
            struct proc *pp;
 
            proc_unlock(t);
            pp = proc_find(t->p_oppid);
            if (pp != PROC_NULL) {
                proc_reparentlocked(t, pp, 1, 0);
                proc_rele(pp);
            } else {
                /* original parent exited while traced */
                proc_list_lock();
                t->p_listflag |= P_LIST_DEADPARENT;
                proc_list_unlock();
                proc_reparentlocked(t, initproc, 1, 0);
            }
            proc_lock(t);
        }
 
        t->p_oppid = 0;
        CLR(t->p_lflag, P_LTRACED);
        CLR(t->p_lflag, P_LSIGEXC);
        proc_unlock(t);
        goto resume;
 
    case PT_KILL:
        /*
         *  Tell child process to kill itself after it
         *  is resumed by adding NSIG to p_cursig. [see issig]
         */
最后于 2024-6-5 21:47 被yuzhouheike编辑 ,原因:
收藏
免费 3
支持
分享
最新回复 (7)
雪    币: 3836
活跃值: (4142)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
感谢分享
2024-6-5 22:29
0
雪    币: 811
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
3
xnuspy 要怎么用?
2024-8-16 09:08
0
雪    币: 552
活跃值: (580)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
没看懂,现在不是只HOOK ptrace不行了吗
2024-8-27 09:10
0
雪    币: 36
活跃值: (1842)
能力值: ( LV3,RANK:25 )
在线值:
发帖
回帖
粉丝
5
63byte xnuspy 要怎么用?[em_16]

可以看看xnuspy 的 readme

最后于 2024-8-27 10:02 被yuzhouheike编辑 ,原因:
2024-8-27 10:00
0
雪    币: 36
活跃值: (1842)
能力值: ( LV3,RANK:25 )
在线值:
发帖
回帖
粉丝
6
zhusg 没看懂,现在不是只HOOK ptrace不行了吗
你指的是什么不行?
2024-8-27 10:06
0
雪    币: 351
活跃值: (196)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
能出一篇怎么找内核符号偏移的文章吗
5天前
0
雪    币: 4324
活跃值: (6541)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
阁下不是大佬才不信
5天前
0
游客
登录 | 注册 方可回帖
返回
//