iOS 禁用抓包实践

# Charles 抓包原理

# 抓普通 HTTP 请求的原理

如果是抓普通的 HTTP 请求的话,Charles 就是一个 HTTP 代理。

An HTTP Proxy is a server that receives requests from your web browser and then makes the request to the Internet on your behalf. It then returns the results to your browser.

Charles 扮演的角色就是中间代理的角色,它记录了经过它的数据请求,然后经过序列化数据格式,通过图形界面的方式展示。

Untitled

但是这种简单的代理方式如果碰到 HTTPS 这种请求的话,还是没有办法直接抓包。

抓 HTTPS 请求的原理

我们回忆一下 HTTPS 的请求流程,主要是在 HTTP 层和 TCP 层中间加入了 SSL 层来进行数据加密,以及增加客户端对服务端证书的校验环节。那 Charles 是如何做到抓 HTTPS 请求的呢。简单来说 Charles 是做了一个中间人的角色。

客户端和 Charles 建立 SSL 连接,然后 Charles 和服务端建立 SSL 连接。 客户端会验证 Charles 发来的证书,而 Charles 会验证 SSL 服务端发来的证书。

还记得我们在手机上通过 Charles 抓包的时候,都会预先安装一个 Charles的证书,这个证书就是用来校验 Charles 返回来的证书的。因为我们将 Charles 的根证书加入到信任区之后,Charles 签发的所有证书都会被认为是可信的,所以客户端不会对 Charles 返回的数据进行怀疑,直接使用,这也是我们在 Charles 上修改 HTTPS 返回数据的理论基础。

Untitled

整个的请求流程就变成了,客户端和 Charles 之间是 SSL 加密传输,Charles 和服务端之间也是加密传输。Charles 官方 (opens new window)对此介绍比较详细。

# 禁用抓包思路

# 检查代理

通过检查手机有没有开代理来判断用户有没有抓包,如果开了代理的话就禁止任何网络传输,具体检查方法如下

//检查代理的方法
func checkProxy() -> Bool {
    let myUrl:URL = URL(string: "http://www.apple.com")!
    let proxySetting:CFDictionary? = CFNetworkCopySystemProxySettings()?.takeRetainedValue()
    let proxies = CFNetworkCopyProxiesForURL(myUrl as CFURL, proxySetting as! CFDictionary).takeRetainedValue() as? NSArray
    let firstEle = proxies?[0] as? NSDictionary
    let value = firstEle?.object(forKey: (kCFProxyTypeKey as? String)) as? String
    let targetValue = kCFProxyTypeNone as? String
    return value == targetValue ? false : true
}

但是这种方法的缺陷在于,如果用户手机不是在抓包,而是仅开了 VPN 的话,这个方法会认为开代理从而不能正常使用我们的 App,容易误伤,所以并不是太完美。

# 路由直连

直连模式简单的说就是不走代理服务器。关键属性是 connectionProxyDictionary,具体配置代码如下,主要就是将 connectionProxyDictionary 设置为空字典,注意不是 nil.

let sessionConfig = URLSessionConfiguration.default
sessionConfig.connectionProxyDictionary = [:]  
session.config = sessionConfig

connectionProxyDictionary 的官方 API 说明

A dictionary containing information about the proxy to use within this session. This property controls which proxy tasks within sessions based on this configuration use when connecting to remote hosts. The default value is NULL, which means that tasks use the default system settings.

这种方案是可以通过本地的一些代理软件的配置来建立虚拟网卡继续抓包的,不够完美。我尝试抓了下微博的包就不能通过这种方式抓到。

# SSL Pinning

可以通过 SSL Pinning 来防止 Charles 抓包,原理是客户端内置服务端的公钥证书,强制客户端验证与其通信的是否为服务端,这样 Charles 就没有办法做中间人了。具体有两种做法,一种是证书锁定(Certificate Pinning),还有一种是公钥锁定(Public Key Pinning)

# 证书锁定(Certificate Pinning)

目前市面上大多数是通过在客户端内置证书的方式实现的 SSL Pinning 的,这种就是证书锁定(Certificate Pinning)的方式。这种方式有一个缺点是,公钥证书是会到期的,服务端需要差不多每年都更新自己的证书,客户端也需要记得更新证书这个事儿,因为公钥证书过期之后客户端就没有办法发起请求了。

有人会在客户端内置服务端证书链的根证书,这样过期时间会稍微长一点,可能十年二十年的,但是也有过期的风险。

# 公钥锁定(Public Key Pinning)

我们其实思考一下,证书的内容其实就是公钥,机构信息和签名。我们验证证书链的时候用到的就是公钥,本质上并不需要别的东西,所以我们只需要将公钥内置到我们的应用里,这样校验证书的时候我们用公钥去解密数据,然后和证书内置签名数据比较,结果一致我们就认为证书是有效的。

同时证书升级,本质上公钥是不变的,我们就可以不需要操心过期的问题。但是注意有的公司服务端是申请全新证书的,所以公钥变了。这个需要确认。

苹果在info.plist增加了公钥锁定的相关配置,我们并不需要写代码,直接在里面配置就好了。

Untitled

# 总结

目前就看到这些,综合起来说,公钥锁定这种方式是最安全的通信方式,配置成本也很低。

参考文档

  1. http://zhaoxincheng.com/index.php/2021/03/14/ios系统抓包入门实践之短链/ (opens new window)
  2. Identity Pinning: How to configure server certificates for your app (opens new window)