[翻译]通过修补内核内存绕过iOS反调试

原文链接:https://h4ck.kr/?p=618

通过修补内核内存绕过iOS反调试

1. ptrace 反调试

void svc80_anti_debug(void) {
#if defined __arm64__
    __asm __volatile("mov x0, #26");
    __asm __volatile("mov x1, #31");
    __asm __volatile("mov x2, #0");
    __asm __volatile("mov x3, #0");
    __asm __volatile("mov x16, #0");
    __asm __volatile("svc #0x80");
#endif
}


xnu代码 (它是如何运作的)

  • mach_process.c:118
 int ptrace(struct proc *p, struct ptrace_args *uap, int32_t *retval)
{
    ...
    if (uap->req == PT_DENY_ATTACH) {
        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_ATTACH) {
        int             err;
        ...
        /* not allowed to attach, proper error code returned by kauth_authorize_process */
        if (ISSET(t->p_lflag, P_LNOATTACH)) {
            psignal(p, SIGSEGV);
        }
        ...
    }
    ...
}

如果在 proc 结构的 p_lflag 中设置了 P_LTRACED,则要调试的进程将以 ENOTSUP(-45) 终止。 (如果调试时执行了ptrace函数)

如果设置了 P_LNOATTACH,则不会终止要调试的进程,而是
使用 SIGSEGV 信号终止调试器进程。 (如果您尝试在执行 ptrace 函数后进行调试)

参考

2. sysctl 反调试

static bool AmIBeingDebugged(void)
    // Returns true if the current process is being debugged (either
    // running under the debugger or has a debugger attached post facto).
{
    int                 junk;
    int                 mib[4];
    struct kinfo_proc   info;
    size_t              size;

    // Initialize the flags so that, if sysctl fails for some bizarre
    // reason, we get a predictable result.

    info.kp_proc.p_flag = 0;

    // Initialize mib, which tells sysctl the info we want, in this case
    // we're looking for information about a specific process ID.

    mib[0] = CTL_KERN;
    mib[1] = KERN_PROC;
    mib[2] = KERN_PROC_PID;
    mib[3] = getpid();

    // Call sysctl.

    size = sizeof(info);
    junk = sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0);
    assert(junk == 0);

    // We're being debugged if the P_TRACED flag is set.

    return ( (info.kp_proc.p_flag & P_TRACED) != 0 );
}

xnu代码(它是如何运作的)

STATIC void
fill_user64_externproc(proc_t p, struct user64_extern_proc *__restrict exp)
{
	exp->p_starttime.tv_sec = p->p_start.tv_sec;
	exp->p_starttime.tv_usec = p->p_start.tv_usec;
	exp->p_flag = p->p_flag;
	if (p->p_lflag & P_LTRACED) {
		exp->p_flag |= P_TRACED;
	}
	if (p->p_lflag & P_LPPWAIT) {
		exp->p_flag |= P_PPWAIT;
	}
	if (p->p_lflag & P_LEXIT) {
		exp->p_flag |= P_WEXIT;
	}
	exp->p_stat = p->p_stat;
	exp->p_pid = p->p_pid;
	exp->p_oppid = p->p_oppid;
	/* Mach related  */
	exp->p_debugger = p->p_debugger;
	exp->sigwait = p->sigwait;
	/* scheduling */
#ifdef _PROC_HAS_SCHEDINFO_
	exp->p_estcpu = p->p_estcpu;
	exp->p_pctcpu = p->p_pctcpu;
	exp->p_slptime = p->p_slptime;
#endif
	exp->p_realtimer.it_interval.tv_sec = p->p_realtimer.it_interval.tv_sec;
	exp->p_realtimer.it_interval.tv_usec = p->p_realtimer.it_interval.tv_usec;

	exp->p_realtimer.it_value.tv_sec = p->p_realtimer.it_value.tv_sec;
	exp->p_realtimer.it_value.tv_usec = p->p_realtimer.it_value.tv_usec;

	exp->p_rtime.tv_sec = p->p_rtime.tv_sec;
	exp->p_rtime.tv_usec = p->p_rtime.tv_usec;

	exp->p_sigignore = p->p_sigignore;
	exp->p_sigcatch = p->p_sigcatch;
	exp->p_priority = p->p_priority;
	exp->p_nice = p->p_nice;
	bcopy(&p->p_comm, &exp->p_comm, MAXCOMLEN);
	exp->p_xstat = (u_short)MIN(p->p_xstat, USHRT_MAX);
	exp->p_acflag = p->p_acflag;
}

如果在 proc 结构的 p_lflag 中设置了 P_LTRACED,
则也在 extern_proc 结构的 p_lflag 中设置了 P_TRACED。

参考

3. 获取ppid

if(getppid() != 1) {
    return YES;    //detected debugger;
}

xnu代码(他是如何工作的)

int getppid(proc_t p, __unused struct getppid_args *uap, int32_t *retval)
{
    *retval = p->p_ppid;
    return 0;
}

检查父pid是否是launchd进程。

launchd进程的pid始终为1,所以
如果不为1,则判断已检测到调试器。

返回 proc 结构的 p_ppid 值。

参考

绕过

只需删除 proc 结构体 p_lflag 中设置的 P_LNOATTACH 和 P_LTRACED 并用 1 覆盖 p_ppid 值即可。

uint64_t getProc(pid_t pid) {
    //  https://github.com/apple/darwin-xnu/blob/main/bsd/sys/proc_internal.h#L193
    //  https://github.com/apple/darwin-xnu/blob/main/bsd/sys/queue.h#L470
    
    uint64_t proc = kread64(kernproc);
    
    while (true) {
        if(kread32(proc + 0x68/*PROC_P_PID_OFF*/) == pid) {
            return proc;
        }
        proc = kread64(proc + 0x8/*PROC_P_LIST_LE_PREV_OFF*/);
    }
    
    return 0;
}

//https://stackoverflow.com/questions/49506579/how-to-find-the-pid-of-any-process-in-mac-osx-c
int find_pids(const char *name)
{
    int ret = -1;
    pid_t pids[2048];
    int bytes = proc_listpids(PROC_ALL_PIDS, 0, pids, sizeof(pids));
    int n_proc = bytes / sizeof(pids[0]);
    for (int i = 0; i < n_proc; i++) {
        struct proc_bsdinfo proc;
        int st = proc_pidinfo(pids[i], PROC_PIDTBSDINFO, 0,
                             &proc, PROC_PIDTBSDINFO_SIZE);
        if (st == PROC_PIDTBSDINFO_SIZE) {
            if (strcmp(name, proc.pbi_name) == 0) {
                /* Process PID */
                // printf("%d [%s] [%s]\n", pids[i], proc.pbi_comm, proc.pbi_name);     
                return pids[i];           
            }
        }       
    }
    return ret;
}

#define P_LNOATTACH     0x00001000 
#define P_LTRACED       0x00000400

#define ISSET(t, f)     ((t) & (f))
#define CLR(t, f)       (t) &= ~(f)
#define SET(t, f)       (t) |= (f)

int main(int argc, char *argv[], char *envp[]) {
    @autoreleasepool {
        libjb = dlopen("/var/jb/basebin/libjailbreak.dylib", RTLD_NOW);

        if(dimentio_init(0, NULL, NULL) != KERN_SUCCESS) {
            printf("failed dimentio_init!\n");
            return 1;
          }

        if(kbase == 0) {
            printf("failed get_kbase\n");
            return 1;
        }

        uint64_t kslide = kbase - 0xFFFFFFF007004000;
        printf("[i] kbase: 0x%llx, kslide: 0x%llx\n", kbase, kslide);
        printf("[i] kread64 from base: 0x%llx\n", kread64(kbase));

        int ptracetest_pid = find_pids("ptracetest");
        printf("[i] ptracetest pid: %d\n", ptracetest_pid);
        if(ptracetest_pid == -1) {
            printf("Not running ptracetest.\n");
            return 1;
        }

        uint64_t ptracetest_proc = getProc(ptracetest_pid);
        printf("[i] ptracetest proc: 0x%llx\n", ptracetest_proc);
        
        // https://github.com/apple/darwin-xnu/blob/main/bsd/kern/mach_process.c#L133
        uint64_t ptracetest_lflag = ptracetest_proc + 0x1c0/*lflagoffset*/;
        unsigned int lflagvalue = kread32(ptracetest_lflag);
        printf("[i] ptracetest proc->p_lflag: 0x%x\n", lflagvalue);

        if(ISSET(lflagvalue, P_LNOATTACH))
        {
            printf("[+] P_LNOATTACH has been set, clearing...\n");
            CLR(lflagvalue, P_LNOATTACH);
            kwrite32(ptracetest_lflag, lflagvalue);
            printf("[+] P_LNOATTACH now unset.\n");

            lflagvalue = kread32(ptracetest_lflag);
            printf("[+] ptracetest proc->p_lflag: 0x%x\n", lflagvalue);
        }

        // https://github.com/apple/darwin-xnu/blob/main/bsd/kern/kern_sysctl.c#L1079
        if(argc == 2 && strcmp(argv[1], "notrace") == 0){
            if(ISSET(lflagvalue, P_LTRACED))
            {
                printf("[+] P_LTRACED has been set, clearing...\n");
                CLR(lflagvalue, P_LTRACED);
                kwrite32(ptracetest_lflag, lflagvalue);
                printf("[+] P_LTRACED now unset.\n");

                lflagvalue = kread32(ptracetest_lflag);
                printf("[+] ptracetest proc->p_lflag: 0x%x\n", lflagvalue);
            }
        }
        
        if(argc == 2 && strcmp(argv[1], "trace") == 0) {
            if(!ISSET(lflagvalue, P_LTRACED))
            {
                printf("[+] P_LTRACED has NOT been set, setting...\n");
                SET(lflagvalue, P_LTRACED);
                kwrite32(ptracetest_lflag, lflagvalue);
                printf("[+] P_LTRACED now set.\n");

                lflagvalue = kread32(ptracetest_lflag);
                printf("[+] ptracetest proc->p_lflag: 0x%x\n", lflagvalue);
            }
        }

        uint64_t ptracetest_ppid = ptracetest_proc + 0x20;
        unsigned int ppidvalue = kread32(ptracetest_ppid);
        printf("[i] ptracetest proc->p_ppid: %d\n", ppidvalue);
        if(ppidvalue != 1) {
            printf("[+] Patching proc->p_ppid to 1...\n");
            kwrite32(ptracetest_ppid, 1);

            ppidvalue = kread32(ptracetest_ppid);
            printf("[+] ptracetest proc->p_ppid: %d\n", ppidvalue);
        }

        dlclose(libjb);

        return 0;
    }
}