“Jailbreaking” has existed since the Appstore was evaluated, when many people jailbroken in order to install paid applications or games. With the richness of Appstore applications and the increase of free APPs, there are few users who jailbreak their phones in order to sacrifice their security. On the other hand, jailbroken devices can install any software or script at will, which also brings the door of convenience to the black market.

image

Sometimes our application wants to know whether the installed device has been jailbroken or not, obviously, Apple will not give the official solution to come, so what do we do? The following are some ways to determine this.

Detecting dynamic libraries

1) Determine whether the dynamic library stat is the system library, and use stat to detect some specific file permissions

stat command is used to determine the file information in OS system, but for the private path call command returns -1, if after jailbreak, because the permission changes, you can use stat to return the file information in the private directory.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
BOOL isStatNotSystemLib() {
    if(TARGET_IPHONE_SIMULATOR)return NO;
    int ret ;
    Dl_info dylib_info;
    int (*func_stat)(const char *, struct stat *) = stat;
    if ((ret = dladdr(func_stat, &dylib_info))) {
        NSString *fName = [NSString stringWithUTF8String: dylib_info.dli_fname];
        if(![fName isEqualToString:@"/usr/lib/system/libsystem_kernel.dylib"]){
            return YES;
        }
    }
    
    char *JbPaths[] = {"/Applications/Cydia.app",
    "/usr/sbin/sshd",
    "/bin/bash",
    "/etc/apt",
    "/Library/MobileSubstrate",
    "/User/Applications/"};
    
    for (int i = 0;i < sizeof(JbPaths) / sizeof(char *);i++) {
        struct stat stat_info;
        if (0 == stat(JbPaths[i], &stat_info)) {
            return YES;
        }
    }
    
    return NO;
}

2) Determine if dynamic libraries are injected

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
BOOL isInjectedWithDynamicLibrary()
{
    int i=0;
    char *substrate = "/Library/MobileSubstrate/MobileSubstrate.dylib";
    while(true){
        // hook _dyld_get_image_name方法可以绕过
        const char *name = _dyld_get_image_name(i++);
        if(name==NULL){
            break;
        }
        if (name != NULL) {
                    if (strcmp(name,substrate)==0) {
                            return YES;
                    }
        }
    }
    return NO;
}

1) Determine if you can open jailbreak software

Most jailbreak devices will automatically cydia, use URL Scheme to see if you can open jailbreak software such as cydia.

1
2
3
4
5
6
7
8
9
- (BOOL)isJailBreak
{
    if ([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"cydia://"]]) {
        NSLog(@"The device is jail broken!");
        return YES;
    }
    NSLog(@"The device is NOT jail broken!");
    return NO;
}

2) Determine if some jailbroken files can be accessed

Jailbreak will generate additional files, and you can determine whether they are jailbroken by judging whether they exist or not, and you can use two different methods to get them, fopen and FileManager.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
BOOL fileExist(NSString* path)
{
    NSFileManager *fileManager = [NSFileManager defaultManager];
    BOOL isDirectory = NO;
    if([fileManager fileExistsAtPath:path isDirectory:&isDirectory]){
        return YES;
    }
    return NO;
}

BOOL directoryExist(NSString* path)
{
    NSFileManager *fileManager = [NSFileManager defaultManager];
    BOOL isDirectory = YES;
    if([fileManager fileExistsAtPath:path isDirectory:&isDirectory]){
        return YES;
    }
    return NO;
}

BOOL canOpen(NSString* path)
{
    FILE *file = fopen([path UTF8String], "r");
    if(file==nil){
        return fileExist(path) || directoryExist(path);
    }
    fclose(file);
    return YES;
}

NSArray* checks = [[NSArray alloc] initWithObjects:@"/Application/Cydia.app",
                       @"/Library/MobileSubstrate/MobileSubstrate.dylib",
                       @"/bin/bash",
                       @"/usr/sbin/sshd",
                       @"/etc/apt",
                       @"/usr/bin/ssh",
                       @"/private/var/lib/apt",
                       @"/private/var/lib/cydia",
                       @"/private/var/tmp/cydia.log",
                       @"/Applications/WinterBoard.app",
                       @"/var/lib/cydia",
                       @"/private/etc/dpkg/origins/debian",
                       @"/bin.sh",
                       @"/private/etc/apt",
                       @"/etc/ssh/sshd_config",
                       @"/private/etc/ssh/sshd_config",
                       @"/Applications/SBSetttings.app",
                       @"/private/var/mobileLibrary/SBSettingsThemes/",
                       @"/private/var/stash",
                       @"/usr/libexec/sftp-server",
                       @"/usr/libexec/cydia/",
                       @"/usr/sbin/frida-server",
                       @"/usr/bin/cycript",
                       @"/usr/local/bin/cycript",
                       @"/usr/lib/libcycript.dylib",
                       @"/System/Library/LaunchDaemons/com.saurik.Cydia.Startup.plist",
                       @"/System/Library/LaunchDaemons/com.ikey.bbot.plist",
                       @"/Applications/FakeCarrier.app",
                       @"/Library/MobileSubstrate/DynamicLibraries/Veency.plist",
                       @"/Library/MobileSubstrate/DynamicLibraries/LiveClock.plist",
                       @"/usr/libexec/ssh-keysign",
                       @"/usr/libexec/sftp-server",
                       @"/Applications/blackra1n.app",
                       @"/Applications/IntelliScreen.app",
                       @"/Applications/Snoop-itConfig.app"
                       @"/var/lib/dpkg/info", nil];
    //Check installed app
    for(NSString* check in checks)
    {
        if(canOpen(check))
        {
            return YES;
        }
    }

3) Check if you have permission to write to the private directory

Determine if you have permission to write to the private directory by checking if it is jailbroken

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
NSString *path = @"/private/avl.txt";
    NSFileManager *fileManager = [NSFileManager defaultManager];
    @try {
        NSError* error;
        NSString *test = @"AVL was here";
        [test writeToFile:path atomically:NO encoding:NSStringEncodingConversionAllowLossy error:&error];
        [fileManager removeItemAtPath:path error:nil];
        if(error==nil)
        {
            return YES;
        }

        return NO;
    } @catch (NSException *exception) {
        return NO;
}

Using system commands to determine

1) Use the lstat command to determine whether some directories of the system exist or have become links

Jailbreak will change some files, these file directories will be migrated to other areas, but the original file location must be valid, so it will create symbolic links to link to the original path, we can detect whether these symbolic links exist, the existence of which means it is jailbroken.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
//symlink verification
    struct stat sym;
    // hook lstat可以绕过
    if(lstat("/Applications", &sym) || lstat("/var/stash/Library/Ringtones", &sym) ||
       lstat("/var/stash/Library/Wallpaper", &sym) ||
       lstat("/var/stash/usr/include", &sym) ||
       lstat("/var/stash/usr/libexec", &sym)  ||
       lstat("/var/stash/usr/share", &sym) ||
       lstat("/var/stash/usr/arm-apple-darwin9", &sym))
    {
        if(sym.st_mode & S_IFLNK)
        {
            return YES;
        }
}

2) Is it possible to fork a child process

Some jailbreak tools will remove the sandbox restrictions so that the program can run without restrictions. fork function allows your program to generate a new process, if the sandbox is broken or the program is running outside the sandbox, then the fork function will be executed successfully, if the sandbox is not tampered with then the fork function will fail. Here we determine the success of the child process by the return value of fork(), the program code is as follows.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#!c
#include <stdio.h>
#include <stdlib.h>
static inline int sandbox_integrity_compromised(void) __attribute__((always_inline));
int sandbox_integrity_compromised(void){
    int result = fork();
    if (!result)
        exit(0);
    if (result >= 0)
        return 1;
    return 0;
}
int main(int argc,char *argv[]){
    if(sandbox_integrity_compromised())
    {
    printf("Device is JailBroken\n");
    }else{
    printf("Device is not JailBroken\n");
    }
    return 0;
}

Check if there are exception classes and dynamic libraries for exceptions

1) Check if there are exception classes

Check if there are classes that inject exceptions,for example,HBPreferences is a common class for jailbreaking,there is no way to bypass it here,just look for more feature classes.Note that many anti-jailbreak plugins can be confusing,so you may have to identify them by checking the key methods.

1
2
3
4
5
6
7
NSArray *checksClass = [[NSArray alloc] initWithObjects:@"HBPreferences",nil];
for(NSString *className in checksClass)
{
  if (NSClassFromString(className) != NULL) {
    return YES;
  }
}

2) Detecting the presence of unusual dynamic libraries

The difference between this and detecting the injection of dynamic libraries is that the general anti-jailbreak plug-in will hook_dyld_get_image_name this method to jailbreak the use of some dynamic libraries to shadow away (such as returning other dynamic library names, or return to normal), resulting in a match can not use the image load when the callback to go from MachO Header dynamic library information, it should be noted that when using dladdr to detect library information, may also be forced to return an error, you need to do a little further judgment, see the code below.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
+ (void)load {
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    _dyld_register_func_for_add_image(_check_image);
  });
}

// 监听image加载,从这里判断动态库是否加载,因为其他的检测动态库的方案会被hook
static void _check_image(const struct mach_header *header,
                                      intptr_t slide) {
  // hook Image load
  if (SCHECK_USER) {
    // 检测后就不在检测
    return;
  }

  // 检测的lib
  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;
  // 0表示加载失败了,这里大概率是被hook导致的
  if (dladdr(header, &info) == 0) {
    char *dlerro = dlerror();
    // 获取失败了 但是返回了dli_fname, 说明被人hook了,目前看的方案都是直接返回0来绕过的
    if(dlerro == NULL && info.dli_fname != NULL) {
      NSString *libName = [NSString stringWithUTF8String:info.dli_fname];
      // 判断有没有在动态列表里面
      if ([dylibSet containsObject:libName]) {
        SCHECK_USER = YES;
      }
    }
    return;
  }
}

Detect if debugging is in progress

1) Check if the environment variable DYLD_INSERT_LIBRARIES is available

1
2
3
4
5
6
#pragma mark 通过环境变量DYLD_INSERT_LIBRARIES检测是否越狱
BOOL dyldEnvironmentVariables ()
{
    if(TARGET_IPHONE_SIMULATOR)return NO;
    return !(NULL == getenv("DYLD_INSERT_LIBRARIES"));
}

2) Determine if the current process is in debugging mode

Use the sysctl method to get information about the current process so that it is indeed whether pTraced debugging is going on, refer to sysctl for details.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
BOOL isDebugged()
{
    int junk;
    int mib[4];
    struct kinfo_proc info;
    size_t size;
    info.kp_proc.p_flag = 0;
    mib[0] = CTL_KERN;
    mib[1] = KERN_PROC;
    mib[2] = KERN_PROC_PID;
    mib[3] = getpid();
    size = sizeof(info);
    junk = sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0);
    assert(junk == 0);
    return ( (info.kp_proc.p_flag & P_TRACED) != 0 );
}