Android applications security — part 1, reverse engineering and token storage problemsAdrian DefusBlockedUnblockFollowFollowingJan 3There are many ways you can store keys and tokens in your Android applications — directly in the code, inside your database or by using NDK layer.
But in terms of security — should you do it at all?This article will show you the most common ways you can use to store different types of keys and tokens in your Android applications, while proving that none of these methods is good enough ????.
The second part will contain more detailed considerations regarding how our application should communicate securely with the server, how to ensure the authentication of both entities, and how the attacker can affect our connection.
Looking inside our applications (reverse engineering)The applications which we use on our devices are actually properly packed .
apk files containing all the data required to operate correctly.
If we want to view their content and see what is hidden there, we have to make some necessary preparations.
First of all, we need a tool that will let us communicate with the mobile device using our computer — Android Debug Bridge is what we are looking for.
Secondly if we want to disassemble our .
apk files into separated source code files (.
smali classes), Apktool seems to be the perfect choice.
Now you’re probably wondering what this “.
smali” format is, so let’s get into some theory.
When you create your application code, the .
apk file contains a .
dex file with a binary Dalvik bytecode inside, which is the format that the platform actually understands, but it’s horrible to read or edit.
Fortunately, there are tools like the aforementioned Apktool that convert .
dex files into (and from) a human readable representation — and the most common format is known as Smali.
So what are the steps we need to perform to look inside any application in our mobile device?Step 1: Print the names of the device packages (installed applications) with an associated base file using the Android Debug Bridge and command line:adb shell list packages -fHere is the sample output:Device packages list outputStep 2: Choose the app and download it using the adb tool:adb pull <package-name>/base.
apk file is now in our computer in the adb directory, so let’s decompile itStep 3: Decompile the downloaded .
apk file using apktool:apktool d -r base.
apk‘d’ — decode, ‘-r’ — do not decode resourcesStep 4: Open the .
smali files inside the created ‘base’ directory and try to find some secrets ????Token storage problemIn most cases when we create a web service designed to handle REST queries from our application, we need to be sure that they come from one specific source.
The most common way to achieve this is to send a specific application token as a parameter directly in the url address, as an http Header or in a POST body.
When the server receives a query containing such a token, it compares it with the one saved internally, and if both are the same, it sends the required information back to the client.
As long as the token stored on the server’s side is relatively secure, there is a problem with the one on the client’s side.
The following example shows the source code of a simple application with some web service included — the token used for authentication is stored as a constant value directly in the code.
Using the reverse engineering techniques described earlier we can now decompile an .
apk file of the application containing the above code and read the contents of the .
Let’s have a closer look at the connectToWebservice() function.
As you can see in line 9 our SECRET_TOKEN value is assigned to the variable v1, which is later used in the connect() method in line 11.
In this example, the obtaining our secret key simply stored as a constant value in the source code was easy and did not require the use of any advanced techniques and tools.
The second example is the technique involving the project build tool — Gradle.
First of all, we create a keystore.
properties file in our root project directory, which contains the definition of our SECRET_TOKEN.
Then we include this file in our app build.
And finally, using the BuildConfig class, we can obtain the SECRET_TOKEN value in our connectToWebservice() function.
Using the same techniques as before, let’s decompile the .
apk file and look into the MainActivity.
As you can see, regardless of the steps we’ve taken to potentially provide a better way to secure our token, after decompiling the application its value is just as visible as before.
It looks like it’s time to bring out the big guns — including the NDK layer.
The Android NDK is a toolset that lets us implement parts of our app in a native code, using languages such as C and C++.
The main advantage of including native support is that the code written in C/C++ is compiled into machine instructions that are run directly by the device CPU.
Many multimedia or video-processing applications use native code for processor-intensive tasks.
Considering that the C/C ++ code is in a different layer, the decompiled class containing the NDK reference should not contain the code from the lower level, so if we place our SECRET_TOKEN there, we will not be able to see it.
Let’s check it in our application.
So when our Activity class is created, the “native-lib” is loaded.
Thanks to this we can use the external getSecretKey() function, returning String with our SECRET_TOKEN directly from the NDK layer.
Let’s take a look into the C++ getSecretKey() implementation.
It’s just a simple function returning the hard-coded token value.
Reverse engineering of our application proves that the SECRET_TOKEN is not visible any more.
Although our token is no longer visible in the .
smali file, the only difficulty we’ve added for the potential attacker is simply that he has to take a few more steps and look deeper into our source code.
Methods written in native layers are stored in the “lib” folder with the .
We are able to decompile them to machine code using, for example, the IDA disassembler.
Due to different processor architectures used by various Android devices there are 4 types of .
so files inside our .
apk — arm64-v8a, armeabi-v7a, x86 and x84_64.
Let’s select x86 version of our native library.
After successfully decompiling the file into machine code we have to search for the Java_<package-name>_secretKey() function and analyse its code.
Our secret token is stored at 00031019, 0003101F, 00031026 and 0003102D as hexadecimal arrays represented in little-endian format, so we have to concatenate them, convert the result to string and reverse its order, using e.
a string-functions website.
Summarizing, the whole process of the retrieval of our SECRET_TOKEN looks like this:3D32676E2D3135553D613126626A → =2gn-15U=a1&bj → jb&1a=U51-ng2=If we continued the process of hiding our token more and more deeply, we would probably eventually reach the OWASP Crackmes website containing applications written as key-retrieval challenges — regardless of their difficulties, the secret token hidden somewhere in the application was in each case more or less easy to recover.
Reverse engineering — code injectionRegardless of how deeply the secret key was hidden in our application, instead of wasting time on searching for it in the place where it was saved, we can try to capture it where it is actually used — e.
placed as a parameter of the function sending it in some REST query.
The easiest way to achieve this is to debug our application by setting a breakpoint in the place where the token has to be assigned to e.
However, the problem is that applications published on Google Play Store are by default non-debuggable.
The solution is to use the simplest form of code injection — setting the android:debuggable=”true” parameter in the application downloaded to our computer.
It has to be set in the AndroidManifest.
xml file located in the directory containing the decompiled source of our app.
After that, we can build a new .
apk file containing changed data using Apktool.
apktool b base -o app-modified.
apk“b” — build “-o” — outputUnfortunately, if you try to install a modified .
apk, you will get an error: [INSTALL_PARSE_FAILED_NO_CERTIFICATES].
It’s because Android requires that all APKs be digitally signed with a certificate before they can be installed.
Let’s solve it by generating a key and keystore and signing it using jarsigner.
jarsigner -verbose -keystore <custom.
keystore> -signedjar <signed-output>.
apk <keystore-alias>Opening our modified re-signed APK using Android Studio’s “Profile or Debug APK” option (with a Smalidea plugin installed) and setting the breakpoint in the desired place results in revealing our secret token directly in the debug window.
Retrieving the secret token using .
smali debuggerBy using more complex methods of code injection, we are able, for example, to change the way our application works to obtain certain data — e.
creating a class that has a static method of accepting a string as a parameter and copying it to the device clipboard.
By injecting a call to this method in a place where the token is used we will be able to easily obtain it.
This is especially useful for applications that have security checks verifying whether our device is rooted, if the debug mode enabled or if the ADB is active — we can install the modified application on a ‘secure’ device and simply read the value directly from the clipboard.
At the same time, it should be kept in mind that any interference with the source code requires us to re-sign the application with our custom certificate.
Some apps may have mechanisms to check whether they have been signed with the appropriate key and in case of their incompatibility, they may not allow a further process to run.
Fortunately, code injection also allows us to disable functions where these conditions are verified (see: OWASP Crackmes level 3 — disabling security check).
SummaryRegardless of how much we will try to protect our applications against the unauthorized use of our secret key, a potential attacker with the appropriate knowledge and tools will not have much difficulty in reading its value.
Therefore, the only reasonable way to protect our key is to never let it touch our application.
This is especially true for keys that can be used from any source, e.
Google Places API keys.
In this case, our application should query our internal server having the appropriate token, which then performs a query to some next source using our API client key.
But if storing tokens inside our apps is not recommended, how can our server verify that the query it has just received comes from a trusted source?.I will try to answer this question in the second part of this article.
.. More details