TL;DR – There are many Android SSL pinning bypass scripts available for Frida. However, those don’t always work on obfuscated applications. If the application uses OkHttp, there’s an easy way to find a convenient place to bypass the pinning by grepping for the right SMALI string.

The target

For this blogpost, I’ve created a little Android application that tries to retrieve the robots.txt file from https://www.nviso.be. The following three techniques are used:

HttpClient (no pinning)

OkHttp (no pinning)

OkHttp (pinning with OkHttp’s CertificatePinner class)

The application itself has a network_security_config.xml file, which contains the following code:

<?xml version="1.0" encoding="utf-8"?> <network-security-config> <domain-config> <domain includeSubdomains="true">nviso.be</domain> <pin-set expiration="2022-01-01"> <pin digest="SHA-256">BKy5MQOotke4ELwm4nHQuEmLvQzLi7kuo0CTTXNmwng=</pin> </pin-set> </domain-config> </network-security-config>

So in theory, all HTTPS connections to nviso.be should only be trusted if the correct certificate is supplied.

You can download the applications (+hooks) and the source code.

Finally, in my test setup, I have installed my NVISO CA as a system certificate so that all default SSL verification logic will succeed.

Running the application with and without an intercepting proxy gives either “OK” on all techniques, or it gives an “ERROR”. This is good news, as it shows that all the SSL pinning functionality is working.

Bypassing pinning controls

As a first step, let’s try disabling the pinning through Objection, a very useful mobile pentesting framework built on Frida. Objection contains many different bypass scripts, including scripts for OkHTTP and Titanium’s Appcelerator.

Objection has detected the OkHttp library, and it has hooked both OkHttp and the default TrustManagerImpl. After pressing the button, we can see that Objection has successfully disabled two of the three methods:

Weirdly enough, the default pinning based on the network security configuration file was not circumvented. This is most likely due to the fact that I’m testing this application on Android 7.0, which has a different internal pinning mechanism than earlier Android versions.

So, let’s add another SSL pinning bypass script: Universal Android SSL Pinning Bypass #2. We can load this script in Objection by calling import hook.js , and it will be added to the list of Frida scripts that is run. This will defeat all the pinning mechanisms, and we can intercept all the traffic, great!

Note that the SSL Pinning Bypass script listed above will be part of Objection v1.5, which is due for release any day now. Depending on when you read this article, Objection will be able to bypass all these pinning controls on its own.

Adding some obfuscation

Most applications we deal with during our assessments are obfuscated, so I’ve taken the original application and run the default ProGuard obfuscation algorithm. This is obfuscated.apk in the zip file.

Running Objection with its basic bypasses and the universal hook now no longer bypasses the OKHttp-pinned request. Objection also no longer reports that it has found OKHttp, as it can not find the correct classes.

In order to once again bypass the pinning, we have to find the correct method to hook and modify it using Frida. This is pretty trivial in the small demo app, but can be quite difficult in a real application with many different libraries and packages.

The easiest way I’ve found, is to hook the CertificatePinner.Builder.add() method. This method takes a URL and a list of pins, and will store them to be verified later when the actual call is evaluated. By either modifying the first argument, or replacing one of the given pins, we can modify or disable any pinning that OKHttp will perform.

The nice thing about the add() method is that it uses a variable number of arguments, which isn’t used that often and allows for very easy grepping. For example, we can use the following pattern to find the correct method:

grep -ri "java/lang/String;\[Ljava/lang/String;)L" application/smali

This pattern searches for a method that takes a String as a first argument, a variable number of Strings as the other arguments, and returns a complex object. The most hits I’ve had on this query has been 3, and by simply hooking the different methods with Frida and printing the arguments, it’s really easy to find the correct method:

So let’s add a quick Frida script and verify that the pinning has been bypassed:

//hook2.js Java.perform(function(){ var Pinner = Java.use("okhttp3.g$a"); Pinner.a.overload('java.lang.String', '[Ljava.lang.String;').implementation = function(a, b) { console.log("Disabling pin for " + a); return this; } });

After importing both hook.js and hook2.js into objection, all pins are bypassed once again:

Conclusion

SSL Pinning can help an application defend against man-in-the-middle attacks, but it is not meant to prevent attackers from taking a look at the communication between their own device and the backend. Inspecting and modifying traffic between a mobile device and the backend should never lead to a vulnerability, as that means that security controls have been implemented on the mobile client, which should have been implemented on the backend.

Finding the correct pinning classes to hook can be a time-intensive job, and hopefully this blogpost can save someone a few hours of searching.

About the author



Jeroen Beckers is a mobile security expert working in the NVISO Cyber Resilience team. He also loves to program, both on high and low level stuff, and deep diving into the Android internals doesn’t scare him. You can find Jeroen on LinkedIn.

Share this: Twitter

Reddit

WhatsApp

Email

