vlambda博客
学习文章列表

全局开启 iOS / mac 的 WebView 调试


确实相当实用,除了测出来一些第三方移动 App 的问题去水漏洞赏金,还搞出来一套 macOS <= 10.14 Safari 的沙箱逃逸,一套 iOS <= 14.3 远程代码执行 0day。




Safari 有一个内置的前端调试器,假定读者已经有一定基础,此处不再赘述。通过局域网或者 USB 连接 iOS,并在手机上启用 Safari 远程调试之后,便可以用电脑端直接打开 js 控制台:




有没有发现这张图有些不同?


正常情况下这个功能只会对 MobileSafari 和 Xcode 编译运行的 App 有效。比如系统内置的 App 或者 AppStore 下载的应用,是不会出现在菜单当中的。


因为这台手机被越狱过,并安装了一个动态补丁,对全局开启了 WebView 调试功能。




macOS 和 iOS 上的远程调试都是由一个 webinspectord 服务处理的。


WebView 相关的组件会和这个服务进程通信。当应用程序的数字签名当中有如下 entitlement 之一时,便会开启远程调试:


  • com.apple.security.get-task-allow

  • com.apple.private.webinspector.allow-remote-inspection

  • com.apple.private.webinspector.allow-carrier-remote-inspection

  • com.apple.webinspector.allow


比如系统自带的 Safari 浏览器就有 com.apple.private.webinspector.allow-remote-inspection,而所有通过 Xcode 运行的真机调试应用都会有 com.apple.security.get-task-allow。



const SecTaskCopyValueForEntitlement = Module.findExportByName(null, 'SecTaskCopyValueForEntitlement');const SecTaskCopyDebugDescription = new NativeFunction(DebugSymbol.getFunctionByName('SecTaskCopyDebugDescription'), 'pointer', ['pointer']);const CFRelease = new NativeFunction(Module.findExportByName(null, 'CFRelease'), 'void', ['pointer']);const CFStringGetCStringPtr = new NativeFunction(Module.findExportByName(null, 'CFStringGetCStringPtr'), 'pointer', ['pointer', 'uint32']);const kCFStringEncodingUTF8 = 0x08000100;const expected = [ 'com.apple.security.get-task-allow', 'com.apple.private.webinspector.allow-remote-inspection', 'com.apple.private.webinspector.allow-carrier-remote-inspection', 'com.apple.webinspector.allow'];Interceptor.attach(SecTaskCopyValueForEntitlement, { onEnter: function (args) { const p = CFStringGetCStringPtr(args[1], kCFStringEncodingUTF8) const ent = Memory.readUtf8String(p) if (expected.indexOf(ent) > -1) { this.shouldOverride = true
const description = SecTaskCopyDebugDescription(args[0]) if (!description.isNull()) { const pDesc = CFStringGetCStringPtr(description, kCFStringEncodingUTF8) console.log('enable inspector for', Memory.readUtf8String(pDesc)) CFRelease(description) } } }, onLeave: function (retVal) { if (!this.shouldOverride) return
if (!retVal.isNull()) CFRelease(retVal)
retVal.replace(ObjC.classes.NSNumber.numberWithBool_(1)); }})


这个脚本可以在 macOS 和 iOS 上使用。如果目标的 WebView 或者 JavaScript context 没有出现在列表当中,请尝试重新运行对应应用并重新打开桌面端的 Safari 浏览器。


为了更省事,笔者还实现了一个越狱插件:


https://github.com/ChiChou/GlobalWebInspect


使用这个越狱插件(tweak)要求读者会安装 deb 包,或者自行配置 THEOS 编译运行。这样就不用每次手动运行 frida,只要手机越狱就会加载插件到 webinspectord 进程完成补丁。




使用脚本 / 插件之后,可以观察到 macOS 系统内置的词典、电子书应用都用到了 WebView,可以直接在 js 控制台测试。


而在 iOS 上,来自 AppStore 的应用也会允许调试,对于分析客户端的 XSS、JavaScript bridge 相关的安全漏洞非常实用。


除了 WebView 之外,iOS App 还经常用到系统自带的 JavaScript 解释器用来实现动态的脚本功能。例如 Weex、React Native 和一些 App 的“小程序”应用,就是使用了 JSContext 来运行 JavaScript 代码直接与二进制库进行交互,实现使用一套 js 业务代码在多个平台上无缝运行。包括很多应用都在用的 JS 热补丁,也会出现在调试列表中。


有意思的是,虽然苹果自己的审核对 JSPatch 等方案尝试过限制,但苹果自己也这么干——从苹果的服务器动态下载 js 代码来实现客户端的逻辑。在 AppStore、Books 和自带音乐播放器当中都能看到。有兴趣的读者可以自行研究。


如下是直接调试某互联网大厂第三方 App 当中内嵌的“小程序”:


全局开启 iOS / mac 的 WebView 调试



除了原生的 macOS + iOS 远程调试搭配之外,有一个开源应用 remotedebug-ios-webkit-adapter 实现了 iOS 的 USB 调试协议,也能在 Windows 和 Linux 上基于 Chrome 的前端做远程调试:


https://github.com/RemoteDebug/remotedebug-ios-webkit-adapter


不过根据笔者实际的体验,这个软件效果并不理想,毕竟 WebKit 和 Chrome 的调试协议并不能完全做兼容。最好还是尝试原生的方案。这个方案只能支持 WebView 的调试,而原生 Safari 还会显示应用当中的 JSContext,对于基于 Weex 等实现的“小程序”平台也是可以用的。


remotedebug-ios-webkit-adapter 现在尝试商业化,开源分支不再维护,而转向收费版的 Inspect 应用。虽说白嫖可耻,可这个东西又是一个几百兆的 Electron 套壳应用,实在令人提不起兴趣。




以下是通过这个插件发现的多个国产应用的 UXSS 漏洞:


全局开启 iOS / mac 的 WebView 调试


iOS 内置词典的跨进程 XSS:



以后也会介绍一些具体的案例,欢迎关注。