工具库iOS 逆向

iOS手机越狱检测的一些方法和总结

2023-04-03  本文已影响0人  王看山
公司安全性检测的部门提出了关于越狱手机插件绕过越狱检测的问题,虽然漏洞等级被评为低危,但是还是要解决的,在此添加了越狱检测的其他方式。

1.检查是否可以写入系统文件

    NSError *error;
    NSString *stringToWrite = @"Jailbreak Test";
    [stringToWrite writeToFile:@"/private/jailbreak_test.txt" atomically:YES encoding:NSUTF8StringEncoding error:&error];
    
    if (!error) {
        //删除测试文件
        [[NSFileManager defaultManager] removeItemAtPath:@"/private/jailbreak_test.txt" error:nil];
        return YES;
    }

2.通过私有方法获取app列表,并从列表中筛选出越狱插件标识

    Class LSApplicationWorkspace_Class = NSClassFromString(@"LSApplicationWorkspace");
    NSObject *workspace = [LSApplicationWorkspace_Class performSelector:NSSelectorFromString(@"defaultWorkspace")];
    NSArray *appList = [workspace performSelector:NSSelectorFromString(@"allApplications")];
    NSString *appStr = @"";
    for (id app in appList) {
        NSString *appId = [app performSelector:NSSelectorFromString(@"applicationIdentifier")];
        if (appStr.length) {
            appStr = [appStr stringByAppendingFormat:@"|%@", appId];
        } else {
            appStr = [appStr stringByAppendingString:appId];
        }
    }
    NSArray *appIds = @[@"Cydia", @"Sileo", @"Zebra", @"AFC2", @"AppSync", @"LibertyLite", @"Liberty Lite", @"OTADisabler"];
    for (NSString *tempStr in appIds) {
        if ([appStr.uppercaseString containsString:tempStr.uppercaseString]) {
            return YES;
        }
    }

3.判断是否存在越狱文件,使用stat通过检测一些越狱后的关键文件是否可以访问来判断是否越狱,但hook stat 方法和dladdr可以绕过

   sDylibSet  = [NSSet setWithObjects:
         @"/usr/lib/CepheiUI.framework/CepheiUI",
         @"/usr/lib/libsubstitute.dylib",
         @"/usr/lib/substitute-inserter.dylib",
         @"/usr/lib/substitute-loader.dylib",
         @"/usr/lib/substrate/SubstrateLoader.dylib",
         @"/usr/lib/substrate/SubstrateInserter.dylib",
         @"/Library/MobileSubstrate/MobileSubstrate.dylib",
         @"/Library/MobileSubstrate/DynamicLibraries/0Shadow.dylib",nil];
    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;
        }
    }

    //检查是否存在越狱文件路径
    NSArray *jailbreakFilePaths = @[@"/Applications/Cydia.app",
                                    @"/Library/MobileSubstrate/MobileSubstrate.dylib",
                                    @"/bin/bash",
                                    @"/usr/sbin/sshd",
                                    @"/etc/apt",
                                    @"/User/Applications/"];
    for (NSString *path in jailbreakFilePaths) {
       if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
           return YES;
       }
    }
    char *device_pathes[] = {
        "/Applications/Cydia.app",
        "/Library/MobileSubstrate/MobileSubstrate.dylib",
        "/bin/bash",
        "/usr/sbin/sshd",
        "/etc/apt",
        "/User/Applications/"
    };
    for (int i = 0; i < sizeof(device_pathes) / sizeof(char *); i++) {
        struct stat stat_info;
        if (0 == stat(device_pathes[i], &stat_info)) {
            return YES;
        }
    }

4.判断是否注入了动态库

    unsigned int outCount = 0;
    const char **images =  objc_copyImageNames(&outCount);
    for (int i = 0; i < outCount; i++) {
      DLog(@"%s\n", images[i]);
    }

    int i = 0;
    while(true) {
      //hook _dyld_get_image_name方法可以绕过
      const char *name = _dyld_get_image_name(i++);
      if(name == NULL){
          break;
      }
      
      if (name != NULL) {
        NSString *libName = [NSString stringWithUTF8String:name];
        if ([sDylibSet containsObject:libName]) {
          return YES;
        }
      }
    }

5.判断是否存在cydia应用,跟方法2类似,这是通过判断能否打开的很容易被hook绕过

    if([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"cydia://package/com.example.package"]]){
        DLog(@"此设备越狱!");
        return YES;
    }

6.读取环境变量

    char *printEnvGDJ(void) {
        return getenv("DYLD_INSERT_LIBRARIES");
    }
    if(printEnvGDJ()){
        DLog(@"此设备越狱!");
        return YES;
    }
  1. 判断Mach-O文件否被篡改
- (BOOL)checkO {
    NSBundle *bundle = [NSBundle mainBundle];
    NSDictionary *info = [bundle infoDictionary];
    if ([info objectForKey: @"SignerIdentity"] != nil){
        //存在这个key,则说明被二次打包了
        return YES;
    }
    return NO;
}

8.防止重签打包验证

- (BOOL)prepareEnv {
    NSString *embeddedPath = [[NSBundle mainBundle] pathForResource:@"embedded" ofType:@"mobileprovision"];
    if ([[NSFileManager defaultManager] fileExistsAtPath:embeddedPath]) {
        NSString *embeddedProvisioning = [NSString stringWithContentsOfFile:embeddedPath encoding:NSASCIIStringEncoding error:nil];
        NSArray *embeddedProvisioningLines = [embeddedProvisioning componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];

        for (int i = 0; i < [embeddedProvisioningLines count]; i++) {
            if ([[embeddedProvisioningLines objectAtIndex:i] rangeOfString:@"application-identifier"].location != NSNotFound) {
                NSInteger fromPosition = [[embeddedProvisioningLines objectAtIndex:i+1] rangeOfString:@"<string>"].location+8;
                NSInteger toPosition = [[embeddedProvisioningLines objectAtIndex:i+1] rangeOfString:@"</string>"].location;
                NSRange range;
                range.location = fromPosition;
                range.length = toPosition - fromPosition;
                NSString *fullIdentifier = [[embeddedProvisioningLines objectAtIndex:i+1] substringWithRange:range];
                NSArray *identifierComponents = [fullIdentifier componentsSeparatedByString:@"."];
                NSString *appIdentifier = [identifierComponents firstObject];
                self.appIdentifier = appIdentifier;
                // 对比签名ID
                if ([appIdentifier caseInsensitiveCompare:@"xxxxxxxxx"] != NSOrderedSame && ![appIdentifier isEqualToString:@"xxxxxxxxx"]) {
                    self.appIdentifier = [NSString stringWithFormat:@"appIdentifier: xxxxxxxxx appIdentifierN:%@", appIdentifier];
                    return YES;
                }
                break;
            }
        }
    }
    return NO;
}

9.防止App文件被篡改校验

- (BOOL)filesMD5Verify {
    NSMutableArray *allImages = @[].mutableCopy;
    //获取app目录下的所有子文件,并取AppIcon和LaunchImage相关图片做标记对比hash值
    NSString *bundlePath = [[NSBundle mainBundle] resourcePath];
    NSArray *dirArray = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:bundlePath error:nil];
    for (NSString *fileName in dirArray) {
        if ([fileName containsString:@".png"] && ([fileName containsString:@"AppIcon"] || [fileName containsString:@"LaunchImage"])) {
            [allImages addObject:fileName];
        }
    }
    if (allImages.count == 0) {
        return YES;
    }
    
   /*验证文件md5时先取出来一遍,以便好记录
    NSMutableDictionary *md5Dic = @{}.mutableCopy;
    for (int i = 0; i < allImages.count; i++) {
        NSString *name = allImages[i];
        NSString *md5N = [self getFileMD5WithPath:[NSString stringWithFormat:@"%@/%@", bundlePath, name]];
        [md5Dic setValue:md5N forKey:name];
    }
    DLog(@"md5Dic.JSONString:\n%@", md5Dic.JSONString);*/
    //IPA包中的图片校验
    NSDictionary *files = @{@"LaunchImage-800-667h@2x.png":@"640fa9b1eea2c213df76d1b46b2541cf",
                            @"LaunchImage-800-Portrait-736h@3x.png":@"d24d66af2ba194200435928ede112163",
                            @"LaunchImage-1100-Portrait-2436h@3x.png":@"26e39ef03140d787d164cf22cf63c8fd",
                            @"LaunchImage-1200-Portrait-1792h@2x.png":@"f8cb1e88aee6f6d4c8f19ab49e8ac126",
                            @"LaunchImage-1200-Portrait-2688h@3x.png":@"95ec433723282568337ca61a35576819",
                            /*以下生成的图片貌似会变,试过了打包出来跟模拟器运行的,md5对不上,需要进一步测试
                            @"AppIcon20x20@2x.png":@"6bbd2d4005f697d92edf0c3e08c8f9c2",
                            @"AppIcon20x20@3x.png":@"e10f24814b6d77c8c7deda214f2b3b22",
                            @"AppIcon29x29@2x.png":@"77ea8da0a59223c319e9409b23ea3549",
                            @"AppIcon29x29@3x.png":@"5e5dfd91aa50abe3d0699ad503315220",
                            @"AppIcon40x40@2x.png":@"e9c07d7049615733226c0d6774e80fc3",
                            @"AppIcon40x40@3x.png":@"31a4a8422afbbd7757f99c15c0ba1683",
                            @"AppIcon60x60@2x.png":@"31a4a8422afbbd7757f99c15c0ba1683",
                            @"AppIcon60x60@3x.png":@"57456909d87f9f741254a43a05af7cc7",
                            @"LaunchImage-700-568h@2x.png":@"7538d4cc1befa8e1fc3481755585d551",
                            @"LaunchImage@2x.png":@"c6e89cc76e226fcfb67b2ddb02ff9bf4",
                            @"LaunchImage-568h@2x.png":@"2fbf6ed2b1c520e7884bad758aae473f",
                            @"AppIcon57x57@2x.png":@"e619f57770cec363936acd40576dda7c",
                            @"LaunchImage-700@2x.png":@"4d6c1e22746b26a118ee5b3f3bda80db",*/
                            };
    
    BOOL isChange = NO;
    for (int i = 0; i < allImages.count; i++) {
        NSString *name = allImages[i];
        if (![files.allKeys containsObject:name]) {
            //若files中没有对应的key值则跳过校验
            continue;
        }
        NSString *md5 = files[name];
        NSString *md5N = [self getFileMD5WithPath:[NSString stringWithFormat:@"%@/%@", bundlePath, name]];
        if (![md5 isEqual:md5N]) {
            DLog(@"%@ md5:%@ md5N:%@", name, md5, md5N);
            self.fileHash = [NSString stringWithFormat:@"%@ md5:%@ md5N:%@", name, md5, md5N];
            isChange = YES;
            break;
        }
    }
    return isChange;
}

在此记录一下,看到的希望能帮到你。本篇GitHubDemo传送阵在此。

上一篇下一篇

猜你喜欢

热点阅读