Contents
iOS应用开发者通常不太注意应用的安全防护(一般情况是完全没有防护),而随着越来越多人研究iOS逆向,以及iOS11越狱的发布,iOS的安全防护也应该得到重视。
作为iOS逆向的首选练手对象,微信会检测用户是否越狱,如果发现越狱了可以对一些功能比如指纹支付等进行限制,我们先来看一下微信是怎么检测越狱的。
首先用Hopper打开微信的执行文件,全局搜索jail,就能看到那么多与越狱检测相关的方法。
OK,fine.
我们先进入 [MidasIAPCommonUtility isDeviceJailBroken]看看
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| sub_100893904: 0000000100893904 stp x22, x21, [sp, #-0x30]! ; Objective C Block defined at 0x1036db330, DATA XREF=0x1036db340 0000000100893908 stp x20, x19, [sp, #0x10] 000000010089390c stp x29, x30, [sp, #0x20] 0000000100893910 add x29, sp, #0x20 0000000100893914 adrp x21, #0x104103000 ; @selector(setSampleratio:) 0000000100893918 ldr x0, [x21, #0xf08] ; objc_cls_ref_NSFileManager,_OBJC_CLASS_$_NSFileManager 000000010089391c adrp x8, #0x104071000 0000000100893920 ldr x19, [x8, #0x138] ; "defaultManager",@selector(defaultManager) 0000000100893924 mov x1, x19 0000000100893928 bl imp___stubs__objc_msgSend 000000010089392c adrp x8, #0x104078000 ; @selector(m_nsJsAppId) 0000000100893930 ldr x20, [x8, #0x700] ; "fileExistsAtPath:",@selector(fileExistsAtPath:) 0000000100893934 adrp x2, #0x1037b8000 0000000100893938 add x2, x2, #0xb58 ; @"/Applications/Cydia.app" 000000010089393c mov x1, x20 0000000100893940 bl imp___stubs__objc_msgSend 0000000100893944 cbz w0, loc_100893954
|
好的很明显这里的越狱检测是通过检查是否存在Cydia实现的,就类似于下面这样:
1 2 3 4 5 6
| -(BOOL)isJailBroken{ if ([[NSFileManager defaultManager]fileExistsAtPath:@"/Applications/Cydia.app"]) { return YES; } else return NO; }
|
当然这种你要是写成那样我不绕过都觉得对不起我自己,只需要轻轻hook一下:
1 2 3 4
| %hook xxxController -(BOOL)isJailBroken{ return NO; }
|
不过就算把检测方法写得比较复杂或者进行校验,攻击者也可以更改越狱软件安装路径曲线救国…
我们还可以看到[JailBreakHelper JailBroken]这个名字也很可疑,我们再来看看这个方法。该方法里有这样一些代码:
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
| ___stack_chk_guard_1036b1528,___stack_chk_guard 0000000102d5418c ldr x8, x8 0000000102d54190 stur x8, [x29, #-0x58] 0000000102d54194 adrp x8, #0x104103000 ; @selector(setSampleratio:) 0000000102d54198 ldr x0, [x8, #0xf00] ; objc_cls_ref_NSArray,_OBJC_CLASS_$_NSArray 0000000102d5419c adrp x8, #0x104074000 ; @selector(setActionCode:) 0000000102d541a0 ldr x1, [x8, #0x9e0] ; "arrayWithObjects:",@selector(arrayWithObjects:) 0000000102d541a4 adrp x8, #0x103911000 0000000102d541a8 add x8, x8, #0xcf8 ; @"/etc/ssh/sshd_config" 0000000102d541ac stp x8, xzr, [sp, #0x30] 0000000102d541b0 adrp x8, #0x103911000 0000000102d541b4 add x8, x8, #0xcd8 ; @"/usr/libexec/ssh-keysign" 0000000102d541b8 adrp x9, #0x103911000 0000000102d541bc add x9, x9, #0xcb8 ; @"/usr/sbin/sshd" 0000000102d541c0 stp x9, x8, [sp, #0x20] 0000000102d541c4 adrp x8, #0x103911000 0000000102d541c8 add x8, x8, #0xc98 ; @"/bin/sh" 0000000102d541cc adrp x9, #0x103911000 0000000102d541d0 add x9, x9, #0xc78 ; @"/bin/bash" 0000000102d541d4 stp x9, x8, [sp, #0x10] 0000000102d541d8 adrp x8, #0x103911000 0000000102d541dc add x8, x8, #0xc58 ; @"/etc/apt" 0000000102d541e0 adrp x9, #0x103911000 0000000102d541e4 add x9, x9, #0xc38 ; @"/Applications/Cydia.app/" 0000000102d541e8 stp x9, x8, sp 0000000102d541ec adrp x2, #0x103911000 0000000102d541f0 add x2, x2, #0xc18 ; @"/Library/MobileSubstrate/MobileSubstrate.dylib" 0000000102d541f4 bl imp___stubs__objc_msgSend
|
可以看出来在这个方法中创建了一个数组,里面放了一些这样的字符串:@”/Applications/Cydia.app/“、@”/Library/MobileSubstrate/MobileSubstrate.dylib”,都是一些敏感路径,换汤不换药啊朋友。
类似的方法还有很多,比如尝试获取应用列表,而只有越狱设备才有权限获取应用列表:
1 2 3 4 5 6 7 8 9 10
| -(void)tryGetApp{ if ([[NSFileManager defaultManager] fileExistsAtPath:@"/User/Applications/"]){ NSLog(@"This Device is JailBroken"); NSArray *applist = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:@"/User/Applications/" error:nil]; NSLog(@"applist : %@",applist); } else{ NSLog(@"Path Not Exists"); } }
|
在越狱设备上会看到看到这样的输出。
当然办法总比问题多,攻击者可以hook NSFileManager,重写其contentsOfDirectoryAtPath方法,甚至不用针对你的程序下手,只需要简简单单地这样做:
1 2 3 4 5 6 7 8 9 10 11 12
| %hook NSFileManager - (bool)fileExistsAtPath:(id)arg1{ if ([arg1 isEqualToString:@"/var/containers/Bundle/Application"]) { NSLog(@"This Device is definitely not JailBroken!"); return NO; } else{ return %orig; } } %end
|
之前的程序输出结果就会变成
。。。
好的可以看到我们上面的手段都被轻易地发现并破解了,其实我们可以用c语言实现某些核心代码,c语言不具有Objective-C的runtime特性,这在安全性方面好处颇多,首先你的函数名和参数不会(那么容易)暴露在class-dump和反汇编中,还可以妙用函数指针进一步隐藏函数名和参数表其次,而且相比Objective-C更难被hook,虽然facebook开发了黑科技框架fishhook可以通过替换mach-o里的符号表来hookC函数,但这无疑给攻击者制造了许多障碍。更进一步地,我们还可以使用block、静态内联函数、代码混淆提高分析破解难度,当然这不能让你的APP变得坚不可摧,这只是这场博弈的开始。
我们来看看用C语言实现越狱检测效果如何,下面这个c函数用于列出该程序已链接的动态库,在越狱机上运行其结果通常会包含MobileSubstrate.dylib之类的东西。
1 2 3 4 5 6 7
| void DetectDyldInserted(){ uint32_t count = _dyld_image_count(); for (uint32_t i = 0 ; i < count; ++i) { NSString *name = [[NSString alloc]initWithUTF8String:_dyld_get_image_name(i)]; NSLog(@"--%@", name); } }
|
接下来我们试着class-dump以及用hopper分析一下这个程序,看看能不能发现找到蛛丝马迹。
首先是class-dump,只能看到前面写的OC方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @interface ViewController : UIViewController { UITextView *_textView; }
@property(retain, nonatomic) UITextView *textView; // @synthesize textView=_textView; - (void).cxx_destruct; - (void)didReceiveMemoryWarning; - (void)tryGetApp; - (_Bool)isJailBroken; - (void)touchesBegan:(id)arg1 withEvent:(id)arg2; - (void)viewDidLoad;
@end
|
接下来进入hopper分析我们的程序,搜索DetectDyldInserted,结果有点令人失望。

定位了函数,hook就是分分钟的事了。那么如何在hopper中隐藏函数名呢?
首先,最简单的,可以将DetectDyldInserted变为静态内联函数,因为内联函数的代码会被直接嵌入在它被调用的地方,不过需要注意的是,inline只是建议编译器将函数编译成内联函数,通常如果某个inline函数被函数指针指向它就不会被编译成inline函数。
其次,可以进行代码混淆,这对OC方法同样适用,就是把含义明确的函数名改成一些无意义的字符串,不过要是直接在代码中手写无意义的字符串会导致代码可读性极差,所以比较简单实用的方式是使用宏替换,可以将宏单独写在一个头文件里,还可以写成脚本将函数名替换成随机字符,参考念茜大神的这篇博客Objective-C代码混淆
上面介绍的还都是增加应用安全性的最基本方法,只是提高了逆向和破解的门槛,对于你的应用和数据安全来说还远远不够,我们可以使用符号表裁剪、反调试反注入、llvm混淆等方法进一步提高应用的安全防护等级。