https://mp.weixin.qq.com/s/y12SCO7PMwNnKLuvrA2ijA 【iOS逆向】Instagram抓包小记
抓包:mitmproxy
版本:419.0.0
重点是学习抓包。
使用之前老的方法已经不能够进行抓包了,所以来研究下新的抓包方案。
根据参考文章,
用 Frida hook NSBundle pathForResource:ofType:,运行时观察 App 加载了哪些资源文件:
运行的时候记得先暂时不要开抓包。
var NSBundle = ObjC.classes.NSBundle['- pathForResource:ofType:'].implementation;
Interceptor.attach(NSBundle, {
onEnter: function (args) {
var source = ObjC.Object(args[2]);
console.log(`pathForResource: ${ObjC.Object(args[2])} ofType: ${ObjC.Object(args[3])}`);
},
onLeave: function (retval) {
}
})输出如下
muyang@Mac-Pro ins % frida -U -f com.burbn.instagram -l ssl.js
____
/ _ | Frida 16.7.19 - A world-class dynamic instrumentation toolkit
| (_| |
> _ | Commands:
/_/ |_| help -> Displays the help system
. . . . object? -> Display information about 'object'
. . . . exit/quit -> Exit
. . . .
. . . . More info at https://frida.re/docs/home/
. . . .
. . . . Connected to iPhone (id=d4be7c38721a15dd242e268d99e4700afd6e1521)
Spawned `com.burbn.instagram`. Resuming main thread!
[iPhone::com.burbn.instagram ]-> pathForResource: SessionlessLatencyConfig ofType: json.gz
pathForResource: zh-Hans ofType: lproj
pathForResource: fbt_language_pack ofType: bin
pathForResource: ds ofType: g10p
pathForResource: metal_rt ofType: metallib
pathForResource: IGTabBarController ofType: nib
pathForResource: IGTabBar ofType: nib
pathForResource: IGMainAppScrollingContainerViewController ofType: nib
pathForResource: IGMainAppScrollingContainerView ofType: nib
pathForResource: CACerts ofType: plist
pathForResource: FacebookSDKStrings ofType: bundle
pathForResource: prepackaged_bloks_config ofType: json重点关注 CACerts,在ins目录下可以找到该文件。

CACerts.plist 是什么?这是 Instagram 自带的 CA 根证书信任列表(Certificate Authority Root Certificates)。
具体来说:
Instagram 不依赖系统的根证书信任链,而是自己打包了一套受信任的 CA 根证书。这是典型的 SSL/TLS Certificate Pinning 策略:
这就是为什么 直接装 Charles/mitmproxy 证书抓不了 Instagram 的包 的原因之一——App 内部有自己的信任锚点(Trust Anchor)。要抓包 Instagram,通常需要:
CACerts.plist,把你的抓包工具的 CA 证书加进去SecTrustEvaluate 等系统 API 让验证永远返回成功你 Frida hook 到的 pathForResource: CACerts ofType: plist 就是 Instagram 在启动时加载这个自定义信任列表的过程。
但是我们不用文章接下去的方法,因为我测试是没有成功的,并且 文章说的 X509_verify_cert 我测试的时候如果证书没过是没有调用的。所以去其他的方法。
既然我们已经拿到了证书名称,接着我们去询问Ai,开发的过程中
反重力 opus4.6 模型
CACerts.plist 一般怎么传递校验的,写一段代码看下接下来是Ai的回答
好的,下面是 Instagram 这种 App 使用 CACerts.plist 进行 SSL Certificate Pinning 的典型实现流程:
// IGCertificatePinningManager.m
@interface IGCertificatePinningManager : NSObject
@property (nonatomic, strong) NSArray<SecCertificateRef> *pinnedCACerts;
+ (instancetype)sharedInstance;
@end
@implementation IGCertificatePinningManager
+ (instancetype)sharedInstance {
static IGCertificatePinningManager *instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
});
return instance;
}
- (instancetype)init {
self = [super init];
if (self) {
[self loadPinnedCertificates]; // 启动时加载一次
}
return self;
}
- (void)loadPinnedCertificates {
// ========== 这就是你 hook 到的那一次调用 ==========
NSString *path = [[NSBundle mainBundle] pathForResource:@"CACerts" ofType:@"plist"];
// 读取 plist —— 得到一个 NSArray<NSData *>(143个 DER 编码的证书)
NSArray<NSData *> *certDataArray = [NSArray arrayWithContentsOfFile:path];
// 把 NSData 转换成 SecCertificateRef 对象,缓存到内存
NSMutableArray *certs = [NSMutableArray array];
for (NSData *certData in certDataArray) {
SecCertificateRef cert = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certData);
if (cert) {
[certs addObject:(__bridge id)cert];
CFRelease(cert);
}
}
self.pinnedCACerts = [certs copy]; // 缓存,后续不再读文件
NSLog(@"Loaded %lu pinned CA certificates", (unsigned long)self.pinnedCACerts.count);
}
@end// NSURLSession 的认证挑战代理方法
- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler
{
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;
// ========== 核心:用自带的 CA 列表替代系统默认的信任锚点 ==========
NSArray *pinnedCerts = [IGCertificatePinningManager sharedInstance].pinnedCACerts;
// 设置自定义锚点证书(只信任这 143 个 CA)
OSStatus status = SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCerts);
// 关键!设为 YES = 只信任我们提供的锚点;设为 NO = 同时也信任系统证书
SecTrustSetAnchorCertificatesOnly(serverTrust, true);
// 执行证书链验证
CFErrorRef error = NULL;
bool trusted = SecTrustEvaluateWithError(serverTrust, &error);
if (trusted) {
// ✅ 服务器证书链可以追溯到我们 CACerts.plist 中的某个 CA
NSURLCredential *credential = [NSURLCredential credentialForTrust:serverTrust];
completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
} else {
// ❌ 验证失败 —— 证书链不在我们的信任列表里
// 比如你用 Charles 代理,Charles 的 CA 不在这 143 个里面,就会走到这里
NSLog(@"SSL Pinning 验证失败: %@", error);
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
}
} else {
completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
}
}App 启动
│
▼
pathForResource:@"CACerts" ofType:@"plist" ← 你 hook 到的就是这一步(只调用1次)
│
▼
读取 plist → 143 个 NSData (DER 证书)
│
▼
转换为 SecCertificateRef 数组 → 缓存到单例内存
│
╔══════════════════════════════════════════════════╗
║ 之后每次 HTTPS 请求(循环多次): ║
║ ║
║ 服务器返回证书 → SecTrustRef ║
║ │ ║
║ ▼ ║
║ SecTrustSetAnchorCertificates(trust, 缓存的CA) ║
║ │ ║
║ ▼ ║
║ SecTrustSetAnchorCertificatesOnly(trust, true) ║
║ │ ║
║ ▼ ║
║ SecTrustEvaluateWithError() ║
║ │ ║
║ ┌──┴──┐ ║
║ ▼ ▼ ║
║ ✅信任 ❌拒绝(如 Charles/mitmproxy 的证书) ║
╚══════════════════════════════════════════════════╝// 方式1:hook SecTrustEvaluateWithError,让它永远返回 true
Interceptor.attach(Module.findExportByName(null, "SecTrustEvaluateWithError"), {
onLeave: function(retval) {
retval.replace(0x1); // 返回 true,表示信任
}
});
// 方式2:hook SecTrustSetAnchorCertificates,阻止设置自定义锚点
Interceptor.attach(Module.findExportByName(null, "SecTrustSetAnchorCertificates"), {
onEnter: function(args) {
// 不设置自定义锚点,让系统用默认信任存储(包含你安装的 Charles CA)
},
onLeave: function(retval) {
retval.replace(0x0); // 返回 errSecSuccess
}
});
// 方式3:hook SecTrustSetAnchorCertificatesOnly,允许系统证书
Interceptor.attach(Module.findExportByName(null, "SecTrustSetAnchorCertificatesOnly"), {
onEnter: function(args) {
args[1] = ptr(0x0); // 改为 false,同时信任系统证书
}
});核心思路:两个关键 API 是 SecTrustSetAnchorCertificates(设置自定义 CA 列表)和 SecTrustSetAnchorCertificatesOnly(是否排斥系统证书)。只要 hook 掉这两个中的任意一个,就能让系统同时信任你安装的 Charles/mitmproxy 证书。
但是上面的frida代码并不能过掉代理校验,我们去打开IDA分析其他的方法看有无。
从上面的关键API入手。搜索 SecTrustSetAnchorCertificates 定位到如下函数
// attributes: thunk
OSStatus __cdecl SecTrustSetAnchorCertificates(SecTrustRef trust, CFArrayRef anchorCertificates)
{
return _SecTrustSetAnchorCertificates(trust, anchorCertificates);
}查看交叉引用。定位到 sub_46B8D4 这个函数


可以询问AI sub_46B8D4 函数的作用是什么。
这个函数位于 FBSharedFramework 中,是 Instagram/Facebook 自研的证书验证器,函数签名大致为:
int64_t MNSCertificateVerifier_verify(void *self, CFArrayRef certChain, CFStringRef hostname)执行流程
1. CRL 吊销检查(0x46B918 - 0x46B968)
MNSCertificateVerifierAppleCrlUtil 处理序列号self+224 处的 CFSet(吊销列表),如果证书序列号在集合中 → 返回错误 "Certificate is revoked."2. 创建 SecTrust 并做标准验证(0x46B978 - 0x46B9B8)
SecPolicyCreateSSL(true, hostname) — 创建 SSL 策略,绑定域名SecTrustCreateWithCertificates(certChain, policy, &trust) — 创建信任对象self+24(一个 flag)为 true,会走一段特殊逻辑:调用 SecTrustSetAnchorCertificates 设置自定义锚点证书,然后 SecTrustSetAnchorCertificatesOnly(trust, false) 允许系统证书也参与验证SecTrustEvaluateWithError(trust, &error) — 执行标准 iOS 证书链验证3. 域名白名单匹配(0x46B9E4 - 0x46BA4C)
self+32 处的一个数组(最多 24 个条目,每条 64 字节),逐一与 hostname 比较strcmp 和通配符 sub_51C1F4(可能是 wildcard match)4. 过期时间检查(0x46BA7C)
self+16 有一个 CFDate,检查当前时间是否已超过该日期5. 证书 Pin 验证 — 核心(0x46BA80 - 0x46BAF8)
sub_3AF0B0(很可能是计算证书的 SHA256 指纹)off_2218550 处存储的 18 个预置 pin 值(144 / 8 = 18 个指针)逐一比较self+24 flag 为 true,还会尝试用证书数据重新构造证书再比较一次"Pinning validation failed."两个调用者
sub_3B8158 — TLS 握手阶段的证书验证回调
sub_51BCCC),如果通过就跳过sub_46B8D4 做完整验证0xFFFFFFFF(-1),成功返回 0sub_130E120 — TLS 1.3 CertificateVerify 签名验证
SecKeyVerifySignature),支持 ECDSA-SHA256/384/512 和 RSA-PSS-SHA256sub_46B8D4 做证书 pinning 检查关键结论
这个函数就是 Instagram SSL Pinning 的核心。要绕过它,有三个切入点:
off_2218550 — 存储了 18 个预置的证书指纹 pin 值,这就是 CACerts.plist 里那些 SHA256 hash 对应的运行时数据。替换 CACerts.plist ` 文件本质上就是替换这些 pin 值。sub_46B8D4 让它返回 0 — 这是最直接的方式,一个 hook 点搞定。Interceptor.attach(Module.findBaseAddress("FBSharedFramework").add(0x46b8d4), {
onEnter: function(args) {
// 不设置自定义锚点,让系统用默认信任存储(包含你安装的 Charles CA)
},
onLeave: function(retval) {
console.log("0x46b8d4",retval)
retval.replace(0x0); // 返回 errSecSuccess
}
});这样就可以抓到包了。
后续如果更新。
"Pinning validation failed"
"Certificate is revoked"
"verify/trust init failed"这个函数里有 "Pinning validation failed." 和 "Certificate is revoked.",非常明显。
SSL 验证绕不开这几个系统 API:
SecTrustEvaluateWithErrorSecTrustCreateWithCertificatesSecTrustSetAnchorCertificatesSecPolicyCreateSSL在 IDA 里对这些函数按 X 查交叉引用,看谁调用了它们。