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 OWASP Top 10 for mobile applications can help you in mobile penetration testing of Android apps and what tools you can use to solve them.
In this article, we discuss how to pentest Android applications and what tools to use for improving the security of your mobile apps. This article will be helpful to Android developers who want to know more about mobile security penetration testing services and improve the security of their apps.
Contents
Strengthen your appโs security with reverse engineering
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. To learn how to fix such issues, explore our article about methods of encryption in Android applications.
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. Best reverse engineering tools 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 for dynamic analysis with Frida and other frameworks
- 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 software 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. Solving these challenges will help you better understand how to improve penetration testing for mobile apps and enhance security of your Android solutions. 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.
Need to protect the sensitive data in your app?
Reach out to our penetration testing team and discover vulnerabilities in your mobile app before hackers find them!
A basic toolset for Android reverse engineering
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.
Read also
Web Application Penetration Testing: Minimum Checklist Based on the OWASP Testing Guide
Identify vulnerabilities before hackers do to safeguard your software from cyber threats with our comprehensive guide to penetration testing using the OWASP checklist.
Solving UnCrackable Apps 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.
Solving UnCrackable App for Android Level 1
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:
adb install UnCrackable-Level1.apk
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:
apktool d -s UnCrackable-Level1.apk -o temp
2. Using a text editor, put the app into debugging mode by changing the manifest file and setting android:debuggable
to "true"
:
<application android:allowBackup="true" android:debuggable="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme">
3. Use Apktool to repack the APK file:
apktool b temp -o UnCrackable-Level1-Repackage.apk
4. Resign the newly created APK file. You can do this using a bash script for resigning Android apps.
Read also
How to Reverse Engineer an iOS App and macOS Software
Ensure compatibility and maintain your legacy code! Explore the essential techniques for macOS and iOS software reverse engineering with our comprehensive guide.
5. Install the new APK file on your device or emulator with the following command:
adb install UnCrackable-Level1-Repackage-Resigned.apk
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:
adb shell am start -D -n "owasp.mstg.uncrackable1/sg.vantagepoint.uncrackable1.MainActivity"
Youโll see the following dialog window:
7. Now display the process IDs (PIDs) of all processes that run on the connected device:
adb shell ps
8. And list only debuggable processes:
adb jdwp
9. Open a listening socket on your host machine and forward its incoming TCP connections to the JDWP transport of a chosen debuggable process:
adb forward tcp:4321 jdwp:PID
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:
< "%JAVA_HOME%\bin\jdb" -connect com.sun.jdi.SocketAttach:hostname=localhost,port=4321
Set uncaught java.lang.Throwable
Set deferred uncaught java.lang.Throwable
Initializing jdb ...
< suspend
All threads suspended.
<
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:
d2j-dex2jar.bat -f UnCrackable-Level1.apk
2) Using the JD-GUI tool, open the newly created JAR file:
public class MainActivity extends Activity {
private void a(String str) {
AlertDialog create = new Builder(this).create();
create.setTitle(str);
create.setMessage(โThis is unacceptable. The app is now going to exit.โ);
create.setButton(-3, โOKโ, new OnClickListener() {
public void onClick(DialogInterface dialogInterface, int i) {
System.exit(0);
}
}};
create,setCancelable(false);
create.show();
}
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.
Read also
Top 7 Methods of Data Encryption in Android Applications
Boost the security of your Android applications! Learn about seven encryption methods and their pros and cons, empowering you to implement robust data protection measures.
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:
All threads suspended.
< stop in android.app.Dialog.setCancelable
Set breakpoint android.app.Dialog.setCancelable
< resume
All threads resumed.
<
Breakpoint hit: โthread-mainโ, android.app.Dialog.setCancelable(), line-1, 205 bci-0
main[1] _
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:
main[1] locals
Method arguments:
flag = true
Local variables:
main[1] _
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 resume
command:
main[1] resume
All threads resumed.
<
Breakpoint hit: โthread-mainโ, android.app.Dialog.setCancelable(), line-1, 205 bci-0
main[1] locals
Method arguments:
flag = false
Local variables:
main[1] _
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:
main[1] set flag = true
flag = true = true
main[1] resume
All threads resumed.
<
Breakpoint hit: โthread-mainโ, android.app.Dialog.setCancelable(), line-1, 205 bci-0
main[1] _
Continue setting flag
to 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:
<> stop in java.lang.String.equals
Set breakpoint java.lang.String.equals
<
Breakpoint hit: โthread-mainโ, java.lang.String.equals(), line=944 bc1=2
main[1] locals
Method arguments:
anObject = โbโ
Local variables:
main[1] cont
<
Breakpoint hit: โthread-mainโ, java.lang.String.equals(), line=944 bci=2
main[1] locals
Method arguments:
anObject = โiโ
Local variables:
main[1] _
โฆโฆโฆ
main[1] locals
Method arguments:
anObject = โI want to believeโ
Local variables:
main[1] _
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.
Related project
Smart Contract Security Audit: Penetration Testing and Static Analysis
Discover how Apriorit blockchain experts identified and addressed vulnerabilities in our clientโs smart contracts, enhancing their blockchain solutionโs integrity and reliability.
Solving UnCrackable App for Android Level 2
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:
dex2jar-2.xu003ed2j-dex2jar.bat -f UnCrackable-Level2.apk
2) Then open the converted file with JD-GUI. Start with the MainActivity class:
public class MainActivity extends c {
private CodeCheck m;
static {
System.loadLibrary(โfooโ);
}
/* access modifies changed from: private */
private void a(String str) {
AlertDialog create = new Builder(this).create();
create.setTitle(str);
create.setMessage(โThis is unacceptable. The app is now going to exit.โ);
create.setButton(-3, โOKโ, new OnClickListener() {
public void onClick(DialogInterface dialogInterface, int i) {
System.exit(0);
}
}};
create,setCancelable(false);
create.show();
}
private native void init();
/* access modifies changed from: protected */
public void onCreate(Bundle bundle) {
init();
if (b.a() || b.b() ||b.c()) {
a(โRoot detected!โ);
}
if (a.a(getApplicationContext())) {
a(โApp is debuggable!โ);
}
new AsyncTask<void, String, String>() {
/* access modifiers changed from protected
renamed from: a */
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:
public class CodeCheck {
private native boolean bar(byte[] bArr);
public boolean a(String str) {
return bar(str.getBytes());
}
}
The a method of CodeCheck (which is most likely obfuscated) uses this function for password verification.
public void verify(View view) {
String str;
String obj = ((EditText)
findViewById(R.id.edit_text)).getText().toString();
AlertDialog create = new Builder(this).create();
if (this.m.a(obj)) {
create.setTitle(โSuccess!โ);
ctr = โThis is the correct secret.โ;
} else {
create.setTitle(โNope...โ);
str = โThatโs not it. Try again.โ;
}
create.setMessage(str);
create.setButton(-3, โOKโ, new OnClickListener() {
public void onClick(DialogInterface dialogInterface, int i) {
dialogInterface.dismiss();
}
}};
create.show();
}
2. Use Apktool to extract the foo library and then decompile it to machine code with Cutter:
apktool d UnCrackable-Level2.apk -o temp
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.
For example:
- 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.
Read also
Anti Debugging Protection Techniques with Examples
Protect your software from malicious reverse engineering with our expert guide. Learn about popular anti-reversing methods from our reverse engineering experts and choose the right strategy for you!
6. Install the application on a rooted device:
adb install UnCrackable-Level2_resigned.apk
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 debugger 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:
.method private a(Ljava/lang/String;)V
.locals 3
return-void
.end method
8. Now repack the APK file with the modified MainActivity.smali file:
apktool b temp -o UnCrackable-Level2-Repackage.apk
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:
adb install UnCrackable-Level2_resigned.apk
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:
adb shell
su
13. Using the command-line interface on your computer, copy the gdb server into the /data/local/tmp/gdb-server folder:
adb push ./gdb-server-android-arm /data/local/tmp/gdb-server
14. Now use the command line on your mobile device to launch the gdb server:
# cd /data/local/tmp/gdb-server
# chmod 755 gdb-server
# ./gdb-server
15. Make sure the server is running:
# ./gdb-server --version
16. Determine the PID of the application:
# ps | grep 'owasp.mstg.uncrackable2'
u0_a59 4925 1159 1250756 37536 ffffffff b7561ed5 S owasp.mstg.uncrackable2
As you can see, the PID of our application is 4925.
17. Attach the gdb server to the chosen Android process:
# ./gdb-server :8888 --attach 4925
Attached; pid = 4925
Listening on port 8888
18. Using the command line on the PC, forward port 8888:
adb forward tcp:8888 tcp:8888
19. On your PC, launch the gdb client.
20. Attach the gdb client to the remote process:
(gdb) target remote :8888
Remote debugging using :8888
21. Using the command line on your device, display the list of all imported libraries:
cat /proc/4925/maps
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:
(gdb) b *0x000000b2d8dffb
Breakpoint 1 at 0xb2d8dffb
(gdb) c
Continuing.
Thread 1 โnt.uncrackable2โ hit Breakpoint 1, 0x000000b2d8dffb in Java_sg_vantagepoint_uncrackable2_CodeCheck_bar () from target: /data/app/sg.vantagepoint.uncrackable2-1/lib/x86/libfoo.so
(gdb) info registers
x0 0xb2d834470 547311273344
x1 0xb2dd49170 549585261184
x2 0x17 23
23. Similarly, run this command from your gdb client on your PC to dump the contents of the registers:
(gdb) info 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:
(gdb) x/s 0xb2d834470
0xb2d834470 : โ12345678901234567890123โ
(gdb) x/s 0xb2dd49170
0xb2dd49170: โthanks for all the fishโ
(gdb) _
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.โ
Conclusion
Android app penetration testing is a complex yet important stage of mobile application development. Developers need to build a sophisticated mobile application pentest methodology and 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, QA specialists, and reverse engineers 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.
Need a team of ethical reverse engineers?
Let’s discuss how our expertise can make your software more secure, resilient, and reliable for your users!