美文网首页
iOS 逆向防护

iOS 逆向防护

作者: 跳跳跳跳跳跳跳 | 来源:发表于2023-10-19 11:17 被阅读0次

    很久没有写博客了,今天将自己工作中要求做的逆向防护功能思路写出来
    其实逆向App的逻辑流程很简单,设备越狱->静态分析->动态调试->有思路之后编写插件->重签名
    既然有了别人逆向的流程,那就针对这个流程做一些防护

    1. 反越狱

    既然逆向的第一步是越狱,那我就检测越狱,只要手机越狱了,我就不让你用app
    检测越狱的逻辑也很简单

    • 检测能否打开cydia
    + (CXDefenseReasonType)hasCydiaInstalled {
        return [[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"cydia://"]] ? CXDefenseReasonTypeCydiaInstalled : CXDefenseReasonTypeNone;
    }
    
    • 检测是否存在一些越狱相关的app
    + (CXDefenseReasonType)isContainSuspiciousApps {
        NSArray *apps = @[@"/Applications/Cydia.app",
                          @"/Applications/blackra1n.app",
                          @"/Applications/FakeCarrier.app",
                          @"/Applications/Icy.app",
                          @"/Applications/IntelliScreen.app",
                          @"/Applications/MxTube.app",
                          @"/Applications/RockApp.app",
                          @"/Applications/SBSettings.app",
                          @"/Applications/WinterBoard.app"];
        for (NSString *path in apps) {
            if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
                if ([path isEqualToString:@"/Applications/Cydia.app"]) {
                    return CXDefenseReasonTypeAppCydia;
                }
                if ([path isEqualToString:@"/Applications/blackra1n.app"]) {
                    return CXDefenseReasonTypeAppBlackra1n;
                }
                if ([path isEqualToString:@"/Applications/FakeCarrier.app"]) {
                    return CXDefenseReasonTypeAppFakeCarrier;
                }
                if ([path isEqualToString:@"/Applications/Icy.app"]) {
                    return CXDefenseReasonTypeAppIcy;
                }
                if ([path isEqualToString:@"/Applications/IntelliScreen.app"]) {
                    return CXDefenseReasonTypeAppIntelliScreen;
                }
                if ([path isEqualToString:@"/Applications/MxTube.app"]) {
                    return CXDefenseReasonTypeAppMxTube;
                }
                if ([path isEqualToString:@"/Applications/RockApp.app"]) {
                    return CXDefenseReasonTypeAppRock;
                }
                if ([path isEqualToString:@"/Applications/SBSettings.app"]) {
                    return CXDefenseReasonTypeAppSBSettings;
                }
                if ([path isEqualToString:@"/Applications/WinterBoard.app"]) {
                    return CXDefenseReasonTypeAppWinterBoard;
                }
            }
        }
        return CXDefenseReasonTypeNone;
    }
    
    • 检测是否存在越狱相关的文件夹
    + (CXDefenseReasonType)isContainSuspiciousSystemPath {
        NSArray *systemPaths = @[@"/Library/MobileSubstrate/DynamicLibraries/LiveClock.plist",
                                 @"/Library/MobileSubstrate/DynamicLibraries/Veency.plist",
                                 @"/private/var/lib/apt",
                                 @"/private/var/lib/apt/",
                                 @"/private/var/lib/cydia",
                                 @"/private/var/mobile/Library/SBSettings/Themes",
                                 @"/private/var/stash",
                                 @"/private/var/tmp/cydia.log",
                                 @"/System/Library/LaunchDaemons/com.ikey.bbot.plist",
                                 @"/System/Library/LaunchDaemons/com.saurik.Cydia.Startup.plist",
                                 @"/usr/bin/sshd",
                                 @"/usr/libexec/sftp-server",
                                 @"/usr/sbin/sshd",
                                 @"/etc/apt",
                                 @"/bin/bash",
                                 @"/Library/MobileSubstrate/MobileSubstrate.dylib"];
        for (NSString *path in systemPaths) {
            if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
                if ([path isEqualToString:@"/Library/MobileSubstrate/DynamicLibraries/LiveClock.plist"]) {
                    return CXDefenseReasonTypePathLiveClock;
                }
                if ([path isEqualToString:@"/Library/MobileSubstrate/DynamicLibraries/Veency.plist"]) {
                    return CXDefenseReasonTypePathVeency;
                }
                if ([path isEqualToString:@"/private/var/lib/apt"] || [path isEqualToString:@"/private/var/lib/apt/"]) {
                    return CXDefenseReasonTypePathLibApt;
                }
                if ([path isEqualToString:@"/private/var/lib/cydia"]) {
                    return CXDefenseReasonTypePathLibCydia;
                }
                if ([path isEqualToString:@"/private/var/mobile/Library/SBSettings/Themes"]) {
                    return CXDefenseReasonTypePathThemes;
                }
                if ([path isEqualToString:@"/private/var/stash"]) {
                    return CXDefenseReasonTypePathStash;
                }
                if ([path isEqualToString:@"/private/var/tmp/cydia.log"]) {
                    return CXDefenseReasonTypePathCydiaLog;
                }
                if ([path isEqualToString:@"/System/Library/LaunchDaemons/com.ikey.bbot.plist"]) {
                    return CXDefenseReasonTypePathBbot;
                }
                if ([path isEqualToString:@"/System/Library/LaunchDaemons/com.saurik.Cydia.Startup.plist"]) {
                    return CXDefenseReasonTypePathStartup;
                }
                if ([path isEqualToString:@"/usr/bin/sshd"]) {
                    return CXDefenseReasonTypePathBinSshd;
                }
                if ([path isEqualToString:@"/usr/libexec/sftp-server"]) {
                    return CXDefenseReasonTypePathSftp;
                }
                if ([path isEqualToString:@"/usr/sbin/sshd"]) {
                    return CXDefenseReasonTypePathSbinSshd;
                }
                if ([path isEqualToString:@"/etc/apt"]) {
                    return CXDefenseReasonTypePathEtcApt;
                }
                if ([path isEqualToString:@"/bin/bash"]) {
                    return CXDefenseReasonTypePathBash;
                }
                if ([path isEqualToString:@"/Library/MobileSubstrate/MobileSubstrate.dylib"]) {
                    return CXDefenseReasonTypePathMobileSubstrate;
                }
            }
        }
        return CXDefenseReasonTypeNone;
    }
    

    2. 代码混淆

    静态分析这一步一般包括

    1. 通过reveal分析页面,拿到VC相关的信息
    2. 通过class-dump导头文件,通过感兴趣的VC的方法名来做一些推测
    3. 或者通过Hopper/IDA查看相关的方法的信息
    4. 或者注入代码查看调用逻辑

    所以,需要添加一些代码混淆来加大攻击者的分析的难度
    具体的混淆工具网上有很多,可以搜一搜关键词confuse

    3. 反调试

    如果别人拿到了你app运行的流程逻辑,就需要动态调试来查看具体的参数之类的,这就需要做反调试阻止别人调试你的app
    反调试有很多种方法,这里只简单介绍两种

    1. ptrace反调试
    #pragma mark ********* PTrace *********
    /*
     lldb调试的原理:debugserver
     在越狱环境下,通过lldb连接手机的debugserver,然后通过debugserver来调试某个app
     而debugserver是通过ptrace函数来调试app
     */
    #ifndef PT_DENY_ATTACH
    //阻止调试器附加
    #define PT_DENY_ATTACH  31
    #endif
    int ptrace(int _request, pid_t _pid, caddr_t _addr, int _data);
    static __attribute__((always_inline)) void antiForCantAttach() {
        ptrace(PT_DENY_ATTACH, 0, 0, 0);
    }
    
    1. sysctl反调试
    #pragma mark ********* sysctl *********
    /*
     当目标进程被调试时,会有一个标志位表明自己正在被调试
     通过sysctl获取标志位来得到当前的调试状态
     */
    static __attribute__((always_inline)) BOOL antiForAttachToExit() {
        int name[] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()};
        u_int nameLen = sizeof(name) / sizeof(*name);
        struct kinfo_proc oldp;
        size_t oldLenp = sizeof(oldp);
        int result = sysctl(name, nameLen, &oldp, &oldLenp, NULL, 0);
        if (result == -1) {
            //错误处理
            return NO;
        }
        if ((oldp.kp_proc.p_flag & P_TRACED) != 0) {
            //检测到调试器,退出
            return YES;
        }
        return NO;
    }
    

    这两种方案都能做到反调试,第二种检测到反调试能做自己的一些业务逻辑,具体怎么用就看个人了

    4. 反注入

    如果别人拿到了你app的逻辑要做破坏就需要hook你的方法,这就需要注入自己的代码,而现在一般的注入代码是通过 TheosLogos 语法来编写插件,而这种方法注入代码依赖 MobileSubstrate.dylib 这个动态库,所以我们检测到这个动态库的话就认为有代码注入

    @implementation CXInjectDetection
    
    + (void)load {
        [self performSelectorOnMainThread:@selector(shared) withObject:nil waitUntilDone:NO];
    #ifndef DEBUG
        //注册回调,只要dylib被加载就会进入此回调
        _dyld_register_func_for_add_image(&cx_image_added);
    #endif
    }
    
    - (instancetype)init
    {
        self = [super init];
        if (self) {
            self.isInject = NO;
        }
        return self;
    }
    
    + (instancetype)shared {
        static dispatch_once_t onceToken;
        static CXInjectDetection *inject;
        dispatch_once(&onceToken, ^{
            inject = [[CXInjectDetection alloc] init];
        });
        return inject;
    }
    
    #pragma clang diagnostic push
    //"-Wunused-variable"这里就是警告的类型
    #pragma clang diagnostic ignored "-Wunused-function"
    
    static void cx_image_added(const struct mach_header *mh, intptr_t slide) {
        Dl_info image_info;
        int result = dladdr(mh, &image_info);
        if (result == 0) {
            return;
        }
        const char *image_name = image_info.dli_fname;
        //检测到第三方库,直接杀掉
        if (strstr(image_name, "DynamicLibraries") || strstr(image_name, "CydiaSubstrate")) {
            [CXInjectDetection shared].isInject = YES;
        }
    }
    
    #pragma clang diagnostic pop
    
    @end
    

    5. 反二次签名

    攻击者注入代码成功之后就已经破坏了app的签名,想要安装到手机上只能做重签名,而做重签名需要mobileProvision文件,正常的从appstore下载的app包里是不存在mobileProvision文件的,且重签名的mobileProvision文件里的bundleId是不同于我们原有的bundleId的,所以简略点可以判定是否存在mobileprovision文件,更精细点可以判断bundleId是否一致

    bool  checkCodesign(NSString *id){
      // 描述文件路径
      NSString *embeddedPath = [[NSBundle mainBundle] pathForResource:@"embedded" ofType:@"mobileprovision"];
      // 读取application-identifier  注意描述文件的编码要使用:NSASCIIStringEncoding
      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[i] rangeOfString:@"application-identifier"].location != NSNotFound) {
    
              NSInteger fromPosition = [embeddedProvisioningLines[i+1] rangeOfString:@""].location+8;
    
              NSInteger toPosition = [embeddedProvisioningLines[i+1] rangeOfString:@""].location;
    
              NSRange range;
              range.location = fromPosition;
              range.length = toPosition - fromPosition;
    
              NSString *fullIdentifier = [embeddedProvisioningLines[i+1] substringWithRange:range];
              NSArray *identifierComponents = [fullIdentifier componentsSeparatedByString:@"."];
              NSString *appIdentifier = [identifierComponents firstObject];
    
              // 对比签名ID
              if (![appIdentifier isEqual:id]) {
                  //exit
                  return YES;
              }
              break;
          }
      }
       return NO;
    }
    

    至此,逆向防护就完成了,但这些操作只能给攻击者增加难度,因为没有完美的防护
    代码地址:iOSAppDefense
    具体使用也很简单, 在didFinishLaunchingWithOptions中调用[CXAppDefense appDefense]即可,此处可根据自己的业务逻辑来决定是否展示具体的越狱信息VC

    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
                if ([self showJailBreakWindow]) {
                      return YES;
                }
    }
    - (BOOL)showJailBreakWindow {
        CXJailDetecteResult *result = [CXAppDefense appDefense];
        if (result.isJailBreak) {
            CXJailBreakVC *vc = [CXJailBreakVC new];
            vc.result = result;
            self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
            self.window.rootViewController = vc;
            [self.window makeKeyAndVisible];
            return YES;
        }
        return NO;
    }
    

    相关文章

      网友评论

          本文标题:iOS 逆向防护

          本文链接:https://www.haomeiwen.com/subject/emkhidtx.html