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 }
작동 원리
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 함수가 수행된 후에 디버그를 시도할 경우)
Reference
https://github.com/apple/darwin-xnu/blob/main/bsd/kern/mach_process.c#L133
https://github.com/apple/darwin-xnu/blob/main/bsd/sys/errno.h#L146
https://github.com/apple/darwin-xnu/blob/main/bsd/kern/mach_process.c#L308
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 ); }
작동 원리
STATIC void fill_user64_externproc(proc_t p, struct user64_extern_proc *__restrict exp) { ... if (p->p_lflag & P_LTRACED) { exp->p_flag |= P_TRACED; } ... }
proc 구조체의 p_lflag에 P_LTRACED가 세트되어있으면,
마찬가지로 extern_proc 구조체의 p_lflag에 P_TRACED가 세트된다.
Reference
https://github.com/apple/darwin-xnu/blob/main/bsd/kern/kern_sysctl.c#L1079
3. getppid
코드
if(getppid() != 1) { return YES; //detected debugger; }
부모 pid가 launchd 프로세스인지 확인한다.
launchd 프로세스의 pid는 항상 1이므로,
1이 아닌 경우 디버거가 탐지된걸로 판단한다.
작동 원리
int getppid(proc_t p, __unused struct getppid_args *uap, int32_t *retval) { *retval = p->p_ppid; return 0; }
proc 구조체의 p_ppid 값을 반환시킨다.
Reference
https://github.com/apple/darwin-xnu/blob/main/bsd/sys/proc_internal.h#L463
https://github.com/apple/darwin-xnu/blob/main/bsd/kern/kern_prot.c#L189
Bypass!
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; } }
간단하게 그냥 proc 구조체의 p_lflag에 세트된 P_LNOATTACH, P_LTRACED를 지우고
p_ppid 값을 1로 덮어씌우면 된다.
Demo
테스트 환경
iPhone SE (2nd generation) / iOS 15.0.2 / Dopamine
안녕하세요, 저는 완전한 초보자입니다. 당신의 글을 보고 다시 구현해보려고 했습니다. 그런데 이 바이너리 파일을 컴파일할 때 다음과 같은 오류가 발생했습니다. 어떻게 해결해야 할지 모르겠습니다:
dimentio.c:7:21: error: call to undeclared function ‘kread64’; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
uint64_t proc = kread64(kernproc);
^
dimentio.c:7:29: error: use of undeclared identifier ‘kernproc’
uint64_t proc = kread64(kernproc);
^
dimentio.c:10:12: error: call to undeclared function ‘kread32’; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
if(kread32(proc + 0x68/*PROC_P_PID_OFF*/) == pid) {
^
dimentio.c:13:16: warning: implicit conversion changes signedness: ‘int’ to ‘uint64_t’ (aka ‘unsigned long long’) [-Wsign-conversion]
proc = kread64(proc + 0x8/*PROC_P_LIST_LE_PREV_OFF*/);
~ ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
dimentio.c:3:10: warning: no previous prototype for function ‘getProc’ [-Wmissing-prototypes]
uint64_t getProc(pid_t pid) {
^
dimentio.c:3:1: note: declare ‘static’ if the function is not intended to be used outside of this translation unit
uint64_t getProc(pid_t pid) {
^
static
dimentio.c:24:17: error: call to undeclared function ‘proc_listpids’; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
int bytes = proc_listpids(PROC_ALL_PIDS, 0, pids, sizeof(pids));
^
dimentio.c:24:31: error: use of undeclared identifier ‘PROC_ALL_PIDS’
int bytes = proc_listpids(PROC_ALL_PIDS, 0, pids, sizeof(pids));
^
dimentio.c:25:18: warning: implicit conversion changes signedness: ‘int’ to ‘unsigned long’ [-Wsign-conversion]
int n_proc = bytes / sizeof(pids[0]);
^~~~~ ~
dimentio.c:27:29: error: variable has incomplete type ‘struct proc_bsdinfo’
struct proc_bsdinfo proc;
^
dimentio.c:27:16: note: forward declaration of ‘struct proc_bsdinfo’
struct proc_bsdinfo proc;
^
dimentio.c:28:18: error: call to undeclared function ‘proc_pidinfo’; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
int st = proc_pidinfo(pids[i], PROC_PIDTBSDINFO, 0,
^
dimentio.c:28:40: error: use of undeclared identifier ‘PROC_PIDTBSDINFO’
int st = proc_pidinfo(pids[i], PROC_PIDTBSDINFO, 0,
^
dimentio.c:29:37: error: use of undeclared identifier ‘PROC_PIDTBSDINFO_SIZE’
&proc, PROC_PIDTBSDINFO_SIZE);
^
dimentio.c:30:19: error: use of undeclared identifier ‘PROC_PIDTBSDINFO_SIZE’
if (st == PROC_PIDTBSDINFO_SIZE) {
^
dimentio.c:20:5: warning: no previous prototype for function ‘find_pids’ [-Wmissing-prototypes]
int find_pids(const char *name)
^
dimentio.c:20:1: note: declare ‘static’ if the function is not intended to be used outside of this translation unit
int find_pids(const char *name)
^
static
dimentio.c:49:5: error: expected expression
@autoreleasepool {
^
dimentio.c:48:14: warning: unused parameter ‘argc’ [-Wunused-parameter]
int main(int argc, char *argv[], char *envp[]) {
^
dimentio.c:48:26: warning: unused parameter ‘argv’ [-Wunused-parameter]
int main(int argc, char *argv[], char *envp[]) {
^
dimentio.c:48:40: warning: unused parameter ‘envp’ [-Wunused-parameter]
int main(int argc, char *argv[], char *envp[]) {
^
7 warnings and 11 errors generated.
make: *** [all] Error 1
lzy@lzydeMacBook-Pro dimentio-main % make
xcrun -sdk iphoneos clang -arch arm64 -mios-version-min=10.0 -Weverything libdimentio.c dimentio.c -o dimentio -framework IOKit -framework CoreFoundation -lcompression -Os