Since the release of the first smartphone in 2007, mobile applications have evolved considerably. There’s a mobile application for everything now, and increasing numbers of companies are choosing to develop mobile applications to meet business needs. When there’s an application, there’s a risk. When Prism Infosec tests mobile applications, one tool used extensively is Frida. It’s a dynamic instrumentation toolkit that lets us interact with apps while they’re running.
This post walks through a few scenarios encountered during assessments, showing how Frida can help bypass certain restrictions on both Android and iOS. The scenarios include using Frida to disable root detection, to modify in-app scores, and to unlock content that should only be accessible to paying users. All of these were encountered in real-world testing and serve as a reminder to not overuse client-side logic.
Android Root Detection Bypass
It’s a common practice for mobile applications to try to block usage on rooted devices. A fair security measure on the surface, but from a testing perspective, it gets in the way—most of our tooling requires root. In one assessment, the app was refusing to launch and standard bypasses weren’t working.
The application code was examined using JADX and two classes responsible for root detection were identified. Using Frida, we overrode their logic to always return false. Specifically, the methods isDeviceRooted() and isJailBroken() were hooked to prevent the app from recognising the test environment as rooted. With the script running, the app launched without complaint.
Java.perform(function () {
var targetClass = Java.use(“io.sentry.android.core.internal.util.RootChecker”);
var targetClass2 = Java.use(“com.gantix.JailMonkey.Rooted.RootedCheck”);
targetClass.isDeviceRooted.implementation = function () {
console.log(“isDeviceRooted() called – overriding return value to false”);
return false;
};
targetClass2.isJailBroken.implementation = function () {
console.log(“isJailBroken() called – overriding return value to false”);
return false;
};
});
This example is a reminder that even seemingly robust protections can often be bypassed if implemented solely on the client side.
Android Custom Score Bypass
In another case, the application included a quiz feature. Here, users could earn scores for getting questions correct which would then be included in the application’s leaderboard. While monitoring network traffic, the score was observed as being sent as an encrypted value, suggesting that the logic to set the score was handled on the device rather than being calculated or verified by the server.
Using Frida, we identified the method responsible for encrypting the score and hooked into it. We modified the script to replace the value with an exaggerated number—in this case, one hundred million. After submitting the quiz with our new score, the app accepted it and displayed it at the top of the leaderboard.
Java.perform(function() {
const ScoreManager = Java.use(“com.<REDACTED>.<REDACTED>.utils.e”);
const encMethod = ScoreManager.c.overload(“java.lang.String”);
encMethod.implementation = function (toEncrypt) {
console.log(“the string “+toEncrypt+” was just passed for encryption.”);
console.log(“Updating score to 100 million…”);
return this.c(“100000000”);
}
});
This proved there was no server-side validation and that the app fully trusted the data sent from the client.
Premium Content Bypass (iOS)
Finally, an iOS application whas assessed where certain video content was locked behind a paywall. Normally, this sort of functionality would be gated by subscription status, with checks on both the client and server. However, the app was using a client-side method called subscribePackageSuccess() to unlock access, and this method could be called directly.
An instance of the relevant view controller was located and triggered the subscription method on the main thread. Once invoked, the application treated the user as subscribed and unlocked all restricted content without performing any server-side validation.
if (ObjC.available) {
console.log(“[*] Invoking subscribePackageSuccess on the main thread…”);
ObjC.choose(ObjC.classes.VideoPackViewController, {
onMatch: function (instance) {
console.log(“[*] Found instance: ” + instance);
ObjC.schedule(ObjC.mainQueue, function () {
try {
console.log(“[*] Calling subscribePackageSuccess…”);
instance.subscribePackageSuccess();
console.log(“[*] subscribePackageSuccess invoked successfully.”);
} catch (err) {
console.log(“[!] Error invoking method: ” + err.message);
}
});
},
onComplete: function () {
console.log(“[*] Method invocation complete.”);
}
});
} else {
console.log(“[!] Objective-C runtime is not available.”);
}
This kind of implementation is risky. It relies entirely on the app to enforce access control, which is easily manipulated with tools like Frida. Anyone with minimal knowledge of dynamic analysis could exploit it the same way.
Conclusion
The examples here highlight how vital it is to enforce access control and validation server-side. Critical data should never be validated client-side only. By catching these issues during testing, the chance of them being exploited in the wild is reduced.
At Prism Infosec, these techniques form part of our regular mobile application assessments. They help ensure that what appears secure on the surface really holds up under scrutiny. If you’re building or managing mobile apps and want to find out whether your protections can withstand this kind of testing, do get in touch!
Written by Courtney Evans