33、网络协议逆向(二):HTTP/HTTPS协议逆向、SSL Pinning绕过
说实话,HTTP/HTTPS协议逆向是逆向工程里最常碰到的活儿。你想想看,现在哪个App不联网?只要联网,十有八九走的就是HTTP或HTTPS。我这些年做过的逆向项目里,至少有一半的时间都在跟这些协议打交道。
这一讲,咱们就聊聊怎么逆向HTTP/HTTPS协议,以及怎么绕过SSL Pinning——说白了,就是怎么让App乖乖地把加密内容吐出来。
HTTP协议逆向:基础但关键
HTTP协议本身是明文的,逆向起来相对简单。但别小看它,很多App虽然用了HTTPS,但在某些场景下还是会回退到HTTP——比如资源文件、图片、或者某些不敏感的数据请求。
我个人的习惯是,先抓HTTP的包看看。有时候你会发现一些意想不到的东西。
抓包工具的选择
常用的抓包工具就那几个:
- Burp Suite:功能最全,适合深度分析
- Fiddler:Windows上顺手,脚本扩展能力强
- Charles:Mac用户的最爱,界面简洁
- Wireshark:底层抓包,适合分析TCP层问题
我个人偏爱Burp Suite。为什么?因为它对请求修改、重放的支持做得最好。你想想看,逆向协议的时候,经常需要改几个参数看看服务器怎么回应,Burp的Repeater功能简直就是为这个场景设计的。
HTTP请求的结构分析
一个典型的HTTP请求长这样:
POST /api/v1/login HTTP/1.1
Host: app.example.com
Content-Type: application/json
User-Agent: MyApp/1.0 (Android 12; Pixel 6)
X-Request-ID: a1b2c3d4-e5f6-7890-abcd-ef1234567890
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
{"username":"test","password":"123456"}
逆向的时候,我通常会关注这几个点:
- 请求头:有没有自定义的Header?比如X-Request-ID这种,可能是防重放攻击的
- 请求体:参数是怎么组织的?JSON?Form?还是Protobuf?
- Cookie:Session是怎么管理的?有没有HttpOnly标记?
- User-Agent:有时候App会通过UA来区分版本,甚至做A/B测试
HTTPS协议逆向:真正的挑战
HTTPS比HTTP麻烦多了。因为数据是加密的,你抓到的包全是乱码。要解密HTTPS流量,核心思路就一个——让客户端信任你的CA证书。
具体怎么做?有两种主流方式:
方式一:安装代理证书(最简单)
这是最常用的方法。流程如下:
- 在抓包工具(比如Burp)里导出CA证书
- 把证书安装到手机或模拟器上
- 设置代理,让流量走抓包工具
- 抓包工具用自己的证书解密HTTPS流量
听起来简单对吧?但这里有个坑——Android 7+ 默认不信任用户安装的证书。也就是说,你装了自己的CA证书,App照样不认。
方式二:Hook SSL库(更通用)
如果方式一搞不定,那就得上Hook了。常用的工具有:
- Frida:动态插桩,可以Hook Java层的SSLContext
- Xposed:框架级Hook,适合系统层面的修改
- JustTrustMe:一个Xposed模块,专门用来绕过SSL Pinning
我个人更推荐Frida。为什么?因为它不需要重启手机,热插拔式的Hook,调试效率高很多。
SSL Pinning绕过:硬骨头怎么啃
SSL Pinning,说白了就是App在代码里写死了服务器的证书或公钥。即使你装了代理证书,App发现证书对不上,直接拒绝连接。
我遇到过最狠的一个App,它不光做了证书Pinning,还做了公钥Pinning,甚至把证书哈希值写在了native层。嗯,那一次确实折腾了我两天。
绕过方法一:Hook证书校验函数
最直接的方法,就是Hook掉证书校验的逻辑。以Android为例,常见的Hook点有:
// Java层Hook示例(Frida脚本)
Java.perform(function() {
var SSLContext = Java.use('javax.net.ssl.SSLContext');
SSLContext.init.implementation = function(km, tm, sr) {
console.log('SSLContext.init called');
// 传入空的TrustManager,信任所有证书
this.init(km, null, sr);
};
});
这段代码干了什么?它Hook了SSLContext的init方法,把TrustManager换成了空的。这样一来,任何证书都会被信任。
绕过方法二:替换Pinning逻辑
有些App用了OkHttp的CertificatePinner,代码里写死了证书哈希值:
// 原始代码
CertificatePinner pinner = new CertificatePinner.Builder()
.add("example.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
.build();
绕过方法也很简单——Hook掉CertificatePinner的check方法,让它什么都不做:
// Frida Hook OkHttp CertificatePinner
Java.perform(function() {
var CertificatePinner = Java.use('okhttp3.CertificatePinner');
CertificatePinner.check.implementation = function(host, peerCertificates) {
console.log('Bypassing SSL Pinning for: ' + host);
// 直接返回,不校验
return;
};
});
绕过方法三:Native层Pinning
这是最头疼的情况。App把证书校验逻辑写在了C/C++层,Java层根本看不到。遇到这种情况,我一般会:
- 先用Frida的Stalker跟踪native函数调用
- 找到SSL_read/SSL_write等OpenSSL函数
- Hook这些函数,直接修改返回值
说实话,native层的Pinning没有通用解法。每个App的实现都不一样,只能具体问题具体分析。
知识体系总览
下面这张图,是我整理的本章节核心知识结构:
实战中的避坑指南
做了这么多年逆向,踩过的坑不少。分享几个典型的:
- 证书安装后还是抓不到包:检查一下代理设置对不对。我曾经花了半小时才发现是VPN和代理冲突了。
- Hook脚本不生效:先确认App有没有做反调试。有些App会检测Frida,检测到了直接退出。
- 抓到的包是乱码:看看是不是用了自定义加密。有些App在HTTPS之上又套了一层加密,比如AES加密请求体。
- App闪退:可能是证书校验失败导致的crash。试试在Hook里加个try-catch,别让异常抛出去。
总结
HTTP/HTTPS协议逆向,说白了就是两件事:抓到包和解出密。抓包靠工具,解密靠Hook。SSL Pinning是App的最后一道防线,但也不是无解的——Java层的Pinning用Frida几行脚本就能绕过,native层的虽然麻烦,但只要耐心分析,总能找到突破口。
我个人觉得,逆向协议最关键的还是思路。工具和脚本都是死的,但你的脑子是活的。遇到一个没见过的协议,先别急着上Hook,静下心来分析一下流量特征,往往能发现一些规律。
好了,这一讲就到这里。记住,多动手,多踩坑,经验就是这么积累起来的。
公众号:蓝海资料掘金营,微信deep3321