SET UP AN ANDROID REVERSE ENGINEERING ENVIRONMENT — PART2

HBCyberSec | June 13, 2024, 2:46 p.m.

Reverse Engineering APKs

In our previous article we set up an Android development environment, suitable to create new applications and load them into an Android system (either physical or emulated). This time we will explore tools and techniques that will help us inspect and edit the application once it’s been compressed as an apk.


An accurate definition of apk can be found in Wikipedia: “The Android Package with the file extension apk is the file format used by the Android operating system”. Here we can also find a list of contents expected to be found in an apk, being some of the most relevant:


a) classes.dex: The classes compiled in the dex file format executed by Android Runtime (or by Dalvik virtual machine used in Android 4.4 KitKat). There might be more than one classes.dex within the apk.

b) AndroidManifest.xml: An additional Android manifest file, describing the name, version, access rights, referenced library files for the application.

c) resources.arsc: a file containing pre-compiled resources, such as binary XML for example.


The full contents list and definition can be found here: https://en.wikipedia.org/wiki/Apk_(file_format)


The compiled bytecode for the application can be found in the classes.dex files. We can use disassemblers like IDA Pro, Ghidra, Radare2, etc. to directly inspect the assembly code (which we will cover in another article), or we could turn the bytecode into pseudo-code (smali) and make it more readable and easy to work with.


A must-have command-line tool for doing this is apktool. It allow us to decompile the application, perform static analysis, modify its contents and compile the project again.


To decompile an Android application we need to type the following:


$ apktool d targetApp.apk






If we want to specify an output folder for the project, we must type as follows:


$ apktool d targetApp.apk -o targetNewDir


This will uncompress the project and decompile the dalvik executable (classes.dex) files, as well as generating the whole project structure (apktool uses smali/baksmali (https://github.com/JesusFreke/smali) as an assembler/disassembler for the dex files)

Once we have edited the code in the project, we might need to rebuild it into an apk file again. This can be achieved by typing:


$ apktool b targetApp


Being targetApp a folder containing the edited project. The newly built apk will be found under the dist/ directory.


After recompiling an application, it needs to be signed again (see https://developer.android.com/studio/publish/app-signing#signing-manually). For this, we will use keytool (part of the Java JDK) and apksigner (native tool found in the Android Sdk).


Full apktool documentation can be found here: https://apktool.org, and its source in Github: https://github.com/iBotPeaches/Apktool


As an example of its usage, we will clone a project called Weather (one of my favourite weather apps) from here: https://codeberg.org/BeoCode/Weather and make some changes to its code.


The first step will be clone the project locally, and build it afterwards:


$ git clone https://codeberg.org/BeoCode/Weather
$ cd Weather/
$ ./gradlew build


In case gradle raises this error:

> SDK location not found.


We need to create a local.properties file in the building path, and include our SDK path, like below:


sdk.dir = /home/USER/Android/Sdk


Once we have the project built, we’ll have two apks generated: one with debugging symbols (under app/build/outputs/apk/debug) and another one with the release version (under app/build/outputs/apk/release)


cd app/build/outputs/apk/release/
app-release-unsigned.apk output-metadata.json


At this stage, we can try to install the generated apk via adb. However, because it has not been signed, we will get an error:


$ adb install app-release-unsigned.apk 
Performing Streamed Install
adb: failed to install app-release-unsigned.apk: Failure [INSTALL_PARSE_FAILED_NO_CERTIFICATES: Failed collecting certificates for /data/app/vmdl954770647.tmp/base.apk: Failed to collect certificates from /data/app/vmdl954770647.tmp/base.apk: Attempt to get length of null array


Just for the sake of having a fully-functional apk that can be installed on a device (so we can disassemble it afterwards), we’re going to reproduce the signing steps here. Please note that we will also be generating the keystore to be used in the signing process, but this is only needed if we don’t have one already.


1) Generate the keystore with the signing key (fill all fields when prompted):

$ keytool -genkey -v -keystore /home/user/example.keystore -storepass fJ0vaH9pFqeZWw -alias androidTesting -keyalg RSA -keysize 2048 -validity 10000
What is your first and last name?
[Unknown]: Testing
What is the name of your organizational unit?
[Unknown]:
What is the name of your organization?
[Unknown]: NoName
What is the name of your City or Locality?
[Unknown]:
What is the name of your State or Province?
[Unknown]:
What is the two-letter country code for this unit?
[Unknown]:
Is CN=Testing, OU=Unknown, O=NoName, L=Unknown, ST=Unknown, C=Unknown correct?
[no]: yes

Generating 2,048 bit RSA key pair and self-signed certificate (SHA256withRSA) with a validity of 10,000 days
for: CN=Testing, OU=Unknown, O=NoName, L=Unknown, ST=Unknown, C=Unknown
[Storing /home/user/example.keystore]


Being fJ0vaH9pFqeZWw a random password we’ve chosen for signing the keystore. Make sure you also change the keystore location path (/home/user/example.keystore in the example) and alias (androidTesting above) to something that suits your needs, along with the information related to first and last name, organization, etc.


2) Sign the apk using apksigner:


$ mkdir signedApk && cp app-release-unsigned.apk signedApk/
cd signedApk/
$ echo "fJ0vaH9pFqeZWw" | apksigner sign --ks /home/user/example.keystore app-release-unsigned.apk && apksigner verify app-release-unsigned.apk


Please note above that we have also verified that the apk has been correctly signed (optional step) with this command:


$ apksigner verify app-release-unsigned.apk


After successfully installing the application in our emulator, we will run the app it and see this screen:


Now we are going to introduce a small change in the code, affecting part of the text above. Specifically in these files:

Weather/app/build/outputs/apk/release/signedApk/apktoolBit/app-release-unsigned/res/values/strings.xml
Weather/app/build/outputs/apk/release/signedApk/apktoolBit/app-release-unsigned/res/values-en-rUS/strings.xml


This is because we’re running our tests in an US-english based emulator.

Modify, in both files:


<string name="reportError">"Any errors or mistakes found should be reported to "<a href="https://codeberg.org/BeoCode/Weather/issues">Codeberg</a> or via <a href="weather@beocode.eu">email</a></string>


into:


<string name="reportError">"Found a mistake? Strange behaviour? 
Report it on "<a href="https://codeberg.org/BeoCode/Weather/issues">Codeberg</a> or via <a href="weather@beocode.eu">email</a></string>


Now use apktool to recompile:


$ apktool b targetAppFolder
I: Using Apktool 2.8.0
I: Checking whether sources has changed...
I: Smaling smali folder into classes.dex...
I: Checking whether resources has changed...
I: Building resources...
I: Copying libs... (/kotlin)
I: Copying libs... (/META-INF/services)
I: Building apk file...
I: Copying unknown files/dir...
I: Built apk into: targetAppFolder/dist/target.apk


We have to sign the compiled apk afterwards:


$ echo "fJ0vaH9pFqeZWw" | apksigner sign --ks /home/reveng/reveng.keystore app-release-unsigned.apk && apksigner verify app-release-unsigned.apk
Keystore password for signer #1:
WARNING: META-INF/services/o3.k not protected by signature. Unauthorized modifications to this JAR entry will not be detected. Delete or move the entry outside of META-INF/.
WARNING: META-INF/services/l3.q not protected by signature. Unauthorized modifications to this JAR entry will not be detected. Delete or move the entry outside of META-INF/.


We can get some warning messages, but all should be fine (please note that, in this example the built and signed apk will be app-release-unsigned/dist/app-release-unsigned.apk)


Now we need to uninstall the original version of the app from the emulator:


$ adb shell pm list packages -3
package:de.beowulf.wetter
$ adb uninstall de.beowulf.wetter
Success


And install the new apk:


adb install app-release-unsigned.apk
Performing Streamed Install
Success


This is the resulting text change in the app (the sentence Found a mistake? Strange behaviour?):



Before we conclude, it is important to clarify that this was an exercise meant to show the process of decompiling, editing and recompiling/signing and app. The above modification was very simple, and didn’t affect any of the actual code, just the strings.xml file which doesn’t live in the classes.dex (in fact, due to the target Weather App project being open source, we didn’t need to use apktool to decompile the apk, just editing the source before compiling and signing would have been enough).


But what if we actually want to modify a part of the code?


apktool will decompile the dalvik executable into smali code (this process is called baksmaling), which is some sort of pseudo-assembly code. Understanding the relationship between the original java code and the generated smali code can be quite difficult, hence why we might need a way to translate them.


In the next article, we will modify the smali code from an application to bypass an ssl pinned certificate check, using apktool and jadx.


SOURCES

Wikipedia: https://en.wikipedia.org/wiki/Apk_(file_format)

Apktool: https://apktool.org/

Weather App: https://codeberg.org/BeoCode/Weather