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

公司安全性检测的部门提出了关于越狱手机插件绕过越狱检测的问题,虽然漏洞等级被评为低危,但是还是要解决的,在此添加了越狱检测的其他方式。

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传送阵在此。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容