Tweak 全局开启任意 App 的 WebView 调试


#1

iOS 7 之后的 Safari 提供了远程调试设备上网页的功能。在设备和 mac 端的 Safari 上均开启开发者功能之后,可以用 USB 连接手机,然后在 Develop 菜单中选择对应的页面打开 WebInspector:

b401ef22702ede3ec6e03fbf499dad95

先说明另一种屡试不爽的办法,砸壳 -> 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 拖回来分析。

  • 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-inspection
LABEL_14:
    v7 = 1;
  }
  else
  {
    v10 = v4[1];
    v9 = *v4;
    v7 = MEMORY[0x18F5A547C](v5, selRef__usedDevelopmentProvisioningProfile_[0], &v9); // 开发版本 App 同样放行
  }
  MEMORY[0x18F5A5484](v6);
  return v7;
}

这正是检查是否允许调试的关键函数。

使用 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 WebInspect
now patch it
com.mx.MxBrowser-iPhone does not allow WebInspect
now patch it
com.apple.WebKit.WebContent allows WebInspect
com.mx.MxBrowser-iPhone does not allow WebInspect
now patch it
com.apple.WebKit.WebContent allows WebInspect
com.mx.MxBrowser-iPhone does not allow WebInspect
now 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)_allowApplication:(void *)ignored bundleIdentifier:(NSString *)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 版本的兼容性还有待进一步分析。

P.S.
顺便求问一下这样需要判断兼容性来 hook 不同类名的 Tweak 应该怎么写呢?


#2

我6s 10.0.2 的dyld cache好像没有RWIRelayDelegateIOS也没有_allowApplication:bundleIdentifier:, 你是什么版本的


#3

感谢发现这个问题。

iOS 9 的类名是 WebInspectorRelayDelegateIOS,10 我手上没设备回头找一下


#5

下了 iOS 10.0.3 的 ipsw 验证了一下,函数不在链接库里,直接逆 webinspectord 就能看到 WebInspectorRelayDelegateIOS - (_Bool)_allowApplication:(CDStruct_6ad76789)arg1 bundleIdentifier:(id)arg2;


#6

兄弟 10.0.2 是hook什么方法 你找到了吗


#7

再往下跟一层, 所有版本用到的API都是一样的


#8

没明白。。


#9

hasRemoteInspectorEntitlement
就是这个的实现,都是用到一个sec api


#10

哦,你说 hook SecTask 啊


#11
const SecTaskCopyValueForEntitlement = Module.findExportByName(null, 'SecTaskCopyValueForEntitlement');
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
  },
  onLeave: function(retVal) {
    if (!this.shouldOverride)
      return
    if (!retVal.isNull())
      CFRelease(retVal);
    retVal.replace(ObjC.classes.NSNumber.numberWithBool_(1));
  }
})

这样应该可以通用了

Tweak:


macOS 调试任意APP webview
#12

有人反馈 iOS 9 不兼容,我看了下 9 下面 webinspectord 的 bundle 没有 id,所以匹配不到 filter

[iPad 4::webinspectord]-> ObjC.classes.NSBundle.bundleForClass_(ObjC.classes.WebInspector
RelayDelegateIOS) + ''
"NSBundle </usr/libexec> (loaded)"

针对这种东西大佬们以前是怎么做的? cc @snakeninny @Zhang @AloneMonkey


#13

没有bundleid就Executables咯


#14

Executables,一个Array,写主程序文件名即可


关于ios webview的调试其它APP的JS疑问
#15

多谢大佬。不过我试了下 iOS9 的适配解决了,iOS 11.1.2 莫名其妙不能加载 tweak。不知道是我手机刚好抽风还是别的问题


#16

完美,11.3.1测试可以,感谢大佬


请教各位大大 如何在自己的程序中拦截safari 发出去的请求