通过修补内核内存绕过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;
}
}