背景
在使用 fiddler 对某 Android 应用进行抓包时发现,即使将 fiddler 生成的证书添加至设备信任列表,仍然会在试图登录时产生如下图错误,而 fiddler 中也抓不到请求
逆向分析
反编译应用后分析其 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
...
成功抓包
其他 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");
}
0 Comments