Skip to content

MASTG-DEMO-0027: Runtime Use of KeyguardManager.isDeviceSecure and BiometricManager.canAuthenticate APIs with Frida

Download MASTG-DEMO-0027 APK Open MASTG-DEMO-0027 Folder Build MASTG-DEMO-0027 APK

Sample

This sample checks if the device has a secure lock screen via KeyguardManager.isDeviceSecure and if the device supports strong biometric authentication using BiometricManager.canAuthenticate.

MastgTest.kt
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package org.owasp.mastestapp

import android.app.KeyguardManager
import android.content.Context
import android.hardware.biometrics.BiometricManager
import android.os.Build

class MastgTest(private val context: Context) {
    fun mastgTest(): String {
        val isLocked = isDeviceSecure(context)
        val biometricStatus = checkStrongBiometricStatus()
        return "Device has a passcode: $isLocked\n\n" +
                "Biometric status: $biometricStatus"
    }

    /**
     * Checks if the device has a secure lock screen (e.g., PIN, pattern, password).
     *
     * @return `true` if the device has a secure lock screen, `false` otherwise.
     */

    fun isDeviceSecure(context: Context): Boolean {
        val keyguardManager = context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
        return keyguardManager.isDeviceSecure
    }

    /**
     * Checks if the device supports strong biometric authentication (e.g., fingerprint, face, iris)
     * and if the user has enrolled biometric credentials.
     *
     * **Note:** This API is available on API level 30 (Android R) and above.
     *
     * @return A human-readable string describing the biometric status.
     */
    fun checkStrongBiometricStatus(): String {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            val biometricManager = context.getSystemService(BiometricManager::class.java)
            val result = biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG)
            return when (result) {
                BiometricManager.BIOMETRIC_SUCCESS ->
                    "BIOMETRIC_SUCCESS - Strong biometric authentication is available."
                BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE ->
                    "BIOMETRIC_ERROR_NO_HARDWARE - No biometric hardware available."
                BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE ->
                    "BIOMETRIC_ERROR_HW_UNAVAILABLE - Biometric hardware is currently unavailable."
                BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED ->
                    "BIOMETRIC_ERROR_NONE_ENROLLED - No biometrics enrolled."
                else ->
                    "Unknown biometric status: $result"
            }
        } else {
            return "Strong biometric authentication check is not supported on this API level."
        }
    }
}

Steps

  1. Install the app on a device ( Installing Apps)
  2. Make sure you have Frida for Android installed on your machine and the frida-server running on the device
  3. Run run.sh to spawn the app with Frida
  4. Click the Start button
  5. Stop the script by pressing Ctrl+C
1
2
#!/bin/bash
frida -U -f org.owasp.mastestapp -l ./script.js -o output.txt
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
Java.perform(() => {

  // Function to print backtrace with a configurable number of lines (default: 5)
  function printBacktrace(maxLines = 8) {
      let Exception = Java.use("java.lang.Exception");
      let stackTrace = Exception.$new().getStackTrace().toString().split(",");

      console.log("\nBacktrace:");
      for (let i = 0; i < Math.min(maxLines, stackTrace.length); i++) {
          console.log(stackTrace[i]);
      }
  }

  // Hook KeyguardManager.isDeviceSecure()
  let KeyguardManager = Java.use("android.app.KeyguardManager");

  KeyguardManager["isDeviceSecure"].overload().implementation = function () {
      console.log(`\n[*] KeyguardManager.isDeviceSecure() called\n`);

      // Java stack trace
      printBacktrace();

      let result = this["isDeviceSecure"]();
      console.log(`Result: ${result}`);
      return result;
  };

  // Hook BiometricManager.canAuthenticate()
  let BiometricManager = Java.use("android.hardware.biometrics.BiometricManager");

  BiometricManager["canAuthenticate"].overload("int").implementation = function (authenticators) {

      let result = this["canAuthenticate"](authenticators);
      let statusMessage;

      // Mapping the authentication result to a readable message
      switch (result) {
          case BiometricManager.BIOMETRIC_SUCCESS.value:
              statusMessage = "BIOMETRIC_SUCCESS - Strong biometric authentication is available.";
              break;
          case BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE.value:
              statusMessage = "BIOMETRIC_ERROR_NO_HARDWARE - No biometric hardware available.";
              break;
          case BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE.value:
              statusMessage = "BIOMETRIC_ERROR_HW_UNAVAILABLE - Biometric hardware is currently unavailable.";
              break;
          case BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED.value:
              statusMessage = "BIOMETRIC_ERROR_NONE_ENROLLED - No biometrics enrolled.";
              break;
          default:
              statusMessage = `Unknown biometric status: ${result}`;
              break;
      }

      console.log(`\n[*] BiometricManager.canAuthenticate(${authenticators}) called with ${statusMessage}\n`);


      // Java stack trace
      printBacktrace();


      return result;
  };
});

Observation

The output reveals the use of KeyguardManager.isDeviceSecure and BiometricManager.canAuthenticate.

output.txt
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
[*] KeyguardManager.isDeviceSecure() called


Backtrace:
android.app.KeyguardManager.isDeviceSecure(Native Method)
org.owasp.mastestapp.MastgTest.isDeviceSecure(MastgTest.kt:24)
org.owasp.mastestapp.MastgTest.mastgTest(MastgTest.kt:10)
org.owasp.mastestapp.MainActivityKt$MyScreenContent$1$1$1.invoke(MainActivity.kt:93)
org.owasp.mastestapp.MainActivityKt$MyScreenContent$1$1$1.invoke(MainActivity.kt:91)
androidx.compose.foundation.ClickablePointerInputNode$pointerInput$3.invoke-k-4lQ0M(Clickable.kt:987)
androidx.compose.foundation.ClickablePointerInputNode$pointerInput$3.invoke(Clickable.kt:981)
androidx.compose.foundation.gestures.TapGestureDetectorKt$detectTapAndPress$2$1.invokeSuspend(TapGestureDetector.kt:255)
Result: false

[*] BiometricManager.canAuthenticate(15) called with BIOMETRIC_ERROR_NONE_ENROLLED - No biometrics enrolled.


Backtrace:
android.hardware.biometrics.BiometricManager.canAuthenticate(Native Method)
org.owasp.mastestapp.MastgTest.checkStrongBiometricStatus(MastgTest.kt:38)
org.owasp.mastestapp.MastgTest.mastgTest(MastgTest.kt:11)
org.owasp.mastestapp.MainActivityKt$MyScreenContent$1$1$1.invoke(MainActivity.kt:93)
org.owasp.mastestapp.MainActivityKt$MyScreenContent$1$1$1.invoke(MainActivity.kt:91)
androidx.compose.foundation.ClickablePointerInputNode$pointerInput$3.invoke-k-4lQ0M(Clickable.kt:987)
androidx.compose.foundation.ClickablePointerInputNode$pointerInput$3.invoke(Clickable.kt:981)
androidx.compose.foundation.gestures.TapGestureDetectorKt$detectTapAndPress$2$1.invokeSuspend(TapGestureDetector.kt:255)

Evaluation

The test passes because the output shows the use of KeyguardManager.isDeviceSecure and BiometricManager.canAuthenticate at runtime. We can see that:

  • KeyguardManager.isDeviceSecure is called from the file MastgTest.kt, class MastgTest, method isDeviceSecure at line 24.
  • BiometricManager.canAuthenticate is called from the file MastgTest.kt, class MastgTest, method checkStrongBiometricStatus at line 38.

Note that in this case the output contains file names and even line numbers, but in real-world scenarios, this information may not be available or not be that useful (e.g. when using a production build or when the app is obfuscated). The output is still valuable because it shows that the APIs are being used at runtime.