
嗨,我叫Andrey Batutin,我是DataArt的高级iOS开发人员。 在
上一篇文章中,我们讨论了如何使用HTTPS代理嗅探我们的移动应用程序的流量。 在本文中,我们将讨论如何绕过SSL固定。 为了以防万一,我建议您阅读第一篇文章(如果您还没有阅读过的话):这将有助于理解下面的内容。
实际上,实际上,使用SSL Pinning是为了使坏人或好奇的厨师无法访问所描述的检查和修改移动应用程序流量的方法。
什么是SSL固定
在上一篇文章中,我们在移动设备上安装了“查尔斯根证书”,从而使我们的查尔斯代理可以接收,解密,向我们显示流量,将其加密回并发送给Dropbox。
如果作为移动应用程序的开发人员,我希望仅由我的服务器检查我的流量,而没有其他人检查我的流量,即使另一人已经在设备上安装了SSL证书,我也可以使用SSL固定。
其本质可以归结为以下事实:在
SSL握手期间
,客户端会检查从服务器接收到的证书。
本文讨论了使用连接到应用程序中的允许的证书列表(白名单)来实现的最简单的SSL固定方法。
有关SSL固定类型的更多信息,请参见
此处 。
FoodSniffer的SSL固定实施
完整的项目代码在
这里 。 首先,我们需要为两个主机获取两个DER格式的证书:
第二台服务器将JSON自身存储有我们的购买清单。
为了获得正确格式的证书,我使用了Mozila Firefox。
在浏览器中打开dropbox.com。
单击地址栏中的锁定符号。


单击更多信息,选择安全性->查看证书。

然后选择详细信息,然后在证书层次结构中找到最终证书。

单击导出并以DER格式保存。

对uc9b17f7c7fce374f5e5efd0a422.dl.dropboxusercontent.com重复相同的过程。
注意事项Dropbox内容服务器(* .dl.dropboxusercontent.com)使用通配符证书。 这意味着您为uc9b17f7c7fce374f5e5efd0a422服务器提取的证书将适用于任何其他Dropbox * .dl.dropboxusercontent.com服务器。
结果,我得到了两个带有证书的文件:
dropboxcom.crt ,
dldropboxusercontentcom.crt ,
我添加到了FoodSniffer iOS应用项目中。

然后,我为FoodListAPIConsumer类添加了扩展,在其中检查了从服务器收到的证书。 为此,我在允许的证书列表中查找它,处理NSURLSessionDelegate协议的Authentication Challenge委托:
extension FoodListAPIConsumer { func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { guard let trust = challenge.protectionSpace.serverTrust else { completionHandler(.cancelAuthenticationChallenge, nil) return } let credential = URLCredential(trust: trust) if (validateTrustCertificateList(trust)) { completionHandler(.useCredential, credential) } else { completionHandler(.cancelAuthenticationChallenge, nil) } } func validateTrustCertificateList(_ trust:SecTrust) -> Bool{ for index in 0..<SecTrustGetCertificateCount(trust) { if let certificate = SecTrustGetCertificateAtIndex(trust, index){ let serverCertificateData = SecCertificateCopyData(certificate) as Data if ( certificates.contains(serverCertificateData) ){ return true } } } return false } }
在
证书数组中,我具有允许的证书的数据表示形式。
现在,当Charles Proxy运行时,由于Charles证书未包含在允许的列表中,因此应用程序将断开连接。 用户将看到以下错误:

黑客打败了!
但是现在有一个小问题-开发人员如何监控自己应用程序的HTTPS流量?
弗里达
一种选择是使用
Frida框架的动态代码注入框架禁用SSL Pinning。
这个想法是
validateTrustCertificateList方法在应用程序开发期间始终返回true。
当然,这可以在不进行动态代码注入的情况下实现,例如,使用
#if targetEnvironment(模拟器)条件在模拟器上禁用SSL固定,但这太简单了。
使用Frida,我们可以编写JavaScript脚本(正确地对吗?),在其中,我们将validateTrustCertificateList的实现替换为始终返回true的实现。
并且此脚本将在执行阶段注入到应用程序中。
Frida如何在iOS上工作,您可以
在此处阅读。
Frida安装( 从此处获取 )。
sudo pip安装frida-toolsFrida脚本
替换validateTrustCertificateList函数的直接脚本如下所示:
// Are we debugging it? DEBUG = true; function main() { // 1 var ValidateTrustCertificateList_prt = Module.findExportByName(null, "_T016FoodSnifferFrida0A15ListAPIConsumerC024validateTrustCertificateD0SbSo03SecG0CF"); if (ValidateTrustCertificateList_prt == null) { console.log("[!] FoodSniffer!validateTrustCertificateList(...) not found!"); return; } // 2 var ValidateTrustCertificateList = new NativeFunction(ValidateTrustCertificateList_prt, "int", ["pointer"]); // 3 Interceptor.replace(ValidateTrustCertificateList_prt, new NativeCallback(function(trust) { if (DEBUG) console.log("[*] ValidateTrustCertificateList(...) hit!"); return 1; }, "int", ["pointer"])); console.log("[*] ValidateTrustCertificateList(...) hooked. SSL pinnig is disabled."); } // Run the script main();
- 我们通过函数的全名在应用程序二进制文件中找到指向validateTrustCertificateList的指针。
- 我们将指针包装在NativeFunction包装器中,以指示参数的类型和函数的输出值。
- 将validateTrustCertificateList函数的实现替换为始终返回1(即true)的函数。
整个脚本位于
{source_root} /fridascrpts/killCertPinnig.js中 。
问题之一是如何获取函数
_T016FoodSnifferFrida0A15ListAPIConsumerC024validateTrustCertificateD0SbSo03SecG0CF的全名
为此,我使用了以下技术。
- 在应用程序中创建了另一个目标FoodSnifferFrida 。
- 我将FridaGadget.dylib库连接到了它,我在这里接了它。 库连接过程在此处更详细地描述。
- 在模拟器上启动了FoodSniffer应用程序。
- 使用此命令查找完全限定的函数名称validateTrustCertificateList :
frida-trace -R -f re.frida.Gadget -i“ * validateTrust *” - 格式如下:

然后在
killCertPinnig.js中使用它。
为什么这样一个“奇怪”的名称最后出现,以及所有这些T016和0A15的含义,可以在
这里看到。
SSL Killing固定
现在终于在禁用SSL Pinnig的情况下启动FoodSniffer!
启动Charles Proxy。
在模拟器的Xcode项目中运行目标FoodSnifferFrida。 我们应该只看到一个白色的屏幕。 该应用程序等待Frida连接到它。

运行Frida以执行
killCertPinnig.js脚本:
frida -R -f re.frida.Gadget -l ./fridascrpts/killCertPinnig.js让我们等待与iOS应用程序的连接:

使用%resume命令继续应用程序:

现在,我们应该在应用程序中看到食物列表:

以及Charles Proxy中的JSON:

赢利!
结论
Frida就像Wireshark的二进制文件一样。 它适用于iOS,Android,Linux,Windows平台。 该框架使您可以跟踪对方法和函数(系统和用户)的调用。 并且还替换参数值,返回值和函数的实现。
在使用Frida的开发环境中绕过SSL固定似乎有点过大。 之所以吸引我,是因为我不需要在应用程序中具有用于调试和应用程序开发的特定逻辑。 这种逻辑会使代码混乱,如果实施不正确,则会泄漏到程序集的发行版中(宏,您好!)。
此外,Frida适用于Android。 这使我有机会使整个团队的工作更加轻松,并确保整个产品系列的开发过程顺利进行。
Frida将自己定位为黑匣子过程代码注入工具。 有了它,就可以在不更改iOS应用程序直接代码的情况下,将方法调用日志记录添加到运行时,这在调试复杂而罕见的错误时是必不可少的。