Advanced.Apple.Debugging.&.Reverse.Engineering.v2.0 学习笔记 2

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,393评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,790评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,391评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,703评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,613评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,003评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,507评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,158评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,300评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,256评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,274评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,984评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,569评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,662评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,899评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,268评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,840评论 2 339

推荐阅读更多精彩内容

  • 转载 与调试器共舞 - LLDB 的华尔兹: https://objccn.io/issue-19-2/ 推荐:i...
    F麦子阅读 3,324评论 0 10
  • LLDB的Xcode默认的调试器,它与LLVM编译器一起,带给我们更丰富的流程控制和数据检测的调试功能。平时用Xc...
    CoderSC阅读 1,342评论 0 2
  • 你是否曾经苦恼于理解你的代码,而去尝试打印一个变量的值? NSLog(@"%@", whatIsInsideThi...
    paraneaeee阅读 1,171评论 0 7
  • 现在你已经学习了如何创建断点, 因此调试器会在你的代码里停下来, 现在是时候从你调试的程序里获取一些有用的信息了....
    股金杂谈阅读 2,340评论 0 3
  • 在一个夜深人静时,才有勇气褪去自己的伪装色,脱下面具,露出破绽百出的自己,佝偻的身子是只有深夜才得以见人,因为夜很...
    言儿阅读 129评论 0 0