IOS逆向(四)-某app证书SSL pinning绕过

IOS逆向(四)-某app证书SSL pinning绕过

渗透测试中经常会遇到app存在SSL证书校验,此时就没办法抓包。需要对ios的SSL pinning的函数进行hook来绕过检测。本文对某app使用frida和objection进行绕过。

难度

★☆☆☆☆

工具

  • 越狱IOS 14.4
  • AppsDump
  • frida
  • objection
  • IDA 7.7
  • HTTP Catcher

分析思路

我们在ios上使用HTTP Catcher进行抓包,当开启SSL Pinning Bypass(代理为localhost生效)的时候,是可以正常抓到包的。但是我们做测试需要把流量抓到mac上的burp,这时SSL Pinning Bypass是不生效的。

虽然知道是证书校验的问题,我们可以直接用SSL Unpinning的脚本进行hook,但还是用IDA简单分析一下,了解其原理。

Appsdump砸壳

最近发现一个IOS上砸壳非常方便的app,Appsdump。支持IOS15的系统,原理就是基于trollstore来进行砸壳的,关键是非越狱的状态下也可以砸壳

支持单脱主程序,然后使用airdrop隔空投送到mac,是真的太方便了!

IDA分析过程

既然是证书问题,把maco扔进IDA中分析完成后,直接搜索证书的关键字来定位关键函数。

双击来的字符串的位置。

查看引用该字符串的方法。

来到-[UASessionOperation URLSession:didReceiveChallenge:completionHandler:]方法,F5。

SecTrustEvaluateWithError是iOS平台上的系统函数。这个函数来自于Security.framework,它是用于评估一个SecTrust对象是否可以被系统信任。

该函数返回一个布尔值,如果为true,证书可以被信任;如果为false,证书不能被信任。如果评估失败,会通过第二个参数返回一个包含错误详情的CFErrorRef对象。这个函数在iOS 13.0及更高版本上可用,因此可以看到在代码中有一个版本检查部分,根据设备的系统版本来选择使用SecTrustEvaluateWithError或者旧版本的SecTrustEvaluate函数。

objection ios sslpinning disable

objection是基于frida封装的命令行hook工具,可以不写代码,使用非常方便。

objection -g <bundleID> explore
ios sslpinning disable

frida 绕过脚本

SSL Pinning的绕过脚本网上有很多现成的,就没必要自己写了。和分析的基本一致,都是hook ssl证书校验的关键系统函数。

// Disables SSL pinning by replacing functions with no-ops.
function unpin() {
  var SecTrustEvaluate_handle = Module.findExportByName('Security', 'SecTrustEvaluate');
  var SecTrustEvaluateWithError_handle = Module.findExportByName('Security', 'SecTrustEvaluateWithError');
  var SSL_CTX_set_custom_verify_handle = Module.findExportByName('libboringssl.dylib', 'SSL_CTX_set_custom_verify');
  var SSL_get_psk_identity_handle = Module.findExportByName('libboringssl.dylib', 'SSL_get_psk_identity');
  var boringssl_context_set_verify_mode_handle = Module.findExportByName('libboringssl.dylib', 'boringssl_context_set_verify_mode');

  if (SecTrustEvaluateWithError_handle) {
      var SecTrustEvaluateWithError = new NativeFunction(SecTrustEvaluateWithError_handle, 'int', ['pointer', 'pointer']);

      Interceptor.replace(
          SecTrustEvaluateWithError_handle,
          new NativeCallback(function (trust, error) {

              console.log('[*] Called SecTrustEvaluateWithError()');
              SecTrustEvaluateWithError(trust, NULL);
              Memory.writeU8(error, 0);
              return 1;
          }, 'int', ['pointer', 'pointer'])
      );

      console.log('[+] SecTrustEvaluateWithError() hook installed.');
  }

  if (SecTrustEvaluate_handle) {
      var SecTrustEvaluate = new NativeFunction(SecTrustEvaluate_handle, 'int', ['pointer', 'pointer']);

      Interceptor.replace(
          SecTrustEvaluate_handle, new NativeCallback(function (trust, result) {
              console.log('[*] Called SecTrustEvaluate()');
              SecTrustEvaluate(trust, result);
              Memory.writeU8(result, 1);
              return 0;
          }, 'int', ['pointer', 'pointer'])
      );
      console.log('[+] SecTrustEvaluate() hook installed.');
  }

  if (SSL_CTX_set_custom_verify_handle) {
      var SSL_CTX_set_custom_verify = new NativeFunction(SSL_CTX_set_custom_verify_handle, 'void', ['pointer', 'int', 'pointer']);

      var replaced_callback = new NativeCallback(function (ssl, out) {
          console.log('[*] Called custom SSL verifier')
          return 0;
      }, 'int', ['pointer', 'pointer']);

      Interceptor.replace(
          SSL_CTX_set_custom_verify_handle,
          new NativeCallback(function (ctx, mode, callback) {
              console.log('[*] Called SSL_CTX_set_custom_verify()');
              SSL_CTX_set_custom_verify(ctx, 0, replaced_callback);
          }, 'int', ['pointer', 'int', 'pointer'])
      );

      console.log('[+] SSL_CTX_set_custom_verify() hook installed.')
  }

  if (SSL_get_psk_identity_handle) {
      Interceptor.replace(
          SSL_get_psk_identity_handle, 
          new NativeCallback(function (ssl) {
              console.log('[*] Called SSL_get_psk_identity_handle()');
              return 'notarealPSKidentity';
          }, 'pointer', ['pointer'])
      );

      console.log('[+] SSL_get_psk_identity() hook installed.')
  }

  if (boringssl_context_set_verify_mode_handle) {
      var boringssl_context_set_verify_mode = new NativeFunction(boringssl_context_set_verify_mode_handle, 'int', ['pointer', 'pointer']);

      Interceptor.replace(
          boringssl_context_set_verify_mode_handle,
          new NativeCallback(function (a, b) {
              console.log('[*] Called boringssl_context_set_verify_mode()');
              return 0;
          }, 'int', ['pointer', 'pointer'])
      );
      
      console.log('[+] boringssl_context_set_verify_mode() hook installed.')
  }
}

unpin()

将上面的脚本保存为 ssl-pinning-bypass.js 文件。

frida -UF -l ssl-pinning-bypass.js

执行结果如下:

然后就可以正常的抓包了。

赞赏

微信赞赏支付宝赞赏

Zgao

愿有一日,安全圈的师傅们都能用上Zgao写的工具。

发表评论