如何在iOS 12上绕过SSL Pinning

栏目: IOS · 发布时间: 5年前

内容简介:两星期之前,我发布了新版的iOS 11和12的网络协议栈发生了较为明显的

如何在iOS 12上绕过SSL Pinning

0x00 前言

两星期之前,我发布了新版的 SSL Kill Switch ,这是我在iOS应用上禁用SSL pinning的一款黑盒工具,新版中我添加了对iOS 12的支持。

iOS 11和12的网络协议栈发生了较为明显的 改变 ,因此针对iOS 11的SSL Kill Switch自然无法适用于(已越狱的)iOS 12设备。在本文中,我将与大家分享这款 工具 中针对iOS 12的适配改动。

0x01 SSL Pinning禁用策略

为了在移动应用上实现SSL pinning,在通过SSL连接服务端时,应用需要自定义服务端证书链的验证逻辑。自定义SSL验证逻辑大多会通过某种回调机制来实现,其中应用代码会在初始TLS握手中接收服务端的证书链,然后决定下一步操作(判断证书链是否“有效”)。比如,在iOS上:

因此,在应用中禁用SSL pinning的较高级策略是阻止系统触发SSL验证回调,这样负责实现pinning的应用代码永远不会执行。

在iOS上,阻止 NSURLSessionDelegate 验证方法被调用相对而言较为简单(这也是 之前版本 SSL Kill Switch的工作原理),但当iOS应用使用低级API时(如 Network.framework )该怎么办?由于iOS上的每个网络API都基于其他API构建,在最底层禁用验证回调可能会禁用所有高级网络API的验证逻辑,这样我们的工具就可以适用于许多应用。

iOS网络协议栈从iOS 8以来经过了多次改动,在iOS 12上,SSL/TLS栈基于 BoringSSL 的自定义fork实现(我个人这么认为)。当某个应用 创建连接 时,我们可以在随机选择的某个BoringSSL符号上设置断点来验证这一点:

如何在iOS 12上绕过SSL Pinning

大家应该还记得前面提到的禁用策略,如果我们定位并patch BoringSSL(iOS上最底层的SSL/TLS API),那么iOS上所有较高级的API(包括 NSURLSession )都会禁用pinning验证。

来测试一下。

0x02 BoringSSL验证回调

使用BoringSSL时,自定义SSL验证的一种方法就是通过 SSL_CTX_set_custom_verify() 函数来配置验证回调函数。

简单的使用示例如下所示:

// Define a cert validation callback to be triggered during the SSL/TLS handshake
ssl_verify_result_t verify_cert_chain_callback(SSL* ssl, uint8_t* out_alert) {
    // Retrieve the certificate chain sent by the server during the handshake
    STACK_OF(X509) *certificateChain = SSL_get_peer_cert_chain(ssl);

    // Do custom validation (pinning or something else)
    if do_custom_validation(certificateChain) == 0 {
        // If validation succeeded, return OK
        return ssl_verify_ok;
    }
    else {
        // Otherwise close the connection
        return ssl_verify_invalid;
    }
}

// Enable my callback for all future SSL/TLS connections implemented using the ssl_ctx
SSL_CTX_set_custom_verify(ssl_ctx, SSL_VERIFY_PEER, verify_cert_chain_callback);

我选择的测试 应用NSURLSession 中启用了SSL pinning,经过测试后我确认 SSL_CTX_set_custom_verify() 的确会在打开连接时被调用:

如何在iOS 12上绕过SSL Pinning

我们还可以看到Apple/默认的iOS验证回调函数会以第3个参数形式传入( x2 寄存器): boringssl_context_certificate_verify_callback() 。很有可能这个回调中包含一些代码逻辑,用来设置所需的环境,使系统最终会调用测试应用的 NSURLSession 回调/委派方法来处理服务端证书。

与我们预期的一样,测试应用中负责pinning验证的委派方法的确会被调用:

如何在iOS 12上绕过SSL Pinning

我也专门设计了测试应用代码,使其自定义/pinning验证逻辑始终会返回失败:

如何在iOS 12上绕过SSL Pinning

因此,如果我们能绕过pinning,那么这个连接应当会成功建立。

现在我们已经有测试方案,也搭建了适当的实验环境(启用pinning的应用、已越狱的设备、Xcode等),我们可以开始研究了。

0x03 修改BoringSSL

首先我想试着处理iOS网络协议栈默认设置的BoringSSL回调( boringssl_context_certificate_verify_callback() ),将其替换为空的回调函数,永远不去检查服务端的证书链:

// My "evil" callback that does not check anything
ssl_verify_result_t verify_callback_that_does_not_validate(void *ssl, uint8_t *out_alert)
{
    return ssl_verify_ok;
}

// My "evil" replacement function for SSL_CTX_set_custom_verify()
static void replaced_SSL_CTX_set_custom_verify(void *ctx, int mode, ssl_verify_result_t (*callback)(void *ssl, uint8_t *out_alert))
{
    // Always ignore the callback that was passed and instead set my "evil" callback
    original_SSL_CTX_set_custom_verify(ctx, SSL_VERIFY_NONE verify_callback_that_does_not_validate);
    return;
}

// Lastly, use MobileSubstrate to replace SSL_CTX_set_custom_verify() with my "evil" replaced_SSL_CTX_set_custom_verify()
void* boringssl_handle = dlopen("/usr/lib/libboringssl.dylib", RTLD_NOW);
void *SSL_CTX_set_custom_verify = dlsym(boringssl_handle, "SSL_CTX_set_custom_verify");
if (SSL_CTX_set_custom_verify)
{
    MSHookFunction((void *) SSL_CTX_set_custom_verify, (void *) replaced_SSL_CTX_set_custom_verify,  NULL);
}

将如上代码实现成 MobileSubstrate tweak并插入测试应用后,发生了一些有趣的事情:测试应用的 NSURLSession 委派方法不再被调用(这意味着该方法已被“绕过”),但应用发起的第一个连接会出现错误,提示“Peer was not authenticated”(“对端未经身份认证”),这是新的/未知的错误,如下所示:

TrustKitDemo-ObjC[3320:160146] === SSL Kill Switch 2: replaced_SSL_CTX_set_custom_verify
TrustKitDemo-ObjC[3320:160146] Failed to clone trust Error Domain=NSOSStatusErrorDomain Code=-50 "null trust input" UserInfo={NSDescription=null trust input} [-50]
TrustKitDemo-ObjC[3320:160146] [BoringSSL] boringssl_session_finish_handshake(306) [C1.1:2][0x10bd489a0] Peer was not authenticated. Disconnecting.
TrustKitDemo-ObjC[3320:160146] NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9810)
TrustKitDemo-ObjC[3320:160146] Task <15E1F3B0-0B73-468A-9132-3E19048DDAE3>.<1> finished with error - code: -1200

在应用中,第一个连接在出错的同时,会弹出与之前不同的一个错误信息:

如何在iOS 12上绕过SSL Pinning

然而,发往该服务器的后续连接会成功建立,不会触发pinning验证回调:

如何在iOS 12上绕过SSL Pinning

因此,对于所有连接(除了第一个连接外)我都已经绕过了pinning,额好吧,其实还并不完美……

0x04 修复第一个连接

我需要更多上下文信息才能理解“Peer was not authenticated”错误的真正含义,所以我从iOS 12设备上提取了共享缓存(其中有Apple的所有库和框架,包括BoringSSL),提取步骤参考此处 链接

libboringssl.dylib 载入Hopper后,我找到了“Peer was not authenticated”错误(如下图红框1处),该错误位于 boringssl_session_finish_handshake() 函数中:

如何在iOS 12上绕过SSL Pinning

我试着去理解这个函数的功能,想更深入理解这个错误,然而我对arm64汇编代码理解并不透彻,因此无法完成这个任务。我试了其他方法(比如patch boringssl_context_certificate_verify_callback() ),但是没有找到有价值的信息。

我决定使用更为极端的方法。如果我们再次观察反编译的 boringssl_session_finish_handshake() 函数,可以看到其中有两条“主”代码路径,由 if/else 语句分条件触发。其中,“Peer was not authenticated”错误位于 if 代码路径中,并不位于 else 路径中。

很自然的一个想法就是阻止执行带有该错误消息的代码路径,也就是 if 路径。如上图所示,触发 if 分支的一个条件就是 (_SSL_get_psk_identity() == 0x0) (上图红框2处)。如果我们patch这个函数,使其不返回 0 ,让系统执行 else 这条代码路径会出现什么情况(这样就不会触发“Peer was not authenticated”错误)?

对应的 MobileSubtrate patch如下所示:

// Use MobileSubstrate to replace SSL_get_psk_identity() with this function, which never returns 0:
char *replaced_SSL_get_psk_identity(void *ssl)
{
    return "notarealPSKidentity";
}
MSHookFunction((void *) SSL_get_psk_identity, (void *) replaced_SSL_get_psk_identity, (void **) NULL);

将这个运行时patch注入测试应用后,我们的确成功了!此时第一个连接已经成功,并且测试应用的验证回调也永远不会被触发。我们通过patch BoringSSL,成功绕过了该应用的SSL pinning验证代码。

0x05 总结

这显然并不是非常完美的运行时patch,虽然patch后一切似乎都正常工作(这一点比较让我惊讶),但每当应用打开一个连接时,我们还是可以在日志中看到一些错误,如下所示:

TrustKitDemo-ObjC[3417:166749] Failed to clone trust Error Domain=NSOSStatusErrorDomain Code=-50 "null trust input" UserInfo={NSDescription=null trust input} [-50]

这个patch还有其他一些问题:

SSL_get_psk_identity()
boringssl_context_certificate_verify_callback()

最后,因为时间有限,我还没有处理其他一些事情:

  • 再次确认这个BoringSSL运行时patch的确会禁用较底层iOS网络API的pinning功能,比如 Network.framework 或者 CFNetwork
  • 添加针对macOS的支持。我有信心这个patch在macOS上应该能正常工作,但我没有找到macOS上hook BoringSSL(或者共享缓存中任何C函数)的方法。我之前使用的一款工具(Facebook的 fishhook )似乎无法正常工作。

大家可以访问 Github 查看源码,下载这个tweak。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Rework

Rework

Jason Fried、David Heinemeier Hansson / Crown Business / 2010-3-9 / USD 22.00

"Jason Fried and David Hansson follow their own advice in REWORK, laying bare the surprising philosophies at the core of 37signals' success and inspiring us to put them into practice. There's no jarg......一起来看看 《Rework》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具