android平台的http/https抓包

抓包环境

android平台通过charles/fiddler + postern进行http/https抓包。以charles为例,需要先将charles的根证书保存。

利用openssl查看charles根证书的md5散列值,然后将文件重命名为: md5散列值.0

接着需要通过挂载修改android /system文件夹的权限为可写,并将重命名后的charles根证书放入/system/etc/security/cacerts/文件夹中,修改放入的根证书文件的权限为777。

adb reboot
adb root
adb remount
adb shell
mount -o rw,remount /system

在android系统信任的根证书列表中中启用charles的根证书。

可以直接在wifi连接处使用手动代理到charles实现抓包。

但是手动配置http代理,有些应用可以选择不使用。类似windows中虽然设置了系统代理但是程序的一些第三方库可以不使用系统代理。这就可以通过使用postern进行tcp流量的转发并设置sock代理为charles(类似于windows的proxifier)。同时charles上需要开启sock代理。

这样就可以愉快的抓包了。。。

SSL pinning

通过导入charles伪造的CA根证书并导入到系统信任列表中的方式可以实现中间人抓包,应用程序为了避免被中间人抓包有可能不使用系统预制的根证书,通过自身携带信任的证书并进行校验。SSL pinning的实现方式有证书绑定和公钥绑定两种方式,例如如下代码使用OKHttp进行证书公钥绑定,设置域名和服务端证书公钥的sha256散列值并进行base64编码,这样OKHttp在SSL握手的时候就会获取到服务端的证书并取得公钥的sha256哈希值并进行base64编码并与已经绑定的信任列表中的base64值进行对比,不相同则会产生异常。

CertificatePinner certPinner = new CertificatePinner.Builder()
        .add("appmattus.com",
              "sha256/4hw5tz+scE+TW+mlai5YipDfFWn1dqvfLG+nU7tq1V8=")
        .build();

OkHttpClient okHttpClient = new OkHttpClient.Builder()
        .certificatePinner(certPinner)
        .build();

分析OKHttp源码发现其最后会调用okhttp3.CertificatePinner.check函数检查服务端的证书与绑定的证书公钥是否相等。所以对于使用了OKHttp框架的app,可以通过hook check函数过掉ssl pinning。

/**
   * Confirms that at least one of the certificates pinned for {@code hostname}
   * is in {@code peerCertificates}. Does nothing if there are no certificates
   * pinned for {@code hostname}. OkHttp calls this after a successful TLS
   * handshake, but before the connection is used.
   *
   * @throws SSLPeerUnverifiedException if {@code peerCertificates} don't match
   *     the certificates pinned for {@code hostname}.
   */
  public void check(String hostname, List<Certificate> peerCertificates)
      throws SSLPeerUnverifiedException {

    Set<ByteString> pins = findMatchingPins(hostname);

    if (pins == null) return;

    for (int i = 0, size = peerCertificates.size(); i < size; i++) {
      X509Certificate x509Certificate = (X509Certificate) peerCertificates.get(i);
      if (pins.contains(sha1(x509Certificate))) return; // Success!
    }

    // If we couldn't find a matching pin, format a nice exception.
    StringBuilder message = new StringBuilder()
        .append("Certificate pinning failure!")
        .append("\n  Peer certificate chain:");
    for (int i = 0, size = peerCertificates.size(); i < size; i++) {
      X509Certificate x509Certificate = (X509Certificate) peerCertificates.get(i);
      message.append("\n    ").append(pin(x509Certificate))
          .append(": ").append(x509Certificate.getSubjectDN().getName());
    }
    message.append("\n  Pinned certificates for ").append(hostname).append(":");
    for (ByteString pin : pins) {
      message.append("\n    sha1/").append(pin.base64());
    }
    throw new SSLPeerUnverifiedException(message.toString());
  }

不同的框架需要针对性的进行hook,objection也支持进行ssl unpinning

混淆后的SSL Pinning

如果apk被混淆则无法直接hook 相关的函数,需要先判断程序使用的具体框架然后找到关键点。针对OKHTTP的证书绑定而言check函数会调用java.io.File打开根证书,还会调用android.security.net.config.RootTrustManager.checkServerTrustedcom.android.org.conscrypt.TrustManagerImpl.checkTrusted等函数,所以可以通过hook这些函数中的任意一个函数回溯找到check函数。

Java.perform(function(){
    // android.security.net.config.RootTrustManager.checkServerTrusted
    Java.use("android.security.net.config.RootTrustManager").checkServerTrusted.overload('[Ljava.security.cert.X509Certificate;', 'java.lang.String').implementation = function(arg0, arg1){
        console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
        return this.checkServerTrusted(arg0, arg1)
    }

    Java.use("android.security.net.config.RootTrustManager").checkServerTrusted.overload('[Ljava.security.cert.X509Certificate;', 'java.lang.String', 'javax.net.ssl.SSLEngine').implementation = function(arg0, arg1, arg2){
        console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
        return this.checkServerTrusted(arg0, arg1, arg2)
    }

    Java.use("android.security.net.config.RootTrustManager").checkServerTrusted.overload('[Ljava.security.cert.X509Certificate;', 'java.lang.String', 'java.lang.String').implementation = function(arg0, arg1, arg2){
        console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
        return this.checkServerTrusted(arg0, arg1, arg2)
    }

    Java.use("android.security.net.config.RootTrustManager").checkServerTrusted.overload('[Ljava.security.cert.X509Certificate;', 'java.lang.String', 'java.net.Socket').implementation = function(arg0, arg1, arg2){
        console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
        return this.checkServerTrusted(arg0, arg1, arg2)
    }
})

请求相关接口的时候,经过栈回溯可以发现混淆后的check函数

双向认证

一般情况https默认进行单项认证,但是有的时候要求进行双向认证即需要额外进行服务端验证客户端。这样就需要客户端向服务端发送自己的证书,在存在双向认证的情况下进行抓包首先要获取到客户端证书和证书的密码。可以通过hook框架层的java.security.KeyStore获取到证书并dump,同时获取到证书密码。注意证书文件有可能在本地,也有可能在内存中,但是最终都会以InputStream的形式传递给java.security.KeyStore

最后将证书导入到Charles中。

posted @ 2022-11-21 23:07  怎么可以吃突突  阅读(689)  评论(0编辑  收藏  举报