搜索
您的当前位置:首页正文

iOS https抓包和反抓包

来源:二三娱乐

https简介

详谈HTTPS通信机制,HTTPS是如何进行安全通信的?

首先来看一种场景:小红发信息约小明放学后去电影。

正常的信息流动是这样的:

1.小红 -> 放学后去看电影吧? -> 小明

2.小明 -> 好,校门口等我。 -> 小红

但如果存在一个中间人把小红和小明的信息拦截,并做了修改,信息流变成了下面这样:

1.小红 -> 放学后去看电影吧?-> 中间人(你) -> 今天上课有点累,放学我就回家了,不要等我。 -> 小明

2.小明 -> 好吧,好好休息。 -> 中间人(你) -> 不去了,放学后我就回家。 -> 小明

小红以为小明不陪自己看电影,小明以为小红放学就回家了,而你就找了个理由约上小红去看电影了。

之所以会产生误解,是因为小红和小明没办法验证对方信息的真假,都以为收到了对方的正确信息,这是个典型的中间人攻击的例子。

HTTPS正是要解决这个问题,在HTTP传输层之上加了一个安全层(SSL或TLS协议实现),可以做到以下3点:

数据的保密性

校验双方身份的真实性

数据的完整性

数据的保密性

数据要保密,就需要对数据进行加密。加密算法可以分为2类,一类是对称加密算法,另一类是非对称加密算法。

对称加密算法,加密和解密使用相同的密钥,优点是加密速度快,缺点是如果密钥泄露的话就无法做到保密了。常见的对称加密算法有DES、AES等。

非对称加密算法,又叫公开密钥加密。需要有2个密钥,公钥和私钥,公钥向所有人公开,私钥不公开。用公钥加密的数据只有私钥才能解密;反之,用私钥加密的数据只有公钥才能解密。因为这种特性,非对称加密算法可以用来校验数字签名,下面会具体讲解。

很显然,仅使用对称加密算法是不现实的,互联网中通信的双方大多是临时建立的连接,不可能提前协商好密钥,而且密钥也要进行传输,无法保证密钥本身的安全性。

如果使用非对称加密,客户端向服务器发送数据是安全的,客户端用服务器的公钥进行加密,只有服务器用自己的私钥才能解密。但如果服务器用私钥对数据进行加密,则所有人都可以用公钥进行解密,这是不安全的。

HTTPS的解决方案是这样的:用非对称算法随机加密出一个对称密钥,然后双方用对称密钥进行通信。具体来说,就是客户端生成一个随机密钥,用服务器的公钥对这个密钥进行非对称加密,服务器用私钥进行解密,然后双方就用这个对称密钥来进行数据加密了。

校验双方身份的真实性

上面说了加密,保证了数据不能被他人读取,但通信的双方怎样校验对方的身份呢?HTTPS使用了数字证书,数字证书就是身份认证机构(Certificate Authority)盖在数字身份证上的一个章或印(或者说加在数字身份证上的一个签名),这一行为表示身份认证机构已认定这个人。证书的合法性可以向CA验证。

数字证书主要包含以下信息:

  • 证书颁发机构

  • 证书颁发机构签名

  • 证书绑定的服务器域名

  • 证书版本、有效期

  • 签名使用的加密算法(非对称算法,如RSA)

  • 公钥

客户端收到服务器的响应后,先向CA验证证书的合法性(根据证书的签名、绑定的域名等信息),如果校验不通过,浏览器会中止连接,向用户提示证书不安全。

需要提一点的是,证书的制作方法是公开的,任何人都可以自己制作证书,所以有些公司不向CA申请,比如12306。但自己制作的证书是得不到CA认证的,所以访问12306网站时,浏览器会有证书不合法的提示,如下图,只有用户选择信任该网站时,才能继续访问。

image

数据的完整性

网络传输过程中需要经过很多中间节点,虽然数据无法被解密,但可能被篡改,那如何校验数据的完整性呢?通过校验数字签名,流程见下图:

image

首先来了解下哈希算法,哈希算法能够将任意长度的字符串转化为固定长度的字符串,该过程不可逆,可用来作数据完整性校验。

服务器在发送报文之前做了3件的事:

  • 用哈希算法对报文提取定长摘要

  • 用私钥对摘要进行加密,作为数字签名

  • 将数字签名附加到报文末尾发送给客户端

客户端接收到报文后:

  • 用公钥对服务器的数字签名进行解密

  • 用同样的算法重新计算出报文的数字签名

  • 比较解密后的签名与自己计算的签名是否一致,如果不一致,说明数据被篡改过。

同样,客户端发送数据时,通过公钥加密报文摘要,服务器用私钥解密,用同样的方法校验数据的完整性。

HTTPS通信的大致过程

image

客户端将自己支持的加密算法发送给服务器,请求服务器证书;

服务器选取一组加密算法,并将证书返回给客户端;

客户端校验证书合法性,生成随机对称密钥,用公钥加密后发送给服务器;

服务器用私钥解密出对称密钥,返回一个响应,HTTPS连接建立完成;

随后双方通过这个对称密钥进行安全的数据通信。

charles手机抓包

  • mac电脑分享wifi热点,手机连接wifii,点击wifi设置代理。服务器地址是网络设置里本机ip,端口8888.
image
  • 如果要访问https的话,则要打开charles->help->SSL Proxying->Install Charles Root Certificate on a Mobile Device or Remote Browser (本地电脑和模拟器的话选择其他选项。)
image
  • 手机端打开safari,访问标红的地址,即会跳转下载并安装描述性文件,安装完成.
image

后到 【通用】->【关于本机】-> 【证书信任设置】,然后就启用完全信任刚才安装的证书


image
  • mac设置代理
    选择Proxy | Proxy Settings,弹出proxy设置选项卡,勾选Enabling transparent HTTP proxying
image

勾选Enable SSL Proxying,在Location部份选择add,按如下图添加,抓取任意站点、443端口的数据 (当然可以指定站点,端口号若不确定可以用*表任意)

image

中间人攻击的情形,浅谈HTTPS抓包原理,为什么Charles能够抓取HTTPS报文?

image

当客户端设置了代理,并且开始发出网络请求的时候,这个网络请求的校验过程就会变成这样

image
  1. 当客户端Client对服务器Server发送请求(带着随机数和加密算法),由于青花瓷做了代理,请求被青花瓷拦截,处理(青花瓷的角色现在对于Client来说是服务器),青花瓷将客户端带的随机数和加密算法处理,然后返回自己的证书通过客户端校验,获取到客户端提交的请求参数等数据,
  2. 青花瓷作为客户端(自己去产生随机数和携带支持的加密算法)去请求刚刚Client想要请求的Server,然后,Server会和青花瓷完成上面讲的那个完整的校验,并且读取青花瓷带错来的具体请求,返回正常的数据结果.
  3. 青花瓷得到服务器数据的返回结果之后,开始继续和过程1中的Client以服务器的身份,去做处理,首先收到客户端的随机数和加密算法,自己生成一个随机数和选择一个客户端的加密算法,然后*********重要********** 青花瓷会返回一个伪造的CA证书(公钥和真实的server不一样,但是域名是一样的,或者说,除了域名是一致的,其他的都不是一致的,而且这个签发机构是青花瓷之前让你安装的根证书 签发的,所以,当返回这个证书的时候,你的客户端的信任链是可以完成的,会被系统信任),然后Client在这个伪造的证书(对于青花瓷和Client是真实证书(验证信任链和证书信息都通过了),但是和真实的域名对应的证书来看,是伪造证书)的基础上,和青花瓷通信,然后青花瓷再和Server通信,成了一个中间人的角色,这样,整个过程的数据传输,都被青花瓷给监听到了
    在此,中间人攻击的过程 就完成了

反抓包

判断是否有网络代理(不推荐)

当进行网络请求的时候,客户端判断当前是否设置了代理,如果设置了代理,不允许进行访问,附带判断是否设置代理的代码:

+ (BOOL)getProxyStatus {
    NSDictionary *proxySettings = NSMakeCollectable([(NSDictionary *)CFNetworkCopySystemProxySettings() autorelease]);
    NSArray *proxies = NSMakeCollectable([(NSArray *)CFNetworkCopyProxiesForURL((CFURLRef)[NSURL  (CFDictionaryRef)proxySettings) autorelease]);
    NSDictionary *settings = [proxies objectAtIndex:0];
    
    NSLog(@"host=%@", [settings objectForKey:(NSString *)kCFProxyHostNameKey]);
    NSLog(@"port=%@", [settings objectForKey:(NSString *)kCFProxyPortNumberKey]);
    NSLog(@"type=%@", [settings objectForKey:(NSString *)kCFProxyTypeKey]);
    
    if ([[settings objectForKey:(NSString *)kCFProxyTypeKey] isEqualToString:@"kCFProxyTypeNone"])
    {
        //没有设置代理
        return NO;
    }
    else
    {
        //设置代理了
        return YES;
    }
}

结果:

没有代理:
[20383:1874008] type=kCFProxyTypeNone
(lldb) po proxies
<__NSArrayM 0x1c064e5e0>(
{
kCFProxyTypeKey = kCFProxyTypeNone;
}
)

设置了代理
[20391:1874714] type=kCFProxyTypeHTTP
(lldb) po proxies
<__NSArrayM 0x1c005f8c0>(
{
kCFProxyHostNameKey = “127.0.0.1”;
kCFProxyPortNumberKey = 1380;
kCFProxyTypeKey = kCFProxyTypeHTTP;
}
)

使用自签证书 ssl-pinning模式

客户端本地做证书校验,并且设置不仅仅校验公钥,设置完整的正式校验模式。

这么做为什么是安全的?了解HTTPS的人都知道,整个验证体系中,最核心的实际上是服务器的私钥。私钥永远,永远也不会离开服务器,或者以任何形式向外传输。私钥和公钥是配对的,如果事先在客户端预留了公钥,只要服务器端的公钥和预留的公钥一致,实际上就已经可以排除中间人攻击了。

AFSSLPinningModeCertificate采用该模式证书有过期时间,自签证书要自己更新证书特别要注意。

#import "HttpRequest.h"
#import "AFNetworking.h"

/**
 *  是否开启https SSL 验证
 *
 *  @return YES为开启,NO为关闭
 */
#define openHttpsSSL YES
/**
 *  SSL 证书名称,仅支持cer格式。
 */
#define certificate @"adn"

@implementation HttpRequest

+ (void)post:(NSString *)url params:(NSDictionary *)params success:(void (^)(id))success failure:(void (^)(NSError *))failure
{
      //对应域名的校验我认为应该在url中去逻辑判断。--》冯龙腾写
      //通过对url字符串的切割对子域名进行逻辑验证处理。
    
    // 1.获得请求管理者
    AFHTTPRequestOperationManager *mgr = [AFHTTPRequestOperationManager manager];
    // 2.申明返回的结果是text/html类型
    mgr.responseSerializer = [AFHTTPResponseSerializer serializer];
    mgr.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"application/json",@"text/json",@"text/javascript",@"text/html",nil];
    // 3.设置超时时间为10s
    mgr.requestSerializer.timeoutInterval = 10;
    
    // 加上这行代码,https ssl 验证。
    if(openHttpsSSL)
    {
        [mgr setSecurityPolicy:[self customSecurityPolicy]];
    }
    
    // 4.发送POST请求
    [mgr POST:url parameters:params
      success:^(AFHTTPRequestOperation *operation, id responseObj) {
          if (success) {
              success(responseObj);
          }
      } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
          if (failure) {
              failure(error);
          }
      }];
}

+ (AFSecurityPolicy*)customSecurityPolicy
{
    // /先导入证书
    NSString *cerPath = [[NSBundle mainBundle] pathForResource:certificate ofType:@"cer"];//证书的路径
    NSData *certData = [NSData dataWithContentsOfFile:cerPath];
    
  //  AFSSLPinningModeNone:完全信任服务器证书;只跟浏览器一样在系统的信任机构列表里验证服务端返回的证书。若证书是信任机构签发的就会通过,若是自己服务器生成的证书,这里是不会通过的。
    //AFSSLPinningModePublicKey:只比对服务器证书和本地证书的Public Key是否一致,如果一致则信任服 务器证书;
   // AFSSLPinningModeCertificate:比对服务器证书和本地证书的所有内容,完全一致则信任服务器证书;

 //  选择那种模式呢?

//  AFSSLPinningModeCertificate:最安全的比对模式。但是也比较麻烦,因为证书是打包在APP中,如果服 
 务器证书改变或者到期,旧版本无法使用了,我们就需要用户更新APP来使用最新的证书。
//  AFSSLPinningModePublicKey:只比对证书的Public Key,只要Public Key没有改变,证书的其他变动都不会影响使用。 如果你不能保证你的用户总是使用你的APP的最新版本,所以我们使用AFSSLPinningModePublicKey。

   AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
    
     // allowInvalidCertificates 是否允许无效证书(不在证书信任链内所以是NO,也就是自建的证书),默认为NO
    // 如果是需要验证自建证书,需要设置为YES
    securityPolicy.allowInvalidCertificates = YES;
    
    //validatesDomainName 是否需要验证域名,默认为YES;
    //假如证书的域名与你请求的域名不一致,需把该项设置为NO;如设成NO的话,即服务器使用其他可信任机构颁发的证书,也可以建立连接,这个非常危险,建议打开。
    //置为NO,主要用于这种情况:客户端请求的是子域名,而证书上的是另外一个域名。;。
    //如置为NO,建议自己添加对应域名的校验逻辑。
    securityPolicy.validatesDomainName = NO;
    if (certData) {
        securityPolicy.pinnedCertificates = @[certData];
    }
    
    
    return securityPolicy;
}
@end

https双向认证

HTTPS双向验证单向验证过程中,客户端会验证自己访问的服务器,服务器对来访的客户端身份不做任何限制。如果服务器需要限制客户端的身份,则可以选择开启服务端验证,这就是双向验证。从这个过程中我们不难发现,使用单向验证还是双向验证,是服务器决定的。一般而言,我们的服务器都是对所有客户端开放的,所以服务器默认都是使用单向验证。如果你使用的是Tomcat服务器,在配置文件server.xml中,配置Connector节点的clientAuth属性即可。若为true,则使用双向验证,若为false,则使用单向验证。如果你的服务,只允许特定的客户端访问,那就需要使用双向验证了。


image.png

下面直接上代码(单向认证和双向认证适用同一套代码,只要证书配置的对就可以):

AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
    //    设置超时时间
    [manager.requestSerializer willChangeValueForKey:@"timeoutInterval"];
    manager.requestSerializer.timeoutInterval = 30.f;
    [manager.requestSerializer didChangeValueForKey:@"timeoutInterval"];
    [manager.requestSerializer setValue:@"Content-Type" forHTTPHeaderField:@"application/json; charset=utf-8"];
    [manager setSecurityPolicy:[self customSecurityPolicy]];
    [self checkCredential:manager];
    [manager POST:@"这里填你的https地址" parameters:nil progress:^(NSProgress * _Nonnull uploadProgress) {
        NSLog(@"123");
    } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        NSLog(@"成功:%@",responseObject);
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        NSLog(@"失败:%@",error);
    }];
- (AFSecurityPolicy*)customSecurityPolicy {
    AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
    //获取证书路径
    NSString * cerPath = [[NSBundle mainBundle] pathForResource:@"server" ofType:@"cer"];
    NSData *certData = [NSData dataWithContentsOfFile:cerPath];
    NSSet  *dataSet = [NSSet setWithArray:@[certData]];
    [securityPolicy setAllowInvalidCertificates:YES];//是否允许使用自签名证书
    [securityPolicy setPinnedCertificates:dataSet];//设置去匹配服务端证书验证的证书
    [securityPolicy setValidatesDomainName:NO];//是否需要验证域名,默认YES
    
    return securityPolicy;
}
//校验证书
- (void)checkCredential:(AFURLSessionManager *)manager
{
    [manager setSessionDidBecomeInvalidBlock:^(NSURLSession * _Nonnull session, NSError * _Nonnull error) {
    }];
    __weak typeof(manager)weakManager = manager;
    [manager setSessionDidReceiveAuthenticationChallengeBlock:^NSURLSessionAuthChallengeDisposition(NSURLSession*session, NSURLAuthenticationChallenge *challenge, NSURLCredential *__autoreleasing*_credential) {
        NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
        __autoreleasing NSURLCredential *credential =nil;
        NSLog(@"authenticationMethod=%@",challenge.protectionSpace.authenticationMethod);
        //判断服务器要求客户端的接收认证挑战方式,如果是NSURLAuthenticationMethodServerTrust则表示去检验服务端证书是否合法,NSURLAuthenticationMethodClientCertificate则表示需要将客户端证书发送到服务端进行检验
        if([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
            // 基于客户端的安全策略来决定是否信任该服务器,不信任的话,也就没必要响应挑战
            if([weakManager.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
                // 创建挑战证书(注:挑战方式为UseCredential和PerformDefaultHandling都需要新建挑战证书)
                credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
                // 确定挑战的方式
                if (credential) {
                    //证书挑战  设计policy,none,则跑到这里
                    disposition = NSURLSessionAuthChallengeUseCredential;
                } else {
                    disposition = NSURLSessionAuthChallengePerformDefaultHandling;
                }
            } else {
                disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
            }
        } else { //只有双向认证才会走这里
            // client authentication
            SecIdentityRef identity = NULL;
            SecTrustRef trust = NULL;
            NSString *p12 = [[NSBundle mainBundle] pathForResource:@"client"ofType:@"p12"];
            NSFileManager *fileManager =[NSFileManager defaultManager];
            
            if(![fileManager fileExistsAtPath:p12])
            {
                NSLog(@"client.p12:not exist");
            }
            else
            {
                NSData *PKCS12Data = [NSData dataWithContentsOfFile:p12];
                
                if ([self extractIdentity:&identity andTrust:&trust fromPKCS12Data:PKCS12Data])
                {
                    SecCertificateRef certificate = NULL;
                    SecIdentityCopyCertificate(identity, &certificate);
                    const void*certs[] = {certificate};
                    CFArrayRef certArray =CFArrayCreate(kCFAllocatorDefault, certs,1,NULL);
                    credential =[NSURLCredential credentialWithIdentity:identity certificates:(__bridge  NSArray*)certArray persistence:NSURLCredentialPersistencePermanent];
                    disposition =NSURLSessionAuthChallengeUseCredential;
                }
            }
        }
        *_credential = credential;
        return disposition;
    }];
}
//读取p12文件中的密码
- (BOOL)extractIdentity:(SecIdentityRef*)outIdentity andTrust:(SecTrustRef *)outTrust fromPKCS12Data:(NSData *)inPKCS12Data {
    OSStatus securityError = errSecSuccess;
    //client certificate password
    NSDictionary*optionsDictionary = [NSDictionary dictionaryWithObject:@"123456"
                                                                 forKey:(__bridge id)kSecImportExportPassphrase];
    
    CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);
    securityError = SecPKCS12Import((__bridge CFDataRef)inPKCS12Data,(__bridge CFDictionaryRef)optionsDictionary,&items);
    
    if(securityError == 0) {
        CFDictionaryRef myIdentityAndTrust =CFArrayGetValueAtIndex(items,0);
        const void*tempIdentity =NULL;
        tempIdentity= CFDictionaryGetValue (myIdentityAndTrust,kSecImportItemIdentity);
        *outIdentity = (SecIdentityRef)tempIdentity;
        const void*tempTrust =NULL;
        tempTrust = CFDictionaryGetValue(myIdentityAndTrust,kSecImportItemTrust);
        *outTrust = (SecTrustRef)tempTrust;
    } else {
        NSLog(@"Failedwith error code %d",(int)securityError);
        return NO;
    }
    return YES;
}

!bug

所有方法都是有漏洞的,iOS的app够安全,但是圈内依然有一群逆向工程师

  • 方法2破解:

    • 一:在逆向工程师眼里也是很简单破解的,app砸壳,再显示包内容,依然可以直接把你证书放在青花瓷中使用。
    • 二:通过hook的方式将此次校验的结果返回true或者干脆不让其做校验。当然这种做法只能在越狱环境下实现,但对于抓包来说,这已经足够了。该已开源,其实现的基本原理在这篇中.

总结:没有破解不了的应用,只有破解成本高到无法承受的应用,愿这个世界更美好。。。。。。。

Top