if(ObjC.available){// 1. Hooking Simple Setters (Booleans)// We can use simpler swizzling here for stabilityconstWKPreferences=ObjC.classes.WKPreferences;constWKWebViewConfiguration=ObjC.classes.WKWebViewConfiguration;constWKWebView=ObjC.classes.WKWebView;// Helper to hook boolean settersfunctionhookBoolMethod(klass,selector,label){if(!klass){console.log(`[!] Skipping ${label}, class is unavailable.`);return;}constmethod=klass[selector];if(!method||!method.implementation){console.log(`[!] Skipping ${label}, selector ${selector} is unavailable.`);return;}constoldImpl=method.implementation;method.implementation=ObjC.implement(method,function(self,sel,value){console.log(`[${label}] = ${value}`);oldImpl(self,sel,value);});}hookBoolMethod(WKPreferences,"- _setAllowFileAccessFromFileURLs:","WKPreferences: allowFileAccess");hookBoolMethod(WKWebViewConfiguration,"- _setAllowUniversalAccessFromFileURLs:","WKWebViewConfig: universalAccess");hookBoolMethod(WKPreferences,"- setJavaScriptEnabled:","WKPreferences: jsEnabled");// 2. Hooking loadFileURL (The tricky one)if(!WKWebView){console.log("[!] Skipping WKWebView loadFileURL hook, class is unavailable.");}else{constloadMethod=WKWebView["- loadFileURL:allowingReadAccessToURL:"];if(!loadMethod||!loadMethod.implementation){console.log("[!] Skipping WKWebView loadFileURL hook, selector is unavailable.");}else{constoldLoadImpl=loadMethod.implementation;loadMethod.implementation=ObjC.implement(loadMethod,function(self,sel,fileURLPtr,readAccessPtr){try{// In ObjC.implement, Frida automatically wraps pointers as ObjC.Object if possible,// but we use ObjC.Object() here to ensure we can call methods on them.constfileURL=newObjC.Object(fileURLPtr);constreadAccess=newObjC.Object(readAccessPtr);console.log("[WKWebView] loadFileURL called");// Use absoluteString safelyconsole.log(" fileURL: "+fileURL.absoluteString().toString());console.log(" allowingReadAccessTo: "+readAccess.absoluteString().toString());}catch(e){console.log("[!] Error reading URLs: "+e);}// Always call the original implementation and return its valuereturnoldLoadImpl(self,sel,fileURLPtr,readAccessPtr);});}}console.log("[+] Hooks deployed successfully.");}else{console.log("Objective-C runtime is not available.");}
The Frida script hooks several WebKit APIs at runtime and logs their arguments when they are invoked:
WKPreferences _setAllowFileAccessFromFileURLs: to detect when the app enables access from file:// pages to other local files.
WKWebViewConfiguration _setAllowUniversalAccessFromFileURLs: to detect when the app allows file:// pages to access any origin.
WKPreferences setJavaScriptEnabled: to determine whether JavaScript execution is enabled in the WebView.
WKWebView loadFileURL:allowingReadAccessToURL: to identify when the app loads local files into a WebView and to capture both the loaded file and the granted read access scope.
The output shows that the application enables file access from file:// URLs in the WebView and loads a local HTML file from a demo directory under the app's caches directory.
The test fails because the application enables file access from file:// URLs for a WKWebView that loads local file:// content from the app's caches directory.
Specifically:
allowFileAccessFromFileURLs is set to true, allowing JavaScript running in a file:// page to access other local files within the granted read scope.
The WebView loads a local HTML file and grants read access to the demoRoot directory under the application's caches directory.
These settings weaken the isolation normally applied to local content and increase the impact of WebView vulnerabilities. If attacker-controlled JavaScript executes in the local page context, it may access files within the granted read scope and potentially exfiltrate them to remote servers.