Bang! Bang! You have been hacked.

Ah the sweet, enticing world of Android. My first true love, always beckoning me, whispering seductively in my ear:

“I have more than 1 Billion active users”

“My revenue opportunities are endless”.

I am not the first to be seduced by the heady romance that Android offers, nor will I be the last. I’m sure many of you reading this have had a whirlwind romance of your own.

It tends to go a little something like this: You decide to build your own app. You plan it, design it, research extensively, establish a crack team of A players to build the first version with you. You even ask your mom for a loan. You know you’re good for it because this all just feels so right.

As the first version of your app comes out, your heart flutters wildly with every download. As more and more users integrate your app into their daily routine, you are in the full throes of the honeymoon period, head over heels in love with the magical world you created. Can life get any better than this?

In love

This is exactly what happened to me while working on one of my startups.

We had an amazing application, with millions of users using our app on a daily basis. We invested a lot in our applications and had a million lines of code serving our customers.

It was beautiful. We were in love.

And then it hit us.

Our app, in which we’d invested so many days, months, and years was brutally reversed engineered, hacked and distributed. We were left bruised, shocked and heartbroken as our honeymoon period evaporated into the harsh reality of the effort it takes to make it work long-term with your one true love.

In this article I will share the architecture decisions we made in order to fight back against the brutal hackers who tried to take our true love away from us.

Disclaimer: I’m not security expert. It’s my journey, and I decided to share it.

The first thing you need to understand when thinking about your application security:

There is no such a thing as a bulletproof Android application. Everything is breakable, hackable, reverse-engineerable. Some are easier to hack, others are harder. But at the end of the day, nothing is safe.

The second thing to bear in mind is that good security requires teamwork. It’s not only up to the Android developer to take responsibility; it requires a concerted, collaborative effort including the backend developer, product manager, QA and everyone in between.

My pro-tip? Save yourself some heartbreak and already accept that security is an endless fight that you will have to constantly to invest in. And it’s hard. If it’s easy — it’s easy to hack.

97% of all Android apps are hacked

“There are risks and costs to a program of action — but they are far less than the long-range cost of comfortable inaction.” — JFK

One of the first signs that we didn’t paying enough attention for security was:

What wrong here?

Our NetworkHttpUnauthorizedReceiver is exported, meaning every other application can send intent with http_unauthorized action and cause the user to logout. One of our competitors used it and wreaked some havoc for us.

So how can you prevent it? Simply add exported=false to AndroidManifest or in the case of dynamic registration use LocalBroadcastManager

But this is just the tip of the iceberg.

Before diving into the technical details of the solution, it’s important to first understand what tools are used to hack your app.

Static Analysis tools

Tools that take your dex code and convert it back to java code. The most popular tools are Apktool & Smali/Backsmali. It’s very easy to install and it takes just a matter of minutes to get the java code decompiled.

Network Analysis tools

These tools, including mitmproxy, charles etc., allow users to monitor everything that happens on the network between the application and server. They enable users to intercept the requests/response, understand which data is transferred, how it is structured and acquire a lot of information about your models. They are used very commonly in MITM attacks.


So you have to start somewhere. There a lot of things to do, like private-public keys exchange, .so apk fixing, customer TypeAdapters etc… 
However there is some basic that you can implement and use with relative low development. It will make your app harder to reverse-engineer at least at the beginning.

I like to divide Android security into 3 stages:

1. Protect your code
2. Hide your network layer
3. Prevent code stripping

Let’s don our hard hats and dig a little deeper into each stage, shall we?

Protect your code

The idea behind this is to make it very hard for reverse engineering.

Most developers are familiar with Proguard. It’s a tool that allows you to optimize & obfuscate your code. However, most developers turn it off so they don’t need to deal with the complex and time-consuming configuration it requires. (*cough cough* me!)

In order to convince you to use it I will show you what you can achieve by using it.

Consider this code:

As you see, if a hacker gets his hands on the code — it’s pretty clear that encryption keys and messages would be super exposed. There are couple of things you can do:

The first step is to use reflection

The seconds step is to hide your key and password using encoding like Base64

The last step is to let Proguard obfuscate class names

Well I guess now it’s very hard to understand and read, but it is still possible.

There are 3 very useful Proguard flags that you can add to your .pro configuration file:

-repackageclasses
-allowaccessmodification
-flattenpackagehierarchy

These flags will allow Proguard to change the access modifiers of classes and class members, repackage all classes and remove your package structure and allow you to achieve this:

Flatten bad named classes in dex file

The last thing to think about for the protection of your code is GSON. Yes Yes. GSON.

We all love to use JSON as our network data structure and GSON library to parse from JSON string to POJO model. It’s human readable, it’s understandable, it’s easy. But remember — if it’s easy , it’s easy to hack. *taps nose knowingly*

In order to use GSON with Proguard we still need to leave our POJO unobfuscated using -keep class com.example.apps.android.model.** { <fields>; } or provide @Serialazable annotation for each class member. Otherwise GSON will not know how to deal with a mapping

Obfuscation of POJO will make GSON not work

Therefore if you care about your APP security, don’t forget:

And this leads me to the second part of the article:

Protect your Network

MITM Output

It might be an obvious one to point out, but remember you have to have your domain using SSL from the trusted certificate authorization.

As I mentioned before, there are a wide variety of tools that allow hackers to intercept traffic and see how the app/backend request/response looks like.

By using plain JSON, even through SSL, we are openly telling hackers about our model, what and how we communicate with a server and which data we expect. MITM attack is just the beginning here.

So what we can do in order to protect ourselves? Encode the data transferred into a different format.

My suggestion is to use ProtoBuf especially Squares implementation of it for Android called Wire.

First, it will improve your serialization/deserialization time.

Second, the data will be transferred in binary format which is less readable and harder for attacker understand.

One feedback I got from colleagues and the VP of RnD was that it’s hard to read and debug while developing. And they were totally right. All together now! ♪ If it’s easy, it’s easy to hack ♪

If you’re using Retrofit you can work with 2 types of data at the same time: JSON & ProtoBuf:

You can ask your backend people to implement a debug switch for traffic data type. The server sends data in JSON format when it’s On, and in binary ProtoBuf format when it’s Off(default).

On the application side you can configure Retrofit to use 2(or more) converters:

Retrofit.Builder().baseUrl(baseUrl)
.client(clientBuilder.build())
.addConverterFactory(WireConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.build();

Retrofit will try to convert received data using the WireConverter and if failed will try to use GSONConverter.

Protect your code from stripping

The third wall of defense to make a hacker’s life a bit harder, is to prevent hackers that have successfully reversed-engineered your code from modifying it and using it regularly without your backend being aware that it is responding to a hacked app.

The goal is to prevent a hacked application where the code is modified from communicating freely with the server.

The first thing you can do is check that the signature of the app is aligned to the signature stored on the server.

For every version you build, you store MD5 on server and compare it with current version sent by the app.

PackageInfo packageInfo = context.getPackageManager()
.getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES);
for (Signature signature : packageInfo.signatures) {
MessageDigest md = MessageDigest.getInstance(“SHA”); md.update(signature.toByteArray());
String currentSignature = Base64.encodeToString(md.digest(), Base64.DEFAULT);
//compare signatures
if (EXPECTED_SIGNATURE.equals(currentSignature)) {
return VALID;
}
return INVALID;
}

The problem is that after some trial and error, hackers will figure out the key and will start to send it properly.

Another option could be to use Google App Licensing to help you figure out if the current app was properly installed from the Play Store. Basically, Google Library query Google Play Services if Application was installed from the play store.

Still, it’s not enough, since a hacker can figure out the existence of this License Checker and just alter it from the code.

These things alone wouldn’t be sufficient to prevent your code change but would be if combined together with a random challenge from the server. The thing is to make this authorization process not only using MD5 and/or App Licensing library but also to get some random challenge from the server and reply to it based on the code that you have in your app and based on having this answer(key) signing the API request.

We can achieve such functionality by using Custom DEX Class Loading

You download extra.dex from Google Play as part of expansion file, which contains random challenge method. You unpack and load this extra.dex. 
Very important to mention that the server should continuously change the extra.dex file (challenge) on the Google Play. (.dex file should be download from Google Play only due to the security issues )

Here is the example of how to load external dex file after unpacking it from expansion file:


DexClassLoader cl = new DexClassLoader(dexInternalStoragePath.getAbsolutePath(),
optimizedDexOutputPath.getAbsolutePath(), null, getClassLoader());
Class libProviderClazz = null;
try {
// Load the library.
libProviderClazz = cl.loadClass(“com.dex.lib.LibraryProvider”);
LibraryInterface lib = (LibraryInterface)libProviderClazz.newInstance();
lib.callForChallengeMethod(this);
} catch (Exception e) {…}

So what can be challenged?

For example, using reflection you can challenge the existence and expected response from some crucial methods. If a hacker erased method is preventing mock location you can challenge that this method exists and return expected results based on different inputs. Or you can check that the App Licensing library still exists in the app and is functioning properly.

Here is a diagram of the process:

Build phase:

1. Build .apk

2. Extract MD5 from release APK file and store it in the server for the built version

API Call Signature Phase

1. Server has predefined challenges and responses in .dex file.

2. Application upon login downloads this extra.dex file.

3. Application obtain MD5

4. Application executing challenge from the extra.dex file and store response

5. The response, MD5, TimeStamp and session token hashed and sent to the server with rest app details.

6. Server, using MD5 stored in the DB, expected challenge response, timestamp & token, generating signature and compare it to the receive signature.

In case the signature matched — the API request executed, otherwise… Log the IP and try to catch the guy :)


Oh, it was a tough journey prising the love of our lives out of the cold hard hands of those pesky hackers. 
And it WILL happen again. 
Security is a never-ending effort that you need constantly to invest in your application. It’s the fight that never ends, but you know you have something worth fighting for!

***Update***
I got some feedback about Proguard and that it does not really help to secure the app.
Application without obfuscation is like giving access to your private repository to the hacker. It’s apparent and easy to follow your code.You provide tons of information to the hacker for free, and it becomes effortless to reverse engineer what exactly happens in the app and what logic used. 
When using Proguard with obfuscation — you make the hackers life much harder, and he will need to invest the much more significant effort to reverse engineer the code. (Still, it’s possible to reverse-engineer it).
DexGuards is the premium version of Proguard. I like to call it big Proguard brother. Unfortunately, It’s very costly (back then I got a quote of $60k for one year license). Before you going to pay — there is a lot of things you can achieve for free by configuring Proguard. Unfortunately, statistics says: 
 “Top 100 apps on Play store, around 17 apps had Proguard in them” — meaning 83% of top 100 applications reverse engineered and hackable. Maybe it explains the poor statistics we have in-app security.

****

Thanks for reading. If you liked it, please give me your 👏 👏 and share this. I’ll also love to hear your comments and suggestions :) Thanks

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.