25

Bypassing Android SSL Pinning with FRIDA

As a pentester, you may come into the need of bypassing security controls to be able to provide a more significant evaluation. SSL Pinning is one of the most important security controls for protecting the communication between the mobile client and the server, been able to bypass this control is an important to also evaluate the server the application is communicating with.


If you are using a black-box approach for pentesting an Android application, you will need a reliable way to intercept and decrypt the traffic between the application and the server.

In a black-box approach, the pentester is given nothing but a target and, in the case of Android mobile applications, that usually boils down to an apk file.

SSL Pinning is a must for protecting the communication.

There are many security test cases, both static and dynamic, that you can run against a mobile application. However, one of the most important cases is to verify whether or not the application implements SSL Pinning to protect the communication.

With SSL Pinning, an application will make sure to connect only to a set of whitelisted servers. And whitelisting is done through the server’s certificate or it’s public key; which is hardcoded into the application’s source code and validated before establishing a secure SSL/TLS communication channel.

SSL Pinning helps ensuring the security of the communication and reduces the risk of man-in-the-middle attacks, in which, an attacker is able to intercept, decrypt and even tamper the communication between two nodes in the network.

Checking if SSL Pinning is enabled

There is no doubt, SSL Pinning is a very important security control. Basically, if you are able to intercept and decrypt the traffic only by routing the traffic of an application through a proxy, there is a problem.

Even if you have to make the device trust your own certificate, by installing it directly on the device, and you are still able to intercept the traffic, there is also a problem. (Do note that the trust behavior for API 24 or greater has changed, user-installed certificates are no longer trusted by default, read more here).

Basically, if you are able to intercept the traffic without the pinned certificate and without running anything on the device, then SSL Pinning is missing and you have identified a high risk.

Blackbox?

Bypassing SSL Pinning gives you access to to analyzing and probing the communication sent and received from the server. It also gives you a more specific target, since now you know where data is flowing to and from.

But, how to bypass a control that is intended to keep attackers out of your application communication? In short, you need to either be able to run code as root (for rooted devices) or reverse the application and add your code (for non-rooted devices ), as mentioned here.

Enter, Frida, “a dynamic code instrumentation toolkit that let’s you inject snippets of Javascript or your own library into a native application“. It is mostly used to allow attaching to an application’s process (through ptrace) and change/override part of it’s behavior.

Getting your hands on bypassing SLL Pinning.

Next, we’ll see how to bypass SSL Pinning with Firda. We won’t be covering the entire environment setup (since that will take a lot of time). But I will be using my Ubuntu computer with VirtualBox and Genymotion for Android Emulation, I will also be using ADB (Android Debug Bridge) for communicating with the device and the community version of Burspsuite as a proxy.

env-lab-setup


Defining the Target

For this exercise we need a target, a mobile application for which we first check if SSL Pinning is enabled and then try to bypass it with the Frida framework. In this case we will be using the popular Twitter mobile application.

Since we are using Genymotion, we can just use the built-in Open Gapps functionality to have the Google apps, and more specifically, the Play Store application installed on our virtual device. Then we proceed to install the Twitter application and run it:

genymotion-twitter


First things first!

Let’s first check if you can intercept the traffic of the application by simple putting a proxy in the middle. Start your proxy (Burpsuite in this case) on port 8080 and on a specific interface address (192.168.0.9 in the image below):

java -jar burpsuite.jar

burpsuite-setup


You should also enable Invisible Proxying on the Request Handling tab.

Now, go to a browser and navigate to the proxy’s IP and port (http://192.168.0.9:8080) and grab the proxy’s certificate by clicking on CA Certificate:

burp-getting-ca


Once downloaded, push the certificate into the virtual device with the ADB push command:

./adb push cacert.der /sdcard/burp.cer

In the device go to Settings > Security > Credential Storage and select Install from SD Card, navigate to where the certificate is located and select it, enter a name and you will most likely be asked to set a Lock Screen PIN or a password (do it and you will see a “burp Installed” message )

virtual-device-install-burp-cert


With your proxy ready to capture traffic, go to the virtual device and into Settings > Wi-Fi > WiredSSID and long press on it, on the popup select Modify Network, on the Proxy dropdown select Manual and you will be able to specify which proxy to connect to (set it to the previous values 192.168.0.9:8080):

virtual-device-proxy-manual


Save the changes (might also be worth to disable/enable the the Wi-Fi). Run the application, try to login and see if you can intercept the traffic (you may need to enter some information and hit the arrow button):

genymotion-twitter-login-fail


The login traffic is not intercepted; it doesn’t show on the proxy and the application fails with “An error has occurred when logging in. Please try again later“.

Why we can’t intercept?

Let’s fire up Wireshark and check what is going on at network level. Attach Wireshark to the same interface your proxy is running on. Try to login and check the network traffic.

wireshark-certificate-unknown


You can see the the TLS handshake gives a fatal error (Level: Fatal, Description: Certificate Unknown), meaning that the certificate presented to the application (our proxy’s certificate) is different to the one used for pinning (in other words, pinning is enabled).

Using Frida

The first thing you need to do is get Frida, there are two components to this; the first is the frida-tools, which you can install on your workstation with the next command:

pip install frida-tools

After that you can just run the frida help to make sure it was correctly installed:

frida-help


Frida-server

The second part comes as an executable file that you have to download, copy into the virtual device and run. Go here and find the version you want to use, I’m using this one.

wget https://github.com/frida/frida/releases/download/12.2.23/frida-server-12.2.23-android-x86.xz

Once downloaded, use unxz to decompress the file:

unxz frida-server-12.2.23-android-x86.xz

Move the file into the virtual device (default path is /data/local/tmp/)

./adb push frida-server-12.2.23-android-x86 /data/local/tmp/frida-server

Give execution permissions to frida-server:

./adb shell chmod 755 /data/local/tmp/frida-server

And now, we go into the device and run the frida-server with the next command:

./frida-server &

frida-server-run


Frida-server

Now, check if frida is working correctly, for this, run the next command on your workstation:

frida-ps -U

You should see something like the following:

frida-ps-U


Pushing the proxy’s CA Certificate

In order to be able to intercept the traffic, frida needs to have access to your proxy’s CA certificate. We can use the same certificate we downloaded before.Push the certificate into the device and into the same location as the frida-server, name it cert-der.crt (below we will see why this is required).

./adb push cacert.der /data/local/tmp/cert-der.crt

Bypassing SSL Pinning

Now, we need to get the frida script that will let you override SSL connections to create and use our own Trust Manager. You can find the script here.

We will first see to use this script to bypass SSL Pinning and then we will analyze what the script does. With the script at hand, you can run the next command:

frida -U -f com.twitter.android -l sslpinning.js

You will see frida doing it’s thing:

frida-sslpinning-bypass


And the twitter application is ran on the device. As before, go into the Login screen and enter an email and password (foo@domain.com and foopass in this case), then hit the Login button:

twitter-login-sslpinning-bypass


And check your proxy, you will see that the traffic was intercepted:

burp-intercepted-traffic


Analyzing the frida script

Open the frida script and review it’s contents, you will see that with Javascript code we can create a Trust Manager containing our proxy’s CA cerificate and that this Trust Manager is used to override the SSL Context (whenever it is created).

Below we will see the most important parts of this frida script. On line 25, the script creates an instance of an x509 CertificateFactory:

cf = CertificateFactory.getInstance("X.509");

At line 28, it reads and loads the previously proxy’s CA certificate into a variable (fileInputStream):

var fileInputStream = FileInputStream.$new("/data/local/tmp/cert-der.crt");

On lines 34 and 35 we see that this variable is used to generate a certificate with the CertificateFactory:

var bufferedInputStream = BufferedInputStream.$new(fileInputStream);
var ca = cf.generateCertificate(bufferedInputStream);

Lines 43 to 46 then create a KeyStore and sets the generated certificate as a certificate entry in the KeyStore:

var keyStoreType = KeyStore.getDefaultType();
var keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
keyStore.setCertificateEntry("ca", ca);

From lines 50 to 52, the script creates a TrustManagerFactory (which gets initialized with the KeyStore previously created:

var tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
var tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(keyStore);

Finally, on line 58, we see that the script is overloads the SSLContext’s init function and sends the created TrustManagerFactory as parameter:

SSLContext.init.overload("[Ljavax.net.ssl.KeyManager;", "[Ljavax.net.ssl.TrustManager;", "java.security.SecureRandom").implementation = function(a,b,c) {...

SSLContext.init.overload("[Ljavax.net.ssl.KeyManager;", "[Ljavax.net.ssl.TrustManager;", "java.security.SecureRandom").call(this, a, tmf.getTrustManagers(), c);