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很容易检测到代理。
我的经验:很多App会检测系统代理。如果检测到代理,直接不走HTTP,或者干脆断网。这时候你就得换VPN模式了。

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]];
注意:NSURLProtocol只能拦截基于URL Loading System的请求。如果App用C++的libcurl或者直接写socket,NSURLProtocol就无能为力了。我曾经遇到一个直播App,弹幕用的是C++的WebSocket库,NSURLProtocol完全抓不到,最后只能上Frida Hook。

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。
  • 使用:在设置里开启开关,然后正常抓包即可。
避坑指南:我曾经在iOS 12上用过SSL Kill Switch 2,结果发现它对某些App无效。后来查了源码才知道,它只Hook了AFNetworking和系统NSURLSession的验证方法,如果App自己实现了SSL验证(比如用OpenSSL),它就管不了了。

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
核心思路:不管App用什么库,只要它最终调用了系统的Security framework或者常见的第三方库(AFNetworking、Alamofire),我们都能Hook。关键是找到那个“验证通过/失败”的返回值,把它改成永远成功。

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)
"
我的经验:如果找不到.proto文件,你可以用protoc的--decode_raw选项,它会尝试自动解析字段。虽然字段名会变成数字(field_1, field_2...),但至少你能看到数据结构。我曾经靠这个逆向了一个直播App的礼物协议,发现它居然把礼物ID和用户ID放在一个int64里用位运算拆分,真是服了。

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)
重要提醒:这只是技术演示。实际逆向过程中,你可能还需要处理心跳包、鉴权token、加密等。而且,未经授权逆向和模拟他人服务是违法的。请只在合法授权范围内使用这些技术。

19.6 本章知识体系

下面这张图总结了iOS网络协议逆向的核心流程和工具链。你可以把它当作一个“作战地图”,遇到具体问题时,按图索骥就好。

iOS网络协议逆向知识体系 抓包配置 Proxy / VPN 请求拦截 NSURLProtocol Hook SSL Pinning绕过 Frida / SSL Kill Switch 2 协议分析与数据提取 识别协议类型(HTTP/WebSocket/自定义TCP) → 提取二进制数据 Protobuf逆向 获取.proto / 反推字段 → 反序列化 协议模拟与自动化 编写脚本模拟客户端行为 实战案例:直播App弹幕协议逆向

这张图从左到右展示了逆向的完整链路:先搞定抓包,再拦截请求,绕过SSL验证,然后分析协议结构,最后逆向Protobuf并模拟。每一步都有对应的工具和技术,缺一不可。

好了,这一章的内容就到这。iOS网络协议逆向是个系统工程,需要耐心和细心。你可能会遇到App用了自定义加密、或者协议里混了随机数之类的骚操作——别慌,思路是一样的:找到入口,Hook关键函数,分析数据流。记住,没有解不开的协议,只有还没找到的Hook点。


公众号:蓝海资料掘金营,微信deep3321