Thoughts for food, part 2: SSL pinning, assembly and black magic

2018-06-28Michal

Remember the Disco app, our goal to get a ton of discount codes and the problems we’ve faced when attempting to do so? In case you don’t, they all have their beginnings in this post. This is the second part of that adventure – the one in which we’ll tackle a lot of tough problems. This will be a in-depth technical post, so if you like tech voodoo, assembler code, SSL pinning and all that fancy sounding stuff, then you’re in for a treat.

While in my medical trickery post I was analyzing an app that was not even trying to prevent a man-in-the-middle proxy attack, Disco was a tougher nut to crack. Its developers were trying hard to protect their API calls from being leaked (rightfully so), so they introduced several security measures that prevent us from doing what we want.

Problem 1: Emulator limitations

Bluestacks unfortunately doesn’t let you change your Android Wi-Fi settings to use a proxy – and if you get around that by using Proxifier or a similar app, there are still problems with installing an SSL certificate – you can use an Android app called Root Certificate Manager to get around that, but that requires you to actually root your emulator… you probably see where this is going: using Bluestacks for encrypted traffic analysis is not the best idea.

Let’s use a different free Android emulator – Nox. It’s bloated with ads too, but at least it’s based on a cleaner Android 4.4.2 version and allows you to install a Fiddler SSL certificate, root the device and change proxy settings without much difficulty.

Problem 2: SSL certificate pinning

But when you do so and launch Disco with Fiddler traffic capture on, you still can’t see the traffic properly! Instead of seeing all the requests, you only see stuff like this:

This means that Fiddler’s SSL certificate isn’t “compatible” with the requests, so they can’t be decrypted in Fiddler. Why is that? This is because Disco uses a technique called SSL certificate pinning.

SSL pinning (also known as public key pinning) is a clever manoeuvre that allows an application to embed an SSL certificate in its resources and make sure that this is the one that’s used when exchanging information with externals systems – be it a single host, a domain, or whole Internet. It checks if a certain certificate appears in the chain – and if not – it refuses to work.

Fortunately there’s a way around that too – if your iOS device is jailbroken or if your Android device is rooted you can use one of many tools available that hook into various web request and SSL methods and classes and bypass the pinning process altogether. Of course their reliability depends on the amount of security measures implemented in the app itself.

Does bypassing work with Disco? Yes. And no. It unpins the cert, but the app doesn’t start after that.

By installing a suite called Xposed Framework and a module named SSLUnpinning 2.0 (and selecting that we want to unpin SSL certs from Disco) we get a little bit more data this time (unfortunately not from Disco API). We still get an error message that wants us to verify our internet connection, but we do get logs of some visible traffic that goes to a sdk.hockeyapp.net – and knowing that HockeyApp is for gathering error logs, we can check if there’s any info on where the app fails:

Scratch that. Let’s first URL decode this, make it more readable and then check if there’s any info on where the app fails.  And yes, there is. See the excerpt below, with key information in red:

Package: ********
Version Code: 1019002
Version Name: 1.9.2
Android: 4.4.2
Android Build: cs03ltezm-user 4.4.2 JLS36C 381180529 release-keys
Manufacturer: samsung
Model: SM-N935F
Thread: null
CrashReporter Key: ********-********-********-********-********
Start Date: Wed Jun 27 00:00:41 CAT 2018
Date: Wed Jun 27 00:00:42 CAT 2018

javax.net.ssl.SSLPeerUnverifiedException: Certificate pinning failure!
  Peer certificate chain:
    sha256/KQNhPdUz7BW36LjhSI/v0eCZFJXw2NdFE1ROlV1BxYI=: CN=********,O=DO_NOT_TRUST,OU=Created by http://www.fiddler2.com
    sha256/GSFLZge8OmV7s8YpqWaMKRmhd+8HVewlnx6IXkNbImc=: CN=DO_NOT_TRUST_FiddlerRoot,O=DO_NOT_TRUST,OU=Created by http://www.fiddler2.com
  Pinned certificates for ********:
    sha256/xStUYd92Pq8bWaEtJPCPiCZpNSNbXZ+9Bi0NXZ5rmBo=
    sha256/33fvxHeptO3Xbd7XVOQ+OgnFWxZlFuGvelt08/Zmq9Q=
   at PersonalizationActivity.getAppInfo(:230)

Aaaand we’ve got ourselves yet another problem.

Problem 3: Custom checks for proper SSL certificates

It turns out that the app has some custom checks embedded in its code. Based on the message above it’s safe to conclude that the app checks the certificate chain, calculates its SHA256 hash and compares it to the SHA256 value of its internally stored certificate. If they are the same, the app works fine – if not it throws an SSLPeerUnverifiedException with a “Certificate pinning failure!” string and says your internet connection is screwed. Should we surrender now?

Of course not. There are a few ways to get around that. We can download the .apk file with the app from our device (using adb) or from some shady internet service that downloads APK’s directly from Google Play. Then we can try to do something weird to it.

My initial plan was to use APK Studio to browse the app’s resources and switch the pinned certs with the certs we get from Fiddler, but there it was – problem number four.

Problem 4: Heavy code obfuscation and complexity

Unfortunately APK Studio doesn’t play well with MultiDex apps that are heavily obfuscated. At least I couldn’t get it to work and have decided not to waste too much time on it.

How obfuscated is the app? Let’s use Apktool on Linux and unpack/decode the .apk file using a simple command:

$ apktool d disco.apk -o disco_unpacked

Here’s a screenshot from my trusty Kali Linux VM. What you see here are the decoded .smali (we’ll get to Smali later) files – each of them correspond to a class or method in the code. Just look at those names – there’s tons of them!

Fortunately there’s a fancy decompiler on GitHub called Jadx that deals with these kinds of things nicely. And has a GUI.

After opening Disco.apk in Jadx we finally get to see some human-readable code!

It’s not super simple, but we’ll work it out, trust me. Of course there’s a lot of code here, as the app is fairly complex and big, but what we’re interested in the most for our analysis is the SSL pinning part of the code. Searching the code for our error message (“Certificate pinning failure!”) actually gets us straight to the method we want to examine. Here’s a snippet of its Java code:

public final void ˊ(String str, List<Certificate> list) throws SSLPeerUnverifiedException {
...

for (int i2 = 0; i2 < size2; i2++) {
   ˊ ˊ3 = (ˊ) list2.get(i2);
   if (ˊ3.ॱ.equals("sha256/")) {
      if (obj2 == null) {
         obj2 = C0972zx.ˏ(x509Certificate.getPublicKey().getEncoded()).ˊ();
      }
      if (ˊ3.ˏ.equals(obj2)) {
         return;
      }
   } else if (ˊ3.ॱ.equals("sha1/")) {
      if (obj == null) {
         obj = C0972zx.ˏ(x509Certificate.getPublicKey().getEncoded()).ˏ();
      }
      if (ˊ3.ˏ.equals(obj)) {
         return;
      }
   } else {
      throw new AssertionError();
   }
}
StringBuilder stringBuilder = new StringBuilder("Certificate pinning failure!\n  Peer certificate chain:");
...

}

We can see the SHA checks on the certificates being done here. If the check succeeds, then the “return” statement exits the method. If not, an AssertionError is thrown and the app gets straight to building and sending an error message.

What can we do with this? Jadx doesn’t really allow you to change the code and recompile the app. Here’s where Smali comes in.

Problem 5: Smali is smali – and that’s in assembly language

As you probably remember (but still can check on one of the screenshots above), decoding the .apk file with Apktool gave us lots of .smali files. As their extension suggests, these contain Smali code. Smali is an assembler for dalvik – Android’s Java VM, so .smali files contain more or less Android specific assembly code. Are you scared out of your mind yet? Well, here’s the best part: we’re going to edit one of those files. If you don’t know assembly code – don’t worry, I don’t know it either. I mean I did some assembler CTF’s, but I’m nowhere near being comfortable with it.

Let’s use our superhero powers of logic and deduction. We’ve already got the app unpacked and each .smali file corresponds to a Java class or method. Let’s see which of these contain our magic error message by navigating the Linux terminal to the unpacked Disco folder and using grep:

$ grep -r -i 'pinning'

This gives us the following result:

smali/o/nS.smali:    const-string v1, "Certificate pinning requires X509 certificates"
smali/o/nS.smali:    const-string v0, "Certificate pinning failure!\n  Peer certificate chain:"

Gotcha. The nS.smali file contains the code we need. Let’s open it in our favorite text editor, look through it, cry a little inside, and find the piece of code responsible for throwing the AssertionError. Here it is in all its glory (the part in red):

.line 182
:cond_8
iget-object v0, v14, Lo/xT$ˊ;->ˏ:Lo/zx;

invoke-virtual {v0, v10}, Ljava/lang/Object;->equals(Ljava/lang/Object;)Z

move-result v0

if-eqz v0, :cond_a

return-void

.line 184
:cond_9
new-instance v0, Ljava/lang/AssertionError;

invoke-direct {v0}, Ljava/lang/AssertionError;-><init>()V

throw v0

.line 175
:cond_a
add-int/lit8 v12, v12, 0x1

I know it still looks intimidating – but remember that when the certificate hash verification succeeded it just ended with a “return” statement? You can clearly see that it does that even in Smali – there’s a return-void statement. So here’s the funny bit: we can change the error throwing part in red to “return-void”, so instead of throwing an error it will just return from the method. Will it successfully defeat cert verification?

Spoiler alert: yes.

Let’s test by saving our changes and repacking the apk with Apktool with $ apktool b disco_unpacked -o disco_modified.apk . We still have to re-sign it – I did that using my debug key and jarsigner: $ jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore debugkey.keystore disco_modified.apk michal. The last part would be installing this in our Android emulator and checking the traffic with Fiddler.

Voila, we can now analyze the traffic! To be honest I was quite amazed when seeing this works for the first time, mostly because I’m not familiar with Android development at all and I felt like I have no idea what I’m doing throughout the process. But then I feel like that almost every time when I’m editing any code that’s not mine.

I won’t get into analyzing API calls here or automating that stuff, because it’s already covered in one of my previous blog posts – in a different context, but the same principles apply.

The result

I would love to say that I was only hungry for knowledge when doing all this, but I must admit that after that I’ve enjoyed a few 5$ lunches coming straight to my door with the discount codes I’ve generated. Unfortunately there were also some questionable businessmen who decided to generate and sell those discount codes on eBay or its local equivalents, but I personally see it as a dick move.

At the time of writing this article the app introduced one more verification mechanism – you have to register an account with Disco first, with the verification being done by e-mail. While I do have a few ideas on how to easily automate that too, I’d rather not give any ideas to the bastard discount code sellers.

Leave a comment

Your email address will not be published. Required fields are marked *

Prev Post Next Post