Recently I tried to test the bypassing of certificate pinning on an Android device. I used the SSLPinningExample.apk, which can be downloaded from here. This sample program downloads the https://github.com HTML page and opens it. It uses the certificate of the github.com site to create the SSL. Unfortunately the original program did not work for me, because the github certificate was renewed and the application was pinned to an expired certificate. With the help of one of my colleagues I managed to fix the original program and learned several ways to bypass the certificate pinning. This was a great exercise for me and I learned a lot from it. Now I would like to share my findings with the security community.

 

What is certificate pinning?

First of all, what is certificate pinning? When the client communicates with the server, the communication can be plaintext, without any protection/encryption. This is the worst scenario. The attacker can listen/change the communication between the client and the server.

If the client-server communication uses SSL, that is a better solution, because it means encryption between them. When the client starts a communication with the server, it downloads the server certificate and verifies, that it is a trusted one. The client can create a secure channel with the server if the server certificate or one of the root CAs of the server certificate is in the trusted CA list of the client. In other words the client can create SSL with the server if the client trusts in the server. The problem is that the user can install any CA certificate in that list. The user decides if he/she trusts in a server.

In this solution an attacker can perform a Man-in-The-Middle attack. When the client downloads the server certificate, the attacker can replace it with his/her own one, then the attacker can initiate a connection to the server. The attacker actually creates two secure channels: one is between the attacker and the server, the another is between the attacker and the client. In this scenario the attacker can still listen/change the communication between the client and the server. However the attacker should make the user accept the attacker’s own certificate to be accepted as trusted (or the attacker gets a certificate, of which CA is already installed on the user’s machine). If the attacker can do that somehow, he/she can initiate a MiTM attack.

The best solution is when the application decides which certificate is accepted. The application pins to a certain certificate. In this case the user cannot decide if a server is trusted or not and an attacker cannot perform a MiTM attack, unless he modifies the application on the user’s machine somehow. However there is another problem. In case the pinned certificate expires, the application should be modified. The best solution is that when the CA is pinned.

During mobile pentest we usually want to analyze the traffic between the mobile device and the backend server. This is a kind of MiTM attack. However certificate pinning could prevent this. As we control the application and the testing device, we can find a solution for this problem. These are our options:

  • Modify the app
  • Use a hooking module, like Xposed JustTrustMe module.
  • Create a custom hooking module

If the application has some kind of root detection, this is the same problem. We have to bypass it if we want to test and analyze the application.

 

Fix the app

As I said, the application did not work for me. First I decompiled the application and tried to understand, how the certificate pinning works. I used jadxgui to decompile the application.

jadxgui SSLPinningExample.apk

The password for the keystore was “testing“. In the temp/res/raw folder, there was a file, called keystore.bks. This was the pinned certificate. The keystore was a Bouncy Castle keystore.

certpin01

 

I decompiled the app with apktool.

apktool d SSLPinningExample.apk -o temp

 

I downloaded the certificate of the https://github.com site and saved as github.com.der. Then I created a custom keystore. As the keystore had to be a Bouncy Castle one, I had to download the appropriate jar file from here, too.

keytool -importcert -v -trustcacerts -file “github.com.der” -keystore “keystore.bks” -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath “bcprov-ext-jdk15on-1.46.jar” -storetype BKS -storepass testing

 

I compared the content of the two keystore.

keytool -list -v -keystore “keystore.bks” -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath “bcprov-ext-jdk15on-1.46.jar” -storetype BKS -storepass testing

It turned out that the original keystore contained a certificate which expired in 2016 Apr 12 (I am writing this post in August). That is why the application did not work.

certpin02

 

I replaced the original keystore with the newly created one and recompiled the application. Then I signed the APK.

apktool b temp -o SSLPinningExample_fixed.apk

jarsigner -verbose -sigalg MD5withRSA -digestalg SHA1 -keystore <keystore> <APK file> <alias>

Finally the application worked perfectly! During mobile pentest we usually have to reverse engineer the application and modify it to fix some problems.

 

Bypass certificate pinning by using the certificate of Burp

One possible way to bypass the certificate pinning is when we replace the pinned certificate with the certificate of the Burp. I followed the same steps as earlier to fix the application, however I also had to install the Burp CA into the trusted CA list of the device. Burp CA can be exported on the Proxy/Options page in Burp. I exported it in DER encoded format.

certpin03

 

Bypass certificate pinning with JustTrustMe Xposed Module

I restored the application to the fixed version of APK and tried to bypass the certificate pinning with the JustTrustMe Xposed module. The module can be downloaded from here. Xposed framework should also be installed on the system.

certpin04

It worked and this was an easy method. JustTrustMe module hooks on lots of system calls in order to bypass certificate validation. Xposed has lot of module for various tasks/problems, but we can also develop our own modules. We can hook on method calls and examine/modify its behaviour.

 

Bypass certificate pinning with my own Xposed Module

The next logical step was to write an Xposed module and bypass the certificate pinning with it. There is a good tutorial about creating an Xposed module. I detail only the important steps here.

  1. First I created an Android project without Activity.
  2. Then I added the api-53.jar to the project and added it to the Build Path.
  3. I added the 3 meta-data tag to the AndroidManifest.xml
  4. I copied the keystore.bks file under res/raw folder. This keystore contains the Burp CA. In the hook method I replaced the original keystore file stream with this one.
  5. I created a file under assets. The file is called xposed_init and contains the full name of the Bypass class.
  6. I created the Bypass class. In this class I hooked on the pinCertificates method of the HttpClientBuilder class and overwrote the keystore file stream.
  7. Finally I exported the Android project. During the export the APK file is signed. After this process the module can be activated in Xposed.

Here is the source code of the Bypass class:

package com.bypass.pincert;

import android.content.res.Resources;
import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam;
import java.io.InputStream;

public class Bypass implements IXposedHookLoadPackage {

	@Override
	public void handleLoadPackage(LoadPackageParam lpparam) throws Throwable {
		if (!lpparam.packageName.equals("com.example.sslpinningexample"))
	        return;
		
		XposedBridge.log("we are hooking the class!");

		
		XposedHelpers.findAndHookMethod("com.example.sslpinningexample.HttpClientBuilder.HttpClientBuilder",
			lpparam.classLoader, "pinCertificates", InputStream.class, char[].class,
			new XC_MethodHook() {
	            @Override
	            protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
	            	XposedBridge.log("override the pinned certificate parameter");
	            	param.args[0] = getClass().getResourceAsStream("/res/raw/keystore.bks");
	            }
	            @Override
	            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
	            }
	    	}
		);
	}

}

The module worked fine and I managed to bypass the certificate pinning. Although creating an Xposed hooking module is not so difficult, this was the hardest solution of all. However there might be a case where this is our only option.

 

All of the related files are uploaded into my github and is accessible here.