Seeing Android App Development through the Lens of Penetration Testing: Insights Crucial for Developers.
Disclaimer: This article aims to raise awareness within the community about the potential risks of app cloning, urging developers to exercise extra caution in app development endeavors.
Having recently stood on the shores of the Baltic Sea in Sopot, the ever-watchful eyes of seagulls left a lasting impression. Just as these coastal aviators scan the horizon for opportunities, developers must exercise a similar vigilance in the expansive realm of app development. Picture app cloning as a perilous territory where innovation and imitation intertwine. This article aims to provide the community with a glimpse into the post-deployment showdown, highlighting the critical pain points in the development process that demand extra attention and care.
As a teenager, I was always fascinated by the games on Android phones. The first time I saw an Android phone was with one of my friends playing a popular game where the main character ran through a railway track. After watching for a while, I noticed that the character was getting unlimited retries. After a point, I figured out from my friend that this game was modded. Wait! What?! Modded? I didn’t completely understand his explanation of how modding works, but still, I knew this was kind of a new world, at least for a few people like me.
Years passed, and now I’m working in Android app development. I came across a project that protects against fraud, modding, and cloning of apps. Each step of the project gave me clarity on how the world of reverse engineering/modding/cloning looks like. So, let’s start by introducing the concept of penetration testing and App cloning to my fellow readers.
What is mobile pen-testing?
Mobile pen testing is like a security checkup for your phone apps. It’s a way to find and fix any weak points that could be used by people with a different motivation to mess with your app or steal information. It’s like a superhero training for your mobile apps to make sure they’re strong and safe!
What’s App cloning?
App cloning is like creating a twin of an app. Imagine you have this fantastic app that you love, but you wish you could have another version of it with a few tweaks or modifications. App cloning allows you to duplicate an existing application, often with the same functionality, but you can customize it to suit your preferences. It’s like having two instances of the same app, each with its own settings and maybe even a personalized touch.
The diagram above illustrates the chronological order of pen testing procedures, guiding us to discover the main pain points in app security.
Before delving into each concept, let me provide insight into our target. Imagine a client app released to the public with two distinct flavors — one being the regular version, and the other, a pro version. These versions operate based on a specific flag received from the backend. Let’s refer to the Model object, determining whether the app functions in PRO mode or not, as AppSpecs. Being that said, let’s discuss each of the points
Reconnaissance
In the initial step, we focus on collecting intelligence about the target, which is essentially understanding the structure and functionalities of the app. This phase is comparable to surveying the terrain before embarking on an expedition. We gather information through various means, such as:
1. Release Notes: Examining the release notes provides insights into recent changes, updates, and improvements made to the app. This helps in understanding the evolution of the application over time.
2. Feature Differences Among Countries: Investigating variations in features among different countries offers a perspective on how the app adapts to diverse markets. It helps in identifying any region-specific functionalities or configurations.
3. Business Benefits of Altering Parameters: Understanding the business rationale behind altering certain parameters in the app is crucial. This involves analyzing how changes contribute to the overall goals and benefits of the app, providing context for potential vulnerabilities or exploits.
By comprehensively gathering this intelligence, we establish a solid foundation for the subsequent steps in the penetration testing process.
So, here the reconnaissance for us is the understanding that the app has two flavours, Normal and Pro Version.
Now, the next step is to find the threat vector, We will use static analysis and dynamic analysis to find the threat vectors.
Static analysis
This step involves a meticulous examination of the code without executing it — an in-depth dissection of the app’s structure to pinpoint potential vulnerabilities. It’s comparable to studying the blueprints of a building before attempting any intrusion.
In this phase, we engage in:
- Analyzing Code for Meaningful Namings: Scrutinizing the code to identify meaningful namings is crucial. This entails deciphering variable names, functions, and classes to gain a comprehensive understanding of the logic and purpose behind each component.
- Finding the Core Functionality Controller — Feature Flags: A key aspect of this analysis is locating the core functionality controller, often achieved through the exploration of feature flags. Understanding how the app dynamically adjusts its behavior based on these flags is essential for identifying points of influence and potential security concerns.
To carry out the static analysis, we will need to decompile. This could be achieved by popular tools like apktool, jadx etc.
As depicted in the above diagram, the APK file undergoes decompression into Manifest, resources, dex, and other supporting files. The dex file is then transformed into .smali and translated into .java to enhance understanding for the attacker. Personally, I utilize Jadx-Gui for static analysis purposes.
For this example, we will be examining the model classes which acts as the POJO for backend response. After examining for a bit, We found out that the there is a model class named AppSpecs that has the following structure,
data class AppSpecs(
val shouldEnableProVersion: Boolean,
val userId: Long
....
)
The name inside the model makes a lot of sense to the person doing the pen testing; shouldEnableProVersion gives the attacker an idea that the flag should be somehow related to enabling pro features in the app. However, as of now, we are not 100% sure about this fact.
As a result of static analysis, we have a starting point for dynamic analysis. Before we delve into dynamic analysis, I would like to ask you to do a bit of research about a tool named Frida and to have a rooted Android device for carrying out the procedure.
Dynamic Analysis
Running the app in different scenarios allows real-time observation of its behavior, revealing vulnerabilities not visible in static analysis. It’s like moving from studying a static image to watching a dynamic movie. In our app, live monitoring of the identified core functionality controller Class (AppSpecs) for the app becomes our primary threat vector. The key is to analyze the control flow of the entry point, achievable by printing stack traces.
By scrutinizing the printed stack traces, we gain insights into the sequence of method calls and interactions within the code. This dynamic analysis aids in identifying potential security loopholes or unexpected behaviors that may arise during runtime. It’s akin to watching the app’s script unfold in real-time, allowing us to spot vulnerabilities that might remain hidden in a static examination. This phase serves as a crucial bridge between theoretical expectations and the actual runtime behavior of the application.
So, in our example, we will be adding a spy aka hook for monitoring AppSpecs model class. Here is how the hook would look like (using Frida):
Java.perform(function() {
let appSpecsModel = Java.use('com.jj.exmaple.app.models.AppSpecs')
appSpecsModel.getShouldEnableProVersion.implementation = function () {
console.log("in the function");
// Return an empty string as an example; you can return the actual string value here.
let returnValue = this.shouldEnableProVersion;
console.log("Return value: " + returnValue);
let stackTrace = Java.use('android.util.Log').getStackTraceString(Java.use('java.lang.Exception').$new());
console.log("Stack trace:\n" + stackTrace);
return returnValue;
};
});
The class is self-explanatory. Here, we specify the package name function to spy on, and in addition to that, we print a stack trace to understand the flow of the app. Keeping that in mind, when we spy on the function, we obtain a stack trace that has a class named AppSpecsRepository. This repository has a function named getProAppStatus(). This discovery introduces another candidate to spy on. So, the second spy is now written for the function getProAppStatus(). Here is how it would look:
Java.perform(function() {
let appSpecsRepository = Java.use('com.jj.exmaple.app.data.AppSpecsRepository')
appSpecsRepository.getProAppStatus.implementation = function () {
console.log("in the function");
// Return an empty string as an example; you can return the actual string value here.
let returnValue = this.getProAppStatus();
console.log("Return value: " + returnValue);
let stackTrace = Java.use('android.util.Log').getStackTraceString(Java.use('java.lang.Exception').$new());
console.log("Stack trace:\n" + stackTrace);
return returnValue;
};
});
Now, from monitoring the stack trace, we find a class named IsAppProEnabled, acting like a use case invoked by the entire app.
Consequently, we will experiment with another hook by enabling the feature in our app and observing the resulting behavior.
Java.perform(function() {
let isAppProEnabled = Java.use('com.jj.exmaple.app.domain.IsAppProEnabled')
isAppProEnabled.invoke.implementation = function () {
return true;
};
});
After injecting this hook, we discovered that we are now able to use the pro version of the app without any problems. We confirmed that the threat vector we tracked was indeed the correct one. Moreover, we now have the exact location and line number inside the .smali file that is responsible for enabling and disabling the pro version of the app.
note: generally, the class names will be in an obfuscated manner, We might need to do a bit of trial and error in the real world scenario.
With this we reached the last stage of the pen-testing. We will be reporting these threats to the owners of the app about the vulnerability.
Let’s go a bit further as we are almost near to finishing line.
As we already have the location of the .smali file, we will be editing the file and returning a true value. Here is how the example would look like.
.method public invoke()Z
.locals 1
const/4, v0, 0x1
return v0
.end method
After editing, we will recompile the app back into the .apk file, as shown in the diagram above.
We can make use of apktool for this purpose. The apk we have is unsigned, and we can use a random key to generate the signed version of the app using keytool or other available signers.
Once we complete these processes, we are ready to use the modded/cloned version of the app.
So, long story short, We now have the pro version of the app without paying.
Conclusion
The purpose of delving into procedures here is to raise awareness within the development community regarding potential threats when implementing features that are entirely controlled on the client side. Such features may be susceptible to complete breakdown and manipulation in unintended ways. So, how secure are we? Let’s exercise caution before developing a feature and transition towards a more secure coding approach.
Cheers to secure coding! 🚀