Advanced.Apple.Debugging.&.R
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时注册了两个touch
的tap
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