With so much discussion around the topic of securing data in transit, I wanted to take a bit of time to dive into the actual process of SSL/TLS encryption and how Android and apps written for Android handle this. The best way to go about it is to get down and do the work, so in this chapter, we will see how to generate an SSL certificate, write a back-end API in Golang, and write an Android client to talk to that back end, and finally we will see how to intercept SSL traffic.
If you’re used to using telnet or nc to verify HTTP, the way to do that with an SSL-enabled site is to use the OpenSSL command s_client . In the preceding code block, after the DONE prompt, you can type in normal HTTP requests like GET / HTTP/1.1 and so on.
SSL Certificates
Generally, an SSL certificate is issued by a Certificate Authority or CA. A CA’s job, among issuing you a certificate, is to verify your identity. The CA is what vouches for you when other clients ask about how trustworthy you are. Once a certificate is issued by a CA, this is one mechanism in which they say “Yeah this guy’s shown us all his docs and we believe he’s legit so trust us when you see that his certificate is issued by us.” In the early days of public key infrastructure (PKI), obtaining an SSL certificate was quite a process. The process is a lot more straightforward now as you will see. But first, let’s talk about the three types of validation for HTTP certificates.
Domain Validation
Domain Validation (DV) certificates do one thing. They verify that the person applying for the certificate is the person who also controls and has ownership over the name on the certificate that he is applying for. We apply for one later on in this section. A DV certificate can be the quickest to register for and download because it is very easy to validate ownership of a server as you will see. Therefore, no further human interaction is involved on the CA’s side.
Organizational Validation
An Organizational Validation (OV) certificate, on the other hand, will check not just for ownership of the server as a DV certificate but will also request for business entity registration documents to prove that the person that is purchasing the certificate can manage the domain name as well as proving that the company is real. This one would require some human inspection and additional checking on the CA’s side. Of course these procedures depend from CA to CA, but it is safe to assume that the bigger more trusted CAs will do the relevant due diligence as far as checking business registration before issuing the certificate.
Extended Validation
The Extended Validation (EV) certificate, you may have heard the term EVSSL being mentioned by people, is the certificate you want to go to if you’re doing an online business that requires you to verify to your customers that you jumped through the relevant hoops to get this certificate. An EV certificate will typically turn modern browser address bars green and show a heightened level of trust visually. To get an EV certificate, the CA will always want to speak with a human from the company. The checks in place are the ones for an OV certificate, but also include a mandatory human-to-human session where the CA staff can verify further details of your business and its legitimacy prior to issuing you a certificate. Generally, you will find that ecommerce companies or companies that may do online financial transactions will get EV certificates.
Self-Signed Certificates
A Note About Verification
Why does the preceding example fail? I mean we have a certificate and private key just like we should. Well, the reason this fails or at least the browser doesn’t honor the certificate is because it doesn’t trust the person issuing it. Essentially, it is like you drawing your own ID card and trying to use it when you buy alcohol. The guy selling it to you, in most cases, will laugh in your face because he doesn’t trust the ID card. He would trust the government that issues the ID card, and he already knows what a government-issued ID card looks like. Your ID card looks nothing like the government one, and hence you wouldn’t be able to get that bottle of Single Malt.
You may then wonder how a client can begin to trust a CA’s issued certificate. Well, each of our web clients these days and even operating systems come with a set of root certificates. A root certificate is a self-signed certificate that a CA issues to itself. Based on this certificate, it will sign all other certificates that it issues. But wait, that’s the same process we used right? Well yes, that’s exactly what we used to generate our root certificate. The difference here is that the bigger CAs gain a reputation first and then begin to gain trust. CAs may ask all clients to include their root certificate in the distribution of the latest browser or operating system. Or a client may choose to bundle the root certificate of the CAs that it believes are more trustworthy. Either way, the entity that controls whether a certain root CA is included or not is the publisher of the browser or operating system. We have grown to know and trust the bigger names that make browsers like Firefox, Safari, or Chrome so we use them. This in itself becomes an attack vector. For example, if I wanted to sniff all your SSL web traffic easily, I would embed my own root certificate in a browser that I build (technically, if I am writing the browser, then I don’t even have to implement SSL, but let’s assume for argument’s sake that I base my browser off Chrome). Then whenever I wish to sniff traffic, I can poison or redirect a legitimate SSL host to a server that belongs to me, issue a certificate for that legitimate site, and begin to collect SSL encrypted traffic. The browser won’t report an error because it sees that the certificate for that site was generated and signed by my root certificate. Since the root certificate is in the browser's repository of certificates, it must be legitimate. The topic of SSL and encryption can take up a book by itself, so I am going to quickly move on to our main topic of how to break SSL.
Getting a DV Certificate
To get ourselves a DV certificate, we’re going to use Let’s Encrypt [https://letsencrypt.org/]. Let’s Encrypt is a nonprofit CA that provides DV certificates for free. The service itself is provided by the Internet Security Research Group that had board members and technical advisors from many of the large companies such as Google, Facebook, and Akamai and from other groups such as the ACLU and EFF. The entire process is automated as we will see shortly. For the issuing of a DV certificate, remember that we have to prove that we own the domain name and the server it is running on? This also means that we need to have a domain name and a server to run it on that can be publicly accessible. If you want to follow along, that means you will need a public IP that is configured on a server or workstation of yours that you have access to. I used DigitalOcean [https://www.digitalocean.com/] and quickly spun up a Debian Linux virtual machine or droplet to run this test. You could also choose to use Ubuntu which is based off of Debian. For the demo, I am going to use my domain name called redteamlife.com and will apply for a subdomain name certificate: aas2.redteamlife.com. OK, let’s get started.
Now we need to use the Certbot to generate our certificate.
Certbot
Certbot [https://certbot.eff.org/] is the EFF’s automated certificate generator. The best way to get a certificate quickly and painlessly, in my opinion, is to use a popular Linux distro with shell access. The reason I say use a popular distro is because the Certbot instructions are a lot easier to follow and generally more precise.
- 1.
A server with a public IP address
- 2.
A domain name purchased and correctly pointing to the IP address from point 1
- 3.
Ports 80 and 443 open on the server from point 1
- 4.
Ability to install and configure a web server properly (e.g., nginx) and pick a server operating system where you can install nginx smoothly
- 5.
There are specific instructions that can be found in the Certbot website that gives you a very good combination of web servers and operating systems to choose from. For further information, you can visit this URL: https://certbot.eff.org/instructions.
I’m going to save you from having to look at any more screenshots for this, but I followed the instructions as stated and ended up with my certificate and private key file like from the preceding openssl example. Let’s Encrypt will usually write your certificates to /etc/letsencrypt/live.<domain name>/.
The Back End
We could technically conduct our demo by using the nginx web server that we just installed and requested a certificate for, but I want to show you a bit more on how API communications will look on the wire, so let’s go ahead and write a back end. I will give you the basic blueprint of the back-end server so you can implement it in the language of your choice. I chose Golang (https://golang.org/) and we will look at the source code later on. You can download the source code here: https://github.com/sheran/aas2-ch09-go_backend.
Note that you will need both the certificate and private key file in the directory that you run this file from.
Back-End Server Specification
- 1.
The server has to listen on port 8443.
- 2.
The server has to implement and use SSL from the certificates we generated.
- 3.
The server should have one API called /secret that accepts HTTP POST requests only.
- 4.
The /secret API should receive JSON data with specification {“code”:<string>} where string is sent from the client.
- 5.
The server should return {“message”:<message of your choice if code is incorrect>} if an incorrect code is set.
- 6.
The server should return {“message”:<message of your choice if code is correct} if the correct code is sent.
- 7.
The code can be a string of your choosing.
Let’s go over the specification.
Line 49 addresses both Spec 1 and Spec 2. My certificate and private key are in my current directory named fullchain.pem and privkey.pem, respectively.
Lines 23–44 and 48 address Spec 3 and Spec 4.
Lines 37–41 address Spec 5.
Lines 30–35 address Spec 6.
And I’ve chosen the code of “gekko”.
In this example, I use the hostname aas2.redteamlife.com. You will have to use the domain name that you selected for this exercise.
It seems to be working just fine, which is great. Next, let’s build an Android client to talk to that API.
Android Client
Similar to how we did in Chapter 3, we’re going to build a very basic Android client that has just one purpose – to capture our input and send it to the back-end api, receive a response, and print it out on the screen. In Android Studio, start a new Android Studio Project and then pick the one with an Empty Activity. Name it accordingly as you would want to (refer to Chapter 3) and let's design a UI.
I am just going to give you my strings.xml, activity_main.xml, MainActivity.kt, and NetUtils.kt source here. These are the files necessary to build the app. You can find the full source code to this client here: https://github.com/sheran/aas2-ch09-android_client_1.
Testing SSL Traffic Interception with Burp Suite
One more thing, switch off your mobile data on your Android device. This is because Android will use this as a backup in case it detects something unusual with the Wi-Fi connection, for example, it detects a fake certificate. OK, I think we’re ready to test SSL traffic interception! Fire up the client app and enter some codes and click submit.
Two things may happen at this point. First, you will see the SSL traffic in Burp’s Proxy section. Or second, the app will crash. When I first did it, the app crashed on me. So I did a little further digging, and I found that since Android version 7 (API 24), the engineers over at Google have reconfigured how trusted Certificate Authorities handle CA certificates. There is a link to a blog post here: https://android-developers.googleblog.com/2016/07/changes-to-trusted-certificate.html. What does this mean for us? Well, it means that even if we add user CAs (like we did) to our Android device, an app has to specifically opt in to work with that user CA certificate – the premise being that if we were able to add it to the device, we should also be able to add it to the network security config. Turns out, though, that we actually can! But we need to play around with the APK first a little. Let’s do that now.
Adding SSL Pinning
If you put that into your app and repackage it with APKTool and test it, your app should crash.
Lines 19–39 and 48 are different as you can see in the code in bold. What we’re doing here is storing the certificate from our server aas2.redteamlife.com in the app. Then, we read it and add it to a KeyStore and then use that KeyStore to initialize a TrustManager which we then pass on to an SSLContext. Then, the SSLContext is passed to our HttpsURLConnection. In this way, the TrustManager will ensure that it can verify all certificates against the one we included and pinned. Now when I test the new client, my app crashes right away. There are a few points here to keep in mind. First is on the pinning side. Generally, it is a better idea to pin a public key as opposed to an actual certificate like we have done. This is because when a certificate gets renewed, its public key still remains the same. Thus, each time we renew a certificate, it wouldn’t mean we have to make a code change on our client. So it’s always better to pin a public key. I will show you that later in the chapter. The next point to consider is the continuation. You have a choice with what to do when your certificate pinning validation fails. If the exception is thrown, then you know that something fishy is going on and at that point can take action. Would you continue with lesser features or would you completely stop? In my opinion, it is better to completely stop communications until the certificate issue has been resolved. In our case, we’re failing fairly inelegantly. You could choose to notify the user that there’s an issue with the server certificate or even just fail with a generic error message. With that said, let’s move on to the real reason you’re here, breaking SSL Pinning.
Breaking SSL Pinning
The TrustManager, as its name implies, plays a pivotal role in ensuring that server certificates are trusted. If you look at the TrustManager class or rather the X509TrustManager, which is the subclass most often used, you can see three public methods: checkClientTrusted(), checkServerTrusted(), and getAcceptedIssuers(). We are most interested in the checkServerTrusted() method which we want to render nonfunctional. There are possibly many ways to do this, but hooking this function and rewriting it is probably the cleanest way. We would use a tool like Frida in this case to load a script that has our own implementation of the X509TrustManager.checkServerTrusted() method. But we can take a shorter cut. Essentially, the easiest way is to create our own TrustManager which is empty and then send that into the SSLContext’s init method by hooking it. Since our TrustManager has no actual checks in place, we should be able to get past the SSL Pinning in this case. So, let’s try that out.
I would like to quickly revisit the topic of the way Android 7 and above handled Certificate Authorities. If you recall, we used Network Security Configuration to trust all our user certificates. There is another way, however – again, by using Frida. But to do that, we have to dig into the Android source a little bit. Google decided to fork OpenSSL and write their own library called BoringSSL [https://boringssl.googlesource.com/boringssl/]. They use the BoringSSL library in their own projects Google Chrome and notably Android. Android’s implementation of BoringSSL is through Conscrypt [https://source.android.com/devices/architecture/modular-system/conscrypt]. Conscrypt is a Java Security Provider used in Android together with the BoringSSL native library to provide Android’s cryptographic functionality such as ciphers, key generation, and message digests. It also provides Android’s TLS implementation. Since there are many common functions with OpenSSL, let’s take a quick look at how OpenSSL does its certificate verification.
then we can easily avoid the pesky issue of having to add a new network_security_config.xml file that asked Android to trust all user-added certificates.
Other Pinning Techniques
OkHTTPv3 pinner
Trustkit pinner
Appcelerator PinningTrustManager pinner
OpenSSLEngineSocketImpl Conscrypt pinner
OpenSSLSocketImpl Apache Harmony pinner
PhoneGap sslCertificateChecker pinner
IBM MobileFirst pinTrustedCertificatePublicKey pinner
IBM WorkLight HostNameVerifierWithCertificatePinning pinner
Conscrypt CertPinManager pinner
CWAC-Netsecurity CertPinManager pinner
Worklight Androidgap WLCertificatePinningPlugin pinner
Netty FingerprintTrustManagerFactory pinner
Squareup CertificatePinner pinner
Squareup OkHostnameVerifier pinner
Apache Cordova WebViewClient pinner
Boye AbstractVerifier pinner
This list comes from the Frida codeshare script frida-multiple-unpinning.js found here: https://codeshare.frida.re/@akabe1/frida-multiple-unpinning/. Frida’s codeshare section is an open platform where users can submit their own scripts. Then by using Frida’s “—codeshare” switch, you can use these scripts without having to download them. If you look further on codeshare, you can see a whole slew of scripts dedicated to removing SSL Pinning. It is important to take note that these scripts usually show up when a new pinning mechanism has been found. That goes to show you that in general, reverse engineers are always going to come out on top. Tools such as obfuscation, moving code to native libraries, and such can act as a deterrent and possibly slow down a reverse engineer. But if there is determination and time, in the end, your app is bound to succumb to the attacks that are thrown at it. This isn’t meant to discourage you but serve as a warning that it is best to not expose as much code or data as possible and hope that the security tools solve the problem.
I’ve made the changes bold so you can clearly see what we are doing. We have two new functions called trustedChain and validatePinning. trustedChain will pick up all the certificates from the server and then run it through its own X509TrustManagerExtensions check. The difference between the X509TrustManagerExtensions and plain X509TrustManager is that the checkServerTrusted method will verify and also return the list of certificates to the caller. If it cannot, then the method will throw an SSLException. If it can verify, then it gets verified by the validatePinning method to see if one of the certificates has a matching public key hash that we compare with. If this also fails, then again an SSLException is thrown. If it passes, the connection proceeds as planned.
The preceding piece of code isn’t mine, but I did translate it into Kotlin from Java. The code itself was featured in a blog post by Matthew Dolan here: https://medium.com/@appmattus/android-security-ssl-pinning-1db8acb6621e. In this post, he also provides a few other mechanisms of SSL Pinning. It is highly worth a read. If you notice this code, it doesn’t actually conform to any of the present SSL pin-breaking mechanisms we have with Frida. I am not a fan of security through obscurity, but if you wanted to further hide what this class was doing, you could rename the methods and fields, but a reverser will always be able to find it through the exceptions. You could then possibly roll your own exceptions, but again, it’s a case of following the breadcrumbs. Lastly, you could obfuscate all of that, but then on a rooted phone, a reverser could trace all the native calls and work backward from there. As discouraging as this sounds, only the most determined reversers with something to gain will go after you. Usually, the current mechanisms for protecting apps in the form of obfuscation are sufficient for the drive-by hackers. You can find the TrustManager pinning client source code here: https://github.com/sheran/aas2-ch09-android_client_2.
Summary
In summary, it is important to keep in mind that your app is effectively a showcase for your code and data. It’s just a more structured way of sending out your intellectual property to the world. Your app will be installed on many mobile devices, and a percentage of those will very likely be hostile. Your goal should be to first limit the amount of exposure by reducing what you release in your app, whether it is code or data. Then, if there are parts that you must lock down with obfuscation, do so. SSL is nonnegotiable of course. Always implement SSL Pinning so that even a drive-by hacker can’t see client and server data exchange. But always be prepared that they will be able to see this data and take steps accordingly. Like I said in my first edition, a healthy dose of paranoia when developing your app will serve you well.