diff --git a/src/mobile-pentesting/android-app-pentesting/android-anti-instrumentation-and-ssl-pinning-bypass.md b/src/mobile-pentesting/android-app-pentesting/android-anti-instrumentation-and-ssl-pinning-bypass.md index 1ca8d83dc9a..dcee8f8c92f 100644 --- a/src/mobile-pentesting/android-app-pentesting/android-anti-instrumentation-and-ssl-pinning-bypass.md +++ b/src/mobile-pentesting/android-app-pentesting/android-anti-instrumentation-and-ssl-pinning-bypass.md @@ -242,6 +242,79 @@ Notes - Extend for OkHttp: hook okhttp3.CertificatePinner and HostnameVerifier as needed, or use a universal unpinning script from CodeShare. - Run example: `frida -U -f com.target.app -l ssl-bypass.js --no-pause` +### mTLS interception: bypass server pinning without breaking client auth + +For **mTLS** apps, `SSLContext.init(KeyManager[], TrustManager[], SecureRandom)` controls **two different trust decisions**: +- **`TrustManager[]`** validates the **server** certificate. +- **`KeyManager[]`** presents the **client** certificate/private key. + +If you replace **both** arrays with a generic “trust all” hook, the app may accept Burp's certificate but **stop sending its client certificate**, so the handshake still fails. In mTLS scenarios, keep the original `KeyManager[]` and replace **only** `TrustManager[]`. + +```js +Java.perform(function () { + var X509TrustManager = Java.use('javax.net.ssl.X509TrustManager'); + var SSLContext = Java.use('javax.net.ssl.SSLContext'); + var TrustAll = Java.registerClass({ + name: 'com.ht.TrustAll', implements: [X509TrustManager], methods: { + checkClientTrusted: function () {}, checkServerTrusted: function () {}, + getAcceptedIssuers: function () { return []; } + } + }); + var init = SSLContext.init.overload('[Ljavax.net.ssl.KeyManager;','[Ljavax.net.ssl.TrustManager;','java.security.SecureRandom'); + init.implementation = function (km, tm, sr) { + return init.call(this, km, Java.array('javax.net.ssl.TrustManager', [TrustAll.$new()]), sr); + }; +}); +``` + +### mTLS client certificate extraction from live keystore reloads + +A common Android mTLS pattern is: +1. generate an app keypair, +2. store the private key + issued client cert in **PKCS12** (`.p12`), often with a runtime-derived password, +3. reload that keystore on every request to build a `KeyManager`. + +That password can be strong at rest and still be useless during runtime: the app must eventually call `KeyStore.load(...)`, `getCertificate(...)`, and `getKey(alias, password)` in-process. Hook the method/constructor that receives the **decrypted `KeyStore`**, alias, and password (often a custom `KeyManager` wrapper) and dump the live material instead of brute-forcing the `.p12` offline. + +Quick triage: +- `privateKey.getEncoded()` returns **bytes** → software/JCE key, usually exportable. +- `privateKey.getEncoded()` returns **`null`** → likely `AndroidKeyStore`/TEE-backed, so direct key export is blocked and you need a different approach. + +
+Frida example: dump client cert/private key from a decrypted PKCS12-backed KeyStore + +```js +Java.perform(function () { + var CKM = Java.use('com.example.app.ClientKeyManager'); + var Base64 = Java.use('android.util.Base64'); + var X509Certificate = Java.use('java.security.cert.X509Certificate'); + + CKM.$init.implementation = function (ks, alias, password) { + this.$init(ks, alias, password); + var cert = Java.cast(ks.getCertificate(alias), X509Certificate); + var certPem = Base64.encodeToString(cert.getEncoded(), 0); + var key = ks.getKey(alias, password); + var raw = key.getEncoded(); + console.log('alias=' + alias + ' password=' + password); + console.log('CERT=' + certPem); + console.log('KEY=' + (raw ? Base64.encodeToString(raw, 0) : 'null')); + }; +}); +``` + +
+ +If the key is exportable, convert the dumped PEM key + certificate into a Burp-compatible client bundle: + +```bash +openssl pkcs12 -export -out client-cert.pfx -inkey privateKey.key -in cert.pem +``` + +Useful extra hook points when Frida is attached **before enrollment**: +- `KeyPairGenerator.generateKeyPair()` +- `KeyStore.setKeyEntry()` +- custom registration code that signs a nonce before the server issues the client certificate + ### OkHttp4 / gRPC / Cronet pinning (2024+) Modern stacks pin inside newer APIs (OkHttp4+, gRPC over Cronet/BoringSSL). Add these hooks when the basic SSLContext hook hangs: @@ -482,4 +555,6 @@ Notes - [Frida OkHttp4 SSL pinning bypass script](https://github.com/Zero3141/Frida-OkHttp-Bypass) - [XDA guide to strong Play Integrity bypass (2025)](https://xdaforums.com/t/updated-11-17-2025-guide-get-strong-integrity-fix-banking-apps-revolut-google-wallet-android-16-working.4753805/) - [Weaponizing LSPosed: Remote SMS Injection and Identity Spoofing in Modern Payment Ecosystems](https://www.cloudsek.com/blog/weaponizing-lsposed-remote-sms-injection-and-identity-spoofing-in-modern-payment-ecosystems-2) +- [How to Bypass mTLS on Android with Frida](https://kiratliygt.medium.com/how-to-bypass-mtls-on-android-with-frida-45c5e71373e8) +- [Demo-mTLS- lab app/server](https://github.com/YigitK-1/Demo-mTLS-) {{#include ../../banners/hacktricks-training.md}}