Android逆向之https

介绍HTTP协议发展史、状态码、方法,整理了几乎所有常见的首部,讲述TLS的单向认证流程,Android中HTTPS抓包方法、防抓包策略以及绕过防抓包策略思路

HTTP协议

超文本传输协议,是一个基于请求与响应,无状态的,应用层的协议,常基于TCP/IP协议传输数据,互联网上应用层最为广泛的一种网络协议。

发展史

协议 发展 说明
HTTP/0.9 1991年定稿最早的HTTP协议,没有作为正式标准 只有GET命令;没有请求头、请求体、返回头;服务器只能读取HTML文件以ASCII字符流返回给客户端;默认80端口
HTTP/1.0 1996年发布,正式作为标准 引入了POST、HEAD等命令、请求头、响应头、状态码;提供了缓存、多字符集支持、multi-part、authorization、内容编码等;默认不是持久连接
HTTP/1.1 1997年发布,2015年前使用最广 默认持久连接不必声明keep-alive;引入pipelining管道机制,同一TCP连接里同时发送多个请求,但服务器需按照请求顺序串行返回响应;请求头新增Host,使同一台物理服务器可以同时部署多个web服务
HTTPS 互联网巨头大力推行 在传统HTTP协议的TCP与HTTP之间加入一层SSL/TLS;通过混合加密、摘要算法、数字证书来保证安全性;使用443端口
SPDY 2009年由Google公开,不是标准已逐渐被HTTP/2取代 基于TLS,在HTTPS的SSL层与HTTP层之间增加一层SPDY层;支持多路复用,可在同一TCP连接并发处理多个HTTP请求;可以赋予请求的优先级顺序;支持请求头和响应头压缩;支持服务器向客户端主动推送、提示
HTTP/2 2015年发布,逐步覆盖市场 基于SPDY的标准化协议,可在TCP上使用不是必须在TLS上;HTTPS连接时使用了NPN的规范版ALPN;消息头的压缩算法采用新算法HPACK,而SPDY采用DEFLATE;依然没有解决TCP对头阻塞问题
QUIC/HTTP3 2012由Google提出,2015年提交给IETF,下一代互联网标准传输协议 不再是基于TCP而是通过UDP;使用 stream 进一步扩展了 HTTP/2 的多路复用;引入 Connection ID,使得 HTTP/3 支持连接迁移以及 NAT 的重绑定;含有一个包括验证、加密、数据及负载的 built-in 的TLS安全机制;将拥塞控制移出了内核,通过用户空间来实现;头部压缩更换成了兼容 HPACK的QPACK压缩方案

状态码

状态码由三个十进制数字组成,第一个十进制数字定义了状态码的类型,共分为5种类型:

分类 说明
1** 表示请求已被接受,需要继续处理的临时响应
2** 成功,操作被成功接收并处理
3** 重定向,需要进一步的操作以完成请求
4** 客户端错误,请求包含语法错误或无法完成请求
5** 服务器错误,服务器在处理请求的过程中发生了错误

方法

方法的名称区分大小写,并且通常是一个简短的单词,由英文大写字母组成。

接收请求时,服务器尝试确定请求的方法,如果失败,则返回带有代码 501 和短语的响应消息 Not Implemented

方法 说明 1.0、1.1中支持的协议版本
GET 获取资源 1.0、1.1
POST 传输实体主体 1.0、1.1
PUT 传输文件 1.0、1.1
HEAD 获得报文首部 1.0、1.1
DELETE 删除资源 1.0、1.1
OPTIONS 删除资源 1.0、1.1
TRACE 追踪路径 1.1
CONNECT 将服务器作为代理,让服务器代替用户去访问 1.1
LINK 建立和资源之间的联系 1.0
UNLINK 断开连接关系 1.0

首部

http首部主要分为五大部分:

  1. 通用首部:各种类型的报文(请求、响应报文)都可以使用,提供有关报文最基本的信息。
  2. 请求首部:专用于请求报文的首部,用于给服务器提供相关信息,告诉服务器客户端的期望和能力。
  3. 响应首部:专用于响应报文的首部,用于告诉客户端是谁在响应以及响应者的能力。
  4. 实体首部:用于描述http报文的负荷(主体),提供了有关实体及其内容的相关信息。
  5. 扩展首部:非标准首部,由应用开发者定义的首部。

(以下整理的首部并非完全按照这五类划分,部分扩展首部也按照功能划入了请求首部、响应首部等部分)

通用首部

字段名 说明 示例
Cache-Control 控制缓存的行为 Cache-Control: no-cache
Connection 逐跳首部、连接的管理(HTTP/1.1默认持久连接) Connection: close
Date 创建报文的日期时间 Date: Tue, 15 Nov 2010 08:12:31 GMT
Pragma HTTP/1.1之前版本的历史遗留字段,用来包含实现特定的指令 Pragma: no-cache
Trailer 说明传输中分块编码的编码信息 Trailer: Max-Forwards
Transfer-Encoding 逐跳首部,指定传输报文主体时使用的编码方式 Transfer-Encoding: chunked
Upgrade 升级为其他协议 Upgrade: HTTP/2.0, SHTTP/1.3, IRC/6.9, RTA/x11
Via 代理服务器的相关信息 Via: 1.0 fred, 1.1 nowhere.com (Apache/1.1)
Warning 错误通知 Warning: 199 Miscellaneous warning

请求首部

字段 说明 示例
Accept 客户端能够接收的内容类型 Accept: text/plain, text/html
Accept-Charset 客户端可以接受的字符编码集 Accept-Charset: iso-8859-5
Accept-Encoding 端到端首部,告知服务器客户端能够处理的编码方式和相对优先级 Accept-Encoding: compress, gzip
Accept-Language 客户端可接受的自然语言 Accept-Language: en,zh
Authorization Web认证信息 Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
DNT 指示该用户的跟踪的偏好:0用户更喜欢在目标站点上进行跟踪;1用户不希望在目标站点上跟踪 DNT: 1
Expect 客户端要求的特殊服务器行为。若服务器不能理解或者满足,则须返回417状态,或者如果请求有其他问题,返回4xx状态 Expect: 100-continue
Forwarded 代理服务器的客户端的信息,此标头标准版本是X-Forwarded-For,X-Forwarded-Host与X-Forwarded-Proto Forwarded: for=192.0.2.60; proto=http; by=203.0.113.43
From 用户的电子邮箱地址 From: user@email.com
Host 指定请求的服务器的域名和端口号 Host: www.zcmhi.com
If-Match 当客户端If-Match的值若与服务端的ETag一致,才会执行请求,否则会拒绝412 If-Match: W/“67ab43”, “54ed21”, “7892dd”
If-Modified-Since 若If-Modifed-Since字段值早于资源的更新时间,则希望服务端能处理该请求 f-Modified-Since: Sat, 29 Oct 2010 19:43:31 GMT
If-None-Match 如果内容未改变返回304代码,参数为服务器先前发送的Etag,与服务器回应的Etag比较判断是否改变 If-None-Match: “737060cd8c284d8af7ad3082f209582d”
If-Range 告知服务器若指定的If-Range字段值和请求资源的ETag值一致时,则作为范围请求处理,否则返回全部资源 If-Range: “737060cd8c284d8af7ad3082f209582d”
If-Unmodified-Since 比较资源的更新时间,与If-Modified-Since相反 If-Unmodified-Since: Sat, 29 Oct 2010 19:43:31 GMT
Max-Forwards 该字段以十进制整数形式指定可经过的服务器最大数目。服务器在往下一个服务器转发请求之前,会将Max-Forwards的值减1后重新赋值,当服务器接收到Max-Forwards值为0的请求时,则不再进行转发,而是直接返回响应 Max-Forwards: 10
Proxy-Authorization 代理服务器要求客户端的认证信息 Proxy-Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
Public-Key-Pins 将特定的加密公钥与特定的Web务器相关联,以降低伪造证书对MITM攻击的风险 Public-Key-Pins: pin-sha256=""; pin-sha256=""; max-age=5184000; includeSubDomains; report-uri=""
Public-Key-Pins-Report-Only 将针对违规的报告发送到头中report-uri指定的报告,但是,Public-Key-Pins如果违反了钉住规则,仍然允许浏览器连接到服务器 Public-Key-Pins-Report-Only: pin-sha256="; pin-sha256=""; includeSubDomains; report-uri=""
Range 实体的节点范围请求 Range: bytes=5001-10000
Referer 指定该请求是从哪个页面跳转页来的,常被用于分析用户来源等信息 Referer: http://www.example.com/index.html
Referrer-Policy 用于过滤Referrer报头的策略 Referrer-Policy: origin-when-cross-origin
Cookie HTTP请求发送时,会把保存在该请求域名下的所有cookie值一起发送给web服务器 Cookie: $Version=1; Skin=new;
TE 逐跳首部,告知服务器客户端能够处理的编码方式和相对优先级 TE: gzip, deflate; q=0.5
Upgrade-Insecure-Requests 向服务器发送一个信号,表示客户对加密和认证响应的偏好, Upgrade-Insecure-Requests: 1
User-Agent HTTP 客户端程序的信息 User-Agent: Mozilla/5.0 (Linux; X11)
X-Forwarded-For 来表示 HTTP 请求端真实 IP X-Forwarded-For: IP0, IP1, IP2
X-Forwarded-Host 可用于确定最初使用哪个主机 X-Forwarded-Host: id42.example-cdn.com
X-Forwarded-Proto 确定客户端和负载平衡器之间使用的协议 确定客户端和负载平衡器之间使用的协议

响应首部

字段 说明 示例
Accept-Ranges 是否接受字节范围请求 Accept-Ranges: bytes
Age 从原始服务器到代理缓存形成的估算时间(以秒计,非负) Age: 12
ETag 资源的匹配信息 ETag: “737060cd8c284d8af7ad3082f209582d”
Expires 响应过期的日期和时间 Expires: Thu, 01 Dec 2010 16:00:00 GMT
Location 配合 3xx : Redirection 的响应,提供重定向的 URI Location: http://www.example.com
Proxy-Authenticate 代理服务器对客户端的认证方式 Proxy-Authenticate: Basic
Retry-After 如果实体暂时不可取,通知客户端在指定时间之后再次尝试 Retry-After: 120
Set-Cookie Http Cookie Set-Cookie: status-enable; expires=Tue, 05 Jul 2018 02:01:22 GMT; path=/; domain=.example.com;
Server web服务器信息 Server: Apache/1.3.27 (Unix) (Red-Hat/Linux)
SourceMap 响应报头链接生成的代码到一个源映射,使浏览器来重构原始源并在调试器呈现重构原始 SourceMap: /path/to/file.js.map
Strict-Transport-Security 通常缩写为HSTS,告诉客户端它应该只使用HTTPS,而不是使用HTTP进行通信 Strict-Transport-Security: max-age=31536000; includeSubDomains
Tk 显示了对相应请求的跟踪情况 Tk: ! (under construction) Tk: ? (dynamic) Tk: G (gateway or multiple parties)
Vary 告知下游的代理服务器,应当如何对以后的请求协议头进行匹配,以决定是否可使用已缓存的响应内容而不是重新从原服务器请求新的内容 Vary: Accept-Encoding,User-Agent
WWW-Authenticate 表明客户端请求实体应该使用的授权方案 WWW-Authenticate: Basic
X-Content-Type-Options 如果服务器发送响应头 “X-Content-Type-Options: nosniff”,则script和styleSheet元素会拒绝包含错误的 MIME 类型的响应。这是一种安全功能,有助于防止基于 MIME 类型混淆的攻击 X-Content-Type-Options: nosniff
X-DNS-Prefetch-Control 控制浏览器的DNS预读取功能 X-DNS-Prefetch-Control: on
X-Frame-Options 给浏览器指示允许一个页面可否在 , 或者 中展现的标记,网站可以使用此功能,来确保自己网站的内容没有被嵌套到别人的网站中去,也从而避免了点击劫持的攻击 X-Frame-Options: ALLOW-FROM https://example.com/
X-XSS-Protection 是 IE,Chrome 和 Safari 的一个特性,当检测到跨站脚本攻击 (XSS)时,浏览器将停止加载页面 X-XSS-Protection: 1; mode=block

实体首部

字段 说明 示例
Allow 服务器支持的HTTP请求方法 Allow: GET, HEAD
Content-Disposition 指示回复的内容是以内联的形式还是以附件的形式下载并保存到本地;也可在multipart/form-data 类型的应答消息体中,用来给出其对应字段的相关信息 Content-Disposition: attachment; filename=“filename.jpg”
Content-Encoding 告知客户端服务器对实体的主体选用的内容编码方式 Content-Encoding: gzip
Content-Language 实体主体使用的自然语言 Content-Language: zh-CN
Content-Length 实体部分大小 Content-Length: 15000
Content-Location 返回报文主体返回资源对应的URI Content-Location: httpo://www.example.com/index.html
Content-MD5 检查报文主体在传输过程中是否保持完整,对报文主体执行 MD5 算法获得218位二进制数,再通过 Base64 编码后将结果写入 Content-MD5: ZTEwYWRjMzk0OWJhNTlhYmJlNTZlMDU3ZjIwZjg4M2U=
Content-Range 针对范围请求,表示当前发送部分及整个实体大小 Content-Range: bytes 5001-10000/10000
Content-type 实体主体内对象的媒体类型 Content-Type: text/html; charset=utf-8
Expires 将资源失效日期告知客户端 Expires: Wed, 04 Jul 2012 08:26:05 GMT
Last-Modified 资源最终修改时间 Last-Modified: wed, 25 May 2018 09:11:40 GMT

跨域资源共享首部

跨域资源共享标准新增了一组HTTP头部字段,属于扩展首部,允许服务器声明哪些源站通过浏览器有权限访问哪些资源。另外,对那些可能对服务器产生副作用的HTTP请求方法,浏览器必须先使用OPTIONS方法发起一个预检请求,从而获知服务器是否允许该跨域请求。服务器允许后才发起实际的HTTP请求。预检请求的返回中,服务器也可以通知客户端是否需要携带身份凭证。

字段 说明 示例
Access-Control-Allow-Credentials 指示的请求的响应是否可以暴露于该页面,当true值返回时它可以被暴露,凭证是 Cookie ,授权标头或 TLS 客户端证书 Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers 用于预检请求中,列出了将会在正式请求的Access-Control-Request-Headers字段中出现的首部信息 Access-Control-Allow-Headers: X-Custom-Header
Access-Control-Allow-Methods 在对预检请求的应答中明确了客户端所要访问的资源允许使用的方法或方法列表 Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Origin 指定了该响应的资源是否被允许与给定的origin共享 Access-Control-Allow-Origin: https://developer.mozilla.org
Access-Control-Expose-Headers 出了哪些首部可以作为响应的一部分暴露给外部 Access-Control-Expose-Headers: Content-Length, X-Kuma-Revision
Access-Control-Max-Age 表示预检请求的返回结果(即 Access-Control-Allow-Methods 和Access-Control-Allow-Headers 提供的信息)可以被缓存多久 Access-Control-Max-Age: 600
Access-Control-Request-Headers 出现于预检请求中,用于通知服务器在真正的请求中会采用哪些请求头 Access-Control-Request-Headers: X-PINGOTHER, Content-Type
Access-Control-Request-Method 出现于预检请求中,用于通知服务器在真正的请求中会采用哪种HTTP方法 Access-Control-Request-Method: POST

安全性的不足

  1. 通信使用明文,内容可能会被窃听(可窃听)。
  2. 无法证明报文的完整性,内容有可能已遭篡改(可篡改)。
  3. 不验证通信方的身份,因此有可能遭遇伪装(可冒充)。

HTTPS协议

可以理解为HTTP+SSL/TLS, 即 HTTP 下加入 SSL/TLS 层,用于安全的 HTTP 数据传输,简单来说HTTP + 加密 + 认证 + 完整性保护 = HTTPS,用来解决HTTP协议安全性的不足。

HTTPS

SSL/TLS历史

HTTPS相比HTTP多出了SSL/TLS 层,SSL 协议原本由网景公司开发,后来被 IETF 标准化,正式名称叫做 TLS,TLS 1.0通常被标示为SSL 3.1,TLS 1.1为SSL 3.2,TLS 1.2为SSL 3.3,TLS1.1和TLS1.0不支持HTTP2,目前应用最广泛的应该还是 TLS 1.2,SSL协议发展历史如下:

协议 发布时间 状态
SSL 1.0 未公布 未公布
SSL 2.0 1995年 已于2011年弃用 RFC6176
SSL 3.0 1996年 已于2015年弃用 RFC7568
TLS 1.0 1999年 RFC2246
TLS 1.1 2006年 RFC4346
TLS 1.2 2008年 RFC5246,目前最广泛应用
TLS 1.3 2018年 RFC8446

TLS1.2单向认证流程

以目前使用最广泛的TLS1.2说明下认证流程即握手流程,认证分为单向和双向两种模式,单向认证客户端验证服务端证书合法即可访问,一般Web应用都是采用SSL单向认证的;双向认证需要客户端和服务器都需要持有证书,两者证书验证均合法才可以继续访问。

使用wireshark抓包TLS1.2,单向认证流程如下:
wireshark抓包TLS1.2

  1. 客户端发送Client Hello。将一个Unix时间戳、TLS版本、支持的所有加密套件、支持的签名算法、生成的随机数Random_C等发送给服务器。

  2. 服务器发送Server Hello。将服务器Unix时间戳、生成的随机数Random_S、协商的加密算法套件等发送给客户端。

  3. 服务器发送Certificate、Server Key Exchange、Server Hello Done。Certificate是数字证书,Server Key Exchange为公钥参数(有时也可不需要),Server Hello Done表明服务器已经将所有预计的握手消息发送完毕。

  4. 客户端发送Client Key Exchange、Change Cipher Spec、Encrypted Handshake Message。客户端首先需要校验证书,证书向上按照证书链逐级校验,每一级证书校验过程是通过拿到证书签发者(Issuer)的证书中的公钥(证书 = 使用者公钥 + 主体信息如公司名称等 + CA对信息的确认签名 + 指纹)对本级证书(Subject)的签名进行数学验证,并校验证书是否被吊销,是否在有效期,是否与域名匹配等,验证成功即证书有效,整个一级一级验证上去,形成信任链,如果校验不通过则中断连接,浏览器弹出警告,校验正确后解析得到服务器公钥,并发送Client Key Exchange、Change Cipher Spec、Encrypted Handshake Message。Client Key Exchange:生成一个随机数 Pre-master,并用证书公钥加密,通过Fuc(random_C, random_S, Pre-Master)生成一个协商密钥;Change Cipher Spec:通知服务器协商完成,以后就使用上面生成的协商密钥进行对称加密;Encrypted Handshake Message:结合之前所有通信参数的 hash 值与其它相关信息生成一段数据,采用协商密钥与算法进行加密。

  5. 服务器发送Change Cipher Spec、Encrypted Handshake Message。服务器使用私钥解密得到 Pre-master数值,基于之前交换的两个明文随机数 random_C 和 random_S,同样通过Fuc(random_C, random_S, Pre-Master)得到协商密钥,计算之前所有接收信息的 hash 值,然后解密客户端发送的 encrypted_handshake_message,验证数据和密钥正确性,验证通过之后,服务器同样发送 change_cipher_spec 以告知客户端后续的通信都采用协商的密钥与算法进行加密通信;encrypted_handshake_message:服务器也结合所有当前的通信参数信息生成一段数据并采用协商密钥与算法加密并发送到客户端。

  6. 客户端计算所有接收信息的hash值,并采用协商密钥解密encrypted_handshake_message,验证服务器发送的数据和密钥,验证通过则握手完成。

  7. 开始使用协商密钥与算法进行加密通信。(Encrypted Alert是由客户端或服务器发送,意味着加密通信因为某些原因需要中断,警告对方不要再发送敏感的数据)。

Android抓包HTTPS

HTTP/HTTPS抓包工具有不少,常见的电脑端抓包工具有fiddlerCharlesBurp SuitewhistleAnyProxy,Android端抓包APP也有HttpCanary,wireshark也可以抓包但是不可以解密HTTPS内容。

使用Charles抓包安卓HTTP很简单,将手机WIFI设置代理为Charles,步骤如下:

  1. 为方便设置代理,使手机与电脑处于同一局域网(连接同一个WIFI,或者电脑连到同一个路由器的LAN端口)。
  2. 电脑使用ipconfig查看局域网ip,并打开Charles。
  3. 手机连接的WIFI–高级设置–代理服务器,代理服务器填写电脑的局域网ip,Charles的代理端口默认8888。
  4. 电脑端Charles允许连接即可。
  5. Charles–Proxy–SSL Proxying Setting,Enable SSL Proxying打勾,add添加抓包的Host和Port,一般Port都是443,Host和Port都填*则抓包所有。

打开APP就能在Charles上看到抓包的HTTP,但是HTTPS都会显示为Unknown,因为还没有安装Charles的证书。手机设置代理以后,浏览器访问chls.pro/ssl下载Charles证书并安装。Android 7.0以下直接安装即可,但是Android 7.0及以上默认不信任用户自己安装的证书,而只信任系统预设的证书,解决方法有:

  • 手动制作Charles证书,按照Android系统预设的格式,并推到/system/etc/security/cacerts目录下,从而让系统把Charles证书当作系统证书

  • 如果使用Magisk实现的root,Magisk安装MagiskTrustUserCerts模块,模块原理同上,可以把自定义的用户证书当作系统证书

  • 反编译apk,资源文件中添加network_security_config.xml,修改AndroidManifest.xml(修改APP的网络安全配置,信任用户证书)

    AndroidManifest.xml修改:

    1
    2
    3
    4
    5
    <manifest ... >
    <application android:networkSecurityConfig="@xml/network_security_config">
    ...
    </application>
    </manifest>

    network_security_config.xml内容:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <?xml version="1.0" encoding="utf-8"?>
    <network-security-config>
    <base-config cleartextTrafficPermitted="true">
    <trust-anchors>
    <certificates src="system" />
    <certificates src="user" />
    </trust-anchors>
    </base-config>
    </network-security-config>
  • frida或者xposed hook实现证书信任

    在JSSE中证书信任管理器类实现了X509TrustManager接口,我们可以自己实现一个X509TrustManager,通过hook修改掉网络请求库的X509TrustManager配置。

    比如自定义X509TrustManager实现信任所有服务端的证书(无论是否过期、是否经过认证):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class TrustAllManager implements X509TrustManager {
    @Override
    public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType)
    throws java.security.cert.CertificateException {
    }

    @Override
    public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType)
    throws java.security.cert.CertificateException {
    }

    @Override
    public java.security.cert.X509Certificate[] getAcceptedIssuers() {
    return new java.security.cert.X509Certificate[0];
    }
    }

    okhttp的X509TrustManager设置为:

    1
    2
    OkHttpClient.Builder builder = new OkHttpClient.Builder();
    builder.sslSocketFactory(createAllSSLSocketFactory(), new TrustAllManager());

    我们可以通过hook OkHttpClient.Builder类的sslSocketFactory方法,实现修改X509TrustManager配置,从而实现用户证书的信任。

HTTPS抓包原理

代理原理

Charles、fiddler、HttpCanary抓包都是基于代理实现的,对HTTPS的抓包原理其实也都差不多,类似于中间人攻击,原理如下(图片来源于谈移动端抓包方式和原理及如何防犯中间人攻击):

Charles抓包原理

  • TLS握手时拦截服务器证书,得到服务器公钥,并将Charles自己的证书发送给客户端,客户端原本校验Charles证书不通过,但我们可以手动使客户端信任Charles证书。
  • Charles拦截请求得到Random_S和Random_C等未加密信息,其他加密部分比如Pre-Master由于是使用Charles的证书公钥加密的,Charles可以使用自己的私钥解密得到内容,再使用服务器证书公钥重新加密后发送给服务器,从而与服务器完成TLS认证,并获取到密钥。
  • 每次发送HTTPS请求报文经过Charles,Charles再使用得到的对称密钥进行解密。

Android防抓包策略及绕过思路

Android上HTTPS抓包成本并不算高,使系统信任第三方证书就能够实现抓包,Android 7.0 (API 24)及以上虽默认不再信任用户CA,提高了安全性但抓包成本也不算高,还可添加其他防抓包策略进一步提高安全性。

设置无代理模式

由于Charles、fiddler这些抓包工具是基于代理实现的(wireshark不是基于代理,而是网卡抓包),所以可以将APP所用的HTTP客户端设置为无代理,设置之后HTTP客户端不会连接到代理服务器,这样的话Charles就无法直接抓包了,比如OkHttp配置无代理:

1
2
3
OkHttpClient.Builder()
.proxy(Proxy.NO_PROXY)
.build()

绕过方案:

  1. 手动修改DNS。由上面的代理原理图可知,若请求不走代理,就要通过DNS解析获取ip地址再发送请求,我们可以直接修改Android的DNS配置,将请求域名解析到Charles代理服务器上,从而实现抓包。
  2. VPN流量转发。使用drony之类的APP先将手机请求导到VPN,再对VPN的网络进行Charles的代理。
  3. 使用frida或者xposed hookOkHttpClient.Builderproxy方法,使无代理配置不起作用。

增强本地证书校验

APP本地做证书校验时不仅仅校验公钥,并且设置更为严格的校验模式,直接安装的Charles证书将因为格式问题不能验证通过,可以通过实现X509TrustManager接口实现。这种方式其实适用于Android 7.0以下,Android 7.0以上通过MagiskTrustUserCerts等方式安装的证书由于已经被当作系统内置证书,这种方式应该不再起作用。

SSL Pinning证书锁定

应用中只信任固定证书或是公钥,将可信 CA 限制在一个很小的 CA 集范围内,应用的服务器将使用这个集合,这样可以防止因泄露系统中其他 100 多个 CA 中的某个 CA 而破坏应用安全通道。

通常有两种锁定方式证书固定公钥固定。证书固定:将证书的某些字节码硬编码在用程序中,证书校验时检查证书中是否存在相同的字节码;公钥固定:网站会提供已授权公钥的哈希列表,指示客户端在后续通讯中只接受列表上的公钥。

OkHttp配置实现证书锁定,对特定的host做证书公钥验证,公钥经过Sha1算法hash一下,然后Base64加密一次,然后在结果前面加上字符串"sha256/“或者"sha1/”:

1
2
3
4
5
6
CertificatePinner certificatePinner = new CertificatePinner.Builder()
.add(example.com, "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
.build();

OkHttpClient client = new OkHttpClient();
client.setCertificatePinner(certificatePinner);

Android 7.0及以上实现证书锁定:

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config>
<domain includeSubdomains="true">example.com</domain>
<pin-set expiration="2018-01-01">
<pin digest="SHA-256">7HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=</pin>
<!-- backup pin -->
<pin digest="SHA-256">fwza0LRMXouZHRC8Ei+4PyuldPDcf3UKgO/04cDM1oE=</pin>
</pin-set>
</domain-config>
</network-security-config>

绕过方案:

  1. 可以尝试Xposed+JustTrustMe模块或者JustTrustMePlus模块或者TrustMeAlready模块
  2. Frida+DroidSSLUnpinning脚本
  3. hook,需要反编译分析源码

TLS双向认证

TLS认证有单向认证和双向认证模式,双向认证除了客户端去验证服务器端的证书外,服务器也同时需要验证客户端的证书,如果没有通过验证,则会拒绝连接,如果通过验证,服务器获得用户的公钥。

绕过方案:双向认证需要客户端证书,所以APP内是要有证书的并且有操作证书的地方,hook这部分代码获取证书及密钥,再将证书格式转换一下,导入到Burp Suite或者Charles这些抓包软件中,实现抓包。

此外,APP内肯定有传输内容SSL解密的实现,针对这部分代码进行hook,也可直接拿到数据,这也是更为通用的获取请求内容的办法,实现方式可以参考Frida的脚本frida_ssl_logger

Android网络安全方面配置可以查看developer android,此外还可通过HTTPS请求的报文密、重要请求走Socket通信,APP加固防止hook等手段提高安全性。

参考