KnightSama‘s Blog

vuePress-theme-reco KnightSama    2021
KnightSama‘s Blog KnightSama‘s Blog

Choose mode

  • dark
  • auto
  • light
首页
分类
  • iOS
  • 集锦
  • JavaScript
  • Github
  • Python
标签
时间线
GitHub
author-avatar

KnightSama

27

文章

14

标签

首页
分类
  • iOS
  • 集锦
  • JavaScript
  • Github
  • Python
标签
时间线
GitHub
  • iOS开发安全笔记

    • 越狱检测
      • 检测应用重签名
        • 阻止动态调试
          • ptrace
          • sysctl
          • syscall
          • arm
        • 字符串加密

        iOS开发安全笔记

        vuePress-theme-reco KnightSama    2021

        iOS开发安全笔记


        KnightSama 2017-07-13 iOS

        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

        # 检测应用重签名

        应用重签名后就可以安装在没有越狱的手机上进行调试,我们可以检测应用是否重签名来阻止运行。一般重签名会改变应用的 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

        # 阻止动态调试

        # 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

        当然该方法也会被调试者下断点的方式来动态绕过或者使用 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

        # 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

        # 字符串加密

        程序中硬编码的字符串会在反编译软件中直接暴露出来,因此我们要对程序中重要的字符串 (如加密秘钥) 进行混淆处理,常用的方法为异或加密。

        // 定义一个宏来作为我们异或的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
        欢迎来到 KnightSama‘s Blog
        看板娘