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
909 stars 179 forks source link

io.moia.neptune: SSLPeerUnverifiedException: Certificate pinning failure! #66

Open niansa opened 6 months ago

niansa commented 6 months ago

Hi,

I am attempting to intercept the traffic of io.moia.neptune, however, I'm running into this error message:

 !!! --- Unexpected TLS failure --- !!!
      SSLPeerUnverifiedException: Certificate pinning failure!
  Peer certificate chain:
    sha256/WRNosUTmmEybGMgY1sPzXeJQ2oPwpPkpSooKAkz1iik=: O=Mockttp Cert - DO NOT TRUST,L=Unknown,C=XX,CN=remote-logging-app-index.trip.prd.moia-group.io
    sha256/WwZe/T7+bzD2vKjH+r9PEK5+vidkPRTRR4Up4WL4u10=: O=HTTP Toolkit CA,C=XX,CN=HTTP Toolkit CA
  Pinned certificates for remote-logging-app-index.trip.prd.moia-group.io:
    sha256/++MBgDH5WGvL9Bcn5Be30cRcL0f5O+NyoXuWtQdX1aI=
    sha256/f0KW/FtqTjs108NpYj42SrGvOB2PpxIVM8nWxjPqJGE=
    sha256/NqvDJlas/GRcYbcWE8S/IceH9cq77kg0jVhZeAPXq8k=
    sha256/9+ze1cZgR9KO1kZrVDxA4HQ6voHRCSVNz4RdTCx4U8U=
    sha256/KwccWaCgrnaw6tsrrSO61FgLacNgG2MMLq8GE6+oP5I=
      Thrown by pm0.h->a
      [+] pm0.h->a (fallback OkHttp patch)
Process terminated

Any idea what that means/how to fix it? To me it sounds like Mockttp Cert - DO NOT TRUST means that the app is explicitly disallowing that certificate.

niansa commented 6 months ago

Patching out the throw in pm0.h->a worked, just replaced it with a return-void.

pimterry commented 6 months ago

To me it sounds like Mockttp Cert - DO NOT TRUST means that the app is explicitly disallowing that certificate.

This is a red herring that's just the default internal certificate subject name.

Patching out the throw in pm0.h->a worked, just replaced it with a return-void.

Excellent, glad this is working for you now :+1:

Any idea what that means/how to fix it?

This output doesn't itself look like a problem - it's just the Process terminated that's concerning.

From what I can see, what's happening here is:

Up until the process terminates, that's kind-of OK. For obfuscated 3rd party libraries like this, the best we can currently do automatically is to detect the first failure, and then ensure that all future checks pass correctly. All apps I've tested will with happily retry a single failing request (kind-of a requirement for a mobile app, where internet can be spotty) so that normally works just fine.

The process terminating is very odd though. That either means we've done something very unusual with Frida (crashing the app) or it means that maybe the app is detecting this first exception, and shutting itself down to block this entirely.

In the latter case, your fix to manually add the hook (so there is never a first failure) would fix this, so that totally makes sense, but that's a very unusual and aggressive result for the app itself.

Would you mind sharing some more info, to work out what's going on here? It would be great if you could disable your fix so you can reproduce the issue, and then:

niansa commented 6 months ago

Sure, of course! I'll do that asap. When the app terminates, it restarts into a "no internet connection" screen and offers a button to restart it again.

My efforts in tracing the HTTP traffic turned out to be kind of useless without further efforts anyways, because the app is either using Protobuf or flat buffers, not JSON.

niansa commented 6 months ago
     ____
    / _  |   Frida 16.1.9 - 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 FP4 (id=5bdc0330)
Spawning `io.moia.neptune`...

*** Starting scripts ***
== Redirecting all TCP connections to 192.168.0.144:8000 ==
Spawned `io.moia.neptune`. Resuming main thread!
[FP4::io.moia.neptune ]-> Ignoring unix:dgram connection
Ignoring unix:dgram connection
== Proxy system configuration overridden to 192.168.0.144:8000 ==
Rewriting <class: sun.net.spi.DefaultProxySelector>
Rewriting <class: java.net.ProxySelector>
Rewriting <class: android.net.PacProxySelector>
== Proxy configuration overridden to 192.168.0.144:8000 ==
[+] Injected cert into com.android.org.conscrypt.TrustedCertificateIndex
[+] Injected cert into org.conscrypt.TrustedCertificateIndex
[ ] Skipped cert injection for org.apache.harmony.xnet.provider.jsse.TrustedCertificateIndex (not present)
== System certificate trust injected ==

    === 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 ***

Ignoring attempt to override http.proxyHost system property
Ignoring attempt to override https.proxyHost system property
Ignoring attempt to override http.proxyPort system property
Ignoring attempt to override https.proxyPort system property
Ignoring attempt to override http.nonProxyHosts system property
Ignoring attempt to override https.nonProxyHosts system property
 => 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)
 => javax.net.ssl.SSLContext init(KeyManager;[], TrustManager;[], SecureRandom)
 => javax.net.ssl.SSLContext init(KeyManager;[], TrustManager;[], SecureRandom)
 => javax.net.ssl.SSLContext init(KeyManager;[], TrustManager;[], SecureRandom)
 => javax.net.ssl.SSLContext init(KeyManager;[], TrustManager;[], SecureRandom)
 => javax.net.ssl.SSLContext init(KeyManager;[], TrustManager;[], SecureRandom)
 => javax.net.ssl.SSLContext init(KeyManager;[], TrustManager;[], SecureRandom)
 => javax.net.ssl.SSLContext init(KeyManager;[], TrustManager;[], SecureRandom)
 => javax.net.ssl.SSLContext init(KeyManager;[], TrustManager;[], SecureRandom)
 => javax.net.ssl.SSLContext init(KeyManager;[], TrustManager;[], SecureRandom)
 => javax.net.ssl.SSLContext init(KeyManager;[], TrustManager;[], SecureRandom)
 => javax.net.ssl.SSLContext init(KeyManager;[], TrustManager;[], SecureRandom)
 => javax.net.ssl.SSLContext init(KeyManager;[], TrustManager;[], SecureRandom)
 => javax.net.ssl.SSLContext init(KeyManager;[], TrustManager;[], SecureRandom)
 => javax.net.ssl.SSLContext init(KeyManager;[], TrustManager;[], SecureRandom)
 => javax.net.ssl.SSLContext init(KeyManager;[], TrustManager;[], SecureRandom)
 => javax.net.ssl.SSLContext init(KeyManager;[], TrustManager;[], SecureRandom)
 => javax.net.ssl.SSLContext init(KeyManager;[], TrustManager;[], SecureRandom)
 => javax.net.ssl.SSLContext init(KeyManager;[], TrustManager;[], SecureRandom)
Manually intercepting connection to [0:0:0:0:0:0:0:0:0:0:ff:ff:8:8:8:8]:53
Manually intercepting connection to [0:0:0:0:0:0:0:0:0:0:ff:ff:8:8:8:8]:53
Ignoring unix:stream connection
Ignoring unix:stream connection
Ignoring unix:stream connection
Ignoring unix:stream connection
Connected tcp6 fd 105 to null (-1)
Connected tcp6 fd 106 to null (-1)
Manually intercepting connection to [0:0:0:0:0:0:0:0:0:0:ff:ff:8:8:8:8]:53
Ignoring unix:stream connection
Ignoring unix:stream connection
Connected tcp6 fd 105 to null (-1)
 => 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 111 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)
Ignoring unix:stream connection
Ignoring unix:stream connection
Connected tcp6 fd 131 to {"ip":"::ffff:192.168.0.144","port":8000} (-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, Dns, SocketFactory, SSLSocketFactory, HostnameVerifier, CertificatePinner, Authenticator, Proxy, List, List, ProxySelector)
 => 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 136 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)
Ignoring unix:stream connection
Ignoring unix:stream connection
Connected tcp6 fd 120 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)
Manually intercepting connection to [0:0:0:0:0:0:0:0:0:0:ff:ff:8:8:8:8]:53
Ignoring unix:stream connection
Ignoring unix:stream connection
Connected tcp6 fd 107 to null (-1)
Ignoring unix:stream connection
Ignoring unix:stream connection
Connected tcp6 fd 123 to null (-1)

 !!! --- Unexpected TLS failure --- !!!
      SSLPeerUnverifiedException: Certificate pinning failure!
  Peer certificate chain:
    sha256/XS42RMah4QIsurqXyAHadfjmJ23XMlsx3WqRuCjoJZQ=: O=Mockttp Cert - DO NOT TRUST,L=Unknown,C=XX,CN=remote-logging-app-index.trip.prd.moia-group.io
    sha256/WwZe/T7+bzD2vKjH+r9PEK5+vidkPRTRR4Up4WL4u10=: O=HTTP Toolkit CA,C=XX,CN=HTTP Toolkit CA
  Pinned certificates for remote-logging-app-index.trip.prd.moia-group.io:
    sha256/++MBgDH5WGvL9Bcn5Be30cRcL0f5O+NyoXuWtQdX1aI=
    sha256/f0KW/FtqTjs108NpYj42SrGvOB2PpxIVM8nWxjPqJGE=
    sha256/NqvDJlas/GRcYbcWE8S/IceH9cq77kg0jVhZeAPXq8k=
    sha256/9+ze1cZgR9KO1kZrVDxA4HQ6voHRCSVNz4RdTCx4U8U=
    sha256/KwccWaCgrnaw6tsrrSO61FgLacNgG2MMLq8GE6+oP5I=
      Thrown by pm0.h->a
      [+] pm0.h->a (fallback OkHttp patch)
Process terminated

Thank you for using Frida!
Fatal Python error: _enter_buffered_busy: could not acquire lock for <_io.BufferedReader name='<stdin>'> at interpreter shutdown, possibly due to daemon threads
Python runtime state: finalizing (tstate=0x0000000000a78a18)

Current thread 0x00007f29dc62a040 (most recent call first):
  <no Python frame>

Here we go

pimterry commented 5 months ago

Back from holidays now, sorry for the delay here.

I did some quick extra testing to reproduce this, and yes I can see the same behaviour - it seems that the app intentionally exits after the first failed request (restarting into the io.moia.neptune/.activity.SecureConnectionErrorActivity activity somehow) which disconnects Frida, so the auto-patching doesn't work. The ADB logs are a bit vague, but the process has clearly exited and yet the app is still running, and that's the only way I can see that happening.

This is quite unusual, and a very interesting find!

For now I think the go-to solution will have to be the manual patch approach here (glad to hear that worked for you at least to be able to see the raw data, even if it wasn't easily readable). Manually patching is annoying, but it shouldn't be too difficult in such cases.

I'll keep an eye on this as a general issue to be aware of for the future. If there are more apps doing the same, we'll need to investigate workarounds to do this more easily automatically... That's technically tricky, but certainly possible I think if it becomes worthwhile.