콘텐츠로 건너뛰기

iOS/iPadOS 탈옥 및 디버깅 탐지 방법

1. 파일 탐지

1-1. Objective-C 메소드

  • – (BOOL)isReadableFileAtPath:(NSString *)path;
  • – (BOOL)fileExistsAtPath: (NSString *)path isDirectory: (BOOL *)isDirectory
  • – (BOOL)fileExistsAtPath: (NSString *)path
  • – (NSArray *)contentsOfDirectoryAtPath:(NSString *)path error:(NSError * _Nullable *)error;

Example

if ([[NSFileManager defaultManager] fileExistsAtPath:@”/Applications/Sileo.app”]]) {
    return YES;    //Detected Jailbroken
}

1-2. C System Library

  • FILE *fopen(const char *pathname, const char *mode);
  • int access(const char *path, int amode);
  • int open(const char *path, int oflag, …);
  • int lstat(const char *path, struct stat * buf);

Example

#include <unistd.h>

if (access("/Applications/Sileo.app", F_OK) == 0) {
    return YES;    //Detected Jailbroken
}

1-3. Supervisor Call (Low-level, SVC #0x80)

Example

static inline int SVC_statfs64(const char* path, struct statfs *buf) {
    int64_t flag = 0;
    __asm __volatile("mov x0, %0" :: "r" ((int64_t)SYS_statfs64)); //SYS_statfs64
    __asm __volatile("mov x1, %0" :: "r" (path)); //path
    __asm __volatile("mov x2, %0" :: "r" (buf));    //struct statfs
    __asm __volatile("mov x16, %0" :: "r" ((int64_t)SYS_syscall));   //SYS_syscall
    __asm __volatile("svc #0x80"); //supervisor call
    __asm __volatile("mov %0, x0" : "=r" (flag));
    return (int)flag;
}

static inline int SVC_Access(const char* detectionPath, int64_t mode) {
    int64_t flag = 0;
    __asm __volatile("mov x0, %0" :: "r" ((int64_t)SYS_access)); //SYS_access
    __asm __volatile("mov x1, %0" :: "r" (detectionPath)); //path
    __asm __volatile("mov x2, %0" :: "r" (mode));    //mode
    __asm __volatile("mov x16, %0" :: "r" ((int64_t)SYS_syscall));   //SYS_syscall
    __asm __volatile("svc #0x80"); //supervisor call
    __asm __volatile("mov %0, x0" : "=r" (flag));
    return (int)flag;
}

if(SVC_Access("Applications/Sileo/app", F_OK) != ENOENT) {
    return YES;    //Detected Jailbroken
}
-(NSArray *)jailbreakFiles {
	NSArray *file = [NSArray arrayWithObjects:
	                 @"/Applications/Cydia.app",
	                 @"/Applications/Sileo.app",
	                 @"/var/binpack",
	                 @"/Library/MobileSubstrate/DynamicLibraries",
	                 @"/Library/PreferenceBundles/LibertyPref.bundle",
	                 @"/Library/PreferenceBundles/ShadowPreferences.bundle",
	                 @"/Library/PreferenceBundles/ABypassPrefs.bundle",
	                 @"/Library/PreferenceBundles/FlyJBPrefs.bundle",
	                 @"/usr/lib/libhooker.dylib",
	                 @"/usr/lib/libsubstitute.dylib",
	                 @"/usr/lib/substrate",
	                 @"/usr/lib/TweakInject",
	                 nil];
	return file;
}
...
+(BOOL)isJailbreakFileExist {
	BOOL check = NO;
	NSArray *jbPatternFile = [[[XFJailbreakPattern alloc] init] jailbreakFiles];
	NSFileManager *fileManager = [NSFileManager defaultManager];
	for (NSString *jbFile in jbPatternFile) {
		const char *jbFile2 = [jbFile cStringUsingEncoding:NSUTF8StringEncoding];

		//NSFileManager fileExistsAtPath
		if ([fileManager fileExistsAtPath:jbFile]) {
			NSLog(@"NSFilemanager: %@", jbFile);
			check = YES;
		}

		//System Library - opendir: Sustitute doesn't like hooking opendir :)
		DIR *dirPoint = opendir(jbFile2);
		if (dirPoint != NULL) {
			NSLog(@"opendir: %@ - %p", jbFile, dirPoint);
			check = YES;
		}

		//syscall - SYS_access
		if(syscall(SYS_access, jbFile2, F_OK) == 0) {
			NSLog(@"Syscall SYS_access: %@", jbFile);
			check = YES;
		}

		//SVC #0x80 - SYS_syscall - SYS_access, SYS_access, SYS_lstat64, SYS_stat64, SYS_statfs64, SYS_open
	#if defined __arm64__ || defined __arm64e__
		int64_t flag = ENOENT;
		__asm __volatile("mov x0, #0x21"); //access
		__asm __volatile("mov x1, %0" :: "r" (jbFile2)); //path
		__asm __volatile("mov x2, #0"); //mode
		__asm __volatile("mov x16, #0");   //syscall
		__asm __volatile("svc #0x80"); //supervisor call
		__asm __volatile("mov %0, x0" : "=r" (flag));
	#else
		int flag = ENOENT;
		__asm __volatile("mov r0, #0x21"); //access
		__asm __volatile("mov r1, %0" :: "r" (jbFile2)); //path
		__asm __volatile("mov r2, #0"); //mode
		__asm __volatile("mov r12, #0"); //syscall
		__asm __volatile("svc #0x80"); //supervisor call
		__asm __volatile("mov %0, r0" : "=r" (flag));
	#endif
		if (flag != ENOENT ) {
			NSLog(@"SVC #0x80 SYS_syscall - SYS_access: %s", jbFile2);
			check = YES;
		}

	#if defined __arm64__ || defined __arm64e__
		flag = ENOENT;
		__asm __volatile("mov x0, %0" :: "r" (jbFile2)); //path
		__asm __volatile("mov x1, #0"); //mode
		__asm __volatile("mov x16, #0x21");   //access
		__asm __volatile("svc #0x80"); //supervisor call
		__asm __volatile("mov %0, x0" : "=r" (flag));
	#else
		flag = ENOENT;
		__asm __volatile("mov r0, %0" :: "r" (jbFile2)); //path
		__asm __volatile("mov r1, #0"); //mode
		__asm __volatile("mov r12, #0x21"); //access
		__asm __volatile("svc #0x80"); //supervisor call
		__asm __volatile("mov %0, r0" : "=r" (flag));
	#endif
		if (flag != ENOENT ) {
			NSLog(@"SVC #0x80 SYS_access: %s", jbFile2);
			check = YES;
		}

		struct stat statPoint;

	#if defined __arm64__ || defined __arm64e__
		flag = ENOENT;
		__asm __volatile("mov x0, %0" :: "r" (jbFile2)); //path
		__asm __volatile("mov x1, %0" :: "r" (&statPoint)); //struct stat
		__asm __volatile("mov x16, #0x154");   //lstat64
		__asm __volatile("svc #0x80"); //supervisor call
		__asm __volatile("mov %0, x0" : "=r" (flag));
	#else
		flag = ENOENT;
		__asm __volatile("mov r0, %0" :: "r" (jbFile2)); //path
		__asm __volatile("mov x1, %0" :: "r" (&statPoint)); //struct stat
		__asm __volatile("mov r12, #0x154"); //lstat64
		__asm __volatile("svc #0x80"); //supervisor call
		__asm __volatile("mov %0, r0" : "=r" (flag));
	#endif
		if (flag != ENOENT ) {
			NSLog(@"SVC #0x80 SYS_lstat64: %s", jbFile2);
			check = YES;
		}

	#if defined __arm64__ || defined __arm64e__
		flag = ENOENT;
		__asm __volatile("mov x0, %0" :: "r" (jbFile2)); //path
		__asm __volatile("mov x1, %0" :: "r" (&statPoint)); //struct stat
		__asm __volatile("mov x16, #0x152");   //stat64
		__asm __volatile("svc #0x80"); //supervisor call
		__asm __volatile("mov %0, x0" : "=r" (flag));
	#else
		flag = ENOENT;
		__asm __volatile("mov r0, %0" :: "r" (jbFile2)); //path
		__asm __volatile("mov x1, %0" :: "r" (&statPoint)); //struct stat
		__asm __volatile("mov r12, #0x152"); //stat64
		__asm __volatile("svc #0x80"); //supervisor call
		__asm __volatile("mov %0, r0" : "=r" (flag));
	#endif
		if (flag != ENOENT ) {
			NSLog(@"SVC #0x80 SYS_stat64: %s", jbFile2);
			check = YES;
		}

		struct statfs statfsPoint;
	#if defined __arm64__ || defined __arm64e__
		flag = ENOENT;
		__asm __volatile("mov x0, %0" :: "r" (jbFile2)); //path
		__asm __volatile("mov x1, %0" :: "r" (&statfsPoint)); //struct statfs
		__asm __volatile("mov x16, #0x159");   //statfs64
		__asm __volatile("svc #0x80"); //supervisor call
		__asm __volatile("mov %0, x0" : "=r" (flag));
	#else
		flag = ENOENT;
		__asm __volatile("mov r0, %0" :: "r" (jbFile2)); //path
		__asm __volatile("mov x1, %0" :: "r" (&statfsPoint)); //struct statfs
		__asm __volatile("mov r12, #0x159"); //statfs64
		__asm __volatile("svc #0x80"); //supervisor call
		__asm __volatile("mov %0, r0" : "=r" (flag));
	#endif
		if (flag != ENOENT ) {
			NSLog(@"SVC #0x80 SYS_statfs64: %s", jbFile2);
			check = YES;
		}

	#if defined __arm64__ || defined __arm64e__
		flag = 0;
		__asm __volatile("mov x0, %0" :: "r" (jbFile2)); //path
		__asm __volatile("mov x1, #0");
		__asm __volatile("mov x2, #0");
		__asm __volatile("mov x16, #0x5");     //open
		__asm __volatile("svc #0x80"); //supervisor call
		__asm __volatile("bcc #0xC");
		__asm __volatile("mov x0, #0x0");
		__asm __volatile("b #0x8");
		__asm __volatile("mov x0, #0x1");
		__asm __volatile("mov %0, x0" : "=r" (flag));
	#else
		flag = 0;
		__asm __volatile("mov r0, %0" :: "r" (jbFile2)); // path
		__asm __volatile("mov r1, #0");
		__asm __volatile("mov r2, #0");
		__asm __volatile("mov r12, #0x5"); // open
		__asm __volatile("svc #0x80"); //supervisor call
		__asm __volatile("bcc #0x6");
		__asm __volatile("mov r0, 0x0");
		__asm __volatile("b #0x4");
		__asm __volatile("mov r0, #0x1");
		__asm __volatile("mov %0, r0" : "=r" (flag));
	#endif
		if(flag == 1) {
			NSLog(@"SVC #0x80 SYS_open: %s", jbFile2);
			check = YES;
		}
	}
	return check;
}

2. 샌드박스 우회 탐지

2-1. Objective-C / C System Library

Example

NSError *error;
[@"Jailbreak Test" writeToFile:@”/private/var/sandbox.txt” atomically:YES encoding:NSUTF8StringEncoding error:&error];

if(error == nil) {
    return YES;    //Detected Jailbroken
}

2-2. Private API

  • int sandbox_check(pid_t, const char *operation, int sandbox_filter_type, …);
    (operation: file-read*)

Example

void *sandbox = dlopen("/usr/lib/system/libsystem_sandbox.dylib", RTLD_NOW);
if(sandbox != NULL)
{
    _sandbox_check = dlsym(sandbox, "sandbox_check");
    if(_sandbox_check != NULL)
    {
        int filter = SANDBOX_FILTER_PATH | SANDBOX_CHECK_NO_REPORT;
        if(_sandbox_check(getpid(), "file-read*", filter, "/Library") != 1)
        {
            return YES;    //Detected Jailbroken
        }
    }
}

3. URL Scheme 탐지

  • -(BOOL)canOpenURL:(NSURL *)arg1;

Example

NSURL *url = [NSURL URLWithString:@"sileo://"];
if([[UIApplication sharedApplication] canOpenURL:url]) {
    return YES;    //Detected Jailbroken
}

4. 후킹 라이브러리 탐지

4-1. Private API

  • kern_return_t task_info(task_name_t target_task, task_flavor_t flavor, task_info_t task_info_out, mach_msg_type_number_t *task_info_outCnt);
  • uint32_t _dyld_image_count(void);
  • const struct mach_header* _dyld_get_image_header(uint32_t image_index);
  • const char* _dyld_get_image_name(uint32_t image_index);
  • kern_return_t vm_region_recurse_64(vm_map_read_t target_task, vm_address_t *address, vm_size_t *size, natural_t *nesting_depth, vm_region_recurse_info_t info, mach_msg_type_number_t *infoCnt)
  • kern_return_t vm_region_64(vm_map_read_t target_task, vm_address_t *address, vm_size_t *size, vm_region_flavor_t flavor, vm_region_info_t info, mach_msg_type_number_t *infoCnt, mach_port_t *object_name);
  • int proc_regionfilename(int pid, uint64_t address, void * buffer, uint32_t buffersize);

Example

+(BOOL)isJailbreakInjectExist {
    BOOL check = NO;

	integer_t task_info_out[TASK_DYLD_INFO_COUNT];
	mach_msg_type_number_t task_info_outCnt = TASK_DYLD_INFO_COUNT;
	if(task_info(mach_task_self_, TASK_DYLD_INFO, task_info_out, &task_info_outCnt) == KERN_SUCCESS) {
		struct task_dyld_info dyld_info = *(struct task_dyld_info*)(void*)(task_info_out);
		struct dyld_all_image_infos* infos = (struct dyld_all_image_infos *) dyld_info.all_image_info_addr;
		struct dyld_uuid_info* pUuid_info  = (struct dyld_uuid_info*) infos->uuidArray;

		for( int i = 0; i < infos->uuidArrayCount; i++, pUuid_info += 1)
		{
			const struct mach_header_64* mheader = (const struct mach_header_64*)pUuid_info->imageLoadAddress;
			if (mheader->filetype == MH_DYLIB) {
				if(mheader->magic == MH_MAGIC_64 && mheader->ncmds > 0)
				{
					void *loadCmd = (void*)(mheader + 1);
					struct segment_command_64 *sc = (struct segment_command_64 *)loadCmd;
					for (int index = 0; index < mheader->ncmds; ++index, sc = (struct segment_command_64*)((BYTE*)sc + sc->cmdsize))
					{
						if (sc->cmd == LC_ID_DYLIB) {
							struct dylib_command *dc = (struct dylib_command *)sc;
							struct dylib dy = dc->dylib;
							const char *detectedDyld = (char*)dc + dy.name.offset;
							for (NSString *jbDyld in jbPatternDyld) {
								if([[NSString stringWithUTF8String:detectedDyld] containsString:jbDyld]) {
									NSLog(@"dyld2: %s", detectedDyld);
									check = YES;
									break;
								}
							}
						}
					}
				}
			}
		}
	}
	return check;
}

Reference

https://knight.sc/reverse%20engineering/2019/04/15/detecting-task-modifications.html

https://www.romainthomas.fr/post/22-08-singpass-rasp-analysis/

https://gist.github.com/ddrccw/8412847#file-hello-h-L122

https://github.com/TannerJin/AntiMSHookFunction/blob/master/AntiMSHookFunction/AntiMSHookFunctionARM.c

4-2. C System Library

  • int dladdr(void *addr, Dl_info *info);
  • void *dlopen(const char *filename, int flag);
  • void *dlsym(void *handle, const char *symbol);

Example

-(NSArray *)jailbreakSymbols {
	NSArray *symbol = [NSArray arrayWithObjects:
	                   @"MSHookFunction",
	                   @"MSHookMessageEx",
	                   @"MSFindSymbol",
	                   @"MSGetImageByName",
	                   @"ZzBuildHook",
	                   @"DobbyHook",
	                   @"LHHookFunctions",
	                   nil];
	return symbol;
}
...
NSArray *jbPatternSymbol = [[[XFJailbreakPattern alloc] init] jailbreakSymbols];
for (NSString *jbSymbol in jbPatternSymbol) {
	const char *jbSymbol2 = [jbSymbol cStringUsingEncoding:NSUTF8StringEncoding];
	void* dlpoint = dlsym((void *)RTLD_DEFAULT, jbSymbol2);
	if(dlpoint != NULL) {
		return YES;    //Detected Jailbroken
	}
}
static void _check_image(const struct mach_header *header, intptr_t slide) {
  NSSet *dylibSet = [NSSet setWithObjects:
                     @"/usr/lib/CepheiUI.framework/CepheiUI",
                     @"/usr/lib/libsubstitute.dylib"
                     @"/usr/lib/substitute-inserter.dylib",
                     @"/usr/lib/substitute-loader.dylib",
                     nil];
  
  Dl_info info;
  if (dladdr(header, &info) == 0) {
    char *dlerro = dlerror();
    if(dlerro == NULL && info.dli_fname != NULL) {
      NSString *libName = [NSString stringWithUTF8String:info.dli_fname];
      if ([dylibSet containsObject:libName]) {
        return YES;    //Detected Jailbroken
      }
    }
    return;
  }
}

bool hasHookedMethods(void)
{
    bool ret = false;
    MethodsList = [[NSMutableArray alloc] init];
    
    int classCount = 5;
    const char* classes[5] =
    {
        "NSFileManager",
        "UIApplication",
        "NSString",
        "NSData",
        "NSBundle",
    };
    
    for(int i = 0; i < classCount; i++)
    {
        Class ourClass = objc_getClass(classes[i]);
        unsigned int methodCount = 0;
        Method *methods = class_copyMethodList(ourClass, &methodCount);
        
        for (unsigned int i = 0; i < methodCount; i++) {
            Method method = methods[i];
            Dl_info image_info;
            if(dladdr(class_getMethodImplementation(ourClass, method_getName(method)), &image_info) != 0)
            {
                struct mach_header_64 *header = image_info.dli_fbase;
                if(header && header->magic == MH_MAGIC_64)
                {
                    struct load_command *loadCmd = (struct load_command *) (header + 1);
                    struct segment_command_64 *sc = (struct segment_command_64 *)loadCmd;

                    for (int index = 0; index < header->ncmds; ++index, sc = (struct segment_command_64*)((char*)sc + sc->cmdsize))
                    {
                        if(sc->cmd == LC_LOAD_DYLIB)
                        {
                            struct dylib_command *dc = (struct dylib_command *)sc;
                            struct dylib dy = dc->dylib;
                            const char *detectedDyld = (char*)dc + dy.name.offset;
                            if(strcmp(detectedDyld, "/Library/Frameworks/CydiaSubstrate.framework/CydiaSubstrate") == 0)
                            {
                                NSString *func = [NSString stringWithFormat:@"%s %s", class_getName(ourClass), sel_getName(method_getName(method))];
                                [MethodsList addObject:func];
                                ret = true;
                            }
                        }
                    }
                }
            }
        }
    }
    
    return ret;
}

5. 커널 접근 탐지

  • kern_return_t task_for_pid(mach_port_t parent, int pid, mach_port_t *task_out );
  • kern_return_t host_get_special_port(host_priv_t host_priv, int node, int which, mach_port_t *port);

Example

bool hasKernelTaskPort(void)
{
    mach_port_t kernel_task = MACH_PORT_NULL;
    kern_return_t ret = task_for_pid(mach_task_self(), 0, &kernel_task);
    if(ret == KERN_SUCCESS && MACH_PORT_VALID(kernel_task))
    {
        kernelTaskPort = kernel_task;
        mach_port_deallocate(mach_task_self(), kernel_task);
        return true;
    }
    else
    {
        host_t host = mach_host_self();
        ret = host_get_special_port(host, HOST_LOCAL_NODE, 4, &kernel_task);
        if(ret == KERN_SUCCESS && MACH_PORT_VALID(kernel_task))
        {
            kernelTaskPort = kernel_task;
            mach_port_deallocate(mach_task_self(), kernel_task);
            return true;
        }
        mach_port_deallocate(mach_task_self(), host);
    }
    return false;
}

if(hasKernelTaskPort()) {
    return YES;    //Detected Jailbroken
}

6. 환경 탐지

  • char ***_NSGetEnviron(void);
  • char *getenv(const char *varname);
  • extern char **environ;
  • [[NSProcessInfo processInfo] environment];

Example

-(NSArray *)jailbreakEnvs {
	NSArray *env = [NSArray arrayWithObjects:
	                @"JB_ROOT_PATH",
	                @"_MSSafeMode",
	                @"DYLD_INSERT_LIBRARIES",
	                @"substitute",
	                nil];
	return env;
}

+(BOOL)isJailbreakInjectExist {
    BOOL check = NO;

    NSArray *jbPatternEnv = [[[XFJailbreakPattern alloc] init] jailbreakEnvs];

	char ***envp = _NSGetEnviron();
	if (envp) {
		char **env = *envp;
		while (*env) {
			for (NSString *jbEnv in jbPatternEnv) {
				if([[NSString stringWithUTF8String:*env] containsString:jbEnv]) {
					check = YES;
				}
			}
			env++;
		}
	}

	//Env Check2
	extern char **environ;
	for(int i=0; environ[i]; i++)
	{
		for (NSString *jbEnv in jbPatternEnv) {
			if([[NSString stringWithUTF8String:environ[i]] containsString:jbEnv]) {
				check = YES;
			}
		}
	}

	return check;
}

7. 루트 파일 시스템 쓰기 여부 / 리마운트 (iOS ~14.x)

rootless 탈옥 환경에서는 /usr/standalone/firmware 마운트된 Device 경로를 참고하여
/private/preboot/(UUID)에 접근해서 stat으로 파일 갯수 확인

  • int statfs(const char *path, struct statfs *buf);
  • int getmntinfo(struct statfs **mntbufp, int flags);

Example

bool hasRenamedRootFS(void)
{
    struct statfs *st;
    
    int num_fs = getmntinfo(&st, MNT_NOWAIT);
    if(num_fs != 0) {
        for (int i = 0; i < num_fs; i++) {
            if(strstr(st[i].f_mntfromname, "com.apple.os.update-") != NULL) {
                return false;
            }
            if(strstr(st[i].f_mntfromname, "orig-fs") != NULL) {
                return true;
            }
            
            if(strcmp(st[i].f_mntfromname, "/dev/disk0s1s1") == 0) {
                if(SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"11.3"))
                    return true;
            }
        }
    }
    
    return false;
}

static inline int SVC_statfs64(const char* path, struct statfs *buf) {
    int64_t flag = 0;
    __asm __volatile("mov x0, %0" :: "r" ((int64_t)SYS_statfs64)); //SYS_statfs64
    __asm __volatile("mov x1, %0" :: "r" (path)); //path
    __asm __volatile("mov x2, %0" :: "r" (buf));    //struct statfs
    __asm __volatile("mov x16, %0" :: "r" ((int64_t)SYS_syscall));   //SYS_syscall
    __asm __volatile("svc #0x80"); //supervisor call
    __asm __volatile("mov %0, x0" : "=r" (flag));
    return (int)flag;
}

bool hasRootFSmountedRW(void)
{
    struct statfs rootfs;
    if(SVC_statfs64("/", &rootfs) == 0)
    {
        if(rootfs.f_flags & MNT_RDONLY)
        {
            return false;
        }
    }
    return true;
}

8. 포트

  • int bind(int sockfd, struct sockaddr *myaddr, socklen_t addrlen);
  • int socket(int domain, int type, int protocol);
  • SSH 포트: 22, 2222 | Frida 포트: 27042

Example

bool hasSSHAvailable(void)
{
    int server_socket = socket(AF_INET, SOCK_STREAM, 0);
    if(server_socket == -1)
    {
        return false;
    }
    
    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(22);
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    
    int status = bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr));
    if(status == 0) {
        close(server_socket);
        return false;
    }
    
    return true;
}

9. 디버깅

  • pid_t getppid(void);
  • int sysctl(const int *name, u_int namelen, void *oldp, size_t *oldlenp, const void *newp, size_t newlen);
  • long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);
  • int isatty(int fd);
  • int ioctl(int fd, unsigned long request, …);
  • kern_return_t task_get_exception_ports(task_t task, exception_mask_t exception_mask, exception_mask_array_t masks, mach_msg_type_number_t *masksCnt, exception_handler_array_t old_handlers, exception_behavior_array_t old_behaviors, exception_flavor_array_t old_flavors);

Example

bool hasDebuggerAttached(void)
{
    bool ret = false;
    
    int mib[4];
    struct kinfo_proc info;
    size_t info_size = sizeof(info);

    info.kp_proc.p_flag = 0;

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

    if (sysctl(mib, 4, &info, &info_size, NULL, 0) == 0)
    {
        int traceStatus = info.kp_proc.p_flag & P_TRACED;
        if(traceStatus != 0)
            ret = true;
    }

    int launchdPid = 1;
    if (getppid() != launchdPid)
        ret = true;

    if(isatty(STDERR_FILENO) != 0)
        ret = true;

    struct winsize ws;
    if (!ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws))
        ret = true;
    
    mach_msg_type_number_t count = 0;
    exception_mask_t masks[EXC_TYPES_COUNT];
    mach_port_t ports[EXC_TYPES_COUNT];
    exception_behavior_t behaviors[EXC_TYPES_COUNT];
    thread_state_flavor_t flavors[EXC_TYPES_COUNT];
    exception_mask_t mask = EXC_MASK_ALL & ~(EXC_MASK_RESOURCE | EXC_MASK_GUARD);
    
    kern_return_t result = task_get_exception_ports(mach_task_self(), mask, masks, &count, ports, behaviors, flavors);
    if (result == KERN_SUCCESS)
    {
        for (mach_msg_type_number_t portIndex = 0; portIndex < count; portIndex++)
        {
            if (MACH_PORT_VALID(ports[portIndex]))
            {
                ret = true;
            }
        }
    }
    
    return ret;
}

10. 코드 서명

  • int fcntl(int fildes, int cmd, …); (cmd = F_ADDSIGS, F_CHECK_LV)
  • int csops(pid_t pid, unsigned int ops, void *useraddr, size_t usersize); (ops = CS_OPS_MARKKILL)

Example

bool hasCodeSigningValidated(void)
{
    bool ret = false;

    FILE* dyld_file = fopen("/usr/lib/dyld", "rb");
    fsignatures_t siginfo;
    if(dyld_file != NULL)
    {
        uint8_t firstPage[4096];
        if(fread(firstPage, 1, 4096, dyld_file) == 4096)
        {
            struct mach_header *mh = (struct mach_header*)firstPage;
            uint32_t cmd_count = mh->ncmds;
            struct load_command *cmds = (struct load_command*)((char*)firstPage+(sizeof(struct mach_header_64)));
            struct load_command *cmd = cmds;
            for (uint32_t i = 0; i < cmd_count; ++i)
            {
                if (cmd->cmd == LC_CODE_SIGNATURE)
                {
                    const struct linkedit_data_command *sigcmd = (struct linkedit_data_command*) cmd;
                    siginfo.fs_file_start = O_DIRECTORY;
                    siginfo.fs_blob_start = malloc(sigcmd->datasize);
                    siginfo.fs_blob_size = sigcmd->datasize;
                }
                cmd = (struct load_command*)(((char*)cmd)+cmd->cmdsize);
            }
        }
    }
    fclose(dyld_file);
    
    int dyld_fd = open("/usr/lib/dyld", O_RDONLY, 0);
    if(dyld_fd != -1)
    {
        int result = fcntl(dyld_fd, F_ADDSIGS, &siginfo);
        if(result == -1)
        {
            if(errno == EBADEXEC || errno == EPERM)
            {
                ret = true;
            }
        }
    }
    close(dyld_fd);
    
    return ret;
}

Reference

https://lapcatsoftware.com/articles/hardened-runtime-xpc.html

https://www.synacktiv.com/sites/default/files/2021-10/2021_sthack_jailbreak.pdf

11. Mach 접근

  • int sandbox_check(pid_t, const char *operation, int sandbox_filter_type, …);
    (operation: mach-lookup)
  • kern_return_t bootstrap_check_in(mach_port_t bp, const name_t service_name, mach_port_t *sp);
  • kern_return_t bootstrap_look_up(mach_port_t bp, const name_t service_name, mach_port_t *sp);

Reference

https://github.com/Lessica/shadow/blob/master/Shadow.dylib/hooks/mach.x#L3

https://github.com/Lessica/shadow/blob/master/Shadow.dylib/hooks/sandbox.x#L104

태그:

“iOS/iPadOS 탈옥 및 디버깅 탐지 방법”의 4개의 댓글

  1. 공유해주신 예시 코드에서

    siginfo.fs_file_start = O_DIRECTORY;
    siginfo.fs_blob_start = malloc(sigcmd->datasize);
    siginfo.fs_blob_size = sigcmd->datasize;

    여기서 fs_file_start가 enum값인 O_DIRECTORY인 것은 어떤 의미이실까요?
    그리고 fs_blob_start가 sigcmd->dataoff가 아니라 malloc으로 들어가는 이유를 알 수 있을까요?

    1. 안녕하세요!
      저 코드는 많이 오래되어서 기억이 가물가물하긴한데 제가 잘못된 코드를 적어넣었을 가능성이 크고 작동될지는 잘 모르겠습니다 ㅠㅠ
      대신에 도파민 탈옥을 감지하고 싶으시다면, /usr/lib 경로에 bindfs 형태로 마운트되었는지 아닌지 판단해서 탈옥감지해보는건 어떨까요?

답글 남기기