Skip to content

MASTG-DEMO-0042: Runtime Use of LAContext.evaluatePolicy with Frida

Download MASTG-DEMO-0042 IPA Open MASTG-DEMO-0042 Folder Build MASTG-DEMO-0042 IPA

Sample

This demo uses the same sample as Uses of LAContext.evaluatePolicy with r2.

../MASTG-DEMO-0041/MastgTest.swift
 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
import Foundation
import LocalAuthentication

struct MastgTest {

    static func mastgTest(completion: @escaping (String) -> Void) {
        let token = "8767086b9f6f976g-a8df76"
        let context = LAContext()
        let reason = "Authenticate to access your token"

        context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason) { success, error in
            DispatchQueue.main.async {
                if success {
                    completion("✅ Retrieved token: \(token)")
                    return
                }

                // Authentication failed: inspect the error code
                let message: String
                if let laError = error as? LAError {
                    switch laError.code {
                    case .userCancel:
                        message = "Authentication was cancelled by the user."
                    case .userFallback:
                        message = "User tapped the fallback button (e.g. entered a password)."
                    case .systemCancel:
                        message = "Authentication was cancelled by the system (e.g. another app came to foreground)."
                    case .passcodeNotSet:
                        message = "Passcode is not set on the device."
                    case .biometryNotAvailable:
                        message = "No biometric authentication is available on this device."
                    case .biometryNotEnrolled:
                        message = "The user has not enrolled any biometrics."
                    case .biometryLockout:
                        message = "Biometry is locked out due to too many failed attempts."
                    default:
                        // For any future or undocumented codes
                        message = laError.localizedDescription
                    }
                } else {
                    // Some other nonLAError error
                    message = error?.localizedDescription ?? "Unknown authentication error."
                }

                completion("❌ \(message)")
            }
        }
    }
}

Steps

  1. Install the app on a device ( Installing Apps)
  2. Make sure you have Frida for iOS installed on your machine and the frida-server running on the device
  3. Run run.sh to spawn your 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.MASTestApp-iOS -l ./script.js -l ../MASTG-DEMO-0044/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
Interceptor.attach(ObjC.classes.LAContext["- evaluatePolicy:localizedReason:reply:"].implementation, {
  onEnter(args) {

      const LAPolicy = {
          1: ".deviceOwnerAuthenticationWithBiometrics",
          2: ".deviceOwnerAuthentication"
          3: ".deviceOwnerAuthenticationWithWatch",
          4: ".deviceOwnerAuthenticationWithBiometricsOrWatch",
          5: ".deviceOwnerAuthenticationWithWristDetection",
      };

      const policy = args[2].toInt32();
      const policyDescription = LAPolicy[policy] || "Unknown Policy";

      console.log(`\nLAContext.canEvaluatePolicy(${args[2]}) called with ${policyDescription} (${args[2]})\n`);

      // Use an arrow function so that `this` remains the same as in onEnter
      const printBacktrace = (maxLines = 8) => {
          console.log("\nBacktrace:");
          let backtrace = Thread.backtrace(this.context, Backtracer.ACCURATE)
              .map(DebugSymbol.fromAddress);

          for (let i = 0; i < Math.min(maxLines, backtrace.length); i++) {
              console.log(backtrace[i]);
          }
      }
      printBacktrace();
  }
});

Observation

output.txt
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
LAContext.canEvaluatePolicy(0x1) called with .deviceOwnerAuthenticationWithBiometrics (0x1)


Backtrace:
0x10095c35c MASTestApp!specialized static MastgTest.mastgTest(completion:)
0x10095d4b4 MASTestApp!closure #1 in closure #1 in closure #1 in ContentView.body.getter
0x19f6cbc54 SwiftUI!partial apply for closure #1 in closure #2 in ContextMenuBridge.contextMenuInteraction(_:willPerformPreviewActionForMenuWith:animator:)
0x19f5d86d0 SwiftUI!partial apply for specialized thunk for @callee_guaranteed () -> (@out A, @error @owned Error)
0x19fca57c4 SwiftUI!specialized static MainActor.assumeIsolated<A>(_:file:line:)
0x19fc721a8 SwiftUI!ButtonAction.callAsFunction()
0x19f154c2c SwiftUI!partial apply for implicit closure #2 in implicit closure #1 in PlatformItemListButtonStyle.makeBody(configuration:)
0x19f6813b4 SwiftUI!ButtonBehavior.ended()

The output reveals the use of LAContext.evaluatePolicy(0x1, ...) in the app. Policy 0x1 is .deviceOwnerAuthenticationWithBiometrics.

Evaluation

The test fails because the output only shows calls to biometric verification with LocalAuthentication API and no calls to any Keychain APIs requiring user presence (SecAccessControlCreateWithFlags).