Http 和 Https

Http 的弊端

Http 在传输数据的过程中, 所有的数据都是明文传输的, 没有安全性可言; 想要让 Http 更加安全, 只能使用加密算法对信息进行加密之后再传输, 只要确保密钥不被第三方获取, 那么就能确保数据传输的安全性;

在 Http 的基础上, 对原有的 Http 进行 TLS 或 SSL 的加密, 提高安全性; Https = Http + SSL/TLS

Https 工作原理

未命名图片

  • 证书验证

    • ① 客户端发起请求, 连接到服务端的443端口(Https 默认端口)
    • ②采用 Http 协议的服务器必须要有一套数字 CA 证书, 颁发证书的同时会产生一个私钥和一个公钥, 私钥由服务端自己保存, 公钥则是附带在证书信息中, 可以公开; 证书本身也附带一个证书电子签名, 这个签名用来验证证书的完整性和真实性, 防止证书被篡改;
    • ③服务器响应客户端请求, 将证书传递给客户端, 证书包含公钥和其他信息, 如: 证书颁发机构, 公司信息, 和证书有效期等;
    • ④客户端解析证书并对其进行验证
      • 如果证书不是可信机构颁布的, 或者证书中的域名和实际域名不一致, 或者证书已经过期, 就会向访问者显示一个警告, 告知风险;
      • 如果证书可信任, 进入接下来的数据传输中的非对称加密流程
  • 数据传输

    • 非对称加密

      img

      • 客户端判断证书可信任, 客户端会从服务器证书中取出服务器的公钥, 并生成一个随机码 Key, 再使用服务器的公钥对 Key 进行加密;
      • ⑤客户端将使用服务器公钥加密之后的 Key 的结果发送给服务器
      • ⑥服务器使用自己的私钥对客户端发送过来的加密之后的 Key 进行解密, 获取到之后对称加密服务器和客户端通信的密钥 Key;
    • 对称加密

      img

      • ⑦使用从客户端获取到的 Key 对信息进行加密, 发送给客户端
      • ⑧之后客户端和服务器都通过这个 Key 对数据进行加解密, 传输所有的内容

Http/TCP/Socket

HTTP是应用层的协议,也是开发中最常用的一个网络协议;TCP是传输层的协议,大学学过计算机网络的都知道,该层还有一个UDP协议; Socket 本质是(API),对TCP/IP的封装,可供程序员做网络通讯开发是所调用, Socket 本质是从传输层抽象出来的, 采用 IP 地址和端口号的形式进行标识, 并不是传统意义上的网络协议;

TCP 和 Http

在网络分层中, Http 协议是基于 TCP 协议的, 客户端向服务器发送一个 Http 请求时, 需要先和服务端建立 TCP 连接, 也就是经典的三次握手, 握手成功后才会进行数据交互; 总结: Http 基于 TCP

TCP 和 Socket

Socket是应用层与传输层之间的同一个抽象层,它是一套接口,所以Socket连接可以基于TCP连接,也有可能基于UDP。我们知道,TCP协议是可靠的,UDP协议是不可靠的,那么基于TCP协议的Socket连接同样是可靠的;基于UDP协议的Socket连接是不可靠的,大多数的即时通讯工具都是基于后者实现的。

Http 和 Socket

HTTP连接中,只有客户端发起请求后服务端才会响应,服务端是无法主动向客户端发消息的。而Socket连接中,通信双方发送消息并没有先后的限制,通信双方中的任何一方可以随时向另一方发送消息。

SSl 和 TLS

SSL 和 TLS 概念

SSL 和 TLS 协议可以为通信双方提供识别和认证通道,从而保证通信的机密性和数据完整性。TLS 协议是从 Netscape SSL 3.0 协议演变而来的,不过这两种协议并不兼容,SSL 已经逐渐被 TLS 取代,所以下文就以 TLS 指代安全层;

SSL/TLS 握手

TLS 握手是启动 HTTPS 通信的过程,类似于 TCP 建立连接时的三次握手。 在 TLS 握手的过程中,通信双方交换消息以相互验证,相互确认,并确立它们所要使用的加密算法以及会话密钥 (用于对称加密的密钥)。可以说,TLS 握手是 HTTPS 通信的基础部分。

TLS 握手过程, 总共做了四件事:

  1. 商定双方通信所使用的 TLS 版本, 例如: TLS1.0, 1.2, 1.3等
  2. 确定双方所要使用的密码组合;
  3. 客户端通过服务器的公钥和数字证书上的数字签名验证服务端的身份;
  4. 生成会话密钥, 并将密钥用于握手结束后的对称加密;

详细过程:

未命名图片 (1)

  1. “client hello” 消息: 客户端通过发送 “client hello” 消息向服务器发起握手请求, 该消息包含了:
    1. 客户端所支持的 TLS 版本和密码组合, 以供服务器进行选择
    2. client random 随机字符串
  2. “server hello” 消息: 服务器发送 “server hello” 消息对客户端进行回应, 该消息包含了:
    1. 数字证书, 服务器选择的密码组合
    2. server random 随机字符串
  3. 验证: 客户端对服务器发来的证书进行验证, 确保对方身份的合法性; 验证步骤为:
    1. 检查数字签名
    2. 验证证书链
    3. 检查证书的有效期
    4. 检查证书撤回状态
  4. “premaster secret” 字符串: 客户端向服务器发送另一个随机字符串 “premaster secret” (预主密码), 这个字符串是经过服务器的公钥加密过的, 只有对应的私钥才能解密
  5. 服务器使用私钥解密客户端发过来的加密的 “premaster secret”
  6. 生成共享密钥: 经过上述步骤之后, 客户端和服务器都拥有了相同的 client random, server randompremaster secret, 并通过相同的算法生成相同的共享密钥 Key;
  7. 客户端就绪: 客户端发送经过共享密钥 Key 加密后的 Finished 信号
  8. 服务器就绪: 服务器发送经过共享密钥 Key 加密后的 Finished 信号
  9. 达成安全通信: 握手完成, 双方使用对称加密进行安全通信

Https 中间人抓包

高版本安卓系统如何导入证书

  1. magisk - Move Certificates 模块

  2. Aosp 直接将证书编译到系统中 参考链接

  3. re 管理器

  4. 命令行

    charles导出.cer证书,选择.cer类型 保存在pc上

    1. 修改证书名称
      系统证书目录:/system/etc/security/cacerts/
      其中的每个证书的命名规则如下:
      .
      文件名是一个Hash值,而后缀是一个数字。
      文件名可以用下面的命令计算出来:
      openssl x509 -subject_hash_old -in
      后缀名的数字是为了防止文件名冲突的,比如如果两个证书算出的Hash值是一样的话,那么一个证书的后缀名数字可以设置成0,而另一个证书的后缀名数字可以设置成1
    2. 复制证书到设备上
      adb push xxxxxxx.0 /sdcard/
    3. 复制到系统目录并修改权限(安卓8.1.0 Magisk Root)
      mount -o rw,remount /system 【不修改 没法写入】
      mount -o rw,remount /
      mv /sdcard/xxxxxxx.0 /etc/security/cacerts/ 移动文件到系统
      chown root:root /etc/security/cacerts/fc365f9d.0 修改用户组
      chmod 644 /system/etc/security/cacerts/xxxxxxx.0 修改权限

抓包失败常见问题

  1. 单向证书校验中 (C 校验 S), 没有将抓包工具证书放在手机中 400 Bad Request
  2. 双向证书检验中 (S 校验 C), 没有向抓包工具配置 App 证书 No required SSL certificate was sent
  3. SSL PINNING 配置好了证书依然无法抓包

证书验证

什么是单向证书验证

单向认证流程中, 服务器端保存着公钥证书和私钥两个文件, 流程如下:
img

  1. 客户端发起建立 Https 连接请求, 将 SSL 协议版本的信息发送给服务器端;
  2. 服务器端将本机的公钥证书发送给客户端;
  3. 客户端读取公钥证书, 取出服务端公钥;
  4. 客户端生成一个随机数(秘钥 R), 用刚才得到的服务器公钥去加密这个随机数形成密文, 发送给服务端;
  5. 服务端用自己的私钥去解密密文, 得到秘钥 R;
  6. 服务端和客户端在之后的通讯中就使用秘钥 R 进行通信;

什么是双向证书验证

双向认证, 客户端和服务器端都需要验证对方的身份, 在建立 Https 连接的过程中, 握手的流程比单向认证多了几步; 单向认证过程, 客户端从服务器端下载服务器端公钥证书进行验证, 然后建立安全通信通道; 双向验证中, 客服端除了需要从服务器端下载服务器的公钥证书进行验证之外, 还需要把客户端的公钥证书上传到服务器端给服务器端进行验证, 等双方都认证通过了, 才开始建立安全通信通道进行数据传输;

img

  1. 客户端发起建立 Https 连接请求, 将 SSL 协议版本的信息发送到服务器端;
  2. 服务器端将本机的公钥证书发送给客户端;
  3. 客户端读取公钥证书, 取出服务器公钥;
  4. 客户端将客户端的公钥证书发送给服务器端;
  5. 服务器端使用根证书解密客户端公钥证书, 拿到客户端公钥;
  6. 客户端发送自己支持的加密方案给服务器端;
  7. 服务器端根据自己和客户端支持的方法, 选择一个双方都能接收的加密方案, 使用客户端的公钥加密后发送给客户端;
  8. 客户端使用自己的私钥去解密密文, 得到秘钥 R;
  9. 服务器和客户端在后续通讯中使用秘钥 R 进行通信;

双向证书获取客户端证书和密码

关键点:

  1. 证书密码
  2. cer 或者 p12 证书

通杀方案:
通过 r0ysuer0capture 进行 hook , 获取到证书;
python r0capture.py -U -f cn.soulapp.android -v >> soul3.txt
img
导出后的证书位于/sdcard/Download/包名xxx.p12路径,导出多次,每一份均可用,密码默认为:r0ysue,推荐使用keystore-explorer打开查看证书

手动方案:
例如 soul apk, 采用了双向证书验证;
对 apk 进行解包, tree 找到 client.cer 和 client.p12;
在 jadx 中进行反编译, 直接搜索 client.p12 定位到对应的 load 方法
img
img
直接 hook 该方法就可以获取到证书的秘钥

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
///<reference path='./index.d.ts'/>

function hook_password() {
Java.perform(function () {
var KeyStore = Java.use("java.security.KeyStore");
KeyStore.load.overload('java.io.InputStream', '[C').implementation = function (inputStream, password) {
console.log("password is", JSON.stringify(password));
return this.load(inputStream, password);
}
})
}

setImmediate(hook_password)

// }%2R+\OSsjpP!w%X

如何将客户端证书配置到抓包工具

charles -> Proxy -> SSL Proxying Settings -> Server Certificates -> Import P12 or PEM -> 输入密码
img

requests 指定证书请求

安装 : pip install requests_pkcs12

1
2
3
from requests_pkcs12 import get

r = get('https://example.com/test', pkcs12_filename='clientcert.p12', pkcs12_password='correcthorsebatterystaple')

代理检测

代理检测原理

检测用户是否使用了代理进行请求, 如果使用了代理, 那么很可能是中间人攻击, 强制中断请求;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*
* 判断设备 是否使用代理上网
* */
private boolean isWifiProxy(Context context) {
final boolean IS_ICS_OR_LATER = Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH;
String proxyAddress;
int proxyPort;
if (IS_ICS_OR_LATER) {
proxyAddress = System.getProperty("http.proxyHost");
String portStr = System.getProperty("http.proxyPort");
proxyPort = Integer.parseInt((portStr != null ? portStr : "-1"));
} else {
proxyAddress = android.net.Proxy.getHost(context);
proxyPort = android.net.Proxy.getPort(context);
}
return (!TextUtils.isEmpty(proxyAddress)) && (proxyPort != -1);
}

VPN 抓包

常用的抓包软件都是通过 wifi 设置 http 代理的方式进行抓包, 只是在应用层进行抓包, 很容易被检测和绕过;

使用以下 api 可以快速检测是否使用了 http 代理, 导致抓包失效

1
2
System.getProperty("http.proxyHost")`
`System.getProperty("http.proxyPort")

VPN 抓包属于网络层, 开启 VPN 之后会在手机上新增一个虚拟网卡, 所有的流量都会从这个网卡上经过, 而且可以逃过上述两个 api 的检测;

VPN 检测和绕过

  1. java.net.NetworkInterface.getName()
    该方法通过检测返回结果是否为 tun0ppp0 来判断是否使用了 vpn ; bypass 的方法也比较简单, hook 这个方法, 然后返回 "rmnet_data1" 即可;
  2. android.net.ConnectivityManager.getNetworkCapabilities()
    该方法的返回值为 null 即可不被检测

SSL Pinning

什么是 SSL Pinning

  • SSL Pinning 是一种防止中间人攻击的技术, 主要机制是: 客户端发起请求 -> 收到服务器发来的证书进行校验 -> 如果收到的证书不被客户端信任, 那么就直接断开请求;
  • 客户端预置了一份服务端的证书, 防止中间人攻击时被中间人伪造的服务端证书欺骗;
  • 使用了 SSL pinning 技术的 App, 只信任指定的证书, 就算将自签名证书按照到系统目录中, App 也不会信任; 以此来对抗中间人攻击;
  1. 证书固定: 开发者将 SSL 证书的某些字节码硬编码在应用程序中; 当应用程序与服务器通信时, 它将检查证书中是否存在相同的字节码; 如果存在, 则应用程序将请求发送到服务器; 如果字节码不匹配, 它将抛出 SSL 证书错误; 这样可以防止攻击者使用自己的自签名证书;
  2. 公钥固定: 在客户端访问网站进行公钥固定中, 服务器将其公钥固定在客户端中, 当客户端重新访问同一个网站时, 服务器将标识其公共秘钥以检测连接的完整性;

双向验证和 SSL Pinning 的区别

  • SSL Pinning 是客户端锁定服务器的证书, 在要与服务器进行交互的时候, 服务器端会将证书发送给客户端, 客户端调用函数对服务器端发来的证书进行校验, 与本地存放的的证书(一般存放在/asset 或/res/raw)进行对比;
  • 双向证书认证是添加了客户端向服务器发送证书, 服务器对客户端的证书进行校验的部分;

如何定位/绕过已知通信框架的证书绑定

  1. 常见的 http 框架, 可以使用 objection 进行 hook : android sslpinning disable
    • Traditional HttpsURLConnection
    • OkHTTP
    • Retrofit (Wraps OkHTTP)
    • Volley (Uses a TrustManager)
    • Picasso (Uses a TrustManager)
  2. objection 未覆盖的可以使用 ObjectionUnpinningPlus 进行
    • SSLcontext(ART only)
    • okhttp
    • webview
    • XUtils(ART only)
    • httpclientandroidlib
    • JSSE
    • network_security_config (android 7.0+)
    • Apache Http client (support partly)
    • OpenSSLSocketImpl
    • TrustKit
    • Cronet
  3. r0capture 应用层抓包通杀
    • spawn 模式: python3 r0capture.py -U -f com.qiyi.video -v
    • attach 模式: python3 r0capture.py -U com.qiyi.video -v
    • -p fileName.pcap : 抓包并保存为 pcap 文件, 后续使用 Wireshark 进行分析
    • >> fileName.txt: 将抓包内容重定向到指定文本中, 方便过滤
    • 客户端证书导出功能, 默认开启, 需要用 spawn 模式运行, 需要手动给 app 添加存储卡读取权限;导出后的证书位于/sdcard/Download/包名xxx.p12路径, 密码默认r0ysue;
    • -H,用于Frida-server监听在非标准端口时的连接

如何定位/绕过未知框架的证书绑定

  1. Hook Java 的 File 类构造函数, 证书肯定要被读取到内存中, 找到证书路径;
  2. 检测客户端证书的读取
  3. 打印调用栈, 定位到证书绑定代码

大众点评通用抓包

1
2
3
4
5
6
Java.perform(function () {
var cls = Java.use("com.dianping.nvnetwork.tunnel2.a");
cls.isSocketConnected.overload().implementation = function () {
return false;
};
});

淘系抓包

使用Charles、Fiddle等抓包工具对淘系App进行抓包时,你会发现总是抓不到包,出现请求不走Charles代理的情况。这是因为淘系app底层网络通信的协议并不是普通的http协议,而是自己实现的一套私有协议Spdy。

https://kevinspider-1258012111.cos.ap-shanghai.myqcloud.com/2020-12-22-062221.jpg

所以我们只要通过hook将是否使用spdy返回false既可;

1
2
3
4
5
6
Java.perform(function () {
var SwitchConfig = Java.use('mtopsdk.mtop.global.SwitchConfig');
SwitchConfig.nQ.overload().implementation = function () {
return false;
}
});

美团外卖抓包

https://zckun.github.io/2019/09/13/python-mtwm-capture/

1
2
3
iptable -A INPUT -s ***.**.***.181 -j DROP #屏蔽`
`iptable -D INPUT -s ***.**.***.181 -j DROP #解除屏蔽`
`service iptables save

r0capture 抓包分析

Hook 抓取 Http 包

原理

hook 点

frida (参考 r0capture)

Hook 抓取 Https 包

原理

结论

Hook 点 (如何确定 SSL 库的关键 API)

协议使用了系统中的 SSL 类库

java

native

协议使用了自我集成的 SSL 类库

java