IOS SSL Pinning 单向证书检验

admin 2023年12月18日14:29:47评论39 views字数 8521阅读28分24秒阅读模式

原创文章移动安全渗透技术

原创声明:转载本文请标注出处和作者。

前言:在 TLS 或 SSL 连接中,单向证书检验可以防止连接的另一端冒充其他身份。应用程序可以使用单向证书检验来验证连接的另一端是否是可信任的服务器。

一、Security.framework 证书检验

1.1 SecTrustEvaluate()函数

该函数目前已经不被官方推荐,因为它仅适用于 IOS <13.0 的系统版本。
IOS SSL Pinning 单向证书检验

函数原型:

OSStatus SecTrustEvaluate(SecTrustRef trust, SecTrustResultType *result);


该函数的参数如下:

  • trust:要评估的信任管理对象。
  • result:返回时,指向反映此评估结果的结果类型。

 

1.2 SecTrustCreateWithCertificates() 函数

该函数在IOS>=13.0 的系统版本用于代替 SecTrustEvaluate() 完成 X.509 证书链评估。函数原型:

bool SecTrustEvaluateWithError(SecTrustRef trust, CFErrorRef _Nullable *error);

该函数的参数如下:

  • trust:指向 X.509 信任对象的指针。
  • error:指向用于存储错误信息的指针。

该函数的返回值为 0 表示成功,非 0 表示失败。

函数的使用示例:

#include <Security/Security.h>
int main(){// 创建 X.509 信任对象  SecTrustRef trust = SecTrustCreateWithCertificates(NULL, NULL);if (trust == NULL) {return 1;  }
// 评估证书链  OSStatus status = SecTrustEvaluateWithError(trust, &error);if (status != errSecSuccess) {// 处理错误    CFStringRef errorStr = CFErrorCopyDescription(error);printf("评估证书链失败:%sn", errorStr);    CFRelease(errorStr);return 1;  }
// 释放资源  SecTrustRelease(trust);
return 0;}

 

二、libboringssl.dylib 证书校验


libboringssl.dylib 库是 Apple 提供的开源安全库,用于实现 TLS 和 SSL 协议。该库基于 Google 的 BoringSSL 库,并经过 Apple 的修改和优化。

 

2.1 SSL_CTX_set_custom_verify() 函数

libboringssl.dylib 中 SSL_CTX_set_custom_verify() 函数用于设置 SSL 上下文的验证回调函数。在 OpenSSL < 1.1.1 的版本中,该函数原型为:

SSL_CTX_set_custom_verify(SSL_CTX *ctx, verify_callback, NULL);

该函数的参数如下:

  • ctx:指向 SSL 上下文的指针。
  • verify_callback:验证回调函数的指针。
  • verify_arg:验证回调函数的参数。

其中验证回调的函数为:

int verify_callback(SSL *ssl, int preverify_ok, X509_STORE_CTX *x509_store_ctx);

该函数的参数如下:

  • ssl:指向 SSL 结构的指针。
  • preverify_ok:表示是否已经完成了证书链的验证。
  • x509_store_ctx:指向 X.509 存储上下文的指针。

函数返回值为 0 表示成功,非 0 表示失败。

以下是该函数的使用示例:

#include <openssl/ssl.h>
int verify_callback(SSL *ssl, int preverify_ok, X509_STORE_CTX *x509_store_ctx){// 检查证书的颁发者  X509 *cert = X509_STORE_CTX_get_current_cert(x509_store_ctx);  X509_NAME *issuer = X509_get_issuer_name(cert);char issuer_str[256];  X509_NAME_oneline(issuer, issuer_str, sizeof(issuer_str));
// 验证成功if (preverify_ok) {printf("证书颁发者:%sn", issuer_str);return 0;  }
// 验证失败printf("证书颁发者不受信任:%sn", issuer_str);return 1;}
int main(){  SSL_CTX *ctx = SSL_CTX_new(TLS_method());if (ctx == NULL) {return 1;  }
// 设置验证回调函数  SSL_CTX_set_custom_verify(ctx, verify_callback, NULL);
// ...
  SSL_CTX_free(ctx);return 0;}

 

在 OpenSSL >= 1.1.1 的版本中,该函数原型为:

SSL_CTX_set_custom_verify(SSL_CTX *ctx, enum ssl_verify_mode_t, enum ssl_verify_result_t(*callback)(SSL *ssl, uint8_t *out_alert)))

函数增加了两个参数:

  • mode:表示验证模式。可以为以下值之一:
    • SSL_VERIFY_NONE:不验证证书。
    • SSL_VERIFY_PEER:验证证书的身份。
    • SSL_VERIFY_FAIL_IF_NO_PEER_CERT:如果没有提供证书,则验证失败。
    • SSL_VERIFY_CLIENT_ONCE:仅在第一次握手时验证客户端证书。
  • out_alert:指向用于存储警报码的指针。

修改后的签名可以提供更灵活的验证控制。例如,可以使用 mode 参数来指定验证模式,并使用 out_alert 参数来指定验证失败时返回的警报码。

函数如果返回值是 int,为 0 表示成功,非 0 表示失败。如果失败,则可以通过 errno 变量来获取错误码。

如果返回值是 void,则表示函数成功执行。但是,在这种情况下,无法通过 errno 变量来获取错误码。

 

2.2 SSL_get_psk_identity() 函数

libboringssl.dylib 中 SSL_get_psk_identity() 函数用于获取 TLS-PSK 握手中使用的身份。该函数的参数如下:

  • ssl:指向 SSL 结构的指针。
  • identity:指向用于存储身份的指针。
  • identity_len:身份的长度。

该函数的返回值为 0 表示成功,非 0 表示失败。

以下是该函数的使用示例:

#include <openssl/ssl.h>
int main(){  SSL *ssl = SSL_new(ctx);if (ssl == NULL) {return 1;  }
// 获取身份char identity[128];int identity_len = sizeof(identity);int ret = SSL_get_psk_identity(ssl, identity, &identity_len);if (ret != 0) {printf("获取身份失败:%dn", ret);return 1;  }
// ...
  SSL_free(ssl);return 0;}

 

2.3 boringssl_context_set_verify_mode_handle()函数

libboringssl.dylib 中 boringssl_context_set_verify_mode_handle()函数用于设置 SSL 上下文的验证模式。该函数的参数如下:

  • ctx:指向 SSL 上下文的指针。
  • mode:验证模式。可以为以下值之一:
    • SSL_VERIFY_NONE:不验证证书。
    • SSL_VERIFY_PEER:验证证书的身份。
    • SSL_VERIFY_FAIL_IF_NO_PEER_CERT:如果没有提供证书,则验证失败。
    • SSL_VERIFY_CLIENT_ONCE:仅在第一次握手时验证客户端证书。

该函数的返回值为 0 表示成功,非 0 表示失败。

以下是该函数的使用示例:

#include <openssl/ssl.h>
int main(){  SSL_CTX *ctx = SSL_CTX_new(TLS_method());if (ctx == NULL) {return 1;  }
// 设置验证模式为 SSL_VERIFY_PEER  boringssl_context_set_verify_mode_handle(ctx, SSL_VERIFY_PEER);
// ...
  SSL_CTX_free(ctx);return 0;}

在该示例中,我们首先创建了一个 SSL 上下文。然后,我们使用 boringssl_context_set_verify_mode_handle()函数将验证模式设置为 SSL_VERIFY_PEER。最后,我们释放 SSL 上下文。

以下是各个验证模式的说明:

  • SSL_VERIFY_NONE:不验证证书。如果服务器提供证书,则会接受该证书。如果服务器没有提供证书,则也会接受该连接。
  • SSL_VERIFY_PEER:验证证书的身份。如果服务器提供证书,则会验证该证书是否由受信任的 CA 颁发。如果验证失败,则会拒绝该连接。
  • SSL_VERIFY_FAIL_IF_NO_PEER_CERT:如果没有提供证书,则验证失败。如果服务器提供证书,则会验证该证书是否由受信任的 CA 颁发。
  • SSL_VERIFY_CLIENT_ONCE:仅在第一次握手时验证客户端证书。在以后的握手中,不会验证客户端证书。

 

三、Frida Hook SSL Pinning

根据上面学习的内容,可以简单实现绕过的通用脚本。主要思路就是替换证书检验相关函数的判断逻辑。

const log = {"trace": function(msg){console.log("x1b[35m", ` ${msg}`, "x1b[0m")    },"debug": function(msg){console.log("x1b[32m", ` ${msg}`, "x1b[0m")    },"info": function(msg){console.log("x1b[34m", ` ${msg}`, "x1b[0m")    },"warn": function(msg){console.log("x1b[33m", ` ${msg}`, "x1b[0m")    },"error": function(msg){console.log("x1b[31m", ` ${msg}`, "x1b[0m")    },};
/** 绕过单向证书校验 */function bypassSSLPinning() {let bypass_status = false;try {/**         * SecTrustEvaluate:Security.framework的系统函数,iOS版本<13.0 可用它评估一个SecTrust对象是否可以被系统信任。         * 函数原型:OSStatus SecTrustEvaluate(SecTrustRef trust, SecTrustResultType *result);         * 参考:https://developer.apple.com/documentation/security/1394363-sectrustevaluate?language=objc         */let SecTrustEvaluate_handle = Module.findExportByName('Security', 'SecTrustEvaluate');    // 如果存在,返回一个地址信息if (SecTrustEvaluate_handle) {            Interceptor.replace(SecTrustEvaluate_handle, new NativeCallback(function (trust, result) {return 0;            }, 'int', ['pointer', 'pointer']));            log.debug(`[${SecTrustEvaluate_handle}] SecTrustEvaluate hook installed.`);            bypass_status = true;        };/**         * SecTrustEvaluateWithError:Security.framework的系统函数,iOS版本>=13.0 可用它评估一个SecTrust对象是否可以被系统信任。         * 函数原型:bool SecTrustEvaluateWithError(SecTrustRef trust, CFErrorRef  _Nullable *error);         * 参考:https://developer.apple.com/documentation/security/2980705-sectrustevaluatewitherror?language=objc         */let SecTrustEvaluateWithError_handle = Module.findExportByName('Security', 'SecTrustEvaluateWithError');if (SecTrustEvaluateWithError_handle) {            Interceptor.replace(SecTrustEvaluateWithError_handle, new NativeCallback(function (trust, error) {return 1;            }, 'int', ['pointer', 'pointer']));            log.debug(`[${SecTrustEvaluateWithError_handle}] SecTrustEvaluateWithError hook installed.`);            bypass_status = true;        };        } catch (error) {         log.error(`[!] Exception: ${error}`);        };try {/**         * SSL_CTX_set_custom_verify:该函数用于覆盖默认的ssl验证逻辑,。         * OpenSSL < 1.1.1 ,函数原型:SSL_CTX_set_custom_verify(SSL_CTX *ctx, verify_callback, NULL);         * OpenSSL >= 1.1.1,函数原型:SSL_CTX_set_custom_verify(SSL_CTX *ctx, enum ssl_verify_mode_t, enum ssl_verify_result_t(*callback)(SSL *ssl, uint8_t *out_alert)))          * 第二个参数是选择检验模式,主要看第三个参数,它是一个回调函数,如果数值为0表示检验通过。         * 参考:https://www.cnblogs.com/theseventhson/p/16051195.html         */let SSL_CTX_set_custom_verify_handle = Module.findExportByName('libboringssl.dylib', 'SSL_CTX_set_custom_verify');if (SSL_CTX_set_custom_verify_handle) {let SSL_CTX_set_custom_verify = new NativeFunction(SSL_CTX_set_custom_verify_handle, 'void', ['pointer', 'int', 'pointer']);let callback = new NativeCallback(function (ssl, out_alert) {return 0;   // 修改数值为0x0,表示检验通过            }, 'int', ['pointer', 'pointer']);            Interceptor.replace(SSL_CTX_set_custom_verify_handle, new NativeCallback(function (ctx, mode, ssl_verify_result_t) {                SSL_CTX_set_custom_verify(ctx, 0, callback);                }, 'void', ['pointer', 'int', 'pointer'])            );            log.debug(`[${SSL_CTX_set_custom_verify_handle}] SSL_CTX_set_custom_verify hook installed.`);            bypass_status = true;        };/**         * SSL_get_psk_identity:用于获取 TLS-PSK 握手中使用的身份,该函数的返回值为 0 表示成功,非 0 表示失败。         * 函数原型:SSL_get_psk_identity(SSL_CTX *ctx, char identity, int identity_len);         * 参考:https://nabla-c0d3.github.io/blog/2019/05/18/ssl-kill-switch-for-ios12/         */let SSL_get_psk_identity_handle = Module.findExportByName('libboringssl.dylib', 'SSL_get_psk_identity');if (SSL_get_psk_identity_handle) {            Interceptor.replace(SSL_get_psk_identity_handle, new NativeCallback(function (ctx, identity, identity_len) {return 0;            }, 'int', ['pointer', 'char', 'int']));            log.debug(`[${SSL_get_psk_identity_handle}] SSL_get_psk_identity hook installed.`);            bypass_status = true;        };/**         * boringssl_context_set_verify_mode:用于设置 SSL 上下文的验证模式,该函数的返回值为 0 表示成功,非 0 表示失败。         * 函数原型:boringssl_context_set_verify_mode_handle(SSL_CTX *ctx, mode);         * 参考:https://gist.github.com/azenla/37f941de24c5dfe46f3b8e93d94ce909         */let boringssl_context_set_verify_mode_handle = Module.findExportByName('libboringssl.dylib', 'boringssl_context_set_verify_mode');if (boringssl_context_set_verify_mode_handle) {            Interceptor.replace(boringssl_context_set_verify_mode_handle, new NativeCallback(function (ctx, mode) {return 0;            }, 'int', ['pointer', 'pointer'])            );            log.debug(`[${SSL_get_psk_identity_handle}] boringssl_context_set_verify_mode() hook installed.`);            bypass_status = true;        };
    } catch (error) {        log.error(`[!] Exception: ${error}`);    };if (bypass_status) {        log.info("[+] bypass ssl pinning.");    };};
bypassSSLPinning();

 

参考链接:

https://developer.apple.com/documentation/security/1394363-sectrustevaluate?language=objc

https://www.cnblogs.com/theseventhson/p/16051195.html

https://nabla-c0d3.github.io/blog/2019/05/18/ssl-kill-switch-for-ios12/

https://gist.github.com/azenla/37f941de24c5dfe46f3b8e93d94ce909

 

 


获取工具或源码。

https://www.123pan.com/s/Nc1mjv-0SxMv.html提取码:368e


 

 

原文始发于微信公众号(Fighter 安全):IOS SSL Pinning 单向证书检验

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年12月18日14:29:47
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   IOS SSL Pinning 单向证书检验http://cn-sec.com/archives/2250245.html

发表评论

匿名网友 填写信息