Skip to content

MASTG-DEMO-0041: Uses of LAContext.evaluatePolicy with r2

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

Sample

The following sample insecurely accesses sensitive resources, a secret token, relying solely on the LocalAuthentication API for access control instead of using the Keychain API and requiring user presence. It does so by using the evaluatePolicy method of the LAContext class to authenticate the user with biometrics (deviceOwnerAuthenticationWithBiometrics).

This method is weak because it depends on an if statement to check if the authentication was successful, which can be bypassed by an attacker using techniques such as Bypassing Biometric Authentication.

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. Unzip the app package and locate the main binary file ( Exploring the App Package), which in this case is ./Payload/MASTestApp.app/MASTestApp.
  2. Run run.sh.
insecureAuthenticationBiometricsApi.r2
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
e asm.bytes=false
e scr.color=false
e asm.var=false

?e Print xrefs to \'Run analysis\"
aaa

?e Print xrefs to \'evaluatePolicy\"
f~evaluatePolicy

?e

?e Print xrefs to 0x100010098
axt @ 0x100010098

?e

?e Print disassembly around \"evaluatePolicy\" in the function
pdf @ 0x100004344 | grep -C 5 "evaluatePolicy:"

?e Print xrefs to \'SecAccessControlCreateWithFlags\"
f~SecAccessControlCreateWithFlags
run.sh
1
2
#!/bin/bash
r2 -q -i insecureAuthenticationBiometricsApi.r2 -e emu.str=true -A MASTestApp > output.asm

Observation

output.asm
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
Print xrefs to 'Run analysis"
Print xrefs to 'evaluatePolicy"
0x100008297 38 str.evaluatePolicy:localizedReason:reply:
0x100010098 8 reloc.fixup.evaluatePolicy:localizedReason:

Print xrefs to 0x100010098
sym.MASTestApp.MastgTest.mastg.completion.nd_n 0x100004344 [DATA:r--] ldr x1, [x8, 0x98]

Print disassembly around "evaluatePolicy" in the function
           0x100004334      bl sym.imp.swift_retain
           0x100004338      mov x0, x23                               ; void *arg0
           0x10000433c      bl sym.imp.swift_release                  ; void swift_release(void *arg0)
                                                                      ; void swift_release(0)
           0x100004340      adrp x8, sym.__METACLASS_DATA__TtC10MASTestAppP33_9471609302C95FC8EC1D59DD4CF2A2DB19ResourceBundleClass ; 0x100010000
           0x100004344      ldr x1, [x8, 0x98]                        ; [0x100010098:4]=0x8297 ; reloc.fixup.evaluatePolicy:localizedReason: ; char *selector
           0x100004348      mov x0, x19                               ; void *instance
           0x10000434c      mov w2, 1
           0x100004350      mov x3, x20
           0x100004354      mov x4, x22
           0x100004358      bl sym.imp.objc_msgSend                   ; void *objc_msgSend(void *instance, char *selector)
Print xrefs to 'SecAccessControlCreateWithFlags"

The output reveals the use of LAContext().evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: ...) in the app. However, it doesn't look exactly the same as in MastgTest.swift because the compiler transforms some functions into Objective-C counterparts. An equivalent Objective-C representation in the binary looks like objc_msgSend(void *address, "evaluatePolicy:localizedReason:", LAPolicyDeviceOwnerAuthenticationWithBiometrics, ...). By looking at the output we can find this pattern at lines 15-20.

The third argument of objc_msgSend(...) is LAPolicyDeviceOwnerAuthenticationWithBiometrics because w2 register at the time of the function invocation is set to 1 with a mov instruction at Line 17. 1 is an enum representation of LAPolicyDeviceOwnerAuthenticationWithBiometrics.

You can find all the possible values defined in LAPublicDefines.h by running (requires Xcode):

grep kLAPolicyDeviceOwnerAuthentication /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/System/Library/Frameworks/LocalAuthentication.framework/Headers/LAPublicDefines.h

#define kLAPolicyDeviceOwnerAuthenticationWithBiometrics        1
#define kLAPolicyDeviceOwnerAuthentication                      2
#define kLAPolicyDeviceOwnerAuthenticationWithWatch             3
#define kLAPolicyDeviceOwnerAuthenticationWithBiometricsOrWatch 4
#define kLAPolicyDeviceOwnerAuthenticationWithWristDetection    5
#define kLAPolicyDeviceOwnerAuthenticationWithCompanion         kLAPolicyDeviceOwnerAuthenticationWithWatch
#define kLAPolicyDeviceOwnerAuthenticationWithBiometricsOrCompanion kLAPolicyDeviceOwnerAuthenticationWithBiometricsOrWatch

Or you can view the full LAPublicDefines.h header online in public SDK mirrors on GitHub such as GitHub - xybp888/iOS-SDKs.

Evaluation

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

This approach can be easily bypassed as shown in Bypassing Biometric Authentication.