When establishing a TLS connection, the client needs to verify that it’s communicating with a legitimate server. Certificates can be verified by comparing the domain name (DN) in the server’s certificate against the requested domain name , and verifying that the certificate’s signature hierarchy chains back to a trusted root certificate authority.

The process of verifying the server’s authenticity can be simplified if the client application associates the host with its expected certificate or public key. Developers can include the server’s public key along with the deployed application. The process of associating a host with their saved or pinned certificate is known as certificate pinning.

Frequently mobile applications implement TLS certificate pinning in order to hinder reverse engineering efforts. With certificate pinning developers can specify which certificates the application is allowed to trust, instead of relying on the device’s certificate store.

Every now and then, I find an application that makes reverse engineering a little more interesting by adding certificate pinning into the mix of a normal TLS implementation. A few weeks ago I found myself looking into the security of a geosocial networking application.

My assessment of the application focused on analyzing the traffic between the client and server. In order to view the communications between the client and server I generated a self-signed certificate with BurpSuite, added the certificate to Android’s certificate store, and carried out a self-MiTM attack. However, as soon as I launched the application I was prompted with a “Connection Error.” This is typically a sign that the application has certificate pinning implemented.

Circumventing certificate pinning involves either modifying the application or its environment. In this blog post, we will be going over how we can circumvent certificate pinning by modifying the application. Now that we have decided that we are going to modify the application, we have three options:

Circumvent certificate pinning by patching the application. Use a debugger to circumvent certificate pinning dynamically. Give up.

If we choose to circumvent certificate pinning dynamically we will have to patch the application every time we want to view the traffic between the client and the server. On the other hand, if we patch the application’s source we can view traffic between the client and the server even after we terminate the application, or restart the device. And, giving up without putting in any work is never an option.

Understanding the application

Android applications are stored in the APK format. To begin, we need to disassemble and unpackage the APK with apktool. Apktool is a tool for decompiling, disassembling, and unpackaging the application’s resources and source code.

apktool d application.apk -o output/

Now, we are left with a couple of directories. With regards to patching the application, we will focus on the contents of the smali directory.

$ ls output/

assets build lib original res smali unknown AndroidManifest.xml apktool.yml

The smali directory contains the application’s smali code, which is the product of converting the application’s dalvik executable (.dex) bytecode into a human readable assembly format. The dalvik executable format is the product of converting the application’s Java source code into a format that is executable by the dalvik virutal machine, or more recently the android run time (art).

There are many blog posts on the Internet that break down the process of circumventing certificate pinning by patching the application’s smali code. In essence, they all boil down to patching the methods that check the certificate’s validity. For an example of how to patch out certificate pinning refer to this whitepaper.

However, in this case even after having patched methods that involved the strings “x509TrustManager”, “check”, and “verify” I was unable to correct the insidious “Connection Error”. From what I was able to discern, none of the patches I made to the application worked. Usually, this means that the application is implementing certificate pinning in native code.

Personally, I did not want to invest the time into finding, reverse engineering, and patching the binary that implemented certificate pinning. I took a step back and thought about the end goal I was trying to achieve. My assessment of the application focused on the traffic between the client and the server. Is there another way of viewing the traffic between the client and the server without acting as a man-in-the-middle? Strictly speaking, I only need to see the URL, and parameters for each request. If I can somehow get access to this information without intercepting any traffic I can both gather all of the data about the application I need, and correct the “Connection Error.”

Looking through the application’s smali code I noticed that the REST class was making use of the Apache HttpGet class. Similarly, the REST class was also making use of the Android util Log class. Interestingly enough, they both accept Strings as parameters. You might be able to see where this is going. If we want to see the URL parameter for the GET request, we can log it using any of the methods from the Android util Log class that output to the device’s log file.

Patching the application

In the figure below you can see some of the smali source code for the REST client class. Below the comment that reads “Injected Code” you can see where code was patched to log the URL for every GET request. First, we populate the v4 register with the text “*** GET Request:”, and then we call the Log.i function with the parameters v4 and v2, where v4 contains the value of the string we created, and v2 contains the value of URL string that is used in the GET request.

cond_4:

new-instance v14, Lorg/apache/http/client/methods/HttpGet; new-instance v2, Ljava/lang/StringBuilder; move-object/from16 v0, p0 iget-object v3, v0, Lcom/example/testapp/util/Rest;->mUrl:Ljava/lang/String; invoke-static {v3}, Ljava/lang/String;->valueOf(Ljava/lang/Object;)Ljava/lang/String; # This is the base URL for the request.

move-result-object v3 invoke-direct {v2, v3}, Ljava/lang/StringBuilder;-><init>(Ljava/lang/String;)V # v8 contains the parameters for the GET request.

invoke-virtual {v2, v8}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; move-result-object v2 # Creates the URL that is used for the request.

invoke-virtual {v2}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String; move-result-object v2 # Injected Code

const-string v4, "*** GET Request: "

invoke-static {v4, v2}, Landroid/util/Log;->i(Ljava/lang/String;Ljava/lang/String;)I invoke-direct {v14, v2}, Lorg/apache/http/client/methods/HttpGet;-><init>(Ljava/lang/String;)V

Now that we have added these lines of code, it’s time to repackage the application and take it for a test drive. To repackage the application, we need to go to parent directory where we output the disassembled application. Then, we can use the b flag in apktool to reassemble the application.

apktool b output/ -o patched.apk

Next, the patched APK needs to be signed. To sign the APK we need to create a certificate. A self-signed certificate can be generated with keytool.

keytool -genkey -v -keystore release.keystore -alias alias_name -keyalg RSA -keysize 2048 -validity 10000

After the certificate has been created we can sign the patched APK with jarsigner.

jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore release.keystore patched.apk alias_name

Now that the application has been signed you can install it on your Android device with the android debug bridge (adb).

adb install patched.apk

Before we launch the application, we should clear the device’s log file. We can clear the log file using the logcat command-line tool for adb while passing the -c flag.

adb logcat -c

We can run the logcat tool and launch the application. Subsequently, as the application begins issuing requests to the server, we will see text similar to that of the figure below.

I/*** GET Request:(20957): http://exampleserver.com/data

As you can see, our patch worked. The application is logging the URL of each of its GET requests. We need to next use this methodology for all other HTTP methods in the REST class. Additionally, we need to log the parameters for requests that do not interpolate their parameters in the requests’ URL. For our example, we will be patching the POST request. In the figure below, you can see another snippet of the smali source code for the REST class, this time it focuses on the HttpPost method.

:cond_f

invoke-interface {v2}, Ljava/util/Iterator;->next()Ljava/lang/Object; move-result-object v12 check-cast v12, Lorg/apache/http/NameValuePair; .line 322

.restart local v12 # “p”:Lorg/apache/http/NameValuePair;

invoke-interface {v12}, Lorg/apache/http/NameValuePair;->getValue()Ljava/lang/String; move-result-object v3 # Injected Code for the Value.

const-string v9, “** Parameter Value:”

invoke-static {v9, v3}, Landroid/util/Log;->i(Ljava/lang/String;Ljava/lang/String;)I if-eqz v3, :cond_d .line 324

invoke-interface {v12}, Lorg/apache/http/NameValuePair;->getName()Ljava/lang/String; move-result-object v3 # Injected Code for the Name.

const-string v9, “** Parameter Name:”

invoke-static {v9, v3}, Landroid/util/Log;->i(Ljava/lang/String;Ljava/lang/String;)I new-instance v4, Lorg/apache/http/entity/mime/content/StringBody; invoke-interface {v12}, Lorg/apache/http/NameValuePair;->getValue()Ljava/lang/String; move-result-object v5 invoke-direct {v4, v5}, Lorg/apache/http/entity/mime/content/StringBody;-><init>(Ljava/lang/String;)V

As you can see, we added code below the “Injected Code for the Value” and “Injected Code for the Name” comments. As we did with our previous patch, we move two strings into different registers, and then we call the Log.i function with both registers we created, and the register that is populated by the normal flow of the application.

After repacking, reassembling, signing, installing, and running the application, the device’s log file will contain text similar to that of the figure below.

I/** Parameter Value:(26391): 11343f5a-9449–4cd4-a508-e02c

I/** Parameter Name:(26391): device_id

I/** Parameter Value:(26391): 2

I/** Parameter Name:(26391): device_type

I/** Parameter Value:(26391): fL4Xsf5g0dv9OzjTbHISYhOVdjscHm6kbUfk

I/** Parameter Name:(26391): device_token

Conclusion

We were able to log all of the data we needed without having to circumvent the application’s use of certificate pinning. The Log class provides us with all of the tools that we need to view the traffic between the client and and server. Now, we can view the content of all of the requests made by the application from the device’s log file, and we can reverse engineer the REST API.