httptoolkit / frida-interception-and-unpinning

Frida scripts to directly MitM all HTTPS traffic from a target mobile application
https://httptoolkit.com/android/
GNU Affero General Public License v3.0
1.04k stars 194 forks source link

TLS Failure Due to Certificate Pinning Mismatch #107

Closed elyeandre closed 3 weeks ago

elyeandre commented 1 month ago

Command Used: frida -U -l ./config.js -l ./native-tls-hook.js -l ./android/android-certificate-unpinning.js -l ./android/android-certificate-unpinning-fallback.js -f com.package.name

Issue: During execution of the application, an unexpected TLS failure occurred because the certificate pinning was mismatched. Furthermore, there were some cases whereby interception of traffic could be done through OkHttp fallback while in other requests, connection was aborted.

     ____
    / _  |   Frida 16.4.8 - A world-class dynamic instrumentation toolkit
   | (_| |
    > _  |   Commands:
   /_/ |_|       help      -> Displays the help system
   . . . .       object?   -> Display information about 'object'
   . . . .       exit/quit -> Exit
   . . . .
   . . . .   More info at https://frida.re/docs/home/
   . . . .
   . . . .   Connected to Android Emulator 5554 (id=emulator-5554)
Spawning `com.mayor.android.mobile`...

*** Starting scripts ***
== Redirecting all TCP connections to 192.168.184.1:8000 ==
Spawned `com.mayor.android.mobile`. Resuming main thread!
[Android Emulator 5554::com.mayor.android.mobile ]->
    === Disabling all recognized unpinning libraries ===
[+] javax.net.ssl.HttpsURLConnection setDefaultHostnameVerifier
[+] javax.net.ssl.HttpsURLConnection setSSLSocketFactory
[+] javax.net.ssl.HttpsURLConnection setHostnameVerifier
[+] javax.net.ssl.SSLContext init(KeyManager;[], TrustManager;[], SecureRandom)
[ ] com.android.org.conscrypt.CertPinManager isChainValid
[+] com.android.org.conscrypt.CertPinManager checkChainPinning
[+] android.security.net.config.NetworkSecurityConfig $init(*) (0)
[+] android.security.net.config.NetworkSecurityConfig $init(*) (1)
[+] com.android.okhttp.internal.tls.OkHostnameVerifier verify(String, SSLSession)
[+] com.android.okhttp.Address $init(String, int, Dns, SocketFactory, SSLSocketFactory, HostnameVerifier, CertificatePinner, Authenticator, Proxy, List, List, ProxySelector)
[ ] com.android.okhttp.Address $init(String, int, SocketFactory, SSLSocketFactory, HostnameVerifier, CertificatePinner, Authenticator, Proxy, List, List, ProxySelector)
[ ] okhttp3.CertificatePinner *
[ ] com.squareup.okhttp.CertificatePinner *
[ ] com.datatheorem.android.trustkit.pinning.PinningTrustManager *
[ ] appcelerator.https.PinningTrustManager *
[ ] nl.xservices.plugins.sslCertificateChecker *
[ ] com.worklight.wlclient.api.WLClient *
[ ] com.worklight.wlclient.certificatepinning.HostNameVerifierWithCertificatePinning *
[ ] com.worklight.androidgap.plugin.WLCertificatePinningPlugin *
[ ] com.commonsware.cwac.netsecurity.conscrypt.CertPinManager *
[ ] io.netty.handler.ssl.util.FingerprintTrustManagerFactory *
[ ] com.silkimen.cordovahttp.CordovaServerTrust *
[ ] com.appmattus.certificatetransparency.internal.verifier.CertificateTransparencyHostnameVerifier *
[ ] com.appmattus.certificatetransparency.internal.verifier.CertificateTransparencyInterceptor *
[ ] com.appmattus.certificatetransparency.internal.verifier.CertificateTransparencyTrustManager *
== Certificate unpinning completed ==
== Unpinning fallback auto-patcher installed ==
*** Scripts completed ***

 => android.security.net.config.NetworkSecurityConfig $init(*) (0)
 => android.security.net.config.NetworkSecurityConfig $init(*) (0)
 => javax.net.ssl.SSLContext init(KeyManager;[], TrustManager;[], SecureRandom)
 => javax.net.ssl.SSLContext init(KeyManager;[], TrustManager;[], SecureRandom)
Ignoring unix:stream connection
Ignoring unix:stream connection
Connected tcp6 fd 77 to null (-1)
Ignoring unix:dgram connection
 => com.android.okhttp.Address $init(String, int, Dns, SocketFactory, SSLSocketFactory, HostnameVerifier, CertificatePinner, Authenticator, Proxy, List, List, ProxySelector)
Ignoring unix:stream connection
Ignoring unix:stream connection
Connected tcp6 fd 92 to {"ip":"::ffff:192.168.184.1","port":8000} (0)
 => com.android.okhttp.internal.tls.OkHostnameVerifier verify(String, SSLSession)
 => javax.net.ssl.SSLContext init(KeyManager;[], TrustManager;[], SecureRandom)
 => javax.net.ssl.SSLContext init(KeyManager;[], TrustManager;[], SecureRandom)
Ignoring unix:stream connection
Ignoring unix:stream connection
Connected tcp6 fd 103 to null (-1)

 !!! --- Unexpected TLS failure --- !!!
      SSLPeerUnverifiedException: Certificate pinning failure!
  Peer certificate chain:
    sha256/45MwqYJYBYaCqUSj9sDiEHpBzO0YUhtCQqYUCYJ5nNc=: O=Mockttp Cert - DO NOT TRUST,L=Unknown,C=XX,CN=mayor.net
    sha256/r3O8hmDiszrhrkw6EtyXuh5Id3OJM/V1MOava55/RlE=: O=HTTP Toolkit CA,C=XX,CN=HTTP Toolkit CA
  Pinned certificates for mayor.net:
    sha256/hxqRlPTu1bMS/0DITB1SSu0vd4u/8l8TjPgfaAp63Gc=
    sha256/Vfd95BwDeSQo+NUYxVEEIlvkOlWY2SalKK1lPhzOx78=
    sha256/QXnt2YHvdHR3tJYmQIr0Paosp6t/nggsEGD4QJZ3Q0g=
    sha256/mEflZT5enoR1FuXLgYYGqnVEoZvmf9c2bVBpiOjYQ0c=
    sha256/CLOmM1/OXvSPjw5UOYbAf9GKOxImEp9hhku9W90fHMk=
    sha256/ICGRfpgmOUXIWcQ/HXPLQTkFPEFPoDyjvH7ohhQpjzs=
    sha256/grX4Ta9HpZx6tSHkmCrvpApTQGo67CYDnvprLg5yRME=
    sha256/C5+lpZ7tcVwmwQIMcRtPbsQtWLABXhQzejna0wHFr8M=
    sha256/Y9mvm0exBk1JoQ57f9Vm28jKo5lFm/woKcVxrYxu80o=
    sha256/x4QzPSC810K5/cMjb05Qm4k3Bw5zBn4lTdO/nEW/Td4=
    sha256/diGVwiVYbubAI3RW4hB9xU8e/CH2GnkuvVFZE8zmgzI=
      Thrown by d7.h->a
      [+] d7.h->a (fallback OkHttp patch)
Ignoring unix:stream connection
Ignoring unix:stream connection
Connected tcp6 fd 105 to null (-1)
 => Fallback OkHttp patch
 => com.android.okhttp.Address $init(String, int, Dns, SocketFactory, SSLSocketFactory, HostnameVerifier, CertificatePinner, Authenticator, Proxy, List, List, ProxySelector)
Ignoring unix:stream connection
Ignoring unix:stream connection
Connected tcp6 fd 102 to null (-1)
 => com.android.okhttp.internal.tls.OkHostnameVerifier verify(String, SSLSession)
 => com.android.okhttp.Address $init(String, int, Dns, SocketFactory, SSLSocketFactory, HostnameVerifier, CertificatePinner, Authenticator, Proxy, List, List, ProxySelector)
[Android Emulator 5554::com.mayor.android.mobile ]-> exit
pimterry commented 4 weeks ago

Thanks for the report @elyeandre.

Unfortunately from that output it's not clear what's happening here. The 'Unexpected TLS failure' means that one request wasn't caught by the built-in unpinning scripts, but the [+] d7.h->a (fallback OkHttp patch) means that the script managed to recognize this as OkHttp (i.e. it's actually the okhttp3.CertificatePinner class obfuscated as d7.h with the check method obfuscated as a) and automatically patch it after that first failure - so there shouldn't be any other failures from this pinning later on, and this isn't a big deal.

That would explain one failed connection, but usually one failure doesn't break an app (since generally they'll retry failures at some point, and then it'll work), and future connections should work fine until the app next restarts.

You can see more about how that works in the script here: https://github.com/httptoolkit/frida-interception-and-unpinning/blob/main/android/android-certificate-unpinning-fallback.js.

If the app isn't working despite this, I think there's two possibilities:

  1. The single failing request is a big enough problem for this specific app that it doesn't recover. To handle this and avoid it failing even one time, you'll need to extend android-certificate-unpinning.js to automatically patch method a of d7.h in the same way as the normal OkHttp methods: https://github.com/httptoolkit/frida-interception-and-unpinning/blob/main/android/android-certificate-unpinning.js#L258-L278
  2. Alternatively, this is a red herring, and the real problem is that something else is failing and the error isn't shown here. In that case, you'll need to do some reverse engineering to work out what's really causing this problem. You can find more info on that here: https://httptoolkit.com/blog/android-reverse-engineering/
elyeandre commented 3 weeks ago

@pimterry Thanks for reply and suggestions

I've look into an app using JADX and found some interesting code snippets. However, I’m unsure how to effectively hook into those methods. Any assistance would be greatly appreciated.

To replicate the issue, use the following app link: app.apk

package p243f1;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;

/* renamed from: f1.b */
/* loaded from: classes.dex */
public final /* synthetic */ class C3609b implements HostnameVerifier {
    @Override // javax.net.ssl.HostnameVerifier
    public final boolean verify(String str, SSLSession sSLSession) {
        return true;
    }
}
package p243f1;

import java.security.cert.X509Certificate;
import javax.net.ssl.X509TrustManager;

/* renamed from: f1.c */
/* loaded from: classes.dex */
public final class C3610c implements X509TrustManager {
    @Override // javax.net.ssl.X509TrustManager
    public final X509Certificate[] getAcceptedIssuers() {
        return new X509Certificate[0];
    }

    @Override // javax.net.ssl.X509TrustManager
    public final void checkClientTrusted(X509Certificate[] x509CertificateArr, String str) {
    }

    @Override // javax.net.ssl.X509TrustManager
    public final void checkServerTrusted(X509Certificate[] x509CertificateArr, String str) {
    }
}
package p299l7;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.cert.TrustAnchor;
import java.security.cert.X509Certificate;
import javax.net.ssl.X509TrustManager;
import p074J6.AbstractC0794h;
import p329p7.InterfaceC4575d;

/* renamed from: l7.b */
/* loaded from: classes.dex */
public final class C4272b implements InterfaceC4575d {

    /* renamed from: a */
    public final X509TrustManager f15563a;

    /* renamed from: b */
    public final Method f15564b;

    public C4272b(X509TrustManager x509TrustManager, Method method) {
        this.f15563a = x509TrustManager;
        this.f15564b = method;
    }

    @Override // p329p7.InterfaceC4575d
    /* renamed from: a */
    public final X509Certificate mo9115a(X509Certificate x509Certificate) {
        AbstractC0794h.m1535f("cert", x509Certificate);
        try {
            Object invoke = this.f15564b.invoke(this.f15563a, x509Certificate);
            if (invoke != null) {
                return ((TrustAnchor) invoke).getTrustedCert();
            }
            throw new NullPointerException("null cannot be cast to non-null type java.security.cert.TrustAnchor");
        } catch (IllegalAccessException e8) {
            throw new AssertionError("unable to get issues and signature", e8);
        } catch (InvocationTargetException unused) {
            return null;
        }
    }

    public final boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (!(obj instanceof C4272b)) {
            return false;
        }
        C4272b c4272b = (C4272b) obj;
        if (AbstractC0794h.m1530a(this.f15563a, c4272b.f15563a) && AbstractC0794h.m1530a(this.f15564b, c4272b.f15564b)) {
            return true;
        }
        return false;
    }

    public final int hashCode() {
        return this.f15564b.hashCode() + (this.f15563a.hashCode() * 31);
    }

    public final String toString() {
        return "CustomTrustRootIndex(trustManager=" + this.f15563a + ", findByIssuerAndSignatureMethod=" + this.f15564b + ')';
    }
}
package p299l7;

import java.security.KeyStore;
import java.security.Provider;
import java.util.Arrays;
import java.util.List;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import org.conscrypt.Conscrypt;
import p074J6.AbstractC0794h;
import p220c4.C2805v;

/* renamed from: l7.h */
/* loaded from: classes.dex */
public final class C4278h extends C4284n {

    /* renamed from: d */
    public static final boolean f15571d;

    /* renamed from: c */
    public final Provider f15572c;

    static {
        boolean z7 = false;
        try {
            Class.forName("org.conscrypt.Conscrypt$Version", false, AbstractC4276f.class.getClassLoader());
            if (Conscrypt.isAvailable()) {
                if (AbstractC4276f.m9148a()) {
                    z7 = true;
                }
            }
        } catch (ClassNotFoundException | NoClassDefFoundError unused) {
        }
        f15571d = z7;
    }

    public C4278h() {
        Provider newProvider = Conscrypt.newProvider();
        AbstractC0794h.m1534e("newProvider()", newProvider);
        this.f15572c = newProvider;
    }

    @Override // p299l7.C4284n
    /* renamed from: d */
    public final void mo9112d(SSLSocket sSLSocket, String str, List list) {
        AbstractC0794h.m1535f("protocols", list);
        if (Conscrypt.isConscrypt(sSLSocket)) {
            Conscrypt.setUseSessionTickets(sSLSocket, true);
            Object[] array = C2805v.m5677d(list).toArray(new String[0]);
            if (array != null) {
                Conscrypt.setApplicationProtocols(sSLSocket, (String[]) array);
                return;
            }
            throw new NullPointerException("null cannot be cast to non-null type kotlin.Array<T of kotlin.collections.ArraysKt__ArraysJVMKt.toTypedArray>");
        }
        super.mo9112d(sSLSocket, str, list);
    }

    @Override // p299l7.C4284n
    /* renamed from: f */
    public final String mo9113f(SSLSocket sSLSocket) {
        if (Conscrypt.isConscrypt(sSLSocket)) {
            return Conscrypt.getApplicationProtocol(sSLSocket);
        }
        return null;
    }

    @Override // p299l7.C4284n
    /* renamed from: l */
    public final SSLContext mo9146l() {
        SSLContext sSLContext = SSLContext.getInstance("TLS", this.f15572c);
        AbstractC0794h.m1534e("getInstance(\"TLS\", provider)", sSLContext);
        return sSLContext;
    }

    @Override // p299l7.C4284n
    /* renamed from: m */
    public final SSLSocketFactory mo9150m(X509TrustManager x509TrustManager) {
        SSLContext mo9146l = mo9146l();
        mo9146l.init(null, new TrustManager[]{x509TrustManager}, null);
        SSLSocketFactory socketFactory = mo9146l.getSocketFactory();
        AbstractC0794h.m1534e("newSSLContext().apply {\n…null)\n    }.socketFactory", socketFactory);
        return socketFactory;
    }

    @Override // p299l7.C4284n
    /* renamed from: n */
    public final X509TrustManager mo9147n() {
        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        trustManagerFactory.init((KeyStore) null);
        TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
        AbstractC0794h.m1532c(trustManagers);
        if (trustManagers.length == 1) {
            TrustManager trustManager = trustManagers[0];
            if (trustManager instanceof X509TrustManager) {
                if (trustManager != null) {
                    X509TrustManager x509TrustManager = (X509TrustManager) trustManager;
                    Conscrypt.setHostnameVerifier(x509TrustManager, C4277g.f15570a);
                    return x509TrustManager;
                }
                throw new NullPointerException("null cannot be cast to non-null type javax.net.ssl.X509TrustManager");
            }
        }
        String arrays = Arrays.toString(trustManagers);
        AbstractC0794h.m1534e("toString(this)", arrays);
        throw new IllegalStateException(AbstractC0794h.m1540k("Unexpected default trust managers: ", arrays).toString());
    }
}
package p243f1;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;

/* renamed from: f1.b */
/* loaded from: classes.dex */
public final /* synthetic */ class C3609b implements HostnameVerifier {
    @Override // javax.net.ssl.HostnameVerifier
    public final boolean verify(String str, SSLSession sSLSession) {
        return true;
    }
}
package p329p7;

import java.security.cert.X509Certificate;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Set;
import javax.security.auth.x500.X500Principal;
import p074J6.AbstractC0794h;

/* renamed from: p7.b */
/* loaded from: classes.dex */
public final class C4573b implements InterfaceC4575d {

    /* renamed from: a */
    public final LinkedHashMap f16718a;

    public C4573b(X509Certificate... x509CertificateArr) {
        AbstractC0794h.m1535f("caCerts", x509CertificateArr);
        LinkedHashMap linkedHashMap = new LinkedHashMap();
        int length = x509CertificateArr.length;
        int i8 = 0;
        while (i8 < length) {
            X509Certificate x509Certificate = x509CertificateArr[i8];
            i8++;
            X500Principal subjectX500Principal = x509Certificate.getSubjectX500Principal();
            AbstractC0794h.m1534e("caCert.subjectX500Principal", subjectX500Principal);
            Object obj = linkedHashMap.get(subjectX500Principal);
            if (obj == null) {
                obj = new LinkedHashSet();
                linkedHashMap.put(subjectX500Principal, obj);
            }
            ((Set) obj).add(x509Certificate);
        }
        this.f16718a = linkedHashMap;
    }

    @Override // p329p7.InterfaceC4575d
    /* renamed from: a */
    public final X509Certificate mo9115a(X509Certificate x509Certificate) {
        AbstractC0794h.m1535f("cert", x509Certificate);
        Set set = (Set) this.f16718a.get(x509Certificate.getIssuerX500Principal());
        Object obj = null;
        if (set == null) {
            return null;
        }
        Iterator it = set.iterator();
        while (true) {
            if (!it.hasNext()) {
                break;
            }
            Object next = it.next();
            try {
                x509Certificate.verify(((X509Certificate) next).getPublicKey());
                obj = next;
                break;
            } catch (Exception unused) {
            }
        }
        return (X509Certificate) obj;
    }

    public final boolean equals(Object obj) {
        if (obj != this && (!(obj instanceof C4573b) || !AbstractC0794h.m1530a(((C4573b) obj).f16718a, this.f16718a))) {
            return false;
        }
        return true;
    }

    public final int hashCode() {
        return this.f16718a.hashCode();
    }
}
package p307m7;

import android.net.http.X509TrustManagerExtensions;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.List;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.X509TrustManager;
import p074J6.AbstractC0794h;
import p385w5.AbstractC5413l0;

/* renamed from: m7.b */
/* loaded from: classes.dex */
public final class C4365b extends AbstractC5413l0 {

    /* renamed from: a */
    public final X509TrustManager f15889a;

    /* renamed from: b */
    public final X509TrustManagerExtensions f15890b;

    public C4365b(X509TrustManager x509TrustManager, X509TrustManagerExtensions x509TrustManagerExtensions) {
        this.f15889a = x509TrustManager;
        this.f15890b = x509TrustManagerExtensions;
    }

    public final boolean equals(Object obj) {
        if ((obj instanceof C4365b) && ((C4365b) obj).f15889a == this.f15889a) {
            return true;
        }
        return false;
    }

    public final int hashCode() {
        return System.identityHashCode(this.f15889a);
    }

    @Override // p385w5.AbstractC5413l0
    /* renamed from: i */
    public final List mo9347i(String str, List list) {
        AbstractC0794h.m1535f("chain", list);
        AbstractC0794h.m1535f("hostname", str);
        Object[] array = list.toArray(new X509Certificate[0]);
        if (array != null) {
            try {
                List<X509Certificate> checkServerTrusted = this.f15890b.checkServerTrusted((X509Certificate[]) array, "RSA", str);
                AbstractC0794h.m1534e("x509TrustManagerExtensio…ficates, \"RSA\", hostname)", checkServerTrusted);
                return checkServerTrusted;
            } catch (CertificateException e8) {
                SSLPeerUnverifiedException sSLPeerUnverifiedException = new SSLPeerUnverifiedException(e8.getMessage());
                sSLPeerUnverifiedException.initCause(e8);
                throw sSLPeerUnverifiedException;
            }
        }
        throw new NullPointerException("null cannot be cast to non-null type kotlin.Array<T of kotlin.collections.ArraysKt__ArraysJVMKt.toTypedArray>");
    }
}