iOS逆向_古法逆向_Instagram最新版抓包

知识点

参考

https://mp.weixin.qq.com/s/y12SCO7PMwNnKLuvrA2ijA 【iOS逆向】Instagram抓包小记

环境

抓包:mitmproxy

版本:419.0.0

重点是学习抓包。

使用之前老的方法已经不能够进行抓包了,所以来研究下新的抓包方案。

根据参考文章,

用 Frida hook NSBundle pathForResource:ofType:,运行时观察 App 加载了哪些资源文件:

运行的时候记得先暂时不要开抓包。

bash 复制代码
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) {
    }
})

输出如下

bash 复制代码
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)。

具体来说:

内容

  • • 一个 plist 数组 ,包含了 143 个 DER 编码的 X.509 CA 根证书

  • • 里面的证书包括各种知名 CA,例如:

  • • COMODO RSA / ECC Certification Authority

  • • GlobalSign Root CA

  • • Telia Root CA

  • • Buypass Class 2 Root CA

  • • Go Daddy Root Certificate Authority

  • • ......等等

作用:SSL Certificate Pinning(证书锁定)

Instagram 不依赖系统的根证书信任链 ,而是自己打包了一套受信任的 CA 根证书。这是典型的 SSL/TLS Certificate Pinning 策略:

对逆向/抓包的意义

这就是为什么 直接装 Charles/mitmproxy 证书抓不了 Instagram 的包 的原因之一------App 内部有自己的信任锚点(Trust Anchor)。要抓包 Instagram,通常需要:

  • • 用 Frida hook 掉 SSL 验证逻辑

  • • 或者 直接 patch 这个 CACerts.plist,把你的抓包工具的 CA 证书加进去

  • • 或者 hook SecTrustEvaluate 等系统 API 让验证永远返回成功

你 Frida hook 到的 pathForResource: CACerts ofType: plist 就是 Instagram 在启动时加载这个自定义信任列表的过程。


定位

但是我们不用文章接下去的方法,因为我测试是没有成功的,并且 文章说的 X509_verify_cert 我测试的时候如果证书没过是没有调用的。所以去其他的方法。

既然我们已经拿到了证书名称,接着我们去询问Ai,开发的过程中

反重力 opus4.6 模型

bash 复制代码
CACerts.plist  一般怎么传递校验的,写一段代码看下

接下来是Ai的回答

好的,下面是 Instagram 这种 App 使用 CACerts.plist 进行 SSL Certificate Pinning 的典型实现流程:

1. 加载阶段(启动时执行一次)

bash 复制代码
// 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

2. 验证阶段(每次 HTTPS 请求都执行)

bash 复制代码
// 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);
    }
}

3. 整个流程图

bash 复制代码
App 启动
   │
   ▼
pathForResource:@"CACerts" ofType:@"plist"    ← 你 hook 到的就是这一步(只调用1次)
   │
   ▼
读取 plist → 143 个 NSData (DER 证书)
   │
   ▼
转换为 SecCertificateRef 数组 → 缓存到单例内存
   │
   ╔══════════════════════════════════════════════════╗
   ║  之后每次 HTTPS 请求(循环多次):                   ║
   ║                                                  ║
   ║  服务器返回证书 → SecTrustRef                      ║
   ║       │                                          ║
   ║       ▼                                          ║
   ║  SecTrustSetAnchorCertificates(trust, 缓存的CA)   ║
   ║       │                                          ║
   ║       ▼                                          ║
   ║  SecTrustSetAnchorCertificatesOnly(trust, true)  ║
   ║       │                                          ║
   ║       ▼                                          ║
   ║  SecTrustEvaluateWithError()                     ║
   ║       │                                          ║
   ║    ┌──┴──┐                                       ║
   ║    ▼     ▼                                       ║
   ║  ✅信任  ❌拒绝(如 Charles/mitmproxy 的证书)      ║
   ╚══════════════════════════════════════════════════╝

4. 绕过方式(Frida hook 关键点)

bash 复制代码
// 方式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 定位到如下函数

bash 复制代码
// attributes: thunk
OSStatus __cdecl SecTrustSetAnchorCertificates(SecTrustRef trust, CFArrayRef anchorCertificates)
{
  return _SecTrustSetAnchorCertificates(trust, anchorCertificates);
}

查看交叉引用。定位到 sub_46B8D4 这个函数

该函数有什么特点了?有许多验证相关的字符串。下面的 sub_760CDC 没有这些字符串。

可以询问AI sub_46B8D4 函数的作用是什么。

5.sub_46B8D4 --- MNSCertificateVerifier 核心证书验证函数

这个函数位于 FBSharedFramework 中,是 Instagram/Facebook 自研的证书验证器,函数签名大致为:

bash 复制代码
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)

  • • 将 hostname 转为 C 字符串

  • • 遍历 self+32 处的一个数组(最多 24 个条目,每条 64 字节),逐一与 hostname 比较

  • • 支持两种匹配:精确 strcmp 和通配符 sub_51C1F4(可能是 wildcard match)

  • • 如果 hostname 不在白名单中(index > 0x17),直接跳过 pinning 检查,返回成功

4. 过期时间检查(0x46BA7C)

  • • 如果 self+16 有一个 CFDate,检查当前时间是否已超过该日期

  • • 超过则跳过 pinning,返回成功(相当于 pinning 有"有效期")

5. 证书 Pin 验证 --- 核心(0x46BA80 - 0x46BAF8)

  • • 遍历信任链中的每个证书

  • • 对每个证书调用 sub_3AF0B0(很可能是计算证书的 SHA256 指纹)

  • • 将指纹与 off_2218550 处存储的 18 个预置 pin 值(144 / 8 = 18 个指针)逐一比较

  • • 如果任一证书的指纹匹配任一 pin → 验证通过,返回 0(成功)

  • • 如果 self+24 flag 为 true,还会尝试用证书数据重新构造证书再比较一次

  • • 全部不匹配 → 返回错误 "Pinning validation failed."

两个调用者

sub_3B8158 --- TLS 握手阶段的证书验证回调

  • • 先做一次快速检查(sub_51BCCC),如果通过就跳过

  • • 否则调用 sub_46B8D4 做完整验证

  • • 验证失败返回 0xFFFFFFFF(-1),成功返回 0

sub_130E120 --- TLS 1.3 CertificateVerify 签名验证

  • • 先验证签名(SecKeyVerifySignature),支持 ECDSA-SHA256/384/512 和 RSA-PSS-SHA256

  • • 签名验证通过后,再调用 sub_46B8D4 做证书 pinning 检查

  • • 这是 Fizz(Facebook 的 TLS 1.3 实现)的验证路径

关键结论

这个函数就是 Instagram SSL Pinning 的核心。要绕过它,有三个切入点:

bash 复制代码
Interceptor.attach(Module.findBaseAddress("FBSharedFramework").add(0x46b8d4), {
    onEnter: function(args) {
        // 不设置自定义锚点,让系统用默认信任存储(包含你安装的 Charles CA)
    },
    onLeave: function(retval) {
        console.log("0x46b8d4",retval)
        retval.replace(0x0);  // 返回 errSecSuccess
    }
});

这样就可以抓到包了。


后续如果更新。

思路一:从字符串入手(最常用)

bash 复制代码
"Pinning validation failed"
"Certificate is revoked"
"verify/trust init failed"

这个函数里有 "Pinning validation failed.""Certificate is revoked.",非常明显。

思路二:从 API 调用入手

SSL 验证绕不开这几个系统 API:

  • SecTrustEvaluateWithError

  • SecTrustCreateWithCertificates

  • SecTrustSetAnchorCertificates

  • SecPolicyCreateSSL

在 IDA 里对这些函数按 X 查交叉引用,看谁调用了它们。

相关推荐
自学AI的鲨鱼儿7 小时前
mac npm 安装 codex 报错 npm ENOTEMPTY
macos·npm·codex
Digitally10 小时前
如何将真我(realme)手机数据传输至 iPhone
ios·智能手机·iphone
Sephiroth.Ma14 小时前
Mac 提示“Docker 已损坏,无法打开”?我这样排查后 10 分钟修好
macos·docker·容器
量子炒饭大师14 小时前
【OpenClaw修炼宝典】—— 【macOS安装篇】想玩《爪子船长》复刻版却卡在安装?OpenClaw 从零环境搭建与编译全攻略 (小白避坑指南)
macos·openclaw·小龙虾·龙虾
JFSJHFZJ14 小时前
解密iPhone核心技术,读懂苹果的硬实力
ios·cocoa·iphone
不才小强15 小时前
macOS 屏幕录制开发完全指南:ScreenCaptureKit与音频采集实战
macos·音视频
JXSJHF16 小时前
iPhone隐藏功能大盘点,免费好用不占内存
ios·iphone
ShiLuoHeroKing1 天前
Mole:面向专业用户的Mac系统清理开源方案
macos
ZZH_AI项目交付1 天前
为什么很多复杂跳转,最后都得先回首页?
flutter·ios