Mobile applications often process sensitive data, which is the key target of many cybercriminals. When working with such data, developers must do their best to ensure its protection. One way to improve the security of a mobile app is to perform mobile application penetration testing.
To find flaws in their application code, developers need at least basic skills in reverse engineering and pentesting Android applications. In this article, we discuss different methods an attacker might use to hack your apps. We also explain how challenges from the Open Web Application Security Project (OWASP) Mobile Security Testing Guide (MSTG) can help you in Android application security testing and what tools you can use to solve them.
This article will be helpful to Android developers who want to know more about mobile app penetration testing and improve the security of their apps.
Android is quite a developer-friendly operating system (OS). Unlike other mobile OSs, Android is an open-source platform on which you can activate Developer Options and sideload applications without jumping through too many hoops. Furthermore, Android allows developers to explore its source code at the Android Open Source Project and to modify operating system functionality however they like.
However, working with Android applications also means you’ll need to deal with Java bytecode and Java native code. Some developers may see this as a disadvantage. Android developers use the Java Native Interface to improve application performance, support legacy code, and, of course, confuse those who try to look inside their apps.
When building mobile applications, one of the top priorities for a development team is to ensure a high level of data security. Developers should do their best to prevent cybercriminals from getting access to a user’s sensitive information.
Some try improving the security of their mobile apps with the help of third-party solutions. However, when working with third-party products, it’s critical to configure them properly. A misconfigured or improperly used solution will be of no help, no matter how expensive it is.
Others try to hide the application’s functionality and data in the native layer. In some cases, they build Android applications in such a way that execution jumps between the native layer and the runtime layer.
There are also developers who use more sophisticated methods, such as reverse engineering. This technique is quite helpful when it comes to ensuring proper protection of an application’s sensitive data. That’s why it’s best for a developer to have at least some basic skills in reverse engineering:
- Unpacking APK files
- Patching .smali files
- Patching .so libraries
- Using debugging tools
- Working with frameworks for dynamic code analysis
With these skills and expertise, mobile app developers will have a better chance of detecting code flaws that might be used by attackers. For instance, in order to break into your application, hackers may use the same techniques that quality assurance (QA) specialists use when they test an application’s security and performance:
- Dynamic analysis is used to find possible ways to manipulate application data when the application is running. For example, hackers may try to crack your app by skipping the multi-factor code check during login.
- Static analysis is used for studying an already packaged application and detecting code weaknesses without having direct access to the source code. With static analysis, we don’t look at the application’s behavior at runtime, as we do during dynamic analysis. Hackers may use static analysis to detect the use of a weak encryption algorithm, for instance.
Developers have their methods of protecting against these types of code analysis. For example, source code can be obfuscated to protect it against static analysis: developers can change the names of application methods and classes, add calls to additional functions, and encrypt lines of code.
Note: While code obfuscation helps strengthen application code against static analysis-based attacks, it also increases the risk of introducing new bugs. Reverse engineering techniques can help you see whether code has been obfuscated and ensure thorough application testing.
There are also several methods for securing mobile applications from dynamic code analysis. In particular, developers can:
- prevent the app from launching on rooted devices
- use libraries that prevent the app from launching in developer mode and deny connections to dynamic analysis frameworks such as Frida
- apply additional protections against repacking and resigning the app.
These tasks are easy for experienced reverse engineers. Less experienced developers might need a bit of practice before pentesting Android apps with reverse engineering techniques. Thankfully, OWASP provides numerous challenges for training and improving your reverse engineering skills.
Later in this article, we give step-by-step solutions for two OWASP Mobile Security Testing Guide CrackMe challenges: UnCrackable App for Android Level 1 and UnCrackable App for Android Level 2. We recommend you try to solve them on your own before reading on. But first, let’s take a look at the tools and frameworks you’ll need for reverse engineering an Android app.
Before you start solving the OWASP CrackMe challenges for Android developers, you need to make sure you have two things:
- Knowledge of Android environments. You need to have some experience working with Java and Linux environments as well as with Android kernels.
- The right set of tools. Working with bytecode and native code running on a Java virtual machine (JVM) requires specific tools.
In this section, we list and briefly describe tools that you can use to solve the OWASP CrackMe challenges and upgrade your reverse engineering skills.
Note: For the purposes of this article, we’ve chosen only tools and frameworks that are either free or have free trial versions.
- Android Studio — The official integrated development environment (IDE) for Android. This is the primary IDE for building native Android apps; it includes an APK analyzer, code editor, visual layout editor, and more. In particular, we’ll use the command-line Android Debug Bridge (adb) tool.
- Apktool — This is a popular free tool for reverse engineering closed, third-party, and binary Android applications. It can disassemble Java bytecode to the .smali format as well as extract and disassemble resources from APK archives. Also, you can use Apktool for patching and changing the manifest file.
Note: Application code is stored in the APK file, which contains the .dex file with Dalvik binary bytecode. Dalvik is a data format understandable by the Android platform but completely unreadable for humans. So for a developer to be able to work with .dex files, they need to be converted to (and from) a readable format, such as .smali.
- Cutter — An open-source cross-platform framework that provides a customizable, easy-to-use reverse engineering platform. This framework is powered by radare2 and is supported by a large community of professional reverse engineers.
- Hex Workshop Editor — A popular set of Windows hexadecimal development tools. This toolset makes editing binary data nearly as simple as working with regular text documents. Hex Workshop Editor is commercial but has a free trial version.
Note: Hex Workshop Editor can only be used on Windows. If you’re working with a Linux-based virtual machine, you can choose any Linux hex editor.
- dex2jar — A free tool for converting bytecode from the .dex format into Java class files.
- JD-GUI — One of the tools created by the Java Decompiler project. This graphical utility makes Java source code readable, displaying it as Java class files.
- Mobexler — An Elementary-based virtual machine for iOS and Android pentesting. Mobexler comes with a set of preinstalled tools and scripts for testing the security of a mobile app, including some of the tools from this list.
- Java Debugger (jdb) — A free command-line tool for debugging Java classes.
Note: In Android applications, debugging can be performed on two layers:
- Runtime layer — Java runtime debugging can be performed with the help of the Java Debug Wire Protocol (JDWP).
- Native layer — Linux/Unix-style debugging can be performed based on ptrace.
JDWP is a standard debugging protocol that helps the debugger communicate with the target JVM. This protocol is supported by all popular command-line tools and Java IDEs, including Eclipse, JEB, IntelliJ, and, of course, jbd.
The JDWP debugger allows you to explore Java code, set breakpoints in Java methods, and check and modify both local and instance variables. It’s often used for debugging regular Android apps that doesn’t make many calls to native libraries.
- GNU Debugger (gdb) — A useful tool for analyzing an application’s code.
We used these tools to solve two reverse engineering challenges for Android apps. In the next section, we’ll give you a basic Android pentesting tutorial based on the standard OWASP challenges.
We'll show you how to solve two OWASP MSTG CrackMe challenges: UnCrackable App for Android Level 1 and UnCrackable App for Android Level 2. These apps were specifically designed as reverse engineering challenges with secrets hidden in the code. Our task is to find these secrets. While solving these challenges, we’ll use static analysis for analyzing the decompiled code and dynamic analysis for modifying some of the application parameters.
First, we need to look inside our training application. A regular Android application is, in fact, a properly packaged APK file containing all the data the application needs to operate normally.
To look at the application from the inside and solve this challenge, we’ll need:
- adb for communicating with our mobile device and the training application
- Apktool for disassembling the APK files of our training app into separate .smali classes
- jdb for debugging our training app
- dex2jar for converting APK files to the JAR format
- JD-GUI for working with the JAR files
Now let’s move on to solving the first challenge.
We’ll begin by installing UnCrackable-Level1.apk on our device or emulator with the following command:
We’ll solve this challenge and debug the Release Android app with the help of the jdb tool. Follow along to find the hidden secret.
1. Unpack the application and decode the manifest file using Apktool:
2. Using a text editor, put the app into debugging mode by changing the manifest file and setting
3. Use Apktool to repack the APK file:
4. Resign the newly created APK file. You can do this using a bash script for resigning Android apps.
5. Install the new APK file on your device or emulator with the following command:
At this point, we face the first big challenge. The UnCrackable App is designed to resist debugging mode. So when we enable it, the app simply shuts down.
You’ll see a modal dialog with a warning. The dialog can be closed by tapping OK, but this will be your last action before the app is terminated.
Fortunately enough, there’s a way to fix this.
6. Launch the application on your device or emulator in Wait For Debugger mode. This mode allows you to connect the debugger to the target application before the application runs its detection mechanism. As a result, the app won’t deactivate the debugging mode.
Run the application in Wait For Debugger mode with this command:
You’ll see the following dialog window:
7. Now display the process IDs (PIDs) of all processes that run on the connected device:
8. And list only debuggable processes:
9. Open a listening socket on your host machine and forward its incoming TCP connections to the JDWP transport of a chosen debuggable process:
10. Note that attaching the debugger (in our case, jdb) will cause the application to resume from the suspended state, and we don’t want that. We need to keep the app suspended for a while to explore it in detail. To prevent the process from resuming, pipe the
suspend command into jdb:
11. Now we need to bypass that moment when the application crashes in the runtime after you tap OK. Decompile the APK file to review the application code using dex2jar and JD-GUI:
1) Use the dex2jar tool to convert the original APK file to a JAR file:
2) Using the JD-GUI tool, open the newly created JAR file:
After reviewing the code, you’ll see that the MainActivity.a method displays the message:
"This is unacceptable..."
The MainActivity.a method creates an alert dialog and sets a listener class for the onClick event. The listener class has a callback method that shuts down the application when the user taps the OK button. To prevent a user from canceling the dialog, the system calls the setCancelable method.
At this point, our best-case scenario is to suspend the application in a state where the secret string we’re looking for is stored in a plaintext variable. Unfortunately, that’s impossible unless you can figure out how the application detects root and tampering.
12. Try tampering with the runtime a little bit to bypass application termination. Set a method breakpoint on
android.app.Dialog.setCancelable when the application is still suspended, then resume the app:
13. The application is suspended at the first setCancelable method instruction. You can use the
locals command to print the arguments passed to the setCancelable method:
As you can see in the code above, the system called the setCancelable(true) method, so that’s not the call we need. Let’s resume the process with the
We’ve reached a call to the setCancelable method with the
false argument. At this point, we need to use the
set command to change the variable to
true and resume the app:
true each time you reach a breakpoint until the alert window is finally displayed. It may take about five or six attempts before you see this window. At this point, you should be able to cancel the app without causing the application to terminate — just tap the screen next to the dialog window and it will close.
14. Finally, it’s time to extract the secret string. Look at the application code once more. You’ll notice that the string we’re looking for is decrypted with the Advanced Encryption Standard and is compared with the string the user enters into the message box. The application uses the equals method of the java.lang.String class to determine if the string input matches the secret string.
Now set a method breakpoint on java.lang.String.equals, enter random text in the edit field, and tap VERIFY. You can read the method argument with the
locals command once you reach the breakpoint:
Bingo! Our secret string is “I want to believe.” You can easily check if that’s right by entering this phrase into the message box and clicking the VERIFY button.
Well done! Now we can move to solving the second challenge.
As in the previous task, there’s a secret string hidden somewhere in the code of this training application, and our goal is to find it. However, in this case, our secret string may have some traces of the native code.
To solve this challenge, we’ll use the following tools:
- adb for communicating with our mobile device
- Apktool for disassembling APK files
- Cutter for working with libraries
- gbd for analyzing application code
- Hex Workshop Editor for editing binary data
- dex2jar for converting APK files to the JAR format
- JD-GUI for working with JAR files
Let’s move right to solving this challenge:
1. Using dex2jar and JD-GUI, analyze the UnCrackable-Level2.apk file.
1) First, convert the APK file to a JAR file with dex2jar:
2) Then open the converted file with JD-GUI. Start with the MainActivity class:
The system loads a native foo library. Then we call the native init function in the onInit method of the MainActivity class.
Next, the CodeCheck class imports a function from the bar library:
The a method of CodeCheck (which is most likely obfuscated) uses this function for password verification.
2. Use Apktool to extract the foo library and then decompile it to machine code with Cutter:
In the temp/lib folder, look for the files with the .so extension. There should be files for different types of processor architectures: arm64-v8a, armeabi-v7a, x86, and x86_64. Choose the library that matches the processor architecture of the device or emulator that you’re using. You can check the architecture of your device with the CPU-Z application.
Analyze the chosen library with Cutter. Start by checking the functions tab to understand the library’s functionality.
- The pthread_create and pthread_exit functions suggest that the functions library uses threads.
- If you see the strncmp string comparator, that’s probably used for validating the passed password. If we’re lucky, the string we’re looking for is hardcoded and the inputted password is compared to it. However, judging by the use of the thread, we may assume that the library calculates the secret string at runtime.
Let’s analyze the strncmp function. Use Cutter to find the address of the strncmp function (0xffb).
- ptrace is a Linux system call used for debugging. In our case, however, it’s used as an anti-debugging technique.
Let’s analyze the functionality of ptrace. This call is used twice: first at 0x777 and then at 0x7a7. In the first case, it’s the current process’s PID, and in the second case, it’s the PTRACE_ATTACH flag, 0x10.
The ptrace function call attaches an Android process to itself. However, an Android process can only have one process attached to it. This means that for now, we can’t attach gdb to an Android process.
3. The problem of attaching gdb can be handled by NOP-ing out two ptrace syscalls. You can change the byte using Hex Workshop Editor. Insert the address of the ptrace function into the go-to field of the tool and change the value:
4. Open the original APK file as a zip archive and replace the original libfoo.so library with the changed one.
5. Resign the modified APK file using the same script as in the previous challenge.
6. Install the application on a rooted device:
Now when you launch the app, you’ll see an alert saying that it can’t be used because the device has been rooted.
7. Remove root and debugging detection from the application code. To do so, find the MainActivity.smali file in the decompiled APK file and remove all contents of the a method.
The a method is called when a problem is detected. It shows a user an alert dialog and then exits the application. To prevent our app from exhibiting such behavior, we need to patch the a method.
This is what the MainActivity.smali file looks like after removing the a method content:
8. Now repack the APK file with the modified MainActivity.smali file:
9. Next, open the UnCrackable-Level2_resigned.apk file as a ZIP archive and replace the classes.dex file in it with the patched one from our repackaged APK file.
10. Resign the initial UnCrackable-Level2.apk file. At this step, our APK file has two replaced patched files: MainActivity.smali from step 8 and libfoo.so from step 4.
11. Install the resigned APK file on your device:
This time, our app launches without any warning messages.
Next, we need to attach gdb to an Android process. Start with setting up the gdb server on your device or emulator.
12. Open a root shell on your device:
13. Using the command-line interface on your computer, copy the gdb server into the /data/local/tmp/gdb-server folder:
14. Now use the command line on your mobile device to launch the gdb server:
15. Make sure the server is running:
16. Determine the PID of the application:
As you can see, the PID of our application is 4925.
17. Attach the gdb server to the chosen Android process:
18. Using the command line on the PC, forward port 8888:
19. On your PC, launch the gdb client.
20. Attach the gdb client to the remote process:
21. Using the command line on your device, display the list of all imported libraries:
22. Now let’s get back to the strncmp function (0xffb) address that we found in step 2. The breakpoint should be set to the address 0x000000b2d8dffb. On your PC, run this command from the launched gdb client:
23. Similarly, run this command from your gdb client on your PC to dump the contents of the registers:
As you can see in step 22, the application passes the addresses of the inputted password and the calculated secret string to the strncmp function as two parameters, passed in the x0 and x1 registers.
Finally, we need to dump these two strings:
Note: The positive outcome of solving this challenge may be unstable due to the differences in processor architectures of the Android devices and the versions of the tools used.
We did it! Our second secret string is “thanks for all the fish.”
Android application penetration testing is a complex yet important stage of mobile application development. Developers need to make sure that sensitive data their apps work with will remain secure no matter what.
To find hidden flaws and vulnerabilities, developers need to be able to look at their applications from the inside. This requires basic reverse engineering skills, which you can acquire by solving the OWASP MSTG CrackMe challenges.
At Apriorit, we have experienced teams of Android developers, testers, reverse engineers, and QA specialists who know how to make your mobile apps robust and secure. Get in touch with us to pentest your Android app and make it resistant to attacks.