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检测手机是否越狱了,GDB防护,自建https证书的主要内容,如果未能解决你的问题,请参考以下文章