在iOS上通过Frida从SSL Context获取SSL固定证书的方法咨询
从SSL上下文/连接中提取服务器证书的Frida解决方案
你当前Hook的SSL_CTX_set_custom_verify操作的是SSL上下文(配置模板),实际的服务器证书存储在单个SSL连接实例中,而非上下文对象。以下是直接提取证书并导出为PEM格式的修改方案:
完整Frida脚本
function dumpSSLCert() { // 定位BoringSSL核心函数 const funcMap = { SSL_get_peer_certificate: Module.findExportByName("libboringssl.dylib", "SSL_get_peer_certificate"), PEM_write_bio_X509: Module.findExportByName("libboringssl.dylib", "PEM_write_bio_X509"), BIO_new_mem_buf: Module.findExportByName("libboringssl.dylib", "BIO_new_mem_buf"), BIO_get_mem_data: Module.findExportByName("libboringssl.dylib", "BIO_get_mem_data"), BIO_free: Module.findExportByName("libboringssl.dylib", "BIO_free"), X509_free: Module.findExportByName("libboringssl.dylib", "X509_free"), SSL_connect: Module.findExportByName("libboringssl.dylib", "SSL_connect") }; // 检查必要函数是否存在 for (const [name, addr] of Object.entries(funcMap)) { if (!addr) { console.log(`[!] 缺失函数: ${name}`); return; } } // 包装Native函数 const ssl_get_peer_cert = new NativeFunction(funcMap.SSL_get_peer_certificate, 'pointer', ['pointer']); const pem_write_bio_x509 = new NativeFunction(funcMap.PEM_write_bio_X509, 'int', ['pointer', 'pointer']); const bio_new_mem_buf = new NativeFunction(funcMap.BIO_new_mem_buf, 'pointer', ['pointer', 'int']); const bio_get_mem_data = new NativeFunction(funcMap.BIO_get_mem_data, 'pointer', ['pointer', 'pointer']); const bio_free = new NativeFunction(funcMap.BIO_free, 'void', ['pointer']); const x509_free = new NativeFunction(funcMap.X509_free, 'void', ['pointer']); // Hook SSL连接成功事件,提取证书 Interceptor.attach(funcMap.SSL_connect, { onLeave: function(retval) { // SSL_connect返回1表示连接成功 if (retval.toInt32() !== 1) return; // ARM64架构下,第一个参数(SSL连接实例)存放在x0寄存器 const sslInstance = this.context.x0; const certPtr = ssl_get_peer_cert(sslInstance); if (certPtr === null) return; // 创建内存缓冲区存储PEM格式证书 const bio = bio_new_mem_buf(null, 0); if (bio === null) { x509_free(certPtr); return; } // 将证书转为PEM格式 if (pem_write_bio_x509(bio, certPtr) === 1) { let lenPtr = ptr(0); const pemData = bio_get_mem_data(bio, lenPtr); const pemStr = pemData.readUtf8String(lenPtr.toInt32()); console.log("[*] 服务器证书PEM:\n" + pemStr); // 可选:将证书发送到Frida客户端保存 send({ type: "cert", data: pemStr }); } // 释放内存,避免泄漏 bio_free(bio); x509_free(certPtr); } }); console.log("[*] SSL_connect已Hook,等待连接触发..."); } // 保留你原有的SSL_CTX Hook(可选) function overrideCustomVerify() { const SSL_CTX_set_custom_verify = Module.findExportByName("libboringssl.dylib", "SSL_CTX_set_custom_verify"); if (!SSL_CTX_set_custom_verify) { console.log("[!] SSL_CTX_set_custom_verify未找到"); return; } Interceptor.replace(SSL_CTX_set_custom_verify, new NativeCallback(function(ssl_ctx, mode, callback) { console.log("[*] SSL_CTX_set_custom_verify触发"); // 调用原函数,避免破坏原有证书验证逻辑 const originalFunc = new NativeFunction(SSL_CTX_set_custom_verify, 'void', ['pointer', 'int', 'pointer']); originalFunc(ssl_ctx, mode, callback); }, 'void', ['pointer', 'int', 'pointer'])); console.log("[*] SSL_CTX_set_custom_verify已Hook"); } // 启动Hook setImmediate(function() { overrideCustomVerify(); dumpSSLCert(); });
关键说明
- 为什么Hook SSL_connect:SSL上下文是全局配置模板,实际的服务器证书仅在单个SSL连接建立后才会加载,
SSL_connect成功返回时,证书已完成协商,此时可通过SSL_get_peer_certificate获取证书指针。 - 内存管理:必须调用
BIO_free和X509_free释放Native层内存,避免应用内存泄漏。 - PEM格式导出:通过BoringSSL的
PEM_write_bio_X509将证书转为标准PEM格式,可直接保存为.crt文件用于代理工具。
使用方法
- 将脚本保存为
dump_cert.js - 运行命令:
frida -U -f com.your.target.app -l dump_cert.js --no-pause - 触发App内的HTTPS请求,控制台会打印证书PEM内容,也可通过Frida客户端接收数据保存到本地。
内容的提问来源于stack exchange,提问作者Darren
相关产品推荐
相关产品推荐

