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混淆等方法进一步提高应用的安全防护等级。

Contents