背景

在使用 fiddler 对某 Android 应用进行抓包时发现,即使将 fiddler 生成的证书添加至设备信任列表,仍然会在试图登录时产生如下图错误,而 fiddler 中也抓不到请求

1.png

逆向分析

反编译应用后分析其 java 源码,发现其 HTTPS 请求类中存在 Certificate Pinning (证书锁定) 措施,主要实现位于:

  • com.amazon.identity.auth.device.framework.security.CertificatePinning
  • com.amazon.identity.auth.device.framework.AuthenticationWebViewClient

其中 CertificatePinning 通过 javax.net.ssl.TrustManagerFactory 先从指定证书生成 TrustManager,再进行 javax.net.ssl.SSLContext.init 方法,实现证书锁定

AuthenticationWebViewClient 继承自 android.webkit.WebViewClient,通过 onReceivedSslError 方法实现证书锁定

编写 frida 脚本

首先,用 fiddler 证书来生成 TrustManagers,对 TrustManagerFactory.getTrustManagers 进行 hook,使其永远返回指定的 TrustManagers

TrustManagerFactory.getTrustManagers.implementation = function () {
    console.log("App invoked TrustManagerFactory.getTrustManagers");
    return TrustManagers;
}

SSLContext.init 进行 hook

var SSLContext_init = SSLContext.init.overload("[Ljavax.net.ssl.KeyManager;", "[Ljavax.net.ssl.TrustManager;", "java.security.SecureRandom");
SSLContext_init.implementation = function (a, b, c) {
    console.log("App invoked javax.net.ssl.SSLContext.init");
    SSLContext_init.call(this, null, TrustManagers, null);
}

对于 WebView,对 WebViewClient.onReceivedSslError 进行 hook,以忽略证书错误

WebViewClient.onReceivedSslError.implementation = function (webView, sslErrorHandler, sslError) {
    sslErrorHandler.proceed();
    // proceed 方法使 WebView 忽略 SSL 证书错误
    return;
}

完整脚本如下

Java.perform(function () {
    console.log("Certificate Pinning Bypass");
    var WebViewClient = Java.use("android.webkit.WebViewClient");
    var CertificateFactory = Java.use("java.security.cert.CertificateFactory");
    var FileInputStream = Java.use("java.io.FileInputStream");
    var BufferedInputStream = Java.use("java.io.BufferedInputStream");
    var X509Certificate = Java.use("java.security.cert.X509Certificate");
    var KeyStore = Java.use("java.security.KeyStore");
    var TrustManagerFactory = Java.use("javax.net.ssl.TrustManagerFactory");
    var SSLContext = Java.use("javax.net.ssl.SSLContext");

    var cf = CertificateFactory.getInstance("X.509");
    var fileInputStream = FileInputStream.$new("/data/local/tmp/fiddler.crt");
    var bufferedInputStream = BufferedInputStream.$new(fileInputStream);
    var ca = cf.generateCertificate(bufferedInputStream);
    bufferedInputStream.close();
    var certInfo = Java.cast(ca, X509Certificate);
    var keyStoreType = KeyStore.getDefaultType();
    var keyStore = KeyStore.getInstance(keyStoreType);
    keyStore.load(null, null)
    keyStore.setCertificateEntry("ca", ca);

    var tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
    var tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
    tmf.init(keyStore);
    var TrustManagers = tmf.getTrustManagers();
    console.log("Crafted TrustManagers");


    /*** TrustManagerFactory.getTrustManagers Hook ***/

    TrustManagerFactory.getTrustManagers.implementation = function () {
        console.log("App invoked TrustManagerFactory.getTrustManagers");
        return TrustManagers;
    }


    /*** SSLContext.init Hook ***/

    var SSLContext_init = SSLContext.init.overload("[Ljavax.net.ssl.KeyManager;", "[Ljavax.net.ssl.TrustManager;", "java.security.SecureRandom");
    SSLContext.init.overload("[Ljavax.net.ssl.KeyManager;", "[Ljavax.net.ssl.TrustManager;", "java.security.SecureRandom").implementation = function (a, b, c) {
        console.log("App invoked javax.net.ssl.SSLContext.init");
        SSLContext.init.overload("[Ljavax.net.ssl.KeyManager;", "[Ljavax.net.ssl.TrustManager;", "java.security.SecureRandom").call(this, null, TrustManagers, null);
    }


    /*** WebView Hooks ***/

    WebViewClient.onReceivedSslError.implementation = function (webView, sslErrorHandler, sslError) {
        console.log("App invoked WebViewClient.onReceivedSslError");
        sslErrorHandler.proceed();
        return;
    }

    WebViewClient.onReceivedError.overload("android.webkit.WebView", "int", "java.lang.String", "java.lang.String").implementation = function (a, b, c, d) {
        console.log("App invoked WebViewClient.onReceivedError");
        return;
    }

    WebViewClient.onReceivedError.overload("android.webkit.WebView", "android.webkit.WebResourceRequest", "android.webkit.WebResourceError").implementation = function () {
        console.log("App invoked WebViewClient.onReceivedError");
        return;
    }
});

注入操作

将 fiddler 证书导出,放至脚本中所设设备路径 (如:/data/local/tmp/fiddler.crt)

$ adb push /path/to/fiddler.crt /data/local/tmp/fiddler.crt

将合适版本的 frida-server 上传至设备,通过 adb shell 启动

$ adb shell /data/local/tmp/frida-server &

进行 spawn 注入

$ frida -U -f com.foo.bar --no-pause -l hooks.js
...
Spawned `com.foo.bar`. Resuming main thread!
[FooBar::com.foo.bar]-> Certificate Pinning Bypass
Crafted TrustManagers
App invoked TrustManagerFactory.getTrustManagers
App invoked TrustManagerFactory.getTrustManagers
...

成功抓包

2.png

其他 pinning 方法的绕过

okhttp

var CertificatePinner = Java.use("com.squareup.okhttp.CertificatePinner");

CertificatePinner.check.overload("java.lang.String", "[Ljava.security.cert.Certificate;").implementation = function (a, b) {
    console.log("App invoked CertificatePinner.check");
    return;
};

CertificatePinner.check.overload("java.lang.String", "java.util.List").implementation = function (a, b) {
    console.log("App invoked CertificatePinner.check");
    return;
};

okhttp3

var CertificatePinner = Java.use("okhttp3.CertificatePinner");

CertificatePinner.check.overload("java.lang.String", "java.util.List").implementation = function () {
    console.log("App invoked CertificatePinner.check");
}

发表评论

电子邮件地址不会被公开。 必填项已用*标注