Android apps are mainly composed of a bunch of binaries bundled together, built from compiled Kotlin or Java code. The original source code can be easily reconstructed by several light-weight tools, found on the top of a google search page.
If you're a happy Android developer and you're not careful enough to apply the best security practices on your projects, you won't stay happy for long. All the precious secrets you think are safely hidden in your code, can and will be used against you in the wild by someone with the right motivations. That's precisely what happened to the Android app of this chain of barber shops.
The developers' disregard of every kind of security best practices resulted in something that, in just a matter of a couple of hours, I was able to decompile, reverse-engineer and identify several vulnerabilities that anyone can exploit to achieve what would be every hacker's life-pursuit dream: free haircuts for life!
Here, I'll walk you through the whole hacking process, show you how important it is to follow the security best practices and how simple decisions can greatly increase the difficulty for reverse-engineering and exploitation of your Android app.
The app of this chain of barber shops has a very simple purpose: Users can book haircuts on the available shops and per each 10 haircuts, the user gets
one for free
So far so good, they all look like pretty good tasks for an app to do. However, after installing the app and looking at my profile page, there was a very explicit and somehow intriguing message: the freebies could only be generated via app - not by any other platform - and they could also be used whenever the user wants: just need to present the app when it's paying time.
This message definitely got my attention.
So I downloaded and decompiled the apk to satisfy my curiosity.
Maybe if I looked at the code, I could, hypothetically, use the app in some creative ways.
And I was right: what I found was a complete mess.
After decompiling the app and reverse-engineering the code, I identified several vulnerabilities. Later on, I'll go one by one and discuss what the original developer could have done to prevent it:
Kotlin and Java sources are first compiled to
files, which consist of
instructions. Traditionally, these bytecode instructions would be executed directly on the JVM. However, Android apps are executed on the
, which uses incompatible instructions, and therefore an additional
step is required, where
files are converted into a single
Here's the compilation process:
There are several tools online that decompile
and convert the
file into readable Kotlin or Java code. The resulting decompiled code is the entry point for understanding the functionality of the targeted app, but will likely not be 100% usable code. In other words, you can read the source, but you can't really modify, recompile and build the original app with it.
However, as I was really motivated on snooping into the code, I used
to decompile the apk along with its
file contents anyway (steps 1 and 2 above)
$ apktool d target_app.apk
I as stated before, the
file is the format that the platform actually understands. However, it's not easy to read or modify binary code, so there are some other tools out there to convert it to and from a human readable representation. The most common human readable format is an assembly language known as
. This is the format
outputs and stores on a
This is what a
file looks like:
files are the ones we'll be editing later, but not clearly the ones we'd want to be looking at for reverse engineering the whole application. Not unless we're forced to.
To look at more friendly code that could help me on the reverse engineering process, I converted the these smali files into standard
(step 3 above):
$ dex2jar decompiled_app/build/apk/classes.dex
and booom! I have now access to the full app source code in a very familiar format, as the original developer wrote it.
Now, let us have a look at what I found
Obfuscation and minification are strategies used to reduce unused code and shorten the names of your app's classes and members - usually by replacing them by something meaningless - not only making it very hard to read but also further reducing the size of your app.
If you don't apply any kind of code obfuscation, you're pretty much giving access to your private repo to the hacker.
In this particular case, It was straightforward to understand and follow the code, identify third party technologies, but also the secrets used to authenticate them.
This could be all easily avoided by using some kind of obfuscation tool, like
, that would at least make the hackers life much harder, and forcing them to invest the much more significant effort to reverse engineer the code.
This is what the code looks like before and after applying obfuscation :
The first class I looked at, had literally an hardcoded variable called
, that would later on be used to authenticate each Http call to the backend.
And suddenly, I have now access to the entire app's private api to mess around and look for new attacking vectors.
If you really need to store secrets on your client applications to provide some kind of authentication, this can be avoided in many ways without compromising these secrets by hardcoding them on your code.
There are several well-known methods like using symmetric key derivation strategies or password-based key derivation functions.
A very good and detailed view on these methods can be found here.
This is where things get interesting... Continuing my quest to get the best haircut - the one I don't have to pay for - I still had to find some vulnerability that would get the app to generate me a free haircut booking.
After spending roughly one hour looking at the code, this is what I've learned:
for each user
for each user via
call to backend is identified by a hardcoded
And now, the cherry on top of the cake:
, by counting how many successful bookings a
was just a standard
REST call with an additional
stating the booking is for a
At this point, you can guess what was the backend response when I promptly issued a
to the booking api with that
set to true:
> 200 OK!
After looking at so many mistakes here, I confess I wasn't surprised.
What we can learn from this is that by letting your
to call such important decisions like: when it's time to grant a free booking, you're literally placing your precious business rules on the most vulnerable end of the whole user-journey.
Even worse, is to not validate the REST calls on the backend when you still have the chance. It would be obviously mandatory to validate if a user has made and payed for enough bookings to get a free one, before accepting and storing on the backend anything incoming from the client's app.
And this leads me to the last but not least vulnerability:
If you're going to code important business logic decisions on the client app - which is totally wrong by the way - the least you can do is to have some mechanism in place to verify the app's authenticity.
A lot of the vulnerabilities above could have been mitigated with some kind of client app integrity check, making it possible for the backend server to verify when the api calls are coming from legit applications and discard the ones incoming from compromised apps. This can be easily achieved by signing the requests with the app's release certificate.
Each Android apk is signed with a release certificate. If the apk is decompiled and recompiled again with code changes, by some process similar to what I describe here, the new recompiled apk must be signed with a different certificate, as the hacker won't have access to the original one used to sign the legit app that only the original developer would have.
Alternatively, Google itself provides App Licensing services to help you validate if the current app was installed from the Play Store or from some other unknown source (the hacker).
So, now that we're happy with our hacking and we know what and where we should place our malicious code, it's time to edit some assembly files and recompile the whole thing. Remember
file per each
so it would be easy, at least, to navigate and identify where the changes should be made. However, from now on, you're on your own: if you're not familiar with some kind of assembly language, this can be a quite painful process.
I'm glad I didn't have to change too much for my tampered booking requests to work. Just adding couple of
and removing a couple of
proved to be enough.
When your done editing, just use the
again to recompile everything:
$ apktool b -f decompiled_app -o hacked_app.apk
Generate a random key pair to sign the recompiled app like this:
$ keytool -genkey -v -keystore some_ks.keystore -alias some_alias -keyalg RSA -keysize 2048 -validity 1000
Sign the recompiled app, I used
$ jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore some_ks.keystore hacked_app.apk some_alias
And you're good to go!
This is it. This is the rejoice moment. I've done what no hacker has done before. I could sit back and install my tampered app, call the barbers shop private api with my tampered requests, and finally, I could book free haircuts!
And it was worth it! (hypothetically)
This was an attempt to show how easy it is to steal your secrets and how generally vulnerable your code is if you don't take the necessary precautions to preserve both your intellectual property and your business logic. Several exploiting tools and general knowledge on how to use them properly are widely available online, meaning it's not just a matter of luck if your app gets hacked, no, It's just a matter of time.
So you better start investing some time learning the best practices; otherwise, you're just leaving the door open for someone to really take advantage of your code and potentially cause significant losses to your business. Not just displaying it for didactic purposes like I did here.