越獄環境全局開啟任意 iOS App 的 WebView 調試
題圖總覺得哪裡不對?沒錯,這是在用 Safari 的 WebInspector 調試 Chrome iOS 端的網頁內容。
iOS 7 之後的 Safari 提供了遠程調試設備上網頁的功能。在設備和 mac 端的 Safari 上均開啟開發者功能之後,可以用 USB 連接手機,然後在 Develop 菜單中選擇對應的頁面打開 WebInspector:
先說明另一種屢試不爽的辦法,砸殼 -> MonkeyDev 重打包。
然後我們來看越獄設備下如何全局地開啟。
App 是否支持 WebInspector 是通過 entitlement 控制的。已知將 com.apple.security.get-task-allow
設置為 true
之後會允許調試 WebView。Xcode 編譯出來的調試版本 App 都會帶上這個 entitlement,這也是 lldb 真機調試必須的配置。
MobileSafari 肯定不允許 lldb 調試,不過可以看到(iOS 11.1.2)它註冊了一個這樣 entitlement:
在 iOS 設備上啟用了 WebInspector 之後會出現一個 webinspectord
的守護進程。關於遠程調試實現的一些技術細節可以參考 Webkit遠程調試協議實戰。幾年前就有的文章,在此膜拜一下。
上面提到的文章同樣也沒有解決 entitlement 的條件,還是需要自己逆向一下。這個進程的代碼只有一點點:
其實是放在鏈接庫里了。
把 dyld_shared_cache 拖回來分析。
之前提到兩個 entitlement 是很明顯的特徵。
com.apple.private.webinspector.allow-remote-inspection
com.apple.security.get-task-allow
很快定位到字元串表
通過交叉引用來到如下函數:
bool __cdecl -[RWIRelayDelegateIOS _allowApplication:bundleIdentifier:](id a1, SEL a2, struct {unsigned int var0[8];} *a3, id a4){ __int128 *v4; // x21 id v5; // x20 __int64 v6; // x19 char v7; // w20 __int128 v9; // [xsp+0h] [xbp-80h] __int128 v10; // [xsp+10h] [xbp-70h] __int128 v11; // [xsp+20h] [xbp-60h] __int128 v12; // [xsp+30h] [xbp-50h] __int128 v13; // [xsp+40h] [xbp-40h] __int128 v14; // [xsp+50h] [xbp-30h] v4 = (__int128 *)a3; v5 = a1; v6 = MEMORY[0x18F5A5488](a4, a2); if ( qword_1B0981AD0 != -1 ) dispatch_once(&qword_1B0981AD0, &unk_1AC56C870); if ( byte_1B0981AC8 ) goto LABEL_14; v14 = v4[1]; v13 = *v4; if ( MEMORY[0x18F5A547C](v5, selRef__hasRemoteInspectorEntitlement_[0], &v13) & 1 ) // 開啟了 allow-remote-inspection goto LABEL_14; if ( qword_1B0981AE0 != -1 ) dispatch_once(&qword_1B0981AE0, &unk_1AC56C8B0); if ( byte_1B0981AD8 && (v12 = v4[1], v11 = *v4, MEMORY[0x18F5A547C](v5, selRef__hasCarrierRemoteInspectorEntitlement_[0], &v11) & 1) ) { // 特定條件下檢查的是 com.apple.private.webinspector.allow-carrier-remote-inspectionLABEL_14: v7 = 1; } else { v10 = v4[1]; v9 = *v4; v7 = MEMORY[0x18F5A547C](v5, selRef__usedDevelopmentProvisioningProfile_[0], &v9); // 開發版本 App 同樣放行 } MEMORY[0x18F5A5484](v6); return v7;}
這正是檢查是否允許 WebInspector 的關鍵函數。
使用 frida hook 框架簡單驗證一下:
? passionfruit git:(master) ? frida -U webinspectord ____ / _ | Frida 10.6.61 - A world-class dynamic instrumentation toolkit | (_| | > _ | Commands: /_/ |_| help -> Displays the help system . . . . object? -> Display information about object . . . . exit/quit -> Exit . . . . . . . . More info at http://www.frida.re/docs/home/[iPad 4::webinspectord]-> Interceptor.attach(ObjC.classes.RWIRelayDelegateIOS[- _allowApplication:bundleIdentifier:].implementation, { onEnter: function(args) { this.bundleId = new ObjC.Object(args[3]); }, onLeave: function(retVal) { const allow = !retVal.equals(NULL) console.log(this.bundleId + (allow ? allows : does not allow) + WebInspect) if (!allow) { console.log(now patch it); retVal.replace(ptr(1)); } } });{}[iPad 4::webinspectord]-> com.tencent.mipadqq does not allow WebInspectnow patch itcom.mx.MxBrowser-iPhone does not allow WebInspectnow patch itcom.apple.WebKit.WebContent allows WebInspectcom.mx.MxBrowser-iPhone does not allow WebInspectnow patch itcom.apple.WebKit.WebContent allows WebInspectcom.mx.MxBrowser-iPhone does not allow WebInspectnow patch it
每次啟動新應用的時候都會調用這個函數做一次判斷,將其返回值 patch 為 TRUE,第三方瀏覽器出現在了 Safari 的調試列表中:
另外 macOS 上的 WebInspector 也有類似函數 __int64 __fastcall -[RWIRelayDelegateMac _allowApplication:bundleIdentifier:]
,檢查的 entitlement 鍵名略有不同。
用 THEOS 寫成 Tweak,簡單粗暴:
{ Filter = { Bundles = ( "com.apple.webinspectord" ); }; }
Tweak.xm
%hook RWIRelayDelegateIOS- (BOOL)_allowApplicationvoid *)ignored bundleIdentifierNSString *)bundleId { %log; NSLog(@"Force WebInspect enable for %@", bundleId); return TRUE;}%end
更新:
本文在 11.1.2 和 10.3.3 上測試通過。
有同學反饋 10.0.2 沒有 RWIRelayDelegateIOS 類,我驗證了一下 10.0.3 的 IPSW 固件,函數是一樣的。只不過直接編譯到 webinspectord 而不是鏈接 WebInspector.framework。拆分鏈接庫應該是 iOS 11 開始的。
在 iOS 9.3.3 上類名不一樣,應該對 WebInspectorRelayDelegateIOS 的 - _allowApplication:bundleIdentifier: 進行 hook。其他 iOS 版本的兼容性還有待進一步分析。
推薦閱讀:
※怎樣設計以用戶為中心的WEB表單?
※我理解的前端性能 & 優化
※2018-02-01第一節課
※WEB前端開發人員須知的常見瀏覽器兼容問題及解決技巧
※前端技能學習導航