Files
frida-interception-and-unpi…/android/android-system-certificate-injection.js
T

70 lines
3.4 KiB
JavaScript

/**
* Once we have captured traffic (once it's being sent to our proxy port) the next step is
* to ensure any clients using TLS (HTTPS) trust our CA certificate, to allow us to intercept
* encrypted connections successfully.
*
* This script does so by attaching to the internals of Conscrypt (the Android SDK's standard
* TLS implementation) and pre-adding our certificate to the 'already trusted' cache, so that
* future connections trust it implicitly. This ensures that all normal uses of Android APIs
* for HTTPS & TLS will allow interception.
*
* This does not handle all standalone certificate pinning techniques - where the application
* actively rejects certificates that are trusted by default on the system. That's dealt with
* in the separate certificate unpinning script.
*/
Java.perform(() => {
// First, we build a JVM representation of our certificate:
const String = Java.use("java.lang.String");
const ByteArrayInputStream = Java.use('java.io.ByteArrayInputStream');
const CertFactory = Java.use('java.security.cert.CertificateFactory');
const certFactory = CertFactory.getInstance("X.509");
const certBytes = String.$new(CERT_PEM).getBytes();
const cert = certFactory.generateCertificate(ByteArrayInputStream.$new(certBytes));
// Then we hook TrustedCertificateIndex. This is used for caching known trusted certs within Conscrypt -
// by prepopulating all instances, we ensure that all TrustManagerImpls (and potentially other
// things) automatically trust our certificate specifically (without disabling validation entirely).
// This should apply to Android v7+ - previous versions used SSLContext & X509TrustManager.
[
'com.android.org.conscrypt.TrustedCertificateIndex',
'org.conscrypt.TrustedCertificateIndex', // Might be used (com.android is synthetic) - unclear
'org.apache.harmony.xnet.provider.jsse.TrustedCertificateIndex' // Used in Apache Harmony version of Conscrypt
].forEach((TrustedCertificateIndexClassname, i) => {
let TrustedCertificateIndex;
try {
TrustedCertificateIndex = Java.use(TrustedCertificateIndexClassname);
} catch (e) {
if (i === 0) {
throw new Error(`${TrustedCertificateIndexClassname} not found - could not inject system certificate`);
} else {
// Other classnames are optional fallbacks
return;
}
}
TrustedCertificateIndex.$init.overloads.forEach((overload) => {
overload.implementation = function () {
this.$init(...arguments);
// Index our cert as already trusted, right from the start:
this.index(cert);
}
});
TrustedCertificateIndex.reset.overloads.forEach((overload) => {
overload.implementation = function () {
const result = this.reset(...arguments);
// Index our cert in here again, since the reset removes it:
this.index(cert);
return result;
};
});
});
// This effectively adds us to the system certs, and also defeats quite a bit of basic certificate
// pinning too! It auto-trusts us in any implementation that uses TrustManagerImpl (Conscrypt) as
// the underlying cert checking component.
console.log('== System certificate trust injected ==');
});