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
65
66
67
68
69
70
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 () {

      let result = this["isDeviceSecure"]();
      console.log(`\n\n[*] KeyguardManager.isDeviceSecure() called - RESULT: ${result}\n`);

      // Java stack trace
      printBacktrace();

      return result;
  };

  // Hook BiometricManager.canAuthenticate()
  const BiometricManager = Java.use("android.hardware.biometrics.BiometricManager");
  const Authenticators = Java.use("android.hardware.biometrics.BiometricManager$Authenticators");

  // Cache original implementation
  const originalCanAuth = BiometricManager.canAuthenticate.overload("int");

  // Map flag values to names
  const flagNames = {
    [Authenticators.BIOMETRIC_WEAK.value]: "BIOMETRIC_WEAK",
    [Authenticators.BIOMETRIC_STRONG.value]: "BIOMETRIC_STRONG",
    [Authenticators.DEVICE_CREDENTIAL.value]: "DEVICE_CREDENTIAL"
  };

  // Map result codes to messages
  const resultMessages = {
    [BiometricManager.BIOMETRIC_SUCCESS.value]: "BIOMETRIC_SUCCESS",
    [BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE.value]: "BIOMETRIC_ERROR_NO_HARDWARE",
    [BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE.value]: "BIOMETRIC_ERROR_HW_UNAVAILABLE",
    [BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED.value]: "BIOMETRIC_ERROR_NONE_ENROLLED"
  };

  originalCanAuth.implementation = function (authenticators) {
    // Build readable authenticators string
    const names = Object.keys(flagNames)
      .map(key => parseInt(key, 10))
      .filter(key => (authenticators & key) === key)
      .map(key => flagNames[key]);
    const readable = names.length ? names.join(" | ") : "NONE";

    // Call original
    const res = originalCanAuth.call(this, authenticators);

    // Lookup result message
    const msg = resultMessages[res] || `Unknown biometric status: ${res}`;

    console.log(`\n\n[*] BiometricManager.canAuthenticate called with: ${readable} (${authenticators}) - RESULT: ${msg} (${res})`);

    printBacktrace();

    return res;
  };
});

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
[*] KeyguardManager.isDeviceSecure() called - RESULT: false


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.MainScreen$lambda$9$lambda$8(MainActivity.kt:53)
org.owasp.mastestapp.MainActivityKt.$r8$lambda$PhzGLzmkS_ibruOfiTT32AhzWl4(Unknown Source:0)
org.owasp.mastestapp.MainActivityKt$$ExternalSyntheticLambda0.run(D8$$SyntheticClass:0)
java.lang.Thread.run(Thread.java:1012)


[*] BiometricManager.canAuthenticate called with: BIOMETRIC_STRONG (15) - RESULT: BIOMETRIC_ERROR_NONE_ENROLLED (11)

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.MainScreen$lambda$9$lambda$8(MainActivity.kt:53)
org.owasp.mastestapp.MainActivityKt.$r8$lambda$PhzGLzmkS_ibruOfiTT32AhzWl4(Unknown Source:0)
org.owasp.mastestapp.MainActivityKt$$ExternalSyntheticLambda0.run(D8$$SyntheticClass:0)
java.lang.Thread.run(Thread.java:1012)

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.