iOS检测手机是否越狱了,GDB防护,自建https证书

Posted 想名真难

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS检测手机是否越狱了,GDB防护,自建https证书相关的知识,希望对你有一定的参考价值。

git地址 : GitHub - guochaoshun/breakPrison: 检测这个手机是否越狱了,禁止GDB依附,自建的https证书验证

如何检测越狱手机。
在应用开发过程中,我们希望知道设备是否越狱,正以什么权限运行程序,好对应采取一些防御和安全提示措施。
一般我们通过一些常规的防御性代码,去做这种检测,当然,这样的检测有一定的误报概率,但是对于APP的开发者来讲,需要确定一个原则,哪怕是越狱手机检测成未越狱,也不能将未越狱的手机检测成越狱手机。

//
//  BreakPrison.m
//  越狱
//
//  Created by apple on 2019/4/23.
//  Copyright © 2019 apple. All rights reserved.
//

#import "BreakPrison.h"
#import <UIKit/UIKit.h>
#import <sys/stat.h>
#import <dlfcn.h>
#import <mach-o/dyld.h>


@implementation BreakPrison

// 参考代码 : https://www.jianshu.com/p/a43be50dc958
// https://github.com/guochaoshun/breakPrison
// load是系统调用的,不会被hook,一定要用真机测试
+ (void)load 
    
    if (isAlreadyBreakPrison()) 
        NSLog(@"该设备已经越狱了");
//        exit(0);
    
    NSLog(@"设备正常");



//为什么用c方法呢?
//因为c语言是静态的,在编译时已经确定了函数的调用地址,不会被hook,而系统的c方法,因为动态链接库的原因,可能被hook
/// 这个设备是否已经越狱了,
bool isAlreadyBreakPrison()
    
    // 1.越狱后会在系统的根目录下添加一些文件,根据这些文件是否存在,判定是否越狱

    // Cydia是越狱过程中装的一个破解软件,类似于App Store
    NSString *cydiaPath = @"/Applications/Cydia.app";
    NSString *aptPath = @"/private/var/lib/apt/";
    // 未越狱的手机没有权限查看系统的按照目录,可以通过尝试读取应用列表,看有无权限:
    NSString *applications = @"/User/Applications/";
    // dylib动态库注入也是常有的攻击手段
    NSString *Mobile = @"/Library/MobileSubstrate/MobileSubstrate.dylib";
    // 这个是脚本(Unix shell),正常ios是没有的
    NSString *bash = @"/bin/bash";
    // linux下控制脚本的
    NSString *sshd =@"/usr/sbin/sshd";
    NSString *sd = @"/etc/apt";
    
    NSArray * fileArray = @[cydiaPath,aptPath,applications,
                            Mobile,bash,sshd,sd];
    for (NSString * filePath in fileArray) 
        
        if([[NSFileManager defaultManager] fileExistsAtPath:filePath]) 
            return YES;
        
        
    
    
    
    //2.使用stat系列函数检测Cydia等工具
    // stat函数: 通过文件名filename获取文件信息,并保存在buf所指的结构体stat中
    // 执行成功则返回0,失败返回-1
    struct stat stat_info;
    if (0 == stat("/Applications/Cydia.app", &stat_info)) 
        return YES;
    
    if([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"cydia://"]])
        return YES;
    
    
    
    //3.检查下stat是否被替换
    // stat函数可以被fishhook替换掉,
    // 看看stat是不是出自系统库,有没有被攻击者换掉
    // 如果结果不是 /usr/lib/system/libsystem_kernel.dylib 的话,那就100%被攻击了。
    int ret;
    Dl_info dylib_info;
    int (*func_stat)(const char *, struct stat *) = stat;
    // dladdr : 获取某个地址的符号信息
    if ((ret = dladdr(func_stat, &dylib_info))) 
        NSString *str = [NSString stringWithFormat:@"%s",dylib_info.dli_fname];
        if (![str isEqualToString:@"/usr/lib/system/libsystem_kernel.dylib"]) 
            return YES;
        
        
    
    
    
    
    
    
    //4.MobileSubstrate.dylib是一个动态库框架,允许第三方的开发者在系统的方法里打一些运行时补丁
    // https://blog.csdn.net/king_jensen/article/details/30746765
    // 越狱机的输出结果会包含字符串: Library/MobileSubstrate/MobileSubstrate.dylib 。
    
    // 获取到所有的动态模块
    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);
        if ([name containsString:@"Library/MobileSubstrate/MobileSubstrate.dylib"]) 
            return YES;
        
        
    
    
    
    
    //5.DYLD_INSERT_LIBRARIES环境变量,可以不修改App的任何字节,实现注入dyld的过程。
    // getenv:从环境中取字符串,获取环境变量的值.
    // 未越狱设备返回结果是null,越狱设备就各有各的精彩了,尤其是老一点的iOS版本越狱环境。
    char *env = getenv("DYLD_INSERT_LIBRARIES");
    if(env)
        return YES;
    
    
    
    
    return NO;







@end

  
C语言本身是静态的 , 但是由于有动态库的加载机制,所以系统提供C方法也是可以被hook的 , 所以也印证了那句话:没有绝对的安全,你唯一能做的就是拖延攻击者的脚步。

load方法是不能被hook的,而且load方法是我们写的代码中最先的被执行的.


禁止GDB调试

什么是GDB?

所有发布的iOS设备都是基于ARM架构的。我们开发iOS应用的时候编写的Objective-C代码会首先转换成ARM汇编,然后转换成机器指令。对ARM汇编语言和使用GDB调试有很好掌握的话,攻击者是能够在运行时解密Objective-C代码甚至修改代码的。

在安全评测的时候这个GDB挂起是必不可少的一个安全评测协议。

代码编写流程:

1:main.m中 引入头文件如下:

#import <dlfcn.h>

#import<sys/types.h>

2:添加对应函数如下:

typedef int(*ptrace_ptr_t)(int _request, pid_t _pid, caddr_t _addr, int _data);

#if!defined(PT_DENY_ATTACH)

#define PT_DENY_ATTACH 31

#endif  // !defined(PT_DENY_ATTACH)

 

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);


3:在工程的mian.m文件的main函数里头就要这样写了:

int main(int argc, char *argv[]) 

    /**防止GDB挂起*/

#ifndef DUBUG

    disable_gdb();

#endif

    @autoreleasepool 

        return UIApplicationMain(argc, argv, nil,NSStringFromClass([AppDelegate class]));

    


最后,附上完整的main.m文件的内容


#import <UIKit/UIKit.h>
#import "AppDelegate.h"

// 禁止GDB调试 :https://blog.csdn.net/yiyaaixuexi/article/details/18222339
#import <dlfcn.h>
#import <sys/types.h>

typedef int (*ptrace_ptr_t)(int _request, pid_t _pid, caddr_t _addr, int _data);
#if !defined(PT_DENY_ATTACH)
#define PT_DENY_ATTACH 31
#endif  // !defined(PT_DENY_ATTACH)

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[]) 
    
    
#ifndef DEBUG
    disable_gdb();
#endif
    
    @autoreleasepool 
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    

使用HTTPS,网络安全 ,

HTTPS证书分为两种,一种是花钱向认证的机构购买的证书,服务端如果使用的是这类证书的话,那一般客户端不需要做什么,只需要把请求的链接地址改成HTTPS就行了,苹果内置了那些受信任的根证书的。

另一种是服务器制作的证书,使用这类证书的话是不受信任的(当然也不用花钱买),因此需要我们在代码中将该证书设置为信任证书。需要后台的人员给一个cer文件,这个cer文件中有服务器的公钥. 

如果他们给的是crt证书 , 那就进到证书路径,执行下面语句

openssl x509 -in 你的证书.crt -out 你的证书.cer -outform der

这样你就可以得到cer类型的证书了。

1. 双击证书,这时证书已经添加到了钥匙串中

2. 将cer 文件拖入工程中

3.1 如果使用的是AFNetwotking 的话,在代码中添加以下代码, 主要参考文章 : iOS开发AFN配置HTTPS的SSL证书 - 简书

typedef NS_ENUM(NSUInteger, AFSSLPinningMode) 
    AFSSLPinningModeNone,
    AFSSLPinningModePublicKey,
    AFSSLPinningModeCertificate,
;一个枚举类型,定义了HTTPS的三种验证模式: 
     AFSSLPinningModeNone这个模式本地没有保存证书,只验证服务器证书是不是系统受信任证书列表里的证书签发的。若证书是信任机构签发的就会通过,若是自己服务器生成的证书就不会通过。
     AFSSLPinningModePublicKey这个模式表示本地存有证书,验证证书的有效期等信息,也就是将本地证书设置为锚点证书,然后验证证书有效性,再判断本地证书和服务器证书是否一致。
     AFSSLPinningModeCertificate这个模式同样是本地存有证书,只是验证时只验证证书里的公钥,不验证证书的有效期等信息。只要公钥是正确的,就能保证通信不会被窃听,因为中间人没有私钥,无法解开通过公钥加密的数据。
+ (AFSecurityPolicy*)customSecurityPolicy

// /先导入证书
NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"hgcang" ofType:@"cer"];//证书的路径
NSData *certData = [NSData dataWithContentsOfFile:cerPath];

/** 
AFSecurityPolicy分三种验证模式: 
AFSSLPinningModeNone:只是验证证书是否在信任列表中 
AFSSLPinningModeCertificate:该模式会验证证书是否在信任列表中,然后再对比服务端证书和客户端证书是否一致 
AFSSLPinningModePublicKey:只验证服务端证书与客户端证书的公钥是否一致
*/
// AFSSLPinningModeCertificate 使用证书验证模式
AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];

// allowInvalidCertificates 是否允许无效证书(也就是自建的证书),默认为NO
// 如果是需要验证自建证书,需要设置为YES
securityPolicy.allowInvalidCertificates = YES;

//validatesDomainName 是否需要验证域名,默认为YES;
//假如证书的域名与你请求的域名不一致,需把该项设置为NO;如设成NO的话,即服务器使用其他可信任机构颁发的证书,也可以建立连接,这个非常危险,建议打开。
//置为NO,主要用于这种情况:客户端请求的是子域名,而证书上的是另外一个域名。
//因为SSL证书上的域名是独立的,假如证书上注册的域名是www.google.com,那么mail.google.com是无法验证通过的;当然,有钱可以注册通配符的域名*.google.com,但这个还是比较贵的。
//如置为NO,建议自己添加对应域名的校验逻辑。
securityPolicy.validatesDomainName = NO;

securityPolicy.pinnedCertificates = @[certData];

return securityPolicy;

在生成AFHTTPSessionManager的时候设置下

AFHTTPSessionManager * manager = [AFHTTPSessionManager manager];
manager.securityPolicy = [self customSecurityPolicy];

3.2 SDWebImage,这样设置, 建议还是直接改类别中的方法,如果图片的路径还是http的话就不用管了,

[imageView sd_setImageWithURL:[NSURL URLWithString:name] placeholderImage:[UIImage imageNamed:@"*.png"]
         options:SDWebImageAllowInvalidSSLCertificates];

3.3如果是用的原生请求的话 , 主要参考文章 : iOS使用自签名证书实现HTTPS请求 - 简书

生成URLSession的时候没什么变化

NSString *urlString = @"https://xxxxxxx";
NSURL *url = [NSURL URLWithString:urlString];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData
     timeoutInterval:10.0f];
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] 
        delegate:self delegateQueue:[NSOperationQueue mainQueue]];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request];
[task resume];

在代理方法中: 

简单版, 简单验证然后信任

// HTTPS证书验证,仿写了AFN的验证
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler 
    
    NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
    NSURLCredential *credential = nil;
    /**
     NSURLAuthenticationMethodHTTPBasic //HTTP基本验证,服务器向客户端询问用户名,密码
     NSURLAuthenticationMethodClientCertificate//客户端证书验证,服务器向客户端询客户端身份证书
     NSURLAuthenticationMethodServerTrust//服务器端证书验证,客户端对服务器端的证书进行验证。HTTPS中的服务器端证书验证属于这一种
     */
    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) 
        // 认证结果,信任这个challenge.protectionSpace.serverTrust
        credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
        if (credential) 
            disposition = NSURLSessionAuthChallengeUseCredential; // 使用证书
         else 
            disposition = NSURLSessionAuthChallengePerformDefaultHandling; // 使用证书但是会进行CA认证
        
    
    // 如果disposition是NSURLSessionAuthChallengeUseCredential,credential必须有值
    // NSURLSessionAuthChallengePerformDefaultHandling,credential可以为nil
    if (completionHandler) 
        completionHandler(disposition, credential);
    

如果需要更严格点的认证, 就看下面, 会读取本地证书并验证, 和AF内部的验证方法对照了一下,大体是相同的, 只是写在了一个方法里面


- (void)URLSession:(NSURLSession *)session
     didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition 
    disposition, NSURLCredential * _Nullable credential))completionHandler 
    NSLog(@"证书认证");
    if ([[[challenge protectionSpace] authenticationMethod] isEqualToString: NSURLAuthenticationMethodServerTrust]) 
        do
        
            SecTrustRef serverTrust = [[challenge protectionSpace] serverTrust];
            NSCAssert(serverTrust != nil, @"serverTrust is nil");
            if(nil == serverTrust)
                break; /* failed */
            /**
             *  导入多张CA证书(Certification Authority,支持SSL证书以及自签名的CA),请替换掉你的证书名称
             */
            NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"ca" ofType:@"cer"];//自签名证书
            NSData* caCert = [NSData dataWithContentsOfFile:cerPath];
            
            NSCAssert(caCert != nil, @"caCert is nil");
            if(nil == caCert)
                break; /* failed */
            
            SecCertificateRef caRef = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)caCert);
            NSCAssert(caRef != nil, @"caRef is nil");
            if(nil == caRef)
                break; /* failed */
            
            //可以添加多张证书
            NSArray *caArray = @[(__bridge id)(caRef)];
            
            NSCAssert(caArray != nil, @"caArray is nil");
            if(nil == caArray)
                break; /* failed */
            
            //将读取的证书设置为服务端帧数的根证书
            OSStatus status = SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)caArray);
            NSCAssert(errSecSuccess == status, @"SecTrustSetAnchorCertificates failed");
            if(!(errSecSuccess == status))
                break; /* failed */
            
            SecTrustResultType result = -1;
            //通过本地导入的证书来验证服务器的证书是否可信
            status = SecTrustEvaluate(serverTrust, &result);
            if(!(errSecSuccess == status))
                break; /* failed */
            NSLog(@"stutas:%d",(int)status);
            NSLog(@"Result: %d", result);
            
            BOOL allowConnect = (result == kSecTrustResultUnspecified) || (result == kSecTrustResultProceed);
            if (allowConnect) 
                NSLog(@"success");
            else 
                NSLog(@"error");
            
            
            /* kSecTrustResultUnspecified and kSecTrustResultProceed are success */
            if(! allowConnect)
            
                break; /* failed */
            
            
#if 0
            /* Treat kSecTrustResultConfirm and kSecTrustResultRecoverableTrustFailure as success */
            /*   since the user will likely tap-through to see the dancing bunnies */
            if(result == kSecTrustResultDeny || result == kSecTrustResultFatalTrustFailure || result == kSecTrustResultOtherError)
                break; /* failed to trust cert (good in this case) */
#endif
            
            // The only good exit point
            NSLog(@"信任该证书");
            
            NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
            completionHandler(NSURLSessionAuthChallengeUseCredential,credential);
           [[challenge sender] useCredential: credential
                          forAuthenticationChallenge: challenge];
            
        
        while(0);
    
    
    // Bad dog
    NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
    completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge,credential);
   [[challenge sender] cancelAuthenticationChallenge: challenge];


iOS应用安全、安全攻防

以上是关于iOS检测手机是否越狱了,GDB防护,自建https证书的主要内容,如果未能解决你的问题,请参考以下文章

iOS安全攻防之越狱设备检测

iOS应用代码注入防护

iOS安全攻防之使用 Frida 绕过越狱设备检测

iOS 绕过SSL认证抓包(需越狱)

iOS开发技术之应用代码注入防护

Nativescript - 在 iOS 上检测越狱和动态检测