Xcode&iOS重学

Advanced.Apple.Debugging.&.R

2018-03-23  本文已影响616人  谁动了我的芝麻糖

Chap 16: Exploring and Method Swizzling Objective-C Frameworks

边看书边实践,写的一个流水账,做一下记录,就理解了这么多 :)
iOS 11之前可以用比较简单的方法显示出UIDebuggingInformation'Overlay,据说iOS 11加了一些checks导致没那么容易了,看这里哦。这本书的chap16讲了在模拟器里,怎样bypass苹果的那些checks,有两个方法,一种使用lldb;另一种使用Method Swizzling在代码中完成。模拟器是 iOS 11.2

ios 11 bypass for UIDebuggingInformationOverlay:
1, 在控制台,使用lldb连接一个APP
# lldb -n MobileSlideShow  // MobileSlideShow对应Photos App

需要attach成功

2, 查询 UIDebuggingInformationOverlay:
(lldb) image lookup -rn UIDebuggingInformationOverlay

输出查询到的内容,98 matches found, 这个正则查询写的不好,匹配到的信息不关键,可以使用之前写好的一个自定义命令(chap8)

3,使用自定义methods命令查询 UIDebuggingInformationOverlay:
(lldb) methods UIDebuggingInformationOverlay
<UIDebuggingInformationOverlay: 0x108c4f2a0>
in UIDebuggingInformationOverlay:
        Class Methods:
        + (void) prepareDebuggingOverlay; (0x10826c036)
        ...
        Properties:
        ...
        Instance Methods:
        ...
        - (id) init; (0x10826bf62)
        ...

注意到其中的init方法所在的address

4, 查询init函数对应的汇编代码:
(lldb) dd 0x10826bf62
UIKit, -[UIDebuggingInformationOverlay init]
...
    6    0x10826bf70 <+14>:   cmp   qword ptr [rip + 0xa04470],  -0x1 UIDebuggingOverlayIsEnabled.__overlayIsEnabled + 7 ; 0x108c703e8 UIKit.__DATA.__bss
   *7    0x10826bf78 <+22>:   jne   0x10826bfe4 <+130>
...

或者

(lldb) disassemble -n "-[UIDebuggingInformationOverlay init]"
UIKit`-[UIDebuggingInformationOverlay init]:
...
    0x10826bf70 <+14>:  cmp    qword ptr [rip + 0xa04470], -0x1 ; UIDebuggingOverlayIsEnabled.__overlayIsEnabled + 7
    0x10826bf78 <+22>:  jne    0x10826bfe4
...

或者用下面的lldb命令获取init函数反汇编的前10行:

(lldb) disassemble -n "-[UIDebuggingInformationOverlay init]" -c10
UIKit`-[UIDebuggingInformationOverlay init]:
...
    0x10826bf70 <+14>: cmp    qword ptr [rip + 0xa04470], -0x1 ; UIDebuggingOverlayIsEnabled.__overlayIsEnabled + 7
    0x10826bf78 <+22>: jne    0x10826bfe4               ; <+130>
...

注意其中<+14>对应的行,此行提到的symbol的名称是错误的,找到正确的symbol名的方法按照下面的步骤。

5, 有两个方法获取到对应的symbol的地址(然后根据地址查找正确的名称):

1)计算地址:

注意上述汇编代码中 [rip + 0xa04470], rip 是当前语句的下一句,rip=0x10826bf78
rip + 0xa04470 = 0x10826bf78 + 0xa04470 = 0x108c703e8

2)通过lldb的image命令查看:

(lldb) image lookup -vs UIDebuggingOverlayIsEnabled.__overlayIsEnabled
1 symbols match 'UIDebuggingOverlayIsEnabled.__overlayIsEnabled' in /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/UIKit.framework/UIKit:
        Address: UIKit[0x00000000015c53e0] (UIKit.__DATA.__bss + 24960)
        Summary: UIKit`UIDebuggingOverlayIsEnabled.__overlayIsEnabled
         Module: file = "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/UIKit.framework/UIKit", arch = "x86_64"
         Symbol: id = {0x0001bb0f}, range = [0x0000000108c703e0-0x0000000108c703e8), name="UIDebuggingOverlayIsEnabled.__overlayIsEnabled"

注意上述结果中的 range 字段后面的值,与1)中计算出来的值相等

6, 使用lldb的image命令,查看对应地址的symbol
(lldb) image lookup -a 0x0000000108c703e8 
或者
 (lldb) image lookup -a 0x108c703e8
Address: UIKit[0x00000000015c53e8] (UIKit.__DATA.__bss + 24968)
      Summary: UIKit`UIDebuggingOverlayIsEnabled.onceToken

这是真正要找的symbol名, app在此处完成是否有debug overlay的检查.

7, 检查此地址存放的变量值
(lldb) x/gx 0x0000000108c703e8
0x108c703e8: 0xffffffffffffffff

如果看到的是 -1,说明之前执行过[UIDebuggingInformationOverlay new];
如果没有执行过这一语句,此时值为 0

为什么是-1就是执行过? 为什么改成-1就能绕过这个check?

因为,dispatch_once_t 变量初始值为1,类似于static,当dispatch_once执行过一次之后,变量被设置为 -1,然后就不会再执行这个逻辑了。

因此,如果能把此值设置为 -1 ,即可以跳过dispatch_once包含的逻辑,而苹果的这个check是在dispatch_once里执行的。
8, 修改这个值
(lldb) mem write 0x0000000108c703e8 0x0 -s 8   // 修改为 0
(lldb) x/gx 0x0000000108c703e8  // 查看修改结果
0x108c703e8: 0x0000000000000000
(lldb) mem write 0x0000000108c703e8 0xffffffffffffffff -s 8  // 修改为 -1
(lldb) x/gx 0x0000000108c703e8  // 查看修改结果
0x108c703e8: 0xffffffffffffffff

或者使用po写入:

(lldb) po *(long *)0x0000000108c703e8=0  // 修改为0
<nil>
(lldb) x/gx 0x0000000108c703e8  // 查看结果
0x108c703e8: 0x0000000000000000
(lldb) po *(long *)0x0000000108c703e8=-1  // 修改为-1
-1
(lldb) x/gx 0x0000000108c703e8   // 查看结果
0x108c703e8: 0xffffffffffffffff

==== 以上方法,可以跳过第一个check:dispatch_once block

9, 再次查看前10行汇编代码:
(lldb) disassemble -n "-[UIDebuggingInformationOverlay init]" -c1

最后两行:

    0x10826bf7a <+24>: cmp    byte ptr [rip + 0xa0445f], 0x0 ; mainHandler.onceToken + 7
    0x10826bf81 <+31>: je     0x10826bfcc               ; <+106>

此处 mainHandler.onceToken symbol名称依然是错误的,使用前面的方法找到正确的名称:

rip + 0xa0445f = 0x10826bf81 + 0xa0445f = 0x108c703e0
(lldb) image lookup -a 0x108c703e0
      Address: UIKit[0x00000000015c53e0] (UIKit.__DATA.__bss + 24960)
      Summary: UIKit`UIDebuggingOverlayIsEnabled.__overlayIsEnabled

这是真正要找的symbol名。同样的,要绕过这个check,需要修改它的值为-1

10, 修改这个值
(lldb) x/gx 0x108c703e0
0x108c703e0: 0x0000000000000000
(lldb) po *(long *)0x108c703e0=-1
-1
(lldb) x/gx 0x108c703e0
0x108c703e0: 0xffffffffffffffff
11, bypass两个 dispatch_once 之后(设置两个-1值),可以检查是否有所改变
(lldb) po [UIDebuggingInformationOverlay new]
<UIDebuggingInformationOverlay: 0x7fd1ea449ee0; frame = (0 0; 375 812); hidden = YES; gestureRecognizers = <NSArray: 0x600000a4b670>; layer = <UIWindowLayer: 0x600000a38b60>>

不再是之前执行时候的nil,而有值了

12, 检查overlay方法是否valid
(lldb) po [UIDebuggingInformationOverlay overlay]
<UIDebuggingInformationOverlay: 0x7fd1ea573590; frame = (0 0; 375 812); hidden = YES; gestureRecognizers = <NSArray: 0x60400085cd40>; layer = <UIWindowLayer: 0x604000635e60>>
13, 确认11,12两步输出都有值而不是nil, 如果仍然没有且肯定前面的操作是正确的,那么可能苹果又增加了check~
14, 接下来就是显示出来这个Overlay
(lldb) po [[UIDebuggingInformationOverlay overlay] toggleVisibility]
0x000060400085dca0

(lldb) continue
Process 24179 resuming

打开模拟器,出现了Overlay但是是blank的,没有截图,可以想像一下,一个空的没内容的window。
原因是没有调用+[UIDebuggingInformationOverlay prepareDebuggingOverlay]

15, 查看prepareDebuggingOverlay方法的汇编代码
(lldb) dd 0x10826c036  // 这个地址是在 methods UIDebuggingInformationOverlay 中查看到的此方法对应的地址
...
   *8    0x10826c044 <+14>:   call  0x10826cfe3  ; _UIGetDebuggingOverlayEnabled
    9    0x10826c049 <+19>:   test  al,  al
   *10   0x10826c04b <+21>:   je    0x10826c154 <+286>
...

注意到这几句,执行_UIGetDebuggingOverlayEnabled 后,检查它的返回值(al是存放返回值的寄存器RAX的低字节)。
如果这个AL里的值是0,直接跳到 0x10826c154 <+286>
而这个偏移对应的是:

   *75   0x10826c152 <+284>:  jmp   rax
    76   0x10826c154 <+286>:  add   rsp,  0x8

是函数 prepareDebuggingOverlay 的结尾

16, 因此,在 _UIGetDebuggingOverlayEnabled 函数中设置断点,然后setp over(finish), 到它返回后的下一句
17, 设置断点
(lldb) b _UIGetDebuggingOverlayEnabled
18, 继续执行代码, 等待进入断点
(lldb) exp -i0 -O -- [UIDebuggingInformationOverlay prepareDebuggingOverlay]

如果之前程序不在中断模式,会报error,需要执行 process interrupt, 中断程序, 然后再执行此句, 进入断点后执行finish进行step over:

(lldb) finish 

程序回到prepareDebuggingOverlay方法, offset 19,测试AL之前

19,打印AL寄存器的值
(unsigned char) $10 = 0x00   // test满足条件,会导致函数直接return

因此, 需要修改这个寄存器的值:

(lldb) po $al=0xff
'�'
(lldb) p/x $al
(unsigned char) $12 = 0xff

通过si命令验证一下, 连续执行两个si:

(lldb) si
Process 24179 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = instruction step into
    frame #0: 0x000000010826c04b UIKit` +[UIDebuggingInformationOverlay prepareDebuggingOverlay]  + 21
UIKit`+[UIDebuggingInformationOverlay prepareDebuggingOverlay]:
->  0x10826c04b <+21>: je     0x10826c154               ; <+286>
    0x10826c051 <+27>: lea    rax, [rip + 0xa05800]     ; UIApp
    0x10826c058 <+34>: mov    rdi, qword ptr [rax]
    0x10826c05b <+37>: mov    rsi, qword ptr [rip + 0x951e9e] ; "statusBarWindow"
    0x10826c062 <+44>: mov    r13, qword ptr [rip + 0x5d859f] ; (void *)0x000000010a866940: objc_msgSend
    0x10826c069 <+51>: call   r13
    0x10826c06c <+54>: mov    rdi, rax
    0x10826c06f <+57>: call   0x108556136               ; symbol stub for: objc_retainAutoreleasedReturnValue
Target 0: (MobileSlideShow) stopped.
(lldb) si
Process 24179 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = instruction step into
    frame #0: 0x000000010826c051 UIKit` +[UIDebuggingInformationOverlay prepareDebuggingOverlay]  + 27
UIKit`+[UIDebuggingInformationOverlay prepareDebuggingOverlay]:
->  0x10826c051 <+27>: lea    rax, [rip + 0xa05800]     ; UIApp
    0x10826c058 <+34>: mov    rdi, qword ptr [rax]
    0x10826c05b <+37>: mov    rsi, qword ptr [rip + 0x951e9e] ; "statusBarWindow"
    0x10826c062 <+44>: mov    r13, qword ptr [rip + 0x5d859f] ; (void *)0x000000010a866940: objc_msgSend
    0x10826c069 <+51>: call   r13
    0x10826c06c <+54>: mov    rdi, rax
    0x10826c06f <+57>: call   0x108556136               ; symbol stub for: objc_retainAutoreleasedReturnValue
    0x10826c074 <+62>: mov    qword ptr [rbp - 0x30], rax
Target 0: (MobileSlideShow) stopped.

说明是继续向下执行而不是return了。

20, 继续执行程序
(lldb) continue

此时对于lldb已经没什么可做的了,已经修改完需要修改的寄存器的值。但是,还需要保证程序是在pause的状态, 后续还有命令要执行。

后续的步骤是为了具体找到触发弹出此Overlay的触发条件-方法(手势),之前在看到prepareDebuggingOverla汇编代码的时候,已经可以看到注册了手势,需要执行下面步骤的查看,确认手势handler执行具体display overlay的条件。

21, 通过以上prepareDebuggingOverlay汇编代码, 观察到启动overlay时注册了两个touchtap statusbar的手势(两个手指),

因此需要查看这个手势的handler。
之前methods UIDebuggingInformationOverlay中与手势handler相关的类名称为:
UIDebuggingInformationOverlayInvokeGestureHandler, 使用methods命令查看这个handler中的方法:

(lldb) methods UIDebuggingInformationOverlayInvokeGestureHandler
<UIDebuggingInformationOverlayInvokeGestureHandler: 0x108c4f278>:
in UIDebuggingInformationOverlayInvokeGestureHandler:
        Class Methods:
            + (id) mainHandler; (0x10826bc36)
        Properties:
            @property (readonly) unsigned long hash;
            @property (readonly) Class superclass;
            @property (readonly, copy) NSString* description;
            @property (readonly, copy) NSString* debugDescription;
        Instance Methods:
            - (BOOL) gestureRecognizer:(id)arg1 shouldRecognizeSimultaneouslyWithGestureRecognizer:(id)arg2; (0x10826bf5a)
            - (void) _handleActivationGesture:(id)arg1; (0x10826bca9)
(NSObject ...) 

注意到处理手势的方法:

(lldb) dd 0x10826bca9
UIKit, -[UIDebuggingInformationOverlayInvokeGestureHandler _handleActivationGesture:]
...
    7    0x10826bcb7 <+14>:   mov   rsi,  qword ptr [rip + 0x94fc62]  ; "state"
    8    0x10826bcbe <+21>:   mov   rdi,  rdx 
   *9    0x10826bcc1 <+24>:   call  qword ptr [rip + 0x5d8941] (void *)0x000000010a866940: objc_msgSend ; 0x108844608 UIKit.__DATA.__got
    10   0x10826bcc7 <+30>:   cmp   rax,  0x3
   *11   0x10826bccb <+34>:   jne   0x10826be0f <+358>
...

注意到,UITapGestureRecognizer实例是作为第一个(only)参数(RDI)传到handler中的, <+24>调用objc_msgSend时, 当前RDI是它的第一个参数,也就是UITapGestureRecognizer的实例, 第二个参数是存放在RSI中的,也就是state这个字符串, 因此, <+24>这句是获取这个gesture的state (RDI=gesture实例,RSI=state, 然后 objc_msgSend),
返回值放入RAX,RAX与0x3比较,如果相等继续向下执行,如果不等于0x3,跳到<+358>return
而在手势状态的定义中,0x3在gesute status中代表UIGestureRecognizerStateEnded.
这意味着需要两个手指在status bar上(注意是sttus bar,而不是navigation bar) 进行tap动作,可以触发这个overlay(touches和taps的设置在prepareDebuggingOverlay汇编代码里可以看到)

22, 模拟器两个手指tap?

平时不用模拟器,现在抓了瞎~~

按住Option, 移动鼠标到模拟器上, 出现了两个手指, 然后移动鼠标, 使这两个手指位置水平且有一定距离(因为要点的是iPhone X的status bar, 中间有齐刘海~~)。然后按住Shift移动这两个手指到status bar 上, 点击,完成:)

出现了期待的UIDebuggingInformationOverlay窗口:


Simulator Screen Shot - iPhone X - 2018-03-23 at 15.19.03.png
23,代码方法补充

使用代码实现的方法,看书和示例代码就好了。只是有一点,书上在The final push 一节之前说,会显示一个空白的OverlayWindow,我的实践结果是此时点按钮什么都不会出现,没有blank窗口。仔细分析,我想应该是没用调用overlay的 toggleVisibility 方法,所以虽然使用Method Swizzling绕过了苹果的check,但是并不会显示出窗口来。因此,在代码里做了一些修改:

class ViewController: UIViewController {

  var hasPerformedSetup: Bool = false
  @IBAction func overlayButtonTapped(_ sender: Any) {
    guard let cls = NSClassFromString("UIDebuggingInformationOverlay") as? UIWindow.Type else {
      print("UIDebuggingInformationOverlay class doesn't exist!")
      return
    }
    
    if !self.hasPerformedSetup {
      cls.perform(NSSelectorFromString("prepareDebuggingOverlay"))
      self.hasPerformedSetup = true
    }

    guard let overlay = cls.perform(NSSelectorFromString("overlay")) else {
      print("UIDebuggingInformationOverlay 'overlay' method returned nil")
      return
    }
    // 加入下面这句,调用toggleVisibility显示overlay window
    _ = (overlay.takeUnretainedValue() as? UIWindow)?.perform(NSSelectorFromString("toggleVisibility"))
   }
}

点按钮,可以显示blank的overlay

上一篇 下一篇

猜你喜欢

热点阅读