<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
	<channel>
		<atom:link href="http://gentoo-zh.org/extern.php?action=feed&amp;tid=481&amp;type=rss" rel="self" type="application/rss+xml" />
		<title><![CDATA[Gentoo中文社区 / linux源码解读（三十三）：android下boringSSL核心源码解析&x音防抓包证书校验原理]]></title>
		<link>http://www.gentoo-zh.org/viewtopic.php?id=481</link>
		<description><![CDATA[linux源码解读（三十三）：android下boringSSL核心源码解析&x音防抓包证书校验原理 最近发表的帖子。]]></description>
		<lastBuildDate>Wed, 12 Oct 2022 03:19:10 +0000</lastBuildDate>
		<generator>FluxBB</generator>
		<item>
			<title><![CDATA[linux源码解读（三十三）：android下boringSSL核心源码解析&x音防抓包证书校验原理]]></title>
			<link>http://www.gentoo-zh.org/viewtopic.php?pid=488#p488</link>
			<description><![CDATA[<p>1、去年逆向x音15.5.0版本时，可以直接用fiddler抓包。后来貌似升级到17版本时fiddler就抓不到包了，看雪有大佬破解了x音防抓包的功能，原理并不复杂：boringssl源码中有个SSL_CTX_set_custom_verify函数，定义如下：</p><div class="codebox"><pre><code>void SSL_CTX_set_custom_verify(
    SSL_CTX *ctx, int mode,
    enum ssl_verify_result_t (*callback)(SSL *ssl, uint8_t *out_alert)) {
  ctx-&gt;verify_mode = mode;
  ctx-&gt;custom_verify_callback = callback;
}</code></pre></div><p>&#160; （1）第二个mode参数就是验证client的关键参数了，有以下4种取值：</p><div class="codebox"><pre><code>// SSL_VERIFY_NONE, on a client, verifies the server certificate but does not
// make errors fatal. The result may be checked with |SSL_get_verify_result|. On
// a server it does not request a client certificate. This is the default.
#define SSL_VERIFY_NONE 0x00

// SSL_VERIFY_PEER, on a client, makes server certificate errors fatal. On a
// server it requests a client certificate and makes errors fatal. However,
// anonymous clients are still allowed. See
// |SSL_VERIFY_FAIL_IF_NO_PEER_CERT|.
#define SSL_VERIFY_PEER 0x01

// SSL_VERIFY_FAIL_IF_NO_PEER_CERT configures a server to reject connections if
// the client declines to send a certificate. This flag must be used together
// with |SSL_VERIFY_PEER|, otherwise it won&#039;t work.
#define SSL_VERIFY_FAIL_IF_NO_PEER_CERT 0x02

// SSL_VERIFY_PEER_IF_NO_OBC configures a server to request a client certificate
// if and only if Channel ID is not negotiated.
#define SSL_VERIFY_PEER_IF_NO_OBC 0x04</code></pre></div><p>&#160; 从注释就能看出：</p><p>&#160; &#160; 0x00：client要验证server的证书，但是不会报错；server不会要求client提供证书，这也是默认的参数<br />&#160; &#160; 0x01：client和server双方都要验证对方的证书，并且会报错<br />&#160; &#160; 0x02：如果client不提供证书，server可以拒绝连接；这个取值要和SSL_VERIFY_PEER一起配合使用，否则无效<br />&#160; &#160; 0x04：server向client索要证书</p><p>&#160; x音默认情况下不能抓包是因为这个参数取值不是0x00，所以直接用frida hook SSL_CTX_set_custom_verify这个函数，把第二个参数改成0x00即可！也可以直接找到libttboringssl.so的源码把第二个参数写死成0x00；</p><p>&#160; （2）第三个参数callback从名字看就知道是个回调函数，函数返回值ssl_verify_result_t取值如下：</p><div class="codebox"><pre><code>enum ssl_verify_result_t BORINGSSL_ENUM_INT {
  ssl_verify_ok,
  ssl_verify_invalid,
  ssl_verify_retry,
};</code></pre></div><p>&#160; 从名字也能看出来返回值取第一个表示验证通过！直接通过hook把第三个参数改成0即可！如果觉得用frida hook麻烦，也可以在libsscronet.so偏移0x1CCBBE处，把“movs R0，1”改成“moves R0，0”即可！也就是把返回值从1改成0！</p><p>&#160; 2、为了更好的逆向和ssl相关的功能（抓包、加解密等），有必要了解一些ssl的关键函数！</p><p>&#160; （1） 站在逆向的角度，我个人觉得最最最重要的就是SSL_write函数了，定义如下：从函数名和参数就能看出是从ssl发送buf的数据，发送长度是num！发送的数据存放在buf的，直接hook这个函数打印buf是不是就能看到网络数据了？</p><div class="codebox"><pre class="vscroll"><code>/*num字节从缓冲区buf写入指定的ssl连接*/
int SSL_write(SSL *ssl, const void *buf, int num) {
  ssl_reset_error_state(ssl);

  if (ssl-&gt;quic_method != nullptr) {
    OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
    return -1;
  }

  if (ssl-&gt;do_handshake == NULL) {
    OPENSSL_PUT_ERROR(SSL, SSL_R_UNINITIALIZED);
    return -1;
  }

  int ret = 0;
  bool needs_handshake = false;
  do {
    // If necessary, complete the handshake implicitly.
    if (!ssl_can_write(ssl)) {//如果还不能通过这个ssl写数据
      ret = SSL_do_handshake(ssl);//开始握手
      if (ret &lt; 0) {
        return ret;
      }
      if (ret == 0) {
        OPENSSL_PUT_ERROR(SSL, SSL_R_SSL_HANDSHAKE_FAILURE);
        return -1;
      }
    }
    //从这里发数据
    ret = ssl-&gt;method-&gt;write_app_data(ssl, &amp;needs_handshake,
                                      (const uint8_t *)buf, num);
  } while (needs_handshake);
  return ret;
}</code></pre></div><p> （2）既然SSL_write是发数据的，SSL_read岂不就是读数据的？代码如下：</p><div class="codebox"><pre><code>int SSL_read(SSL *ssl, void *buf, int num) {
  int ret = SSL_peek(ssl, buf, num);
  if (ret &lt;= 0) {
    return ret;
  }
  // TODO(davidben): In DTLS, should the rest of the record be discarded?  DTLS
  // is not a stream. See https://crbug.com/boringssl/65.
  ssl-&gt;s3-&gt;pending_app_data =
      ssl-&gt;s3-&gt;pending_app_data.subspan(static_cast&lt;size_t&gt;(ret));
  if (ssl-&gt;s3-&gt;pending_app_data.empty()) {
    ssl-&gt;s3-&gt;read_buffer.DiscardConsumed();
  }
  return ret;
}</code></pre></div><p>&#160; &#160; 代码很简单，对于逆向人员来说，hook这两个函数是可以获取发送和接收数据的，也就是绕开了证书校验，对部分app是有用的！详细代码可以参考文章末尾第4个链接！</p><p>&#160; （3）通信双方最重要的莫过于密钥的协商了，handshake最重要的就是干这个的，整个方法如下；handshake内部最重要的又莫过于change_cipher_spec：为了保证安全，通信双方每隔一段时间就会改变加解密的参数！</p><div class="codebox"><pre class="vscroll"><code>int ssl_run_handshake(SSL_HANDSHAKE *hs, bool *out_early_return) {
  SSL *const ssl = hs-&gt;ssl;
  for (;;) {
    // Resolve the operation the handshake was waiting on. Each condition may
    // halt the handshake by returning, or continue executing if the handshake
    // may immediately proceed. Cases which halt the handshake can clear
    // |hs-&gt;wait| to re-enter the state machine on the next iteration, or leave
    // it set to keep the condition sticky.
    /*handshake等待时可能有很多种情况：*/
    switch (hs-&gt;wait) {
      case ssl_hs_error://报错提示
        ERR_restore_state(hs-&gt;error.get());
        return -1;

      case ssl_hs_flush: {//刷新缓存？
        int ret = ssl-&gt;method-&gt;flush_flight(ssl);
        if (ret &lt;= 0) {
          return ret;
        }
        break;
      }

      case ssl_hs_read_server_hello:
      case ssl_hs_read_message:
      /*为保证安全，每隔一段时间就需要改变加解密参数*/
      case ssl_hs_read_change_cipher_spec: {
        if (ssl-&gt;quic_method) {//双方用quic协议
          // QUIC has no ChangeCipherSpec messages.
          //quic本身比较简单，就没有改变加解密参数的说法
          assert(hs-&gt;wait != ssl_hs_read_change_cipher_spec);
          // The caller should call |SSL_provide_quic_data|. Clear |hs-&gt;wait| so
          // the handshake can check if there is sufficient data next iteration.
          ssl-&gt;s3-&gt;rwstate = SSL_ERROR_WANT_READ;
          hs-&gt;wait = ssl_hs_ok;
          return -1;
        }

        uint8_t alert = SSL_AD_DECODE_ERROR;
        size_t consumed = 0;
        ssl_open_record_t ret;
        //现在的状态是要改变加解密参数
        if (hs-&gt;wait == ssl_hs_read_change_cipher_spec) {
          //开始和对方协商改变加解密参数
          ret = ssl_open_change_cipher_spec(ssl, &amp;consumed, &amp;alert,
                                            ssl-&gt;s3-&gt;read_buffer.span());
        } else {
          /*否则重新handshake；其实handshake的本质就是协商加解密协议和参数，
          目的和change_cipher_spec没本质区别*/
          ret = ssl_open_handshake(ssl, &amp;consumed, &amp;alert,
                                   ssl-&gt;s3-&gt;read_buffer.span());
        }
        if (ret == ssl_open_record_error &amp;&amp;
            hs-&gt;wait == ssl_hs_read_server_hello) {
          uint32_t err = ERR_peek_error();
          if (ERR_GET_LIB(err) == ERR_LIB_SSL &amp;&amp;
              ERR_GET_REASON(err) == SSL_R_SSLV3_ALERT_HANDSHAKE_FAILURE) {
            // Add a dedicated error code to the queue for a handshake_failure
            // alert in response to ClientHello. This matches NSS&#039;s client
            // behavior and gives a better error on a (probable) failure to
            // negotiate initial parameters. Note: this error code comes after
            // the original one.
            //
            // See https://crbug.com/446505.
            OPENSSL_PUT_ERROR(SSL, SSL_R_HANDSHAKE_FAILURE_ON_CLIENT_HELLO);
          }
        }
        bool retry;
        int bio_ret = ssl_handle_open_record(ssl, &amp;retry, ret, consumed, alert);
        if (bio_ret &lt;= 0) {
          return bio_ret;
        }
        if (retry) {
          continue;
        }
        ssl-&gt;s3-&gt;read_buffer.DiscardConsumed();
        break;
      }

      case ssl_hs_read_end_of_early_data: {
        if (ssl-&gt;s3-&gt;hs-&gt;can_early_read) {
          // While we are processing early data, the handshake returns early.
          *out_early_return = true;
          return 1;
        }
        hs-&gt;wait = ssl_hs_ok;
        break;
      }

      case ssl_hs_certificate_selection_pending:
        ssl-&gt;s3-&gt;rwstate = SSL_ERROR_PENDING_CERTIFICATE;
        hs-&gt;wait = ssl_hs_ok;
        return -1;

      case ssl_hs_handoff:
        ssl-&gt;s3-&gt;rwstate = SSL_ERROR_HANDOFF;
        hs-&gt;wait = ssl_hs_ok;
        return -1;

      case ssl_hs_handback: {
        int ret = ssl-&gt;method-&gt;flush_flight(ssl);
        if (ret &lt;= 0) {
          return ret;
        }
        ssl-&gt;s3-&gt;rwstate = SSL_ERROR_HANDBACK;
        hs-&gt;wait = ssl_hs_handback;
        return -1;
      }

        // The following cases are associated with callback APIs which expect to
        // be called each time the state machine runs. Thus they set |hs-&gt;wait|
        // to |ssl_hs_ok| so that, next time, we re-enter the state machine and
        // call the callback again.
      case ssl_hs_x509_lookup:
        ssl-&gt;s3-&gt;rwstate = SSL_ERROR_WANT_X509_LOOKUP;
        hs-&gt;wait = ssl_hs_ok;
        return -1;
      case ssl_hs_private_key_operation:
        ssl-&gt;s3-&gt;rwstate = SSL_ERROR_WANT_PRIVATE_KEY_OPERATION;
        hs-&gt;wait = ssl_hs_ok;
        return -1;
      case ssl_hs_pending_session:
        ssl-&gt;s3-&gt;rwstate = SSL_ERROR_PENDING_SESSION;
        hs-&gt;wait = ssl_hs_ok;
        return -1;
      case ssl_hs_pending_ticket:
        ssl-&gt;s3-&gt;rwstate = SSL_ERROR_PENDING_TICKET;
        hs-&gt;wait = ssl_hs_ok;
        return -1;
      case ssl_hs_certificate_verify:
        ssl-&gt;s3-&gt;rwstate = SSL_ERROR_WANT_CERTIFICATE_VERIFY;
        hs-&gt;wait = ssl_hs_ok;//握手已成功
        return -1;

      case ssl_hs_early_data_rejected:
        assert(ssl-&gt;s3-&gt;early_data_reason != ssl_early_data_unknown);
        assert(!hs-&gt;can_early_write);
        ssl-&gt;s3-&gt;rwstate = SSL_ERROR_EARLY_DATA_REJECTED;
        return -1;

      case ssl_hs_early_return:
        if (!ssl-&gt;server) {
          // On ECH reject, the handshake should never complete.
          assert(ssl-&gt;s3-&gt;ech_status != ssl_ech_rejected);
        }
        *out_early_return = true;
        hs-&gt;wait = ssl_hs_ok;
        return 1;

      case ssl_hs_hints_ready:
        ssl-&gt;s3-&gt;rwstate = SSL_ERROR_HANDSHAKE_HINTS_READY;
        return -1;

      case ssl_hs_ok:
        break;
    }

    // Run the state machine again.
    hs-&gt;wait = ssl-&gt;do_handshake(hs);
    if (hs-&gt;wait == ssl_hs_error) {
      hs-&gt;error.reset(ERR_save_state());
      return -1;
    }
    if (hs-&gt;wait == ssl_hs_ok) {
      if (!ssl-&gt;server) {
        // On ECH reject, the handshake should never complete.
        assert(ssl-&gt;s3-&gt;ech_status != ssl_ech_rejected);
      }
      // The handshake has completed.
      *out_early_return = false;
      return 1;
    }

    // Otherwise, loop to the beginning and resolve what was blocking the
    // handshake.
  }
}</code></pre></div><p>&#160; &#160;（4）还有个很不起眼、容易被忽视的函数：</p><div class="codebox"><pre><code>void SSL_CTX_set_keylog_callback(SSL_CTX *ctx,
                                 void (*cb)(const SSL *ssl, const char *line)) {
  ctx-&gt;keylog_callback = cb;
}</code></pre></div><p>&#160; 从名字就能看出来是存放key日志的，里面记录的全是ssl协商的密钥！有了这些密钥，是不是就能解密双方通信的数据了？https://www.cnblogs.com/theseventhson/p/14618157.html&#160; 这是我之前在PC上用浏览器打开网页时做的操作，记录了ssl协议双方协商的密钥，然后wireshark就能用这些密钥解密数据了！但在android上默认是不记录这些的，需要手动hook来记录，js脚本代码如下：</p><div class="codebox"><pre><code>function startTLSKeyLogger(SSL_CTX_new, SSL_CTX_set_keylog_callback) {
    function keyLogger(ssl, line) {
        console.log(new NativePointer(line).readCString());
    }
    const keyLogCallback = new NativeCallback(keyLogger, &#039;void&#039;, [&#039;pointer&#039;, &#039;pointer&#039;]);

    Interceptor.attach(SSL_CTX_new, {
        onLeave: function(retval) {
            const ssl = new NativePointer(retval);
            const SSL_CTX_set_keylog_callbackFn = new NativeFunction(SSL_CTX_set_keylog_callback, &#039;void&#039;, [&#039;pointer&#039;, &#039;pointer&#039;]);
            SSL_CTX_set_keylog_callbackFn(ssl, keyLogCallback);
        }
    });
}
startTLSKeyLogger(
    Module.findExportByName(&#039;libssl.so&#039;, &#039;SSL_CTX_new&#039;),
    Module.findExportByName(&#039;libssl.so&#039;, &#039;SSL_CTX_set_keylog_callback&#039;)
)</code></pre></div><p>&#160; 这是我hook x音的结果：<br /><span class="postimg"><img src="https://img2022.cnblogs.com/blog/2052730/202203/2052730-20220325221833109-270481208.png" alt="FluxBB bbcode 测试" /></span></p><p>&#160; 注意：这里抓的是libssl.so的keylog函数，也可以把libssl.so换成libttboringssl.so去获取x音的sslkey！具体操作方式可以参考文章末尾第6个！</p><p> </p><p> </p><p> </p><p>参考：</p><p>1、https://bbs.pediy.com/thread-267940.htm&#160; android抓包整理归纳</p><p>2、https://onejane.github.io/2021/05/06/frida%E6%B2%99%E7%AE%B1%E8%87%AA%E5%90%90%E5%AE%9E%E7%8E%B0/#AOSP%E7%BD%91%E7%BB%9C%E5%BA%93%E8%87%AA%E5%90%90&#160; &#160;frida沙箱自吐实现</p><p>3、https://bbs.pediy.com/thread-268014.htm&#160; 绕过非标准http框架和非系统ssl库app的sslpinning</p><p>4、https://blog.csdn.net/tzwsoho/article/details/119346275&#160; [frida]拦截SSL_read/SSL_write函数获得HTTPS请求和响应</p><p>5、http://buaq.net/go-29171.html 关于抓包碎碎念</p><p>6、http://www.zhuoyue360.com/crack/73.html&#160; android硬核抓包</p>]]></description>
			<author><![CDATA[dummy@example.com (batsom)]]></author>
			<pubDate>Wed, 12 Oct 2022 03:19:10 +0000</pubDate>
			<guid>http://www.gentoo-zh.org/viewtopic.php?pid=488#p488</guid>
		</item>
	</channel>
</rss>
