Skip to content

MASTG-DEMO-0098: References to File Access in WebViews with radare2

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

Sample

This sample demonstrates a WKWebView with the undocumented allowFileAccessFromFileURLs property enabled, allowing JavaScript to read other local file:// URLs even when loadFileURL:allowingReadAccessToURL: is intentionally granting broad access to the demoRoot directory. This is required because in the end, the sandbox always applies and this API is the one controlling it.

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
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
import UIKit
import WebKit

struct MastgTest {

    private static let cacheDir = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
    private static let demoRoot = cacheDir.appendingPathComponent("demoRoot", isDirectory: true)
    private static let appDir = demoRoot.appendingPathComponent("app", isDirectory: true)
    private static let otherDir = demoRoot.appendingPathComponent("other", isDirectory: true)

    private static let indexURL = appDir.appendingPathComponent("index.html")
    private static let apiKeyURL = appDir.appendingPathComponent("api-key.txt")
    private static let otherURL = otherDir.appendingPathComponent("other.html")

    public static func mastgTest(completion: @escaping (String) -> Void) {
        createDirectories()
        createSecretFile()
        createOtherFile()
        createHtmlFile()

        DispatchQueue.main.async {
            showWebView(completion: completion)
        }
    }

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

        // CONDITION 1: JavaScript allowed, default: true
        preferences.allowsContentJavaScript = true
        configuration.defaultWebpagePreferences = preferences

        // CONDITION 2: file:// URLs must have access to files.
        // This demo sets it to true so the fetch succeeds; change to false to block the fetch.
        configuration.preferences.setValue(true, forKey: "allowFileAccessFromFileURLs")
        configuration.setValue(false, forKey: "allowUniversalAccessFromFileURLs")

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

        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("Opening WebView...")
            completion(fileTreeString(at: demoRoot))

            // CONDITION 3: Overly broad file read access to demoRoot
            // JavaScript will be able to access any file within demoRoot
            // the content in the iframe will show
            webView.loadFileURL(indexURL, allowingReadAccessTo: demoRoot)

            // FIX: restrict allowingReadAccessTo to indexURL or appDir:
            // JavaScript will lose access to the files
            // the content in the iframe will be blocked
            // webView.loadFileURL(indexURL, allowingReadAccessTo: indexURL)

        }
    }

    private static func createDirectories() {
        try? FileManager.default.createDirectory(at: appDir, withIntermediateDirectories: true)
        try? FileManager.default.createDirectory(at: otherDir, withIntermediateDirectories: true)
    }

    private static func fileTreeString(at url: URL, indent: String = "") -> String {
        var isDirectory: ObjCBool = false
        let exists = FileManager.default.fileExists(atPath: url.path, isDirectory: &isDirectory)

        guard exists else {
            return "\(indent)\(url.lastPathComponent) [missing]"
        }

        if !isDirectory.boolValue {
            return "\(indent)\(url.lastPathComponent)"
        }

        let children = (try? FileManager.default.contentsOfDirectory(
            at: url,
            includingPropertiesForKeys: [.isDirectoryKey],
            options: [.skipsHiddenFiles]
        ))?
        .sorted { $0.lastPathComponent < $1.lastPathComponent } ?? []

        var lines = ["\(indent)\(url.lastPathComponent)/"]

        for child in children {
            lines.append(fileTreeString(at: child, indent: indent + "    "))
        }

        return lines.joined(separator: "\n")
    }

    private static func createHtmlFile() {
        let htmlContent = """
        <!DOCTYPE html>
        <html>

        <head>
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <title>Local File Access Demo</title>
        </head>

        <body>
            <h1>Local File Access Demo</h1>
            <p>This page attempts to read another local file using JavaScript.</p>

            <h3>JavaScript File Access via <code>fetch</code>:</h3>
            <p>Attempting to read: <code>./api-key.txt</code></p>
            <pre id="result">Loading...</pre>

            <script>
                async function readLocalSecret() {
                    const result = document.getElementById("result");
                    try {
                        const response = await fetch("./api-key.txt");
                        const text = await response.text();
                        result.textContent = text;
                        result.style.color = "green";
                    } catch (error) {
                        result.textContent = "BLOCKED: Failed to read local file: " + error;
                        result.style.color = "red";

                    }
                }

                readLocalSecret();
            </script>

            <h3>JavaScript File Access via <code>XMLHttpRequest</code>:</h3>
            <p>Attempting to read: <code>../other/other.html</code></p>
            <div id="result2" style="border: 1px solid #ccc; min-height: 220px;"></div>

            <script>
                function readLocalPage() {
                    const display = document.getElementById("result2");
                    const xhr = new XMLHttpRequest();

                    xhr.open("GET", "../other/other.html", true);

                    xhr.onload = function () {
                        if (xhr.status === 200 || (xhr.status === 0 && xhr.responseText.length > 0)) {
                            const frame = document.createElement("iframe");
                            frame.width = "100%";
                            frame.height = "200";
                            frame.style.border = "0";
                            frame.srcdoc = xhr.responseText;

                            display.innerHTML = "";
                            display.appendChild(frame);
                        } else {
                            display.textContent = "BLOCKED: Failed with status " + xhr.status;
                            display.style.color = "red";
                        }
                    };

                    xhr.onerror = function () {
                        display.textContent = "BLOCKED: Failed to read local file.";
                        display.style.color = "red";
                    };

                    xhr.send();
                }

                setTimeout(readLocalPage, 500);
            </script>

            <h3>Direct Embedding into <code>&lt;iframe src=</code>:</h3>
            <p>Attempting to read: <code>../other/other.html</code></p>
            <iframe src="../other/other.html" width="100%" height="200"></iframe>

        </body>

        </html>
        """
        try? htmlContent.write(to: indexURL, atomically: true, encoding: .utf8)
    }

    private static func createOtherFile() {
        let otherHtmlContent = """
        <!DOCTYPE html>
        <html>
        <head>
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <title>Other File</title>
        </head>
        <body style="background-color: green;">
            <h4>Loaded from sibling folder</h4>
            <p>If you see this green iframe, the iframe loaded.</p>
        </body>
        </html>
        """
        try? otherHtmlContent.write(to: otherURL, atomically: true, encoding: .utf8)
    }

    private static func createSecretFile() {
        try? "MASTG_API_KEY=072037ab-1b7b-4b3b-8b7b-1b7b4b3b8b7b".write(to: apiKeyURL, atomically: true, encoding: .utf8)
    }

    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
    }
}

The sample sets up two sibling directories under cachesDirectory/demoRoot/:

  • app/: contains index.html (the loaded page) and api-key.txt (a sensitive secret).
  • other/: contains other.html (a page in a separate directory).

Using KVC, allowFileAccessFromFileURLs is set to true and allowUniversalAccessFromFileURLs is left false. The WebView loads index.html with loadFileURL(_:allowingReadAccessTo:), intentionally passing demoRoot to the allowingReadAccessTo parameter. This means that JavaScript running in the WebView should be able to read any file in demoRoot (including index.html and api-key.txt).

index.html contains JavaScript that demonstrates three access methods:

  • fetch("./api-key.txt"): reads api-key.txt from the same app/ directory.
  • XMLHttpRequest to ../other/other.html: reads a file from the sibling other/ directory.
  • <iframe src="../other/other.html">: embeds the sibling file directly.

The allowingReadAccessTo: demoRoot choice is intentional: it shows that simply setting allowFileAccessFromFileURLs = true is not enough for JavaScript to reach any local file:// URL via fetch or XHR. The allowingReadAccessTo parameter is not the target of this demo but it's required to demonstrate the impact in terms of the sandbox boundary. If the WebView were loaded with allowingReadAccessTo: indexURL instead, then JavaScript would only be able to read index.html and not api-key.txt, even with allowFileAccessFromFileURLs = true.

The app logs would show:

0x1110180c0 - [PID=709] WebProcessProxy::checkURLReceivedFromWebProcess: Received an unexpected URL from the web process
0x103870c18 - [pageProxyID=6, webPageID=7, PID=709] WebPageProxy::Ignoring request to load this main resource because it is outside the sandbox

Steps

  1. Extract the app binary from the IPA ( Obtaining and Extracting Apps).
  2. Run radare2 (iOS) (radare2) using the provided script to search for references to the relevant WebView methods.
 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
e asm.bytes=false
e scr.color=false
e scr.interactive=false
e asm.var=false

?e;?e

?e Uses of the APIs:
f~AccessFromFileURLs
f~loadFileURL
f~setValue

?e

?e xrefs:

axt 0x10000da70
axt 0x10000da90
axt 0x100014190
axt 0x1000141d0

?e

?e Use of the APIs:

pd-- 10 @ 0x100004b64

?e ...

pd-- 10 @ 0x100004bac

?e ...

pd-- 5 @ 0x10000c684

pdf @ 0x100004ad8 > function.asm
pdf @ 0x1000050c0 > function2.asm
1
2
3
#!/bin/bash

r2 -q -e bin.relocs.apply=true -i webview_file_access.r2 -A MASTestApp > output.txt

The script searches for:

  • References to allowFileAccessFromFileURLs and allowUniversalAccessFromFileURLs.
  • References to the loadFileURL:allowingReadAccessToURL: method.

Observation

The output shows all cross-references and disassembled snippets.

 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
Uses of the APIs:
0x10000da70 28 str.allowFileAccessFromFileURLs
0x10000da90 33 str.allowUniversalAccessFromFileURLs
0x10000eb54 37 str.loadFileURL:allowingReadAccessToURL:
0x100014190 8 reloc.fixup.loadFileURL:allowingReadAccessT
0x10000ec2c 17 str.setValue:forKey:
0x1000141d0 8 reloc.fixup.setValue:forKey:

xrefs:
sym.func.100004ad8 0x100004b64 [STRN:r--] add x8, x8, str.allowFileAccessFromFileURLs
sym.func.100004ad8 0x100004bac [STRN:r--] add x8, x8, str.allowUniversalAccessFromFileURLs
fcn.10000c680 0x10000c684 [DATA:r--] ldr x1, reloc.fixup.loadFileURL:allowingReadAccessT
fcn.10000c780 0x10000c784 [DATA:r--] ldr x1, reloc.fixup.setValue:forKey:

Use of the APIs:
│           0x100004b3c      bl fcn.10000c760
│           0x100004b40      mov x0, x19
│           0x100004b44      bl fcn.10000c6a0
│           0x100004b48      mov x29, x29
│           0x100004b4c      bl sym.imp.objc_retainAutoreleasedReturnValue ; void objc_retainAutoreleasedReturnValue(void *instance)
│           0x100004b50      mov x23, x0
│           0x100004b54      mov w0, 1
│           0x100004b58      bl sym.imp.Foundationbool_...ridgeToObjectiveCSo8NSNumberCyF_ ; Foundationbool(...ridgeToObjectiveCSo8NSNumberCyF)
│           0x100004b5c      mov x24, x0
│           0x100004b60      adrp x8, 0x10000d000
│           0x100004b64      add x8, x8, 0xa70                         ; 0x10000da70 ; "allowFileAccessFromFileURLs"
│           0x100004b68      sub x8, x8, 0x20
│           0x100004b6c      orr x1, x8, 0x8000000000000000
│           0x100004b70      mov x0, 0x1b
│           0x100004b74      movk x0, 0xd000, lsl 48
│           0x100004b78      bl sym.imp.Foundationbool_...ridgeToObjectiveCSo8NSStringCyF_ ; Foundationbool(...ridgeToObjectiveCSo8NSStringCyF)
│           0x100004b7c      mov x25, x0
│           0x100004b80      mov x0, x23
│           0x100004b84      mov x2, x24
│           0x100004b88      mov x3, x25
...
│           0x100004b84      mov x2, x24
│           0x100004b88      mov x3, x25
│           0x100004b8c      bl fcn.10000c780
│           0x100004b90      bl sym.imp.objc_release_x23
│           0x100004b94      bl sym.imp.objc_release_x24
│           0x100004b98      bl sym.imp.objc_release_x25
│           0x100004b9c      mov w0, 0
│           0x100004ba0      bl sym.imp.Foundationbool_...ridgeToObjectiveCSo8NSNumberCyF_ ; Foundationbool(...ridgeToObjectiveCSo8NSNumberCyF)
│           0x100004ba4      mov x23, x0
│           0x100004ba8      adrp x8, 0x10000d000
│           0x100004bac      add x8, x8, 0xa90                         ; 0x10000da90 ; "allowUniversalAccessFromFileURLs"
│           0x100004bb0      sub x8, x8, 0x20
│           0x100004bb4      add x0, x26, 5
│           0x100004bb8      orr x1, x8, 0x8000000000000000
│           0x100004bbc      bl sym.imp.Foundationbool_...ridgeToObjectiveCSo8NSStringCyF_ ; Foundationbool(...ridgeToObjectiveCSo8NSStringCyF)
│           0x100004bc0      mov x24, x0
│           0x100004bc4      mov x0, x19
│           0x100004bc8      mov x2, x23
│           0x100004bcc      mov x3, x24
│           0x100004bd0      bl fcn.10000c780
...
│           0x10000c670      br x16
└           0x10000c674      brk 1
            0x10000c678      brk 1
            0x10000c67c      brk 1
            ; CALL XREF from sym.func.1000050c0 @ 0x10000520c(x)
┌ 24: fcn.10000c680 ();
│           0x10000c680      adrp x1, segment.__DATA                   ; 0x100014000
│           0x10000c684      ldr x1, [x1, 0x190]                       ; [0x10000eb54:4]=0x64616f6c ; "loadFileURL:allowingReadAccessToURL:"
│           0x10000c688      adrp x16, segment.__DATA_CONST            ; 0x100010000
│           0x10000c68c      ldr x16, [x16, 0x118]                     ; [0x100010118:4]=0
│                                                                      ; reloc.objc_msgSend
│           0x10000c690      br x16
└           0x10000c694      brk 1
  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
            ; CODE XREF from sym.func.100007c60 @ 0x100007c64(x)
 580: sym.func.100004ad8 (int64_t arg1, int64_t arg2, int64_t arg_30h, int64_t arg_40h, int64_t arg_50h, int64_t arg_60h, int64_t arg_70h, int64_t arg_80h);
           0x100004ad8      sub sp, sp, 0x80
           0x100004adc      stp x26, x25, [var_30h]
           0x100004ae0      stp x24, x23, [var_40h]
           0x100004ae4      stp x22, x21, [var_50h]
           0x100004ae8      stp x20, x19, [var_60h]
           0x100004aec      stp x29, x30, [var_70h]
           0x100004af0      add x29, sp, 0x70
           0x100004af4      mov x20, x1                               ; arg2
           0x100004af8      mov x22, x0                               ; arg1
           0x100004afc      mov x26, 0x1b
           0x100004b00      movk x26, 0xd000, lsl 48
           0x100004b04      adrp x8, segment.__DATA                   ; 0x100014000
           0x100004b08      ldr x0, [x8, 0x200]                       ; [0x100014200:4]=0
                                                                      ; reloc.WKWebViewConfiguration ; void *arg0
           0x100004b0c      bl sym.imp.objc_allocWithZone             ; void *objc_allocWithZone(void *arg0)
           0x100004b10      bl fcn.10000c620
           0x100004b14      mov x19, x0
           0x100004b18      adrp x8, segment.__DATA                   ; 0x100014000
           0x100004b1c      ldr x0, [x8, 0x208]                       ; [0x100014208:4]=0
                                                                      ; reloc.WKWebpagePreferences ; void *arg0
           0x100004b20      bl sym.imp.objc_allocWithZone             ; void *objc_allocWithZone(void *arg0)
           0x100004b24      bl fcn.10000c620
           0x100004b28      mov x21, x0
           0x100004b2c      mov w2, 1
           0x100004b30      bl fcn.10000c740
           0x100004b34      mov x0, x19
           0x100004b38      mov x2, x21
           0x100004b3c      bl fcn.10000c760
           0x100004b40      mov x0, x19
           0x100004b44      bl fcn.10000c6a0
           0x100004b48      mov x29, x29
           0x100004b4c      bl sym.imp.objc_retainAutoreleasedReturnValue ; void objc_retainAutoreleasedReturnValue(void *instance)
           0x100004b50      mov x23, x0
           0x100004b54      mov w0, 1
           0x100004b58      bl sym.imp.Foundationbool_...ridgeToObjectiveCSo8NSNumberCyF_ ; Foundationbool(...ridgeToObjectiveCSo8NSNumberCyF)
           0x100004b5c      mov x24, x0
           0x100004b60      adrp x8, 0x10000d000
           0x100004b64      add x8, x8, 0xa70                         ; 0x10000da70 ; "allowFileAccessFromFileURLs"
           0x100004b68      sub x8, x8, 0x20
           0x100004b6c      orr x1, x8, 0x8000000000000000
           0x100004b70      mov x0, 0x1b
           0x100004b74      movk x0, 0xd000, lsl 48
           0x100004b78      bl sym.imp.Foundationbool_...ridgeToObjectiveCSo8NSStringCyF_ ; Foundationbool(...ridgeToObjectiveCSo8NSStringCyF)
           0x100004b7c      mov x25, x0
           0x100004b80      mov x0, x23
           0x100004b84      mov x2, x24
           0x100004b88      mov x3, x25
           0x100004b8c      bl fcn.10000c780
           0x100004b90      bl sym.imp.objc_release_x23
           0x100004b94      bl sym.imp.objc_release_x24
           0x100004b98      bl sym.imp.objc_release_x25
           0x100004b9c      mov w0, 0
           0x100004ba0      bl sym.imp.Foundationbool_...ridgeToObjectiveCSo8NSNumberCyF_ ; Foundationbool(...ridgeToObjectiveCSo8NSNumberCyF)
           0x100004ba4      mov x23, x0
           0x100004ba8      adrp x8, 0x10000d000
           0x100004bac      add x8, x8, 0xa90                         ; 0x10000da90 ; "allowUniversalAccessFromFileURLs"
           0x100004bb0      sub x8, x8, 0x20
           0x100004bb4      add x0, x26, 5
           0x100004bb8      orr x1, x8, 0x8000000000000000
           0x100004bbc      bl sym.imp.Foundationbool_...ridgeToObjectiveCSo8NSStringCyF_ ; Foundationbool(...ridgeToObjectiveCSo8NSStringCyF)
           0x100004bc0      mov x24, x0
           0x100004bc4      mov x0, x19
           0x100004bc8      mov x2, x23
           0x100004bcc      mov x3, x24
           0x100004bd0      bl fcn.10000c780
           0x100004bd4      bl sym.imp.objc_release_x23
           0x100004bd8      bl sym.imp.objc_release_x24
           0x100004bdc      adrp x8, segment.__DATA                   ; 0x100014000
           0x100004be0      ldr x0, [x8, 0x210]                       ; [0x100014210:4]=0
                                                                      ; reloc.WKWebView ; void *arg0
           0x100004be4      bl sym.imp.objc_allocWithZone             ; void *objc_allocWithZone(void *arg0)
           0x100004be8      fmov d0, xzr
           0x100004bec      fmov d1, xzr
           0x100004bf0      fmov d2, xzr
           0x100004bf4      fmov d3, xzr
           0x100004bf8      mov x2, x19
           0x100004bfc      bl fcn.10000c640
           0x100004c00      mov x24, x0
           0x100004c04      adrp x8, segment.__DATA                   ; 0x100014000
           0x100004c08      ldr x0, [x8, 0x218]                       ; [0x100014218:4]=0
                                                                      ; reloc.UIViewController ; void *arg0
           0x100004c0c      bl sym.imp.objc_allocWithZone             ; void *objc_allocWithZone(void *arg0)
           0x100004c10      bl fcn.10000c620
           0x100004c14      mov x23, x0
           0x100004c18      mov x2, x24
           0x100004c1c      bl fcn.10000c7a0
           0x100004c20      mov x0, 0                                 ; int64_t arg1
           0x100004c24      bl sym.func.100004d48
       ┌─< 0x100004c28      cbz x0, 0x100004cd8
          0x100004c2c      mov x25, x0
          0x100004c30      adrp x0, segment.__DATA_CONST             ; 0x100010000
          0x100004c34      add x0, x0, 0x8b8                         ; 0x1000108b8
                                                                     ; aav.0x1000108b8
          0x100004c38      mov w1, 0x28                              ; '('
          0x100004c3c      mov w2, 7
          0x100004c40      bl sym.imp.swift_allocObject
          0x100004c44      stp x22, x20, [x0, 0x10]                  ; 0x1000108d0
                                                                     ; aav.0x1000108d0
          0x100004c48      str x24, [x0, 0x20]
          0x100004c4c      adrp x8, 0x100007000
          0x100004c50      add x8, x8, 0xd54
          0x100004c54      stp x8, x0, [var_20h]
          0x100004c58      adrp x8, segment.__DATA_CONST             ; 0x100010000
          0x100004c5c      ldr x8, [x8, 0x1d0]                       ; [0x1000101d0:4]=0
                                                                     ; reloc._NSConcreteStackBlock
          0x100004c60      str x8, [sp]
          0x100004c64      adrp x8, sym.imp.Foundation.Encoding.utf8_...vgZ_ ; 0x10000c000
          0x100004c68      ldr d0, [x8, 0x820]                       ; [0x10000c820:4]=0x42000000 ; section.3.__TEXT.__const
                                                                     [03] -r-x section size 1700 named 3.__TEXT.__const
          0x100004c6c      str d0, [var_0h_2]
          0x100004c70      adrp x8, sym.func.100004000               ; 0x100004000
          0x100004c74      add x8, x8, 0xd1c
          0x100004c78      adrp x9, segment.__DATA_CONST             ; 0x100010000
          0x100004c7c      add x9, x9, 0x8d0                         ; 0x1000108d0
                                                                     ; aav.0x1000108d0
          0x100004c80      stp x8, x9, [var_0hx10]
          0x100004c84      mov x0, sp
          0x100004c88      bl sym.imp._Block_copy
          0x100004c8c      mov x22, x0
          0x100004c90      ldr x26, [var_28h]
          0x100004c94      mov x0, x20
          0x100004c98      bl sym.imp.swift_retain
          0x100004c9c      bl sym.imp.objc_retain_x24
          0x100004ca0      mov x20, x0
          0x100004ca4      mov x0, x26                               ; void *arg0
          0x100004ca8      bl sym.imp.swift_release                  ; void swift_release(void *arg0)
          0x100004cac      mov x0, x25
          0x100004cb0      mov x2, x23
          0x100004cb4      mov w3, 1
          0x100004cb8      mov x4, x22
          0x100004cbc      bl fcn.10000c6c0
          0x100004cc0      mov x0, x22
          0x100004cc4      bl sym.imp._Block_release
          0x100004cc8      bl sym.imp.objc_release_x20
          0x100004ccc      bl sym.imp.objc_release_x23
          0x100004cd0      bl sym.imp.objc_release_x25
      ┌──< 0x100004cd4      b 0x100004cf8
      ││   ; CODE XREF from sym.func.100004ad8 @ 0x100004c28(x)
      │└─> 0x100004cd8      adrp x8, 0x10000d000
          0x100004cdc      add x8, x8, 0xac0                         ; 0x10000dac0 ; "Failed to present, no view controller."
          0x100004ce0      sub x8, x8, 0x20
          0x100004ce4      add x0, x26, 0xb
          0x100004ce8      orr x1, x8, 0x8000000000000000
          0x100004cec      blr x22
          0x100004cf0      bl sym.imp.objc_release_x24
          0x100004cf4      bl sym.imp.objc_release_x23
          ; CODE XREF from sym.func.100004ad8 @ 0x100004cd4(x)
      └──> 0x100004cf8      bl sym.imp.objc_release_x19
           0x100004cfc      bl sym.imp.objc_release_x21
           0x100004d00      ldp x29, x30, [var_70h]
           0x100004d04      ldp x20, x19, [var_60h]
           0x100004d08      ldp x22, x21, [var_50h]
           0x100004d0c      ldp x24, x23, [var_40h]
           0x100004d10      ldp x26, x25, [var_30h]
           0x100004d14      add sp, sp, 0x80                          ; 0x178000
           0x100004d18      ret
  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
            ; CODE XREF from sym.func.100007d54 @ 0x100007d5c(x)
 440: sym.func.1000050c0 (int64_t arg1, int64_t arg2, int64_t arg3);
           0x1000050c0      stp x28, x27, [sp, -0x60]!
           0x1000050c4      stp x26, x25, [var_10h]
           0x1000050c8      stp x24, x23, [var_20h]
           0x1000050cc      stp x22, x21, [var_30h]
           0x1000050d0      stp x20, x19, [var_40h]
           0x1000050d4      stp x29, x30, [var_50h]
           0x1000050d8      add x29, sp, 0x50
           0x1000050dc      mov x19, x2                               ; arg3
           0x1000050e0      mov x20, x1                               ; arg2
           0x1000050e4      mov x24, x0                               ; arg1
           0x1000050e8      mov x0, 0
           0x1000050ec      bl sym.imp.Foundation.URL...VMa
           0x1000050f0      mov x21, x0
           0x1000050f4      ldur x28, [x0, -8]
           0x1000050f8      ldr x8, [x28, 0x40]
           0x1000050fc      mov x9, x8
           0x100005100      adrp x16, segment.__DATA_CONST            ; 0x100010000
           0x100005104      ldr x16, [x16, 0x1d8]                     ; [0x1000101d8:4]=0
                                                                      ; reloc.__chkstk_darwin
           0x100005108      blr x16
           0x10000510c      mov x9, sp
           0x100005110      add x8, x8, 0xf
           0x100005114      and x8, x8, 0xfffffffffffffff0
           0x100005118      sub x22, x9, x8
           0x10000511c      mov sp, x22
           0x100005120      adrp x8, segment.__DATA                   ; 0x100014000
           0x100005124      ldr x8, [x8, 0x338]
           0x100005128      cmn x8, 1
       ┌─< 0x10000512c      b.ne 0x100005248
          ; CODE XREF from sym.func.1000050c0 @ 0x10000525c(x)
      ┌──> 0x100005130      adrp x1, segment.__DATA                   ; 0x100014000
      ╎│   0x100005134      add x1, x1, 0x340                         ; int64_t arg2
      ╎│   0x100005138      mov x0, x21                               ; int64_t arg1
      ╎│   0x10000513c      bl sym.func.100007be4
      ╎│   0x100005140      mov x23, x0
      ╎│   0x100005144      ldr x27, [x28, 0x10]
      ╎│   0x100005148      mov x0, x22
      ╎│   0x10000514c      mov x1, x23
      ╎│   0x100005150      mov x2, x21
      ╎│   0x100005154      blr x27
      ╎│   0x100005158      mov x0, x22                               ; int64_t arg1
      ╎│   0x10000515c      mov x1, 0                                 ; int64_t arg2
      ╎│   0x100005160      mov x2, -0x2000000000000000               ; int64_t arg3
      ╎│   0x100005164      bl sym.func.100005278
      ╎│   0x100005168      mov x25, x0
      ╎│   0x10000516c      mov x26, x1
      ╎│   0x100005170      ldr x28, [x28, 8]
      ╎│   0x100005174      mov x0, x22
      ╎│   0x100005178      mov x1, x21
      ╎│   0x10000517c      blr x28
      ╎│   0x100005180      mov x0, x25
      ╎│   0x100005184      mov x1, x26
      ╎│   0x100005188      blr x24
      ╎│   0x10000518c      mov x0, x26                               ; void *arg0
      ╎│   0x100005190      bl sym.imp.swift_bridgeObjectRelease      ; void swift_bridgeObjectRelease(void *arg0)
      ╎│   0x100005194      adrp x8, segment.__DATA                   ; 0x100014000
      ╎│   0x100005198      ldr x8, [x8, 0x2f8]
      ╎│   0x10000519c      cmn x8, 1
     ┌───< 0x1000051a0      b.ne 0x100005260
     │╎│   ; CODE XREF from sym.func.1000050c0 @ 0x100005274(x)
    ┌────> 0x1000051a4      adrp x1, segment.__DATA                   ; 0x100014000
    ╎│╎│   0x1000051a8      add x1, x1, 0x300                         ; int64_t arg2
    ╎│╎│   0x1000051ac      mov x0, x21                               ; int64_t arg1
    ╎│╎│   0x1000051b0      bl sym.func.100007be4
    ╎│╎│   0x1000051b4      mov x1, x0
    ╎│╎│   0x1000051b8      mov x0, x22
    ╎│╎│   0x1000051bc      mov x2, x21
    ╎│╎│   0x1000051c0      blr x27
    ╎│╎│   0x1000051c4      mov x20, x22
    ╎│╎│   0x1000051c8      bl sym.imp.Foundation.URL._bridgeToObjectiveC.NSURL_...F_ ; Foundation.URL._bridgeToObjectiveC.NSURL(...F)
    ╎│╎│   0x1000051cc      mov x24, x0
    ╎│╎│   0x1000051d0      mov x0, x22
    ╎│╎│   0x1000051d4      mov x1, x21
    ╎│╎│   0x1000051d8      blr x28
    ╎│╎│   0x1000051dc      mov x0, x22
    ╎│╎│   0x1000051e0      mov x1, x23
    ╎│╎│   0x1000051e4      mov x2, x21
    ╎│╎│   0x1000051e8      blr x27
    ╎│╎│   0x1000051ec      bl sym.imp.Foundation.URL._bridgeToObjectiveC.NSURL_...F_ ; Foundation.URL._bridgeToObjectiveC.NSURL(...F)
    ╎│╎│   0x1000051f0      mov x20, x0
    ╎│╎│   0x1000051f4      mov x0, x22
    ╎│╎│   0x1000051f8      mov x1, x21
    ╎│╎│   0x1000051fc      blr x28
    ╎│╎│   0x100005200      mov x0, x19
    ╎│╎│   0x100005204      mov x2, x24
    ╎│╎│   0x100005208      mov x3, x20
    ╎│╎│   0x10000520c      bl fcn.10000c680
    ╎│╎│   0x100005210      mov x29, x29
    ╎│╎│   0x100005214      bl sym.imp.objc_retainAutoreleasedReturnValue ; void objc_retainAutoreleasedReturnValue(void *instance)
    ╎│╎│   0x100005218      mov x19, x0
    ╎│╎│   0x10000521c      bl sym.imp.objc_release_x24
    ╎│╎│   0x100005220      bl sym.imp.objc_release_x20
    ╎│╎│   0x100005224      bl sym.imp.objc_release_x19
    ╎│╎│   0x100005228      sub sp, x29, 0x50
    ╎│╎│   0x10000522c      ldp x29, x30, [var_50h]
    ╎│╎│   0x100005230      ldp x20, x19, [var_40h]
    ╎│╎│   0x100005234      ldp x22, x21, [var_30h]
    ╎│╎│   0x100005238      ldp x24, x23, [var_20h]
    ╎│╎│   0x10000523c      ldp x26, x25, [var_10h]
    ╎│╎│   0x100005240      ldp x28, x27, [sp], 0x60
    ╎│╎│   0x100005244      ret
    ╎│╎│   ; CODE XREF from sym.func.1000050c0 @ 0x10000512c(x)
    ╎│╎└─> 0x100005248      adrp x0, segment.__DATA                   ; 0x100014000
    ╎│╎    0x10000524c      add x0, x0, 0x338
    ╎│╎    0x100005250      adrp x1, sym.func.100004000               ; 0x100004000
    ╎│╎    0x100005254      add x1, x1, 0x120
    ╎│╎    0x100005258      bl sym.imp.swift_once
    ╎│└──< 0x10000525c      b 0x100005130
    ╎│     ; CODE XREF from sym.func.1000050c0 @ 0x1000051a0(x)
    ╎└───> 0x100005260      adrp x0, segment.__DATA                   ; 0x100014000
          0x100005264      add x0, x0, 0x2f8
          0x100005268      adrp x1, sym.func.100004000               ; 0x100004000
          0x10000526c      add x1, x1, 0x348
          0x100005270      bl sym.imp.swift_once
    └────< 0x100005274      b 0x1000051a4

Evaluation

The test fails because the binary contains a reference to allowFileAccessFromFileURLs that is set to true, and the binary also calls loadFileURL:allowingReadAccessToURL:.

In sym.func.100004ad8 (the showWebView function), around 0x100004b54, we can see that the app sets allowFileAccessFromFileURLs = true. This follows the classic Swift-to-Objective-C bridging pattern:

  • mov w0, 1 loads boolean true, then bridges it to an NSNumber via objc_retainAutoreleasedReturnValue.
  • At 0x100004b64, "allowFileAccessFromFileURLs" is constructed as an NSString.
  • setValue:forKey: is called via fcn.10000c780 at 0x100004b8c.

Immediately after, around 0x100004b9c, the app sets allowUniversalAccessFromFileURLs = false using the same pattern:

  • mov w0, 0 loads boolean false, bridged to NSNumber.
  • At 0x100004bac, "allowUniversalAccessFromFileURLs" is constructed as an NSString.
  • setValue:forKey: is called at 0x100004bd0.

The actual call to loadFileURL:allowingReadAccessToURL: is inside the presenter.present completion closure, compiled into sym.func.1000050c0. Earlier in this function, around 0x100005130, the demoRoot static property is resolved and stored in x23:

0x100005130      adrp x1, segment.__DATA                   ; 0x100014000
0x100005134      add x1, x1, 0x340                         ; demoRoot storage
0x100005138      mov x0, x21
0x10000513c      bl sym.func.100007be4                      ; resolve demoRoot static
0x100005140      mov x23, x0                               ; x23 = demoRoot

Then the key sequence leading to the loadFileURL:allowingReadAccessToURL: call at 0x10000520c is:

0x1000051a4      adrp x1, segment.__DATA                   ; 0x100014000
0x1000051a8      add x1, x1, 0x300                         ; indexURL storage
0x1000051ac      mov x0, x21
0x1000051b0      bl sym.func.100007be4                      ; resolve indexURL static
0x1000051b4      mov x1, x0                               ; x1 = indexURL (passed directly)
0x1000051b8      mov x0, x22
0x1000051bc      mov x2, x21
0x1000051c0      blr x27                                   ; initialize Swift URL in x22 from indexURL
0x1000051c8      bl Foundation.URL._bridgeToObjectiveC.NSURL  ; bridge to NSURL
0x1000051cc      mov x24, x0                               ; x24 = NSURL(indexURL) [fileURL arg]
...
0x1000051d8      blr x28                                   ; destroy URL buffer
0x1000051dc      mov x0, x22
0x1000051e0      mov x1, x23                               ; x1 = x23 = demoRoot (from earlier)
0x1000051e4      mov x2, x21
0x1000051e8      blr x27                                   ; initialize Swift URL in x22 from demoRoot
0x1000051ec      bl Foundation.URL._bridgeToObjectiveC.NSURL  ; bridge to NSURL
0x1000051f0      mov x20, x0                               ; x20 = NSURL(demoRoot) [readAccess arg]
...
0x100005204      mov x2, x24                               ; fileURL = NSURL(indexURL)
0x100005208      mov x3, x20                               ; allowingReadAccessTo = NSURL(demoRoot)
0x10000520c      bl fcn.10000c680                          ; loadFileURL:allowingReadAccessToURL:

x2 and x3 are produced from two different static properties. The first URL (x24) comes from the indexURL static (resolved at 0x1000051b0 from [segment.__DATA + 0x300]), while the second URL (x20) comes from the demoRoot static (resolved earlier at 0x10000513c from [segment.__DATA + 0x340] and stored in x23). This confirms that loadFileURL is called with indexURL as the first argument (the file to load) and demoRoot as the second argument (the read-access boundary), as written in the Swift source.

Because allowingReadAccessTo: demoRoot grants WebKit native file access to the entire demoRoot directory, and allowFileAccessFromFileURLs = true independently grants JavaScript the ability to issue fetch() and XMLHttpRequest calls to file:// URLs within that boundary, JavaScript running in the WebView can read both api-key.txt and other.html.

iOS Simulator vs. Physical Device

The iOS Simulator is more permissive than a physical device when it comes to file:// access in WebViews. On the Simulator, JavaScript can reach local files via fetch or XHR even when allowingReadAccessTo is restricted to a single file (e.g., indexURL). On a physical device, WebKit strictly enforces the sandbox boundary set by allowingReadAccessTo, so JavaScript can only access files within the directory (or file) specified by that parameter. Always validate WebView file-access behavior on a real device to get accurate results.