Introduction
Android reverse engineering refers to the process of decompiling the APK for the purpose of investigating the source code that is running in the background of an application. An attacker would ideally be able to change the lines of bytecode to make the application behave in the way that the attacker wants. However, as easily as it is put, reversing and rebuilding an APK takes more than just a shallow statement. In this article, we’ll be looking at the basics of decompilation, rebuilding, signing and changing the behaviour of an application while we do this. Let’s start.
Table
of Content
- Installation
of Uncrackable Level 1 APK
- Decompilation
- Android
bytecode viewer
- Smali
files and modification
- Signing
APK and Rebuilding
- Solving Challenge
Installation
Uncrackable is an
intentionally vulnerable APK created
by Bernhard Mueller which was later undertaken by the OWASP MSTG project. Level
1 of the 4 leveled challenge of APKs focuses on basics of root detection bypass
and hooking to find a secret encryption key. To install this application,
follow here.
After you download the apk and install using adb in your genymotion emulator, you’d see something like this:
This means that the application has some kind of logic hard coded that prevents it from opening in rooted devices and since genymotion’s android APIs are root by default this is presenting the user with this problem. In real life environment, you’ll see many applications in which developers code this root detection logic as a security measure to prevent aid to an attacker in his campaign and thus safeguard PIIs. However, this could also pose the possibility of poor coding practice and is exploitable. There are multiple ways to solve this first hurdle; hooking and removing this restriction while runtime is one option, making the application debuggable and injecting while executing is also one method but we’ll follow the third method, which is reversing method. We’d decompile the application and remove the exit logic of the application to prevent exit.
Decompilation
The Android decompilation process is fairly simple and resembles
java decompilation in many ways. Basics of the decompilation process has
already been covered in a previous article here. It is highly recommended you
read para 3 of the article mentioned first and then resume this part.
It is to be noted that Dalvik bytecode is stored in *.dex format.
This dex is the compiled version of source code which is further packed with
resources, manifest, META-INF (certificate) into a zip file also known as an
android app with an extension *.apk.
This *.dex file can be decompiled using dexdump which is provided in android sdk. In articles prior to
this, we’ve used the dex2jar tool to
convert dex files in readable jar format. This same was done by first unpacking
the APK using apktool and then further converting classes.dex file into readable jar variant. So, let’s unpack the
APK first:
apktool d -f
-r UnCrackable-Level1.apk
Here is something different from previous time; -r option has
automatically converted classes.dex into smali
files.
Smali
files and modification
Smali in android is similar to what Assembly in Windows is. This
is the human readable version of dalvik bytecode. Baksmali is the tool which decompiles dex into smali files. Here,
note that baksmali has converted classes.dex in smali files.
A nifty little tool known as bytecode viewer converts APK directly
into readable format java code thus eliminating the need to use apktool then
dex2jar and then jd-gui to view a readable java format. Here is how the
application looks decompiled in bytecode viewer.
Oh, wait, while just decompiling this APK, my eye went on an interesting piece of code under MainActivity$1.class.
One thing to be noted is that since this application was
obfuscated while building, it is forcing it to display ambiguous information
like same class name multiple times, change of name of methods etc. This is due
to Proguard obfuscation technique, which, is not properly implemented since the
code is still pretty much readable. Strong obfuscation makes it a headache to
reverse an application and makes it near impossible for an average attacker to
patch APKs.
Now, let's have a look at MainActivity$1.class
Did you note as well that application is exiting using an onClick
popup. Ahh! This popup is the popup that we saw in our installation step where
application is detecting whether the device is root or not. So, hypothetically
speaking, if I remove the logic to detect SU binaries, system won’t exit. Yes,
that is one correct method, but I leave it to you readers to do and implement
that. Other easier method is to remove the exit dialogue itself. This way, even
if the application detects SU binaries, it will still not exit since system.exit won’t be existing now. To
do that, I need to open the smali file of this class.
Do you see the line where I’ve marked red. This is the same
system.exit logic that we just saw. Now, it takes a little practice to
understand smali instructions and is certainly not possible to understand this
in a day or two but with a little smart work we can make our way around to
bypass root detection. Here, invoke-static
refers to a function being invoked, that is defined in the very adjoining
line of code: Ljava/lang/System. This
is the path where package of system is stored. Next, exit(I) corresponds to exit() method of System, with I as in
integer as a value which is denoted by V.
Pretty simple right? Now let’s delete this line altogether!
That’s more than just pretty. This way we can rely on return-void
instruction to return null value every time application detects a SU package
and so, whole logic is rendered useless just by this alteration. Let’s try to
rebuild this APK now.
apktool b UnCrackable-Level1
-o new_uncrackable.apk
And just like that, we’ve built a new application. Let’s try to
install this new application in our genymotion device.
adb install
new_uncrackable.apk
OOPS! That’s peculiar. Did this work for you? Probably not. Let’s
understand why.
Signing
APK and Rebuilding
The error I, and by extension, you must have received is a
certificate error. Android uses something called a certificate and a keystore.
A public-key certificate, also known as a digital certificate or an identity
certificate, contains the public key of a public/private key pair, as well as
some other metadata identifying the owner of the key (for example, name and
location). The owner of the certificate holds the corresponding private key.
When you sign an APK, the signing tool attaches the public-key
certificate to the APK. The public-key certificate serves as a
"fingerprint" that uniquely associates the APK to you and your
corresponding private key. This helps Android ensure that any future updates to
your APK are authentic and come from the original author. The key used to
create this certificate is called the app signing key.
A keystore is a binary file that contains one or more private
keys.
Every app must use the same certificate throughout its lifespan in
order for users to be able to install new versions as updates to the app.
When running or debugging your
project from the IDE, Android Studio automatically signs your APK with a debug
certificate generated by the Android SDK tools. The first time you run or debug
your project in Android Studio, the IDE automatically creates the debug
keystore and certificate in $HOME/.android/debug.keystore, and sets the
keystore and key passwords.
Because the debug certificate is created by the build tools and is
insecure by design, most app stores (including the Google Play Store) will not
accept an APK signed with a debug certificate for publishing.
But you must
be wondering WHY IS THIS IMPORTANT?
We’d be creating our own keystore and signing our APK using it. To
do this we’ll use a tool called keystore.
keytool
-genkey -v -keystore harshit_key.keystore -alias harsh_key -keyalg RSA -keysize
2048 -validity 10000
After that you need to fill up your keystore password, name, org,
city details and you’d have prepared yourself a keystore.
Basically, your keystore now saves a self-signed certificate with
10,000 days of validity, which is an RSA 2048 bit key. Now, let’s sign our
patched app using this key.
jarsigner
-verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore harshit_key.keystore
new_uncrackable.apk harsh_key
Now, lets try once again to install our apk in genymotion device
using adb and see if this time it throws an error or not.
adb install
new_uncrackable.apk
Perfect! Now that we’ve installed this, let’s test run our
application.
Voila! We’ve done it successfully. Let’s finish the challenge now
by using Frida hooking technique.
Solving
Challenge
Now, the challenge is to extract the secret string and get it
validated as a flag. Upon further investigating it came to our notice that
method a() is returning the value of the secret string. Ha! This is a poor
practice but helpful for our case.
Now, all we need to do is to draft out a javascript hook for frida
that will change the implementation of this a() and give the secret as an
output in our very own console. Huge shoutout to 0daylabs for giving the code
for this hook. Here is the code:
Java.perform(function () {
var aes =
Java.use("sg.vantagepoint.a.a");
// Hook the
function inside the class.
aes.a.implementation
= function(var0, var1) {
//
Calling the function itself to get its return value
var
decrypt = this.a(var0, var1);
var
flag = "";
//
Converting the returned byte array to ascii and appending to a string
for(var
i = 0; i < decrypt.length; i++) {
flag
+= String.fromCharCode(decrypt[i]);
}
// Leaking our
secret
console.log(flag);
return decrypt;
}
});
Now, we need to run this code using frida and check the output.
frida -U -f
owasp.mstg.uncrackable1 -l expl.js --no-pause
As you can see that the output is successfully dumped now! Let’s
see what the output is in the genymotion device.
And just like that, we’ve solved this challenge. Thanks for
reading.
0 comments:
Post a Comment