Skip to content

MASTG-DEMO-0112: Text Input Fields Not Hiding Sensitive Data

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

Sample

The app exposes a login form with the following text input fields:

  • Username: Not considered sensitive data. Uses UITextField text field, no explicit isSecureTextEntry — defaults to false.
  • Password: Considered sensitive data. Uses UITextField text field, isSecureTextEntry = false explicitly.
  • OTP 1: Considered sensitive data. Uses UITextField text field, isSecureTextEntry = true explicitly.
  • OTP 2: Considered sensitive data. Uses SecureField text field.
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
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
import SwiftUI
import UIKit

// SUMMARY: This sample demonstrates the use of both secure and insecure text input fields in an iOS app. It includes a login alert with a username field (insecure), a password field (insecure), and an OTP field (secure). Additionally, it presents a SwiftUI view for entering a second OTP, which is also secure. The test checks for the presence of text input fields and whether they are configured to mask sensitive information.

struct MastgTest {

    @inline(never) @_optimize(none)
    public static func mastgTest(completion: @escaping (String) -> Void) {
        DispatchQueue.main.async {
            let alert = UIAlertController(
                title: "Login",
                message: "Please enter your credentials.",
                preferredStyle: .alert
            )

            alert.addTextField { tf in
                tf.placeholder = "Username"
                tf.autocorrectionType = .no
                tf.accessibilityIdentifier = "username_field"
            }

            // FAIL: [MASTG-TEST-0x57] The password field has isSecureTextEntry set to false.
            alert.addTextField { tf in
                tf.placeholder = "Password"
                tf.isSecureTextEntry = false
                tf.accessibilityIdentifier = "password_field"
            }

            // PASS: [MASTG-TEST-0x57] The OTP 1 field has isSecureTextEntry set to true.
            alert.addTextField { tf in
                tf.placeholder = "OTP 1"
                tf.isSecureTextEntry = true
                tf.keyboardType = .numberPad
                tf.accessibilityIdentifier = "otp_1_field"
            }

            alert.addAction(UIAlertAction(title: "Next", style: .default, handler: { _ in
                let username = alert.textFields?[0].text ?? ""
                let otp1 = alert.textFields?[2].text ?? ""
                let baseMessage = "Logged in as: \(username), OTP 1: \(otp1)"

                let otpView = OTPView { otp2 in
                    if let presenter = topViewController() {
                        presenter.dismiss(animated: true) {
                            completion("\(baseMessage), OTP 2: \(otp2)")
                        }
                    } else {
                        completion("\(baseMessage), OTP 2: \(otp2)")
                    }
                }

                let hosting = UIHostingController(rootView: otpView)

                if let presenter = topViewController() {
                    presenter.present(hosting, animated: true)
                }
            }))

            if let presenter = topViewController() {
                presenter.present(alert, animated: true)
            } else {
                completion("Failed to present alert.")
            }
        }
    }

    struct OTPView: View {
        @State private var otp2: String = ""
        var onSubmit: (String) -> Void

        var body: some View {
            VStack(spacing: 20) {
                Text("Enter OTP 2")

                SecureField("OTP 2", text: $otp2)
                    .keyboardType(.numberPad)
                    .textFieldStyle(RoundedBorderTextFieldStyle())
                    .accessibilityIdentifier("otp_2_field")
                    .padding()

                Button("Submit") {
                    onSubmit(otp2)
                }
            }
            .padding()
        }
    }

    private static func topViewController(
        base: UIViewController? = {
            let scenes = UIApplication.shared.connectedScenes
                .compactMap { $0 as? UIWindowScene }
            let keyWindow = scenes
                .flatMap { $0.windows }
                .first { $0.isKeyWindow }
            return keyWindow?.rootViewController
        }()
    ) -> UIViewController? {
        if let nav = base as? UINavigationController {
            return topViewController(base: nav.visibleViewController)
        }
        if let tab = base as? UITabBarController {
            return topViewController(base: tab.selectedViewController)
        }
        if let presented = base?.presentedViewController {
            return topViewController(base: presented)
        }
        return base
    }
}

The "Password" field is not masked, and is therefore visible to bystanders (shoulder surfing) or potentially captured in screenshots and screen recordings.

The "OTP 1" and "OTP 2" fields use text input fields that are configured to mask the inputted data.

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. Execute run.sh to open the app with radare2 (iOS).
 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
e asm.bytes=false
e scr.color=false
e scr.interactive=false
e asm.var=false
e bin.relocs.apply=true

?e List all references to "UITextField", "setSecureTextEntry" and "SecureField":
f~UITextField,setSecureTextEntry,SecureField

?e
?e Cross-references to the "setSecureTextEntry:" selector:
axt @ 0x000201c0

?e
?e Cross-references to the "SecureField" initializer:
axt @ 0x000040a4

?e
?e Disassembly around the password field setup (isSecureTextEntry = false, FAIL):
pd--10 @ 0x1a30

?e
?e Disassembly around the OTP 1 field setup (isSecureTextEntry = true, PASS):
pd--10 @ 0x1b40

?e
?e Disassembly around the SecureField (OTP 2) setup (PASS):
pd 8 @ 0x3c50
1
2
#!/bin/bash
r2 -q -i textfield_masking.r2 -A MASTestApp > output.txt

Observation

The output reveals:

  • 3 UITextField closures at addresses 0x00001828, 0x000019a8, and 0x00001ab8.
  • 2 calls to setSecureTextEntry: at 0x00001a24 and 0x00001b38.
  • 1 call to the SecureField initializer at 0x00003c60.
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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
List all references to "UITextField", "setSecureTextEntry" and "SecureField":
0x00001828 272 sym.MASTestApp.MastgTest.mastg.completion.UITextField
0x00001938 112 sym....sSo11UITextFieldCIegg_ABIeyBy_TR
0x000019a8 272 sym.MASTestApp.MastgTest.mastg.completion.UITextField...U0_
0x00001ab8 296 sym.MASTestApp.MastgTest.mastg.completion.UITextField._
0x000040a4 52 sym.SwiftUI.SecureField...VA2A4TextVRszrlE_4textACyAEGAA18LocalizedStringKeyV_AA7BindingVySSGtcfC
0x0000567c 116 sym.SwiftUI.SecureField...VyAA4TextVGACyxGAA4ViewAAWl
0x00005db8 4 sym.SwiftUI.SecureField...VA2A4TextVRszrlE_4textACyAEGAA18LocalizedStringKeyV_AA7BindingVySSGtcfcyycfU_
0x00006508 96 sym....sSo11UITextFieldCMa
0x00015570 12 sym.imp.SwiftUI.SecureField...VA2A4TextVRszrlE_4text8onCommitACyAEGAA18LocalizedStringKeyV_AA7BindingVySSGyyctcfC
0x00016e90 22 str.v16__0__UITextField_8
0x000177a5 20 str.setSecureTextEntry:
0x00017900 0 sym.SwiftUI.SecureField...VyAA4TextVGMR
0x0001c248 8 reloc.SwiftUI.SecureField...VA2A4TextVRszrlE_4text8onCommitACyAEGAA18LocalizedStringKeyV_AA7BindingVySSGyyctcfC
0x0001c250 8 reloc.SwiftUI.SecureField...VMn
0x0001c258 8 reloc.SwiftUI.SecureField...VyxGAA4ViewAAMc
0x0001d578 8 reloc.UITextField
0x000201c0 8 reloc.fixup.setSecureTextEntry:
0x000202d8 0 sym.SwiftUI.SecureField...VyAA4TextVGMd
0x000202e8 0 sym.SwiftUI.SecureField...VyAA4TextVGACyxGAA4ViewAAWL
0x00020398 0 sym....sSo11UITextFieldCML

Cross-references to the "setSecureTextEntry:" selector:
sym.MASTestApp.MastgTest.mastg.completion.UITextField...U0_ 0x1a24 [DATA:r--] ldr x1, reloc.fixup.setSecureTextEntry:
sym.MASTestApp.MastgTest.mastg.completion.UITextField._ 0x1b38 [DATA:r--] ldr x1, reloc.fixup.setSecureTextEntry:

Cross-references to the "SecureField" initializer:
sym.MASTestApp.MastgTest.OTPView.body.SwiftUI.TupleView.Text...G0J0PAGE7paddingyQrAG4EdgeO3SetV_12CoreGraphics7CGFloatVSgtFQOyAG15ModifiedContentVyAmGE14textFieldStyleyQrqd__AG0kuV0Rd__lFQOyAmGE12keyboardTypeyQrSo010UIKeyboardX0VFQOyAG06SecureU0VyAKG_Qo__AG013RoundedBorderkuV0VQo_AG31AccessibilityAttachmentModifierVG_Qo_AG6ButtonVyAKGtGyXEfU_ 0x3c60 [CALL:--x] bl sym.SwiftUI.SecureField...VA2A4TextVRszrlE_4textACyAEGAA18LocalizedStringKeyV_AA7BindingVySSGtcfC

Disassembly around the password field setup (isSecureTextEntry = false, FAIL):
           0x00001a08      bl sym.imp.objc_msgSend                    ; void *objc_msgSend(void *instance, char *selector)
           0x00001a0c      ldr x0, [var_10h]
           0x00001a10      adrp x8, segment.__DATA_CONST              ; 0x1c000
           0x00001a14      ldr x8, [x8, 0xe0]                         ; [0x1c0e0:4]=26
                                                                      ; reloc.objc_release
           0x00001a18      blr x8
           0x00001a1c      ldur x0, [x29, -0x10]                      ; void *instance
           0x00001a20      adrp x8, sym.__METACLASS_DATA__TtC10MASTestApp11DemoResults ; objc_class__TtC10MASTestApp11DemoResults
                                                                      ; 0x20000
           0x00001a24      ldr x1, [x8, 0x1c0]                        ; [0x201c0:4]=0x177a5 str.setSecureTextEntry: ; reloc.fixup.setSecureTextEntry: ; char *selector
           0x00001a28      mov w8, 0
           0x00001a2c      and w2, w8, 1
           ; DATA XREF from mach0_section___TEXT___swift5_assocty @ +0xc(r)
           0x00001a30      bl sym.imp.objc_msgSend                    ; void *objc_msgSend(void *instance, char *selector)
           0x00001a34      ldur x0, [x29, -0x10]
           0x00001a38      adrp x8, segment.__DATA_CONST              ; 0x1c000
           0x00001a3c      ldr x8, [x8, 0xe8]                         ; [0x1c0e8:4]=27
                                                                      ; reloc.objc_retain
           0x00001a40      blr x8
           0x00001a44      ldr w8, [var_1ch]
           0x00001a48      adrp x0, 0x16000
           0x00001a4c      add x0, x0, 0xf26                          ; 0x16f26 ; "password_field"
           0x00001a50      mov w9, 0xe
           0x00001a54      mov x1, x9

Disassembly around the OTP 1 field setup (isSecureTextEntry = true, PASS):
           0x00001b18      bl sym.imp.objc_msgSend                    ; void *objc_msgSend(void *instance, char *selector)
           0x00001b1c      ldr x0, [var_10h]
           0x00001b20      adrp x8, segment.__DATA_CONST              ; 0x1c000
           0x00001b24      ldr x8, [x8, 0xe0]                         ; [0x1c0e0:4]=26
                                                                      ; reloc.objc_release
           0x00001b28      blr x8
           0x00001b2c      ldr w8, [var_1ch]
           0x00001b30      ldur x0, [x29, -0x10]                      ; void *instance
           0x00001b34      adrp x9, sym.__METACLASS_DATA__TtC10MASTestApp11DemoResults ; objc_class__TtC10MASTestApp11DemoResults
                                                                      ; 0x20000
           0x00001b38      ldr x1, [x9, 0x1c0]                        ; [0x201c0:4]=0x177a5 str.setSecureTextEntry: ; reloc.fixup.setSecureTextEntry: ; char *selector
           0x00001b3c      and w2, w8, 1
           0x00001b40      bl sym.imp.objc_msgSend                    ; void *objc_msgSend(void *instance, char *selector)
           0x00001b44      ldur x0, [x29, -0x10]                      ; void *instance
           0x00001b48      adrp x8, sym.__METACLASS_DATA__TtC10MASTestApp11DemoResults ; objc_class__TtC10MASTestApp11DemoResults
                                                                      ; 0x20000
           0x00001b4c      ldr x1, [x8, 0x1c8]                        ; [0x201c8:4]=0x17794 str.setKeyboardType: ; reloc.fixup.setKeyboardType: ; char *selector
           0x00001b50      mov w8, 4
           0x00001b54      mov x2, x8
           0x00001b58      bl sym.imp.objc_msgSend                    ; void *objc_msgSend(void *instance, char *selector)
           0x00001b5c      ldur x0, [x29, -0x10]
           0x00001b60      adrp x8, segment.__DATA_CONST              ; 0x1c000
           0x00001b64      ldr x8, [x8, 0xe8]                         ; [0x1c0e8:4]=27
                                                                      ; reloc.objc_retain

Disassembly around the SecureField (OTP 2) setup (PASS):
           0x00003c50      ldr x7, [x19, 0x40]
           0x00003c54      ldr x0, [x19, 0x48]                        ; int64_t arg1
           0x00003c58      mov x8, x20
           0x00003c5c      and w2, w2, 1
           0x00003c60      bl sym.SwiftUI.SecureField...VA2A4TextVRszrlE_4textACyAEGAA18LocalizedStringKeyV_AA7BindingVySSGtcfC ; func.000040a4
           0x00003c64      bl sym.SwiftUI.SecureField...VyAA4TextVGACyxGAA4ViewAAWl ; func.0000567c
           0x00003c68      ldr x1, [x19, 0x60]
           0x00003c6c      ldr x8, [x19, 0x88]

Evaluation

The test case fails because the password field calls setSecureTextEntry: with argument false, leaving the sensitive input unmasked.

Interpreting the Disassembly:

Although MastgTest.swift is written in Swift, it interacts with UIKit (an Objective-C framework). The compiler translates these interactions into calls to the objc_msgSend function. We analyze the arguments passed to this function using the ARM64 calling convention:

  • x0 register: holds self (the instance of the UITextField).
  • x1 register: holds the selector (the method name).
  • x2 register: holds the argument passed to that method.

Password Field (FAIL):

The setSecureTextEntry: call at 0x00001a24 corresponds to the password field. The disassembly shows:

At 0x00001a28, mov w8, 0 sets the value to 0. At 0x00001a2c, and w2, w8, 1 prepares the boolean argument, resulting in w2 = 0 (false).

This means isSecureTextEntry is set to false for the password field, causing the password to be displayed in plain text instead of being masked with bullet characters.

OTP 1 Field (PASS):

The setSecureTextEntry: call at 0x00001b38 corresponds to the OTP 1 field. The disassembly shows:

At 0x00001ae0, mov w8, 1 stores the value 1 (true) into a local variable. At 0x00001b2c, the stored value is reloaded. At 0x00001b3c, and w2, w8, 1 results in w2 = 1 (true).

This means isSecureTextEntry is true for the OTP 1 field, so the input is correctly masked.

OTP 2 Field (PASS):

The call at 0x00003c60 invokes the SecureField initializer directly (bl sym.SwiftUI.SecureField...). SwiftUI's SecureField always masks input by design — there is no isSecureTextEntry setter involved.