前言
-
加固保护方案
- 数据加密:
- 网络传输的加密使用二进制协议传输数据
- 本地存储的加密
- 静态字符串的加密
-
反调试\ 注入检测、hook检测、越狱检测、签名检测
-
静态混淆:类名方法名、属性的混淆
-
代码混淆
-
核心代码使用swift、c\c++ 实现
-
将代码分块、扁平化、增加干扰代码
-
- 数据加密:
I 、数据加密:静态字符串、本地存储及网络传输加密
对用户的敏感信息进行加密保存
- 不管是本地的数据还是网络中传输的数据,对敏感数据需要采用加密算法进行加密处理,而常用的加密算法有 AES,RSA、BASE64、MD5、RSA。
根据加密结果是否可以解密,算法分为可逆加密和不可逆加密
不可逆加密算法,一般用于数据校验
- MD5: 将数据运算变为另一固定长度值,用于数据校验,不可逆
可逆加密,又可以根据密钥的对称性可以分为对称加密和非对称加密
- other
- 非对称加密算法:存在一个公钥和一个私钥
- RSA: 速度比对称加密算法慢很多
- 对称加密算法:加密和解密使用同一密钥
- DES:对二进制数据进行加密;明文、密文、密钥的分组长度都是64位
- AES:高级加密标准,本质上是一种对称分组密码体制,用来代替DES
1.1 本地存储加密
剪切板的缓存
- 我们在使用剪切板进行操作时,iOS系统会缓存所操作的数据,如果数据涉及隐私,将会带来安全问题。如下图,iOS的三种剪切板和功能,其中系统级别的剪切板优先级最高。
系统键盘缓存
- 我们打字在使用系统键盘打字时时,会出现提示,键盘一般会缓存这些提示字符串,缓存在手机的“/private/var/mobile/Library/Keyboard/xx-dynamic-text.dat”文件中,攻击者可以提取出来,出现频率最高的往往是账户名和密码。
1.2 网络传输加密
对于网络传输的数据可以使用 AES+RSA 的方式对数据进行加密,如果使用 Https 的方式需要对证书进行校验
1.2.0 在传输过程使用RSA+AES 进行加密传输
客户端随机生成AES的key(可以采用md5进行生成key)
使用RSA 公钥加密算法来 加密步骤一的aeskey
使用步骤一的aeskey 对数据进行aes加密
将RSA加密之后的key 和AES加密之后的数据一起发送给服务端,
- 同时报文中可附加时间戳字段,来防止重复请求攻击
1.2.1 https 证书验证
验证方法
AFNetworking的验证策略
在NSURLConnection 的
(void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;
中处理在NSURLSession 的`- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler;` 获取证书信息进行验证
本文重点介绍AFNetworking的验证策略。
AFNetworking 请求HTTPS时 SSL的身份验证设置
0) 获取到站点的证书:
我们可以使用以下openssl命令来获取到服务器的公开二进制证书(以google为例):
"openssl s_client -connect www.google.com:443 </dev/null 2>/dev/null | openssl x509 -outform DER > https.cer"
1)web端服务器必须要提供一个叫SSL的证书,证书里面包含有一个叫.crt的文件,你需要将它转换成.cer的格式,转换方法如下: openssl x509 -in your certificate.crt -out your certificate.cer -outform der
转换成功后需要找到你转换成.cer的文件.添加到你的xcode工程当中去;然后读取二进制数据对其赋值
项目中的文件,攻击者很容易通过解包取出,所以我们一般,在代码中存放证书的二进制流数据
2)AFN2.x 需要添加AFSecuriPolicy和setAFHTTPRequestOperationManager
设置安全策略
setSecurityPolicy
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManagermanager]; [manager setSecurityPolicy:[selfSecurityPolicy]]; manager.responseSerializer = [AFHTTPResponseSerializerserializer]; [manager GET:@"your Url" parameters:nilsuccess:^(AFHTTPRequestOperation *operation,id responseObject) { NSLog(@"成功了"); } failure:^(AFHTTPRequestOperation *operation,NSError *error) { NSLog(@"Error: %@", error); }]; }
#pragma mark - ******** ssl证书检测设置
添加安全策略+ (AFSecurityPolicy*)setupSecurityPolicy { NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"cmpay"ofType:@"cer"]; NSData *certData = [NSData dataWithContentsOfFile:cerPath]; // AFSecurityPolicy *securityPolicy = [[AFSecurityPolicy alloc] init]; AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModePublicKey]; [securityPolicy setPinnedCertificates:@[certData]]; securityPolicy.allowInvalidCertificates =NO;//定义了客户端是否信任非法证书 securityPolicy.validatesDomainName =YES;//是指是否校验在证书中的domain这一个字段 securityPolicy.validatesCertificateChain =NO;//指的是是否校验其证书链。 return securityPolicy; } //ps:正确的设置 securityPolicy.allowInvalidCertificates =NO; securityPolicy.validatesDomainName =YES; securityPolicy.validatesCertificateChain =NO; 1)allowInvalidCertificates 是否允许无效证书(也就是自建的证书),默认为NO;如果是需要验证自建证书,需要设置为YES 2)validatesDomainName 是否需要验证域名,默认为YES;假如证书的域名与你请求的域名不一致,需把该项设置为NO; 如设成NO的话,即服务器使用其他可信任机构颁发的证书,也可以建立连接,这个非常危险,建议打开。 设置为NO,主要用于这种情况:客户端请求的是子域名,而证书上的是另外一个域名。因为SSL证书上的域名是独立 3)validatesCertificateChain 是否验证整个证书链,默认为YES, 设置为YES,
1.2.2 ssl证书安全策略的的属性分析
validatesCertificateChain: 指的是是否校验其证书链。
通常来讲,一个CA证书颁发机构有很多个子机构,用来签发不同用途的子证书,然后这些子证书又再用来签发相应的证书。只有证书链上的证书都正确,CertificateChain才算验证完成。
以Google为例:
从上图可以看到,Google.com的证书的根CA证书是GeoTrust Global CA; 而CA并没有直接给google.com签证书,而是先签名了Google Internet Authority G2, 然后G2再签名了google.com。这时候就需要设备中保存有Google Internet Authority G2证书才能通过校验。
一般来讲,我推荐将validatesCertificateChain设置为NO,因为并不是太有必要做CertificateChain的校验。并且,在AFNetworking 2.6.0中,也正式将validatesCertificateChain拿掉了(https://github.com/AFNetworking/AFNetworking/blob/master/CHANGELOG.md), 其原因也同样为:There was no documented security advantage to pinning against an entire certificate chain。
因此,在2.6.0之后,可以不管这个字段。而在此之前,从效率上来说,设定为NO会是个比较明智的选择。
validatesDomainName: 是指是否校验在证书中的domain这一个字段。每个证书都会包含一个DomainName, 它可以是一个IP地址,一个域名或者一端带有通配符的域名。
- 如*.google.com, www.google.com 都可以成为这个证书的DomainName。设置validatesDomainName=YES将严格地保证其安全性
pinnedCertificates: 就是用来校验服务器返回证书的证书。通常都保存在mainBundle 下。
- 通常默认情况下,AFNetworking会自动寻找在mainBundle的根目录下所有的.cer文件并保存在pinnedCertificates数组里,以校验服务器返回的证书。
allowInvalidCertificates: allowInvalidCertificates 定义了客户端是否信任非法证书。一般来说,每个版本的iOS设备中,都会包含一些既有的CA根证书。如果接收到的证书是iOS信任的CA根证书签名的,那么则为合法证书;否则则为“非法”证书。allowInvalidCertificates 就是用来确认是否信任这样的证书的。当然,我们也可以给iOS加入新的信任的CA证书。iOS已有的CA根证书,可以在这里了解到:https://support.apple.com/en-us/HT204132
AFSSLPinningMode :如何去校验服务器端给予的证书
typedef NS_ENUM(NSUInteger, AFSSLPinningMode) { AFSSLPinningModeNone, AFSSLPinningModePublicKey, AFSSLPinningModeCertificate, };
AFSSLPinningModeNone: 代表客户端无条件地信任服务器端返回的证书。
AFSSLPinningModePublicKey: 代表客户端会将服务器端返回的证书与本地保存的证书中,PublicKey的部分进行校验;如果正确,才继续进行。
AFSSLPinningModeCertificate: 代表客户端会将服务器端返回的证书和本地保存的证书中的所有内容,包括PublicKey和证书部分,全部进行校验;如果正确,才继续进行。
see also :
https://github.com/AFNetworking/AFNetworking/blob/master/CHANGELOG.md
http://nelson.logdown.com/posts/2015/04/29/how-to-properly-setup-afnetworking-security-connection/
SSLPinningMode 的更详细解释
AFSSLPinningModeCertificate
- 客户端需要一份证书文件的拷贝
- 第一步验证、先验证证书的域名/有效期等信息
- 第二步验证、对比服务端返回的证书跟客户端存储的证书是否一致
AFSSLPinningModePublicKey
- 客户端需要一份证书文件的拷贝
- 验证时只验证证书里的公钥,不验证证书的有效期等信息
- 即使伪造证书的公钥,也不能解密传输的数据,必须要私钥
AFSSLPinningModeNone
- 这个模式不做本地证书验证(不做 SSL Pinning 操作)
- 直接从客户端系统中的受信任颁发机构 CA 列表中去验证
1.2.3 常见错误& 解决方案
- 问题现象
- 使用AFSSLPinningModeCertificate 方式验证的时候,获取 DER 表示的数据时
- 发生如下错误: Create a certificate Return NULL 。
- 根据“@result Return NULL if the passed-in data is not a valid DER-encoded ”,从而推测cer证书 is not a valid DER-encoded。
问题分析--**to uncaught exception 'NSInvalidArgumentException', reason: '\*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil'**
问题定位–获取DER信息函数
/*! @function SecCertificateCreateWithData @abstract Create a certificate given it's DER representation as a CFData. @param allocator CFAllocator to allocate the certificate with. @param certificate DER encoded X.509 certificate. @result Return NULL if the passed-in data is not a valid DER-encoded X.509 certificate, return a SecCertificateRef otherwise. */ __nullable SecCertificateRef SecCertificateCreateWithData(CFAllocatorRef __nullable allocator, CFDataRef data) __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_2_0);
报错信息
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil'
报错代码
[pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)];
- 关于 DER 的概念:
- 1) 可参见 Wikipedia -》 https://en.wikipedia.org/wiki/X.690#DER_encoding
- 2)或者 如何查看证书的16进制DER编码,及证书的各个域DER格式-》 http://blog.csdn.net/taolinke/article/details/6248968
解决方案
错误信息是编码不对,证书有BASE64和DER两种编码,邮件中提供的begin-end的证书是常用的BASE64编码,der二进制编码可以使用openssl进行转换。
转变格式为DER
openssl x509 -outform der -in kncmpay.cer -out kncmpay.cer
1.2.4 绕过证书校验
- pinning/disable.js: This hook attempts many ways to kill SSL pinning and certificate
1.2.5 BufferXorEncrypt
在传输buffer的时,对每个传输的字符串进行自定义的加密传输。
//自定义格式 AMBuffer* buffer = [AMBuffer defaultForAPI]; [buffer setNSString:@"username"]; [buffer setNSString:username]; [buffer setNSString:@"password"]; [buffer setNSString:password]; [buffer setLengthWithFirst]; NSData* data = [NSData dataWithBytes:[buffer getBody] length:[buffer getLength]]; request.HTTPBody = data;
1.2.6 protobuf/objectivec/
1.4 code
See Also
iOS安全攻与防,详细的列出了,在iOS开发中,项目会存在的安全漏洞以及解决办法。
- 我们使用AFNetworking网络框架来配置证书验证,而我们需要配置这个框架下的类AFSecurityPolicy相关属性即可。在此我们使用AFSSLPinningModeCertificate对证书进行完整校验。
#pragma mark - ******** 判断设备是否越狱
#import <sys/stat.h> #import <dlfcn.h> - (BOOL)mgjpf_isJailbroken { //以下检测的过程是越往下,越狱越高级 // /Applications/Cydia.app, /privte/var/stash BOOL jailbroken = NO; NSString *cydiaPath = @"/Applications/Cydia.app"; NSString *aptPath = @"/private/var/lib/apt/"; if ([[NSFileManager defaultManager] fileExistsAtPath:cydiaPath]) { jailbroken = YES; } if ([[NSFileManager defaultManager] fileExistsAtPath:aptPath]) { jailbroken = YES; } //可能存在hook了NSFileManager方法,此处用底层C stat去检测 struct stat stat_info; if (0 == stat("/Library/MobileSubstrate/MobileSubstrate.dylib", &stat_info)) { jailbroken = YES; } if (0 == stat("/Applications/Cydia.app", &stat_info)) { jailbroken = YES; } if (0 == stat("/var/lib/cydia/", &stat_info)) { jailbroken = YES; } if (0 == stat("/var/cache/apt", &stat_info)) { jailbroken = YES; } // /Library/MobileSubstrate/MobileSubstrate.dylib 最重要的越狱文件,几乎所有的越狱机都会安装MobileSubstrate // /Applications/Cydia.app/ /var/lib/cydia/绝大多数越狱机都会安装 // /var/cache/apt /var/lib/apt /etc/apt // /bin/bash /bin/sh // /usr/sbin/sshd /usr/libexec/ssh-keysign /etc/ssh/sshd_config //可能存在stat也被hook了,可以看stat是不是出自系统库,有没有被攻击者换掉 //这种情况出现的可能性很小 int ret; Dl_info dylib_info; int (*func_stat)(const char *,struct stat *) = stat; if ((ret = dladdr(func_stat, &dylib_info))) { // NSLog(@"lib:%s",dylib_info.dli_fname); //如果不是系统库,肯定被攻击了 if (strcmp(dylib_info.dli_fname, "/usr/lib/system/libsystem_kernel.dylib")) { //不相等,肯定被攻击了,相等为0 jailbroken = YES; } } //还可以检测链接动态库,看下是否被链接了异常动态库,但是此方法存在appStore审核不通过的情况,这里不作罗列 //通常,越狱机的输出结果会包含字符串: Library/MobileSubstrate/MobileSubstrate.dylib——之所以用检测链接动态库的方法,是可能存在前面的方法被hook的情况。这个字符串,前面的stat已经做了 //如果攻击者给MobileSubstrate改名,但是原理都是通过DYLD_INSERT_LIBRARIES注入动态库 //那么可以,检测当前程序运行的环境变量 char *env = getenv("DYLD_INSERT_LIBRARIES"); if (env != NULL) { jailbroken = YES; } return jailbroken; }
https://github.com/githubPagesio/nabla-c0d3.github.io
/Users/devzkn/bin//knpost Security 安全保护 -t security #原来""的参数,需要自己加上""