Defeating SSL Pinning in Coin's Android Application


Overview

"Coin is a connected device that holds and behaves like your cards with a magnetic stripe. Use the Coin mobile app to provide key information that we need in order to ship your Coin and to manage the original plastic cards (credit, debit, gift, membership, loyalty and more) that you use with the Coin device."

This weekend my buddy @_lavalamp and I decided to take a look at his Coin device and its accompanied Android application. The device and the application have an interesting attack surface, which we are still researching, so expect more updates from both of us to follow. In this post we are going to cover how the Coin Android application implements SSL pinning, and the techniques we used to defeat it.

Recon

When decompiling the application the first thing we noticed were a handful of Bouncy Castle key stores located within the assets directory.

[~/Downloads/com.onlycoin.android-1/assets]> ls
ble_config.json      coincert_new.bks     ocra.otf             segment14.otf        zxcvbn.html  
coincert.bks         coincert_staging.bks rewards.json         zxcvbn-async.js      zxcvbn.js  

There was already an assumption the application was probably going to be using a lot of crypto, and rightfully so, considering its sole purpose.

After installing the application, we immediately wanted to start proxying all outbound HTTP and HTTPS, so we setup a global proxy and attempted to log into the application. We were greeted with the following error.

error

They appeared to have implemented SSL pinning in their application, which was pretty obvious to be the source of this error.

Deep Dive

We needed to defeat this protection mechanism, in order to start understanding how the application was interacting with any api endpoints. It did not take very long to pin point where the SSL Pinning functionality was being implemented.

We found in the class com.onlycoin.android.secure.SSLPinning a static method that would return an SSLSocketFactory.

ssl

This would simply open a Bouncy Castle key store, load up all of the stored certificates and validate them with the methods checkClientTrusted and checkServerTrusted within the CompositeX509TrustManager class. If the certificate was not inside of the Bouncy Castle key store, the corresponding methods would throw an exception with an error message that gets displayed back in the application's UI.

We spent quite bit of time trying to figure out how to actually patch out this implementation in order to achieve a bypass, but realized it would be more efficient to use instrumentation instead.

Instrumentation

We were able to craft a Frida script that allowed us to hook the method that returned the SSLSocketFactory, and then proceeded working through ideas on how to actually modify the overall control flow, until @_lavalamp had the genius idea of just returning the arguments being passed to the method. This would actually give us the password and the name to the Bouncy Castle key store being used.

PoC

# Written by rotlogix & lavalamp
# 
#
import frida  
import sys  
from subprocess import Popen

def on_message(message, data):  
    try:
        if message:
            print("[*] {0}".format(message["payload"]))
    except Exception as e:
        print(message)
        print(e)

def do_ssl():

    ssl = """

        Dalvik.perform(function () {

                var SSLPinning = Dalvik.use("com.onlycoin.android.secure.SSLPinning");

                    SSLPinning.a.overload("android.content.Context", "[Ljava.lang.String;", "java.lang.String").implementation = function (c, s, ss) {

                        send("SSLPinning");
                        send(c.toString());
                        send(s.toString());
                        send(ss.toString());
                        this.a.overload("android.content.Context", "[Ljava.lang.String;", "java.lang.String").call(this, c, s, ss);


                    };

                });

    """
    return ssl

if __name__ == "__main__":  
    try:
         Popen(["adb forward tcp:27042 tcp:27042"], shell=True).wait()
         process = frida.get_device_manager().enumerate_devices()[-1].attach("com.onlycoin.android")
         script = process.create_script(do_ssl())
         script.on('message', on_message)
         script.load()
         sys.stdin.read()
    except KeyboardInterrupt as e:
        sys.exit(0)

We set our script in motion and ...

[~/Tools/mobile/android/frida]> python onlycoin.py
[*] SSLPinning
[*] [email protected]
[*] coincert.bks,coincert_new.bks
[*] laggardness287{satisfactoriness

Excellent! Now we had the name of the key store and the password. We could now take our Burpsuite exported certificated and add it to this key store, which would be loaded at runtime and added as a trusted certificate.

Bypass

First we added our Burpsuite certificate to the coincert.bks key store.

keytool -importcert -v -trustcacerts -file /Users/benjaminwatson/Tools/web/burp/burp.cer -providerpath /Users/benjaminwatson/Downloads/bcprov-jdk15on-152.jar -provider org.bouncycastle.jce.provider.BouncyCastleProvider -storetype BKS -keystore ~/Downloads/com.onlycoin.android-1/assets/coincert.bks  
[~]> keytool -list -v -providerpath /Users/benjaminwatson/Downloads/bcprov-jdk15on-152.jar -provider org.bouncycastle.jce.provider.BouncyCastleProvider -storetype BKS -keystore ~/Downloads/com.onlycoin.android-1/assets/coincert.bks
Enter keystore password:

Keystore type: BKS  
Keystore provider: BC

Your keystore contains 2 entries

Alias name: ca  
Creation date: Mar 19, 2015  
Entry type: trustedCertEntry

Owner: OU=Domain Control Validated,OU=COMODO SSL Wildcard,CN=*.coin.vc  
Issuer: C=GB,ST=Greater Manchester,L=Salford,O=COMODO CA Limited,CN=COMODO SSL CA  
Serial number: e9f4e34ca8a073842a7c144bdc176796  
Valid from: Tue Apr 08 20:00:00 EDT 2014 until: Thu Apr 09 19:59:59 EDT 2015  
Certificate fingerprints:  
     MD5:  72:FA:98:D3:31:F3:6A:60:A8:A0:31:87:B5:42:17:84
     SHA1: E7:A2:41:B6:30:7D:06:6F:39:16:86:0C:F0:FC:A3:19:FD:69:A1:5B
     SHA256: 4E:9C:3B:52:84:AA:D8:5A:55:DA:AB:DE:B9:3F:5A:D6:29:1A:90:2A:63:CE:A5:0D:65:D4:DE:36:99:E7:EB:0F
     Signature algorithm name: SHA1WITHRSA
     Version: 3


*******************************************
*******************************************


Alias name: mykey  
Creation date: Sep 12, 2015  
Entry type: trustedCertEntry

Owner: C=PortSwigger,ST=PortSwigger,L=PortSwigger,O=PortSwigger,OU=PortSwigger CA,CN=PortSwigger CA  
Issuer: C=PortSwigger,ST=PortSwigger,L=PortSwigger,O=PortSwigger,OU=PortSwigger CA,CN=PortSwigger CA  
Serial number: 55c1130b  
Valid from: Tue Aug 04 15:31:23 EDT 2015 until: Mon Jul 30 15:31:23 EDT 2035  
Certificate fingerprints:  
     MD5:  F1:BD:1A:DA:E1:1B:D3:92:69:7A:9B:02:01:31:4A:8F
     SHA1: EE:BA:11:91:D8:6B:FA:A5:26:2C:4C:60:55:AB:05:61:F7:F3:67:79
     SHA256: 97:AC:9F:7E:1B:BE:38:D8:3D:ED:4D:ED:85:D9:95:C7:87:41:D5:BC:F0:04:AE:43:38:D3:E3:7D:68:8E:13:13
     Signature algorithm name: SHA256WITHRSA
     Version: 3


*******************************************
*******************************************

We then rebuilt the Coin application and deployed it back to device. Now for the moment of truth ..

bypass

Success! We had defeated the application's SSL Pinning.

Conclusion

SSL Pinning provides some level of security for any mobile application, but should not be solely relied upon, and often can be easily defeated.

Update

Apparently I wasn't explicit enough for the Internet in my conclusion. The application's SSL Pinning implementation is spot on and there is nothing wrong with it. This post is merely teaching different techniques you can use to reverse engineer and "defeat" said implementation, which allows you to perform things like proxying HTTPS traffic.