19、iOS网络协议逆向:iOS抓包配置(Proxy、VPN)、NSURLProtocol Hook、SSL Pinning绕过(Frida/SSL Kill Switch 2)、Protobuf逆向、实战:逆向一个直播App的弹幕协议
好,咱们今天聊点硬核的。iOS网络协议逆向,说白了就是搞清楚App在跟服务器“说悄悄话”时,到底说了什么。我这些年做逆向,遇到最多的场景就是:App跑得挺欢,但你就是不知道它发了什么包、收了什么数据。尤其是直播App,弹幕、礼物、心跳,全是自定义协议,抓包工具根本看不懂。
这一章,我会带你从最基础的抓包配置开始,一路杀到SSL Pinning绕过、Protobuf逆向,最后拿一个直播App的弹幕协议练手。嗯,内容有点多,但每一步我都会告诉你“为什么这么做”以及“我踩过哪些坑”。
19.1 iOS抓包基础:Proxy与VPN两种模式
抓包,说白了就是中间人攻击。只不过我们是合法的“中间人”。iOS上主流就两种方式:HTTP代理和虚拟VPN。
19.1.1 HTTP代理抓包(Charles / Burp Suite)
这种方式最简单。你把手机WiFi代理设到电脑的Charles或Burp上,所有HTTP/HTTPS流量都会经过你的电脑。
- 优点:配置简单,上手快,适合HTTPS解密。
- 缺点:只能抓HTTP/HTTPS,非HTTP协议(比如自定义TCP)抓不到。而且App很容易检测到代理。
19.1.2 VPN抓包(如:rvi、Shadowrocket + mitmproxy)
VPN模式是在iOS上创建一个虚拟网卡,所有流量都经过它。你可以用mitmproxy配合VPN工具,抓到所有TCP/UDP流量。
- 优点:能抓所有流量,包括非HTTP协议。App很难检测VPN(相比代理)。
- 缺点:配置稍复杂,需要安装描述文件。速度会慢一点。
我个人习惯是:先用Charles快速验证,如果App有反代理机制,立刻切到VPN模式。你想想看,一个直播App,弹幕走的是WebSocket或者自定义TCP,你用代理根本抓不到,必须上VPN。
19.2 NSURLProtocol Hook:拦截App内部网络请求
NSURLProtocol是iOS系统提供的一个URL加载系统钩子。你可以注册自定义的NSURLProtocol子类,拦截所有基于URL Loading System的请求(包括NSURLSession、UIWebView等)。
为什么要用这个?因为有些App不走系统代理,也不走VPN,它自己用NSURLSession直接发请求。这时候,NSURLProtocol就是你的“内应”。
// 自定义Protocol示例
@interface MyProtocol : NSURLProtocol
@end
@implementation MyProtocol
+ (BOOL)canInitWithRequest:(NSURLRequest *)request {
// 拦截所有HTTP请求
if ([request.URL.scheme isEqualToString:@"http"] ||
[request.URL.scheme isEqualToString:@"https"]) {
return YES;
}
return NO;
}
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
return request;
}
- (void)startLoading {
// 在这里可以修改请求、记录数据、或者直接返回假数据
NSLog(@"[Hook] %@", self.request.URL.absoluteString);
// 继续原请求...
}
- (void)stopLoading {
// 清理工作
}
@end
// 注册
[NSURLProtocol registerClass:[MyProtocol class]];
19.3 SSL Pinning绕过:Frida与SSL Kill Switch 2
SSL Pinning是App用来防中间人攻击的手段。App在代码里固定了服务器的证书或公钥,如果抓包工具提供的证书不匹配,App直接拒绝连接。
说白了,就是App只认“自己人”的证书。你拿Charles的假证书去骗它,它不认。
19.3.1 使用SSL Kill Switch 2(越狱设备)
SSL Kill Switch 2是一个Cydia插件,它通过MobileSubstrate Hook了iOS的SSL验证函数,强制信任所有证书。
- 安装:在Cydia搜索“SSL Kill Switch 2”,安装后重启SpringBoard。
- 使用:在设置里开启开关,然后正常抓包即可。
19.3.2 使用Frida绕过SSL Pinning(非越狱也可)
Frida是动态插桩工具,可以在运行时Hook任意函数。绕过SSL Pinning,本质上就是Hook掉证书验证相关的函数,让它们永远返回“验证通过”。
// Frida脚本:绕过iOS SSL Pinning
// 适用于NSURLSession、AFNetworking等
if (ObjC.available) {
// Hook NSURLSession的didReceiveChallenge
var NSURLSession = ObjC.classes.NSURLSession;
var oldImp = NSURLSession['- URLSession:didReceiveChallenge:completionHandler:'];
if (oldImp) {
Interceptor.attach(oldImp.implementation, {
onEnter: function(args) {
// 修改challenge为允许所有证书
var challenge = new ObjC.Object(args[3]);
var protectionSpace = challenge.protectionSpace();
protectionSpace.setAuthenticationMethod_('NSURLAuthenticationMethodServerTrust');
// 直接调用completionHandler,传NSURLSessionAuthChallengeUseCredential
var completionHandler = new ObjC.Block(args[4]);
var credential = ObjC.classes.NSURLCredential.credentialForTrust_(protectionSpace.serverTrust());
completionHandler(0, credential);
}
});
console.log('[+] Hooked NSURLSession challenge');
}
// Hook AFNetworking的验证方法
var AFSecurityPolicy = ObjC.classes.AFSecurityPolicy;
if (AFSecurityPolicy) {
var methods = AFSecurityPolicy.$methods;
methods.forEach(function(m) {
if (m.indexOf('evaluateServerTrust:forDomain:') !== -1) {
Interceptor.attach(AFSecurityPolicy[m].implementation, {
onLeave: function(retval) {
// 永远返回YES
retval.replace(0x1);
}
});
console.log('[+] Hooked AFNetworking: ' + m);
}
});
}
} else {
console.log('[-] Objective-C runtime not available');
}
你把这个脚本保存成ssl_bypass.js,然后用Frida注入到App里:
frida -U -f com.example.app -l ssl_bypass.js --no-pause
19.4 Protobuf逆向:从二进制到可读数据
Protobuf(Protocol Buffers)是Google的序列化协议,很多App用它来传输结构化数据。抓包抓到的是一堆二进制乱码,根本看不懂。
要逆向Protobuf,你需要两样东西:.proto文件(定义数据结构)和序列化后的二进制数据。
19.4.1 如何获取.proto文件
- 从App二进制里提取:很多App会把.proto文件编译进二进制里,或者以资源文件形式打包。你可以用class-dump、Hopper、或者直接搜字符串“message”、“field”等关键词。
- 从网络流量反推:如果没有.proto文件,你可以根据二进制数据的规律反推字段类型和编号。比如Varint编码、固定长度字段等。
19.4.2 使用protoc反序列化
假设你拿到了.proto文件,比如danmu.proto:
syntax = "proto3";
message DanmuMessage {
int32 uid = 1;
string content = 2;
int64 timestamp = 3;
int32 room_id = 4;
}
然后用protoc反序列化抓到的二进制数据:
# 先把二进制数据保存到文件
echo "0A0B..." | xxd -r -p > danmu.bin
# 用protoc反序列化
protoc --decode_raw < danmu.bin
# 或者用python
python3 -c "
import sys
sys.path.append('/path/to/protobuf')
import danmu_pb2
msg = danmu_pb2.DanmuMessage()
with open('danmu.bin', 'rb') as f:
msg.ParseFromString(f.read())
print(msg)
"
19.5 实战:逆向一个直播App的弹幕协议
好,理论说完了,咱们来真的。假设我们要逆向一个直播App的弹幕协议。目标:搞清楚弹幕是怎么发送和接收的,然后写一个脚本模拟发弹幕。
19.5.1 第一步:确定网络协议类型
先用Charles抓包,发现HTTP请求很少,而且没有弹幕相关的。说明弹幕不是走HTTP。
换VPN模式,用Wireshark抓包。发现App连接了一个IP地址的TCP端口(比如8080),而且数据全是二进制,没有明显的HTTP头。嗯,大概率是自定义TCP协议或者WebSocket。
19.5.2 第二步:分析数据包结构
用Wireshark的“Follow TCP Stream”功能,把整个TCP流导出来。你会发现数据包有固定的格式:
- 前4字节:数据包长度(大端序)
- 接下来4字节:协议类型(比如0x0001表示弹幕,0x0002表示礼物)
- 后面是Protobuf编码的数据
你看,这就是典型的“自定义头部 + Protobuf Body”结构。
19.5.3 第三步:提取Protobuf数据并反序列化
从TCP流里提取出Protobuf部分,然后用protoc --decode_raw尝试解析。你会看到类似这样的输出:
1: 123456 // uid
2: "666666" // 弹幕内容
3: 1700000000 // 时间戳
4: 88888 // 房间号
嗯,基本可以确定这就是弹幕消息了。字段1是用户ID,字段2是内容,字段3是时间戳,字段4是房间号。
19.5.4 第四步:编写模拟脚本
现在我们可以写一个Python脚本,模拟App发送弹幕:
import socket
import struct
import danmu_pb2
def send_danmu(host, port, uid, content, room_id):
# 构建Protobuf消息
msg = danmu_pb2.DanmuMessage()
msg.uid = uid
msg.content = content
msg.timestamp = int(time.time())
msg.room_id = room_id
# 序列化
body = msg.SerializeToString()
# 构建自定义头部
header = struct.pack('!II', len(body), 0x0001) # 0x0001表示弹幕类型
# 发送
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((host, port))
sock.send(header + body)
sock.close()
# 使用示例
send_danmu('live.example.com', 8080, 123456, '666666', 88888)
19.6 本章知识体系
下面这张图总结了iOS网络协议逆向的核心流程和工具链。你可以把它当作一个“作战地图”,遇到具体问题时,按图索骥就好。
这张图从左到右展示了逆向的完整链路:先搞定抓包,再拦截请求,绕过SSL验证,然后分析协议结构,最后逆向Protobuf并模拟。每一步都有对应的工具和技术,缺一不可。
好了,这一章的内容就到这。iOS网络协议逆向是个系统工程,需要耐心和细心。你可能会遇到App用了自定义加密、或者协议里混了随机数之类的骚操作——别慌,思路是一样的:找到入口,Hook关键函数,分析数据流。记住,没有解不开的协议,只有还没找到的Hook点。
公众号:蓝海资料掘金营,微信deep3321