iOS 系统虽然安全,但并非坚不可破,在应用的开发过程中我们应该要注意很多安全上的问题,这里记录了一些常用的安全方案。
# 越狱检测
通过下面的方法可以检测手机是否越狱
#import <sys/stat.h>
#import <sys/sysctl.h>
+ (BOOL)isJailbreak{
if (TARGET_IPHONE_SIMULATOR) {
return NO;
}else{
//越狱检查
if ([[[UIDevice currentDevice] systemVersion] floatValue]<9.0) {
//CanOpenURL检查
@try {
#if !(defined(__has_feature) && __has_feature(attribute_availability_app_extension))
NSURL *FakeURL = [NSURL URLWithString:@"cydia://package/com.fake.package"];
if ([[UIApplication sharedApplication] canOpenURL:FakeURL])
return YES;
#endif
}
@catch (NSException *exception) {
}
}
//Cydia应用检查
@try {
NSString *appPath =@"/Applications/Cydia.app";
FILE *vf = fopen([appPath cStringUsingEncoding:NSASCIIStringEncoding],"r");
if (vf != NULL)
return YES;
}
@catch (NSException *exception) {
}
//fork函数检查
if (fork()>=0) {
return YES;
}
//注入动态库检查
const char *env = getenv("DYLD_INSERT_LIBRARIES");
if (env) {
return YES;
}
NSInteger motzart =0;
//无法访问文件检查
@try {
NSArray *files =@[
@"/Applications/RockApp.app",
@"/Applications/Icy.app",
@"/usr/sbin/sshd",
@"/usr/bin/sshd",
@"/usr/libexec/sftp-server",
@"/Applications/WinterBoard.app",
@"/Applications/SBSettings.app",
@"/Applications/MxTube.app",
@"/Applications/IntelliScreen.app",
@"/Library/MobileSubstrate/DynamicLibraries/Veency.plist",
@"/Library/MobileSubstrate/DynamicLibraries/LiveClock.plist",
@"/Library/MobileSubstrate/DynamicLibraries/xCon.plist",
@"/private/var/lib/apt",@"/private/var/stash",
@"/System/Library/LaunchDaemons/com.ikey.bbot.plist",
@"/System/Library/LaunchDaemons/com.saurik.Cydia.Startup.plist",
@"/private/var/tmp/cydia.log",@"/private/var/lib/cydia",
@"/etc/clutch.conf", @"/var/cache/clutch.plist",
@"/etc/clutch_cracked.plist",
@"/var/cache/clutch_cracked.plist",
@"/var/lib/clutch/overdrive.dylib",
@"/var/root/Documents/Cracked/"];
for (NSString *fileName in files) {
FILE *vf = fopen([fileName cStringUsingEncoding:NSASCIIStringEncoding],"r");
if (vf != NULL){
motzart +=2;
break;
}
}
}
@catch (NSException *exception) {
}
//plist文件检查
@try {
NSString *exePath = [[NSBundle mainBundle] executablePath];
NSDictionary *infoDict = [[NSBundle mainBundle] infoDictionary];
FILE *vf = fopen([exePath cStringUsingEncoding:NSASCIIStringEncoding],"r");
if (vf == NULL || infoDict == nil || infoDict.count <= 0) {
motzart +=2;
}
}
@catch (NSException *exception) {
}
if ([[[UIDevice currentDevice] systemVersion] floatValue]<9.0) {
//进程检查
@try {
NSArray *processes = [self runningProcesses];
for (NSDictionary * dict in processes) {
NSString *process = [dict objectForKey:@"ProcessName"];
if ([process isEqualToString:@"MobileCydia"]||[process isEqualToString:@"Cydia"]||[process isEqualToString:@"afpd"]) {
motzart +=2;
break;
}
}
}
@catch (NSException *exception) {
}
//符号链接检查
@try {
struct stat s;
if (lstat("/Applications", &s) != 0 && (s.st_mode & S_IFLNK)) {
motzart += 2;
}
}
@catch (NSException *exception) {
}
}
return motzart >= 3;
}
}
+ (NSArray *)runningProcesses {
int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0};
size_t miblen = 4;
size_t size = 0;
int st;
struct kinfo_proc * process = NULL;
struct kinfo_proc * newprocess = NULL;
do {
size += (size / 10);
newprocess = realloc(process, size);
if (!newprocess){
if (process){
free(process);
}
return nil;
}
process = newprocess;
st = sysctl(mib, (int)miblen, process, &size, NULL, 0);
} while (st == -1 && errno == ENOMEM);
if (st == 0){
if (size % sizeof(struct kinfo_proc) == 0){
int nprocess = (int)(size / sizeof(struct kinfo_proc));
if (nprocess){
NSMutableArray * array = [[NSMutableArray alloc] init];
for (int i = nprocess - 1; i >= 0; i--){
NSString * processID =[[NSString alloc] initWithFormat:@"%d", process[i].kp_proc.p_pid];
NSString * processName =[[NSString alloc] initWithFormat:@"%s", process[i].kp_proc.p_comm];
NSString *processPriority =[[NSString alloc] initWithFormat:@"%d", process[i].kp_proc.p_priority];
NSDate *processStartDate =[NSDate dateWithTimeIntervalSince1970:process[i].kp_proc.p_un.__p_starttime.tv_sec];
NSDictionary *dict =@{
@"ProcessID":processID,
@"ProcessPriority":processPriority,
@"ProcessName":processName,
@"ProcessStartDate":processStartDate};
[array addObject:dict];
}
free(process);
return array;
}
}
}
free(process);
return nil;
}
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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# 检测应用重签名
应用重签名后就可以安装在没有越狱的手机上进行调试,我们可以检测应用是否重签名来阻止运行。一般重签名会改变应用的 bundle id ,我们可以通过比较当前应用的 bundle id 是否与我们自己的一致来判断。
// 获取当前应用的bundleId来比较
NSString *bundleId = [[[NSBundle mainBundle] infoDictionary] objectForKey:(__bridge NSString *)kCFBundleIdentifierKey];
if (![bundleId isEqualToString:@"My Bundle Id"]) {
// 与自己的id不同说明被重签名
return NO;
}
//取出embedded.mobileprovision这个描述文件的内容进行判断
NSString *mobileProvisionPath = [[NSBundle mainBundle] pathForResource:@"embedded" ofType:@"mobileprovision"];
if (mobileProvisionPath) {
// 获取文件的内容
NSData *mobileProvisionData = [NSData dataWithContentsOfFile:mobileProvisionPath];
NSString *mobileProvisionContent = [[NSString alloc] initWithData:mobileProvisionData encoding:NSASCIIStringEncoding];
// 获取文件中plist的起始位置
NSRange plistStartRange = [mobileProvisionContent rangeOfString:@"<plist>"];
// 获取文件中plist的结束位置
NSRange plistEndRange = [mobileProvisionContent rangeOfString:@"</plist>"];
if (plistStartRange.location != NSNotFound && plistEndRange.location != NSNotFound) {
// 获取plist的内容
NSString *plistContent = [mobileProvisionContent substringWithRange:NSMakeRange(plistStartRange.location, NSMaxRange(plistEndRange))];
// 解析plist
NSDictionary *result = [NSPropertyListSerialization propertyListWithData:[plistContent dataUsingEncoding:NSUTF8StringEncoding] options:NSPropertyListImmutable format:nil error:nil];
// 获取前缀
NSArray *applicationIdentifierPrefix = [result objectForKey:@"ApplicationIdentifierPrefix"];
// 获取带有前缀的bundleId
NSString *bundleId = [[result objectForKey:@"Entitlements"] objectForKey:@"application-identifier"];
// 检测带有前缀的bundleId是否与自己的一致
if (bundleId && [bundleId isEqualToString:@"My Bundle Id"]) {
return YES;
}
}
}
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
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
# 阻止动态调试
# ptrace
我们可以在 main 函数中添加下面的方法来阻止动态调试
#import <dlfcn.h>
#import <sys/types.h>
#if !defined(PT_DENY_ATTACH)
#define PT_DENY_ATTACH 31
#endif // !defined(PT_DENY_ATTACH)
typedef int (*ptrace_ptr_t)(int _request, pid_t _pid, caddr_t _addr, int _data);
void disable_gdb() {
void* handle = dlopen(0, RTLD_GLOBAL | RTLD_NOW);
ptrace_ptr_t ptrace_ptr = dlsym(handle, "ptrace");
ptrace_ptr(PT_DENY_ATTACH, 0, 0, 0);
dlclose(handle);
}
int main(int argc, char * argv[]) {
//在release模式下防动态调试
#ifdef __OPTIMIZE__
disable_gdb();
#endif
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
当然该方法也会被调试者下断点的方式来动态绕过或者使用 IDA 等反汇编工具打补丁绕过,所以我们应该在程序的多处地方调用 disable_gdb()
。
# sysctl
当一个进程被调试的时候,该进程会有一个标记来标记自己正在被调试,所以可以通过 sysctl 去查看当前进程的信息,看有没有这个标记位即可检查当前调试状态。
BOOL isDebuggerPresent(){
int name[4]; //指定查询信息的数组
struct kinfo_proc info; //查询的返回结果
size_t info_size = sizeof(info);
info.kp_proc.p_flag = 0;
name[0] = CTL_KERN;
name[1] = KERN_PROC;
name[2] = KERN_PROC_PID;
name[3] = getpid();
if(sysctl(name, 4, &info, &info_size, NULL, 0) == -1){
NSLog(@"sysctl error ...");
return NO;
}
return ((info.kp_proc.p_flag & P_TRACED) != 0);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# syscall
为从实现从用户态切换到内核态,系统提供了一个系统调用函数 syscall,上面讲到的 ptrace 也是通过系统调用去实现的。
在 Kernel Syscalls 这里可以找到 ptrace 对应的编号。
26. ptrace 801e812c T
1
所以如下的调用等同于调用 ptrace:
syscall(26,31,0,0,0);
1
# arm
syscall 是通过软中断来实现从用户态到内核态,也可以通过汇编 svc 调用来实现。
#ifdef __arm__
asm volatile(
"mov r0,#31\n"
"mov r1,#0\n"
"mov r2,#0\n"
"mov r12,#26\n"
"svc #80\n"
);
#endif
#ifdef __arm64__
asm volatile(
"mov x0,#26\n"
"mov x1,#31\n"
"mov x2,#0\n"
"mov x3,#0\n"
"mov x16,#0\n"
"svc #128\n"
);
#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 字符串加密
程序中硬编码的字符串会在反编译软件中直接暴露出来,因此我们要对程序中重要的字符串 (如加密秘钥) 进行混淆处理,常用的方法为异或加密。
// 定义一个宏来作为我们异或的key
#define KEY 0xBB
// 将要加密的字符串拆解为字符数组,将每个字符与key进行异或
// 以加密 hello 为例,末尾加 \0 为结束
unsigned char str[] = {(KEY ^ 'h'),
(KEY ^ 'e'),
(KEY ^ 'l'),
(KEY ^ 'l'),
(KEY ^ 'o'),
(KEY ^ '\0')};
// 解密方法,恢复加密后的字符串
void recoverString(unsigned char *str, unsigned char key){
unsigned char *p = str;
while( ((*p) ^= key) != '\0') p++;
}
// 使用字符串时先解密
// str 为加密后的字符数组
recoverString(str, KEY);
static unsigned char result[6];
memcpy(result, str, 6);
NSLog(@"%s",result); //output: hello
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25