在前两章中,你已经探讨了动态加载以及如何使用dlopen和dlsym函数。只要你知道函数的名称,即使编译器试图对你隐藏一个函数也没有关系。
你将通过挖掘Objective-C框架使用Objective-C运行时来钩住和执行感兴趣的方法来结束这一轮的动态框架探索。
在这一章中,你将学习一系列的UIKIt私有类,以帮助进行可视化调试。这些私有类中的首席,UIDebuggingInformationOverlay在iOS 9.0中引入,并在2017年5月受到广泛关注,这要感谢@ryanipete的文章http://ryanipete.com/blog/ios/swift/objective-c/uidebugginginformationoverlay/ 强调了这些类和用法。
不幸的是,从iOS 11开始,苹果发现了开发者访问这个类的风声(可能是通过上述文章的普及),并增加了一些检查,以确保只有链接到UIKIt的内部应用程序才能访问这些私有调试类。截至iOS 12,苹果没有添加额外的检查,但由于即将在WWDC 2018上宣布的macOS/iOS整合,逻辑已经从UIKIt迁移到UIKItCore(可能)。在iOS 12中,UIKIt不包含任何代码,而只是简单地链接到几个框架。
你将探索UIDebuggingInformationOverlay,并了解为什么这个类在iOS 11及以上版本中无法工作,以及探索绕过苹果施加的这些检查的途径,即首先通过LLDB写到内存中的特定区域。然后,你将学习其他策略,通过Objective-C的方法swizzling启用UIDebuggingInformationOverlay。
我特别要求你在本章中使用iOS 12,因为苹果在未来可能会对这些类施加新的检查,而本章并没有涉及。
在iOS 9和10中,设置和显示叠加是相当微不足道的。在这两个iOS版本中,只需使用以下LLDB命令即可。
(lldb) po [UIDebuggingInformationOverlay prepareDebuggingOverlay]
(lldb) po [[UIDebuggingInformationOverlay overlay] toggleVisibility]
这将产生以下的覆盖物。
如果你的电脑上有一个iOS 10模拟器,我建议你附加到任何iOS进程,并尝试上述LLDB命令,以便你知道预期的情况。
不幸的是,有些事情在iOS 12中发生了变化。在iOS 12中执行完全相同的LLDB命令将不会产生任何结果。
要了解发生了什么,你需要探索UIDebuggingInformationOverlay包含的重写方法,并涉足汇编。
使用LLDB附加到任何iOS 12模拟器进程,这可以是MobileSafari、SpringBoard、你在前面章节中探索的任何应用程序,或你自己的作品。是否是你自己的应用程序并不重要,因为你将在UIKitCore模块中探索汇编。
在这个例子中,我将在模拟器中启动照片应用程序。前往终端,然后输入以下内容。
(lldb) lldb -n MobileSlideShow
一旦你连接到任何iOS模拟器进程,使用LLDB来搜索UIDebuggingInformationOverlay类的任何重写方法。
你可以使用LLDB的image lookup命令。
(lldb) image lookup -rn UIDebuggingInformationOverlay
或者,使用你在第15章 "动态框架 "中创建的方法命令,也可以。
(lldb) methods UIDebuggingInformationOverlay
如果你决定跳过那一章,下面的命令将是等同的。
(lldb) exp -lobjc -O -- [UIDebuggingInformationOverlay _shortMethodDescription]
注意在这两个命令的输出中发现的被重写的init实例方法。
你需要探索这个init在做什么。你可以使用LLDB的反汇编命令,但是为了视觉上的清晰,我将使用我自己定制的LLDB反汇编程序dd,它以彩色输出,可以在这里找到:https://github.com/DerekSelander/LLDB/blob/master/lldb_commands/disassemble.py 。
这里是iOS 10中init方法的汇编。如果你想在LLDB中以白纸黑字跟读,请输入。
(lldb) disassemble -n "-[UIDebuggingInformationOverlay init]"
同样,这显示的是这个方法在iOS 10中的装配。
颜色(和绿色标记的dd的注释)使阅读x64汇编变得更加容易。在伪Objective-C代码中,这可以转化为以下内容。
对于iOS 10来说,这很好,很简单。让我们来看看iOS 12的相同方法。
这大概可以翻译成以下内容。
由于UIDebuggingOverlayIsEnabled()的存在,在iOS 11及以上版本中加入了检查,如果这段代码不是苹果内部设备,则返回nil。
你可以通过在iOS 12模拟器上的LLDB中输入以下内容,自己验证这些令人失望的预防措施。
(lldb) po [UIDebuggingInformationOverlay new]
这是一个分配/启动UIDebuggingInformationOverlay的速记方法。你会得到nil。
使用LLDB,反汇编[UIDebuggingInformationOverlay init]的前10行汇编。
(lldb) disassemble -n "-[UIDebuggingInformationOverlay init]" -c10
你的程序集不会有颜色编码,但这是一个足够小的块,可以理解发生了什么。
你的输出将看起来类似于。
密切注意偏移量14和22。
值得庆幸的是,苹果公司将DWARF调试信息与他们的框架一起包括在内,所以我们可以看到他们使用什么符号来访问某些内存地址。
请注意反汇编中的UIDebuggingOverlayIsEnabled.__overlayIsEnabled + 7注释。实际上,我认为LLDB这样做相当烦人,而且会认为这是一个错误。LLDB没有正确地引用内存中的符号,而是在注释中引用之前的值并加上一个+7。在UIDebuggingOverlayIsEnabled.__overlayIsEnabled + 7的值是我们想要的,但是注释没有帮助,因为它的反汇编中有错误的符号的名字。这就是为什么我经常选择使用我的dd命令而不是LLDB的命令,因为我检查这个逐个错误,并用我自己的注释代替它。
但不管LLDB在其注释中选择了什么不正确的名字,这个地址正在与-1(又称64位进程中的0xffffffffffff)进行比较,如果这个地址不包含-1,则跳转到一个特定的地址。哦......既然我们在讨论这个问题,dispatch_once_t变量开始时是0(因为它们可能是静态的),一旦dispatch_once块完成,就会被设置为-1(提示,提示)。
是的,在内存中进行的第一次检查是看代码是否应该在dispatch_once块中执行。你想跳过dispatch_once逻辑,所以你要在内存中把这个值设为-1。
从上面的汇编中,你有两个选择来获得感兴趣的内存地址。
这意味着在我的例子中,[rip + 0x9fae84]将解析为[0x10d800254 + 0x9fae84]。然后这将解析为0x000000010e1fb0d8,这个内存地址保护着覆盖层不被初始化。
找到UIDebuggingOverlayIsEnabled.__overlayIsEnabled的加载地址。
(lldb) image lookup -vs UIDebuggingOverlayIsEnabled.__overlayIsEnabled
从输出结果看,寻找结束地址的范围field。同样,这是由于LLDB没有给你正确的符号。对于我的进程,我得到了range = [0x000000010e1fb0d0-0x000000010e1fb0d8)。
这意味着我感兴趣的字节位于。0x000000010e1fb0d8。如果我想知道这个地址实际上指的是什么符号,我可以输入。
(lldb) image lookup -a 0x000000010e1fb0d8
然后就会输出。
Address: UIKitCore[0x00000000015b00d8] (UIKitCore.__DATA.__bss + 24824)
Summary: UIKitCore`UIDebuggingOverlayIsEnabled.onceToken
这个UIDebuggingOverlayIsEnabled.onceToken是你想去的符号的正确名称。
我们现在知道这个布尔检查发生的确切字节。
让我们先看看这个值是多少。
(lldb) x/gx 0x000000010e1fb0d8
这将倾倒出位于0x000000010e1fb0d8的8个十六进制字节(你的地址会不同)。如果你之前执行了po [UIDebuggingInformationOverlay new]命令,你会看到-1;如果你没有,你会看到0。
让我们来改变这一点。在LLDB中键入。
(lldb) mem write 0x000000010e1fb0d8 0xffffffffffffffff -s 8
-s选项规定了要写入的字节数。如果你不喜欢输入16个f,总有其他方法来完成同样的任务。例如,下面的方法是等同的。
(lldb) po *(long *)0x000000010e1fb0d0 = -1
当然,你可以通过再次检查内存来验证你的工作。
(lldb) x/gx 0x000000010e1fb0d8
现在的输出应该是0xffffffffffffff。
我刚刚向你展示了如何取消对UIDebuggingOverlayIsEnabled.onceToken的初始检查,以使dispatch_once块认为它已经运行,但还有一个检查会阻碍你的进程。
重新运行你之前输入的反汇编命令。
(lldb) disassemble -n "-[UIDebuggingInformationOverlay init]" -c10
在输出的最底部有这两行。
这个mainHandler.onceToken又是一个错误的符号;你关心的是内存中紧跟它的符号。我想让你对UIDebuggingOverlayIsEnabled.__overlayIsEnabled进行同样的操作,但要把它应用到内存中的指向的内存地址。一旦你执行RIP运算,引用mainHandler.onceToken,你会意识到正确的符号。UIDebuggingOverlayIsEnabled.__overlayIsEnabled,就是你要找的符号。
你首先需要找到mainHandler.onceToken在内存中的位置。你可以从上面的汇编中进行RIP运算,或者使用图像查找-vs mainHandler.onceToken来找到终端位置。一旦你找到了内存地址。写一个-1的值到这个内存地址。
现在你已经成功地将-1值写入mainHandler.onceToken,现在是时候检查你的工作了,看看你所做的任何改变是否绕过了初始化检查。
在LLDB中键入。
(lldb) po [UIDebuggingInformationOverlay new]
只要你正确地增强了内存,就会有一些更欢快的输出来迎接你。
当你在做的时候,确保类方法overlay返回一个有效的实例。
(lldb) po [UIDebuggingInformationOverlay overlay]
如果你在上面的LLDB命令中得到了零,请确保你已经在内存中增强了正确的地址。如果你完全确定你已经增强了正确的地址,但你仍然得到一个零的返回值,请确保你运行的是iOS 12模拟器,因为苹果可能在本书编写后的版本中增加了额外的检查,以防止这种情况的发生。
如果一切顺利,并且你有一个有效的实例,让我们把这个东西放在屏幕上吧
在LLDB中,键入。
(lldb) po [[UIDebuggingInformationOverlay overlay] toggleVisibility]
然后恢复这个过程。
(lldb) continue
好了......我们在屏幕上得到了一些东西,但它是空白的!?
UIDebuggingInformationOverlay是空白的,因为我们没有调用类方法,+ [UIDebuggingInformationOverlay prepareDebuggingOverlay] 。
倾倒这个方法的汇编,我们可以立即看到一个有关的检查。
偏移量14、19和21。调用一个名为_UIGetDebuggingOverlayEnabled的函数测试AL(RAX的单字节表亲)是否为0,如果是,跳到这个函数的结尾。这个函数的逻辑是由_UIGetDebuggingOverlayEnabled的返回值决定的。
由于我们仍在使用LLDB建立POC,让我们在这个函数上设置一个断点,跳出_UIGetDebuggingOverlayEnabled,然后在偏移量19的检查发生之前增强存储在AL寄存器的值。在_UIGetDebuggingOverlayEnabled上创建一个断点。
(lldb) b _UIGetDebuggingOverlayEnabled
LLDB将表明它已经成功地在_UIGetDebuggingOverlayEnabled方法上创建了一个断点。
现在,让我们执行[UIDebuggingInformationOverlay prepareDebuggingOverlay]方法,但让LLDB尊重断点。输入以下内容。
(lldb) exp -i0 -O -- [UIDebuggingInformationOverlay prepareDebuggingOverlay]
这使用了-i选项,决定LLDB是否应该忽略断点。你指定0,表示LLDB不应该忽略任何断点。
如果一切顺利,执行将从prepareDebuggingOverlay方法开始,并调用_UIGetDebuggingOverlayEnabled,执行将停止。
让我们告诉LLDB恢复执行,直到它走出这个_UIGetDebuggingOverlayEnabled函数。
(lldb) finish
控制流将在_UIGetDebuggingOverlayEnabled中结束,我们将回到 prepareDebuggingOverlay方法,就在测试偏移量19的AL寄存器之前。
通过LLDB,打印出AL寄存器中的值。
(lldb) p/x $al
除非你在一家位于 "宇宙飞船 "园区内的特殊水果公司工作,否则你可能会得到0x00。
把它改成0xff。
(lldb) po $al = 0xff
让我们通过单指令步进来验证这个工作。
(lldb) si
这将使你进入下面一行。
je 0x11191a430 ; <+286>
如果在测试汇编指令时AL是0x0,这将使你移到偏移量286。如果在测试指令时AL不是0x0,你将继续执行,没有条件的jmp指令。
通过再执行一个指令步骤,确保此举成功。
(lldb) si
如果你在偏移量286上,这就失败了,你需要重复这个过程。但是,如果你发现指令指针没有条件性跳转,那么这就成功了!现在你不需要再做什么了。
现在你已经没有什么需要做的了,所以继续在LLDB中执行。
(lldb) continue
那么,在+[UIDebuggingInformationOverlay prepareDebuggingOverlay]中的逻辑到底做了什么?
为了帮助减轻视觉负担,这里粗略地翻译了+[UIDebuggingInformationOverlay prepareDebuggingOverlay]方法的作用。
这很有意思。在UIApp的statusBarWindow上有处理两根手指轻敲的逻辑。一旦发生这种情况,一个名为_handleActivationGesture:的方法将在UIDebugging上执行。的方法将在UIDebuggingInformationOverlayInvokeGestureHandler单例上执行mainHandler。
这让你不禁要问,这里面的逻辑是什么?[UIDebuggingInformationOverlayInvokeGestureHandler _handleActivationGesture:] 的逻辑是为了什么?
使用dd进行快速的汇编转储,会发现一个有趣的地方。
由RDI寄存器(你在第11章 "汇编寄存器调用约定 "中了解到)传递进来的UITapGestureRecognizer实例,正在获得与0x3值(见偏移量30)的比较状态。如果是3,则控制继续,如果不是3,则控制向函数的末端跳转。
在UIGestureRecognizer的头文件中快速查找,我们发现状态有如下枚举值。
typedef NS_ENUM(NSInteger, UIGestureRecognizerState) {
UIGestureRecognizerStatePossible,
UIGestureRecognizerStateBegan,
UIGestureRecognizerStateChanged,
UIGestureRecognizerStateEnded,
UIGestureRecognizerStateCancelled,
UIGestureRecognizerStateFailed,
UIGestureRecognizerStateRecognized = UIGestureRecognizerStateEnded
};
从0开始算起,我们可以看到只有当UITapGestureRecognizer的状态等于UIGestureRecognizerStateEnded时,控件才会执行大部分的代码。
那么这到底是什么意思呢?UIKIt开发者不仅对访问UIDebuggingInformationOverlay类(你已经在内存中修改过了)进行了限制,他们还在状态栏窗口中添加了一个 "秘密 "的UITapGestureRecognizer,只有当你在上面完成两根手指的点击时才会执行设置逻辑。
这有多酷啊?
在我们尝试这个东西之前,让我们快速回顾一下你所做的事情,以防你需要重新启动的时候。
你找到了UIDebuggingOverlayIsEnabled.onceToken的内存地址。
(lldb) image lookup -vs UIDebuggingOverlayIsEnabled.onceToken
然后通过LLDB的内存写入将其设置为-1,或者直接将地址铸造为一个长指针,并将其值设置为-1,像这样。
(lldb) po *(long *)0x000000010e1fb0d0 = -1
你还对以下内容进行了同样的操作
UIDebuggingOverlayIsEnabled.__overlayIsEnabled。
然后你在_UIGetDebuggingOverlayEnabled()上创建了一个断点,执行了+[UIDebuggingInformationOverlay prepareDebuggingOverlay]命令,并改变_UIGetDebuggingOverlayEnabled()产生的返回值,以便该方法的其余部分能够继续执行。
这是绕过苹果公司的检查以防止你使用这些类的许多方法之一。
由于你使用的是模拟器,这意味着你需要按住键盘上的Option来模拟两次触摸。一旦你得到了两个触摸的平行,按住Shift键在屏幕上拖动点击圈。将点触圆圈放在你的应用程序的状态栏上,然后点击。
你就会看到功能齐全的UIDebuggingInformationOverlay!
思考一下,这需要多长时间?此外,每次UIKIt被加载到一个进程中时,我们都必须通过LLDB手动设置。在内存中寻找和设置这些值无疑可以通过一个自定义的LLDB脚本来完成,但有一个优雅的替代方法,即使用Objective-C的方法swizzling。
但在研究如何操作之前,让我们先谈谈 "什么"。
Method swizzling是在运行时动态改变Objective-C方法的过程。在二进制文件的__TEXT部分编译的代码不能被修改(好吧,它可以通过适当的授权,但苹果不会给你,但我们不会去讨论这个)。
然而,当执行Objective-C代码时,我们知道objc_msgSend会发挥作用,这要感谢第11章。如果你忘记了,objc_msgSend会接收一个实例(或类)、一个Selector和一个可变数量的参数,并跳转到函数的位置。
方法swizzling有很多用途,但很多时候人们使用这种策略来修改一个参数或返回值。另外,他们可以窥探并看到一个函数何时在执行代码,而不用在汇编中搜索引用。事实上,苹果公司甚至在它自己的代码库中(岌岌可危)使用了方法swizzling,比如KVO!
由于互联网上有很多关于方法刷新的参考资料,我就不从头开始了(但如果你想的话,我想说http://nshipster.com/method-swizzling/ ,它的讨论是最清晰、最简洁的)。
相反,我们将从基本的例子开始,然后迅速提升到我还没有见过的方法刷卡的做法:用它来跳入一个方法的偏移量,以避免任何不需要的检查!这就是方法刷卡。
本章包括一个名为Overlay的示例项目,它非常简约。它只有一个UIButton在中间,执行预期的逻辑来显示UIDebuggingInformationOverlay。
你将建立一个Objective-C NSObject类别,在模块加载后立即对感兴趣的代码执行Objective-C swizzling,使用只用Objective-C的加载类方法。
构建并运行该项目。点击可爱的UIB按钮。你只会从stderr得到一些愤怒的输出说。
UIDebuggingInformationOverlay 'overlay' method returned nil
正如你已经知道的,这是因为UIDebuggingInformationOverlay的init方法被架空了。
让我们先解决这个简单的问题;打开NSObject+UIDebuggingInformationOverlayInjector.m,跳到第一节,用pragma标记。在这一节中,添加以下Objective-C类。
//***************************************************
*/ #pragma mark - Section 1 - FakeWindowClass
/****************************************************/
@interface FakeWindowClass : UIWindow
@end
@implementation FakeWindowClass
- (instancetype)initSwizzled {
if (self= [super init]) {
[self _setWindowControlsStatusBarOrientation:NO];
}
return self;
}
@end
对于这一部分,你声明了一个名为FakeWindowClass的Objective-C类,它是UIWindow的一个子类。不幸的是,这段代码不会被编译,因为_setWindowControlsStatusBarOrientation: 是一个私有方法。
跳到第0节,向前声明这个私有方法。
//****************************************************/
#pragma mark - Section 0 - Private Declarations
//****************************************************/
@interface NSObject()
- (void)_setWindowControlsStatusBarOrientation:(BOOL)orientation;
- @end
这将使编译器安静下来,让代码构建。UIDebuggingInformationOverlay的init方法有检查以返回nil。由于init
方法相当简单,你只要完全避开这个逻辑,自己重新实现它,并删除所有的 "坏东西 "就可以了。
现在,将UIDebuggingInformationOverlay的init代码替换为
FakeWindowClass的initSwizzled方法。跳到第2节中NSObject的load
方法中的第2节,用下面的内容替换加载方法。
用这段新的代码重新运行并建立Overlay应用程序。点击UIB按钮,看看现在发生了什么,你已经替换了init来产生一个有效的实例。
UIDebuggingInformationOverlay现在弹出,没有任何内容。差不多了!
你将为即将被替换的prepareDebuggingOverlay方法编写最后的代码片段。
如果这个方法返回0x0,那么控制就会跳到函数的结尾。
为了解决这个问题,你将复制你在第13章 "汇编和堆栈 "中观察到的x86汇编的相同动作。也就是说,你将通过把返回地址推入堆栈来 "模拟 "一个调用指令,但不是调用,而是jmp到一个超过_UIGetDebuggingOverlayEnabled检查的偏移。这样,你就可以在你的堆栈帧中执行函数序号,直接跳过prepareDebuggingOverlay开头的可怕的检查。
在NSObject+UIDebuggingInformationOverlayInjector.m中,向下导航到第3节 - prepareDebuggingOverlay,并添加以下代码片段。
+ (void)prepareDebuggingOverlaySwizzled {
Class cls = NSClassFromString(@"UIDebuggingInformationOverlay");
SEL sel = @selector(prepareDebuggingOverlaySwizzled);
Method m = class_getClassMethod(cls, sel); IMP imp = method_getImplementation(m); // 1
void (*methodOffset) = (void *)((imp + (long)27)); // 2
void *returnAddr = &&RETURNADDRESS; // 3
// You'll add some assembly here in a sec
RETURNADDRESS: ; // 4
}
让我们把这个疯狂的巫术分解一下。
获取原始prepareDebuggingOverlay的起始地址。然而,这将是swizzled代码,所以当这个代码执行时,prepareDebuggingOverlaySwizzled实际上将指向真正的prepareDebuggingOverlay起始地址。
获取原始prepareDebuggingOverlay的起始地址(通过imp变量),并将该值在内存中偏移到_UIGetDebuggingOverlayEnabled()检查之后。LLDB用于计算准确的偏移量。转储程序并计算偏移量(disassemble -n "+ [UIDebuggingInformationOverlay prepareDebuggingOverlay]")。这样做是非常脆弱的因为任何新的代码或来自clang的编译器变化都可能会破坏它。我强烈建议你自己计算一下,以防在iOS 12之后发生变化。在这之后你会看到一个更优雅的替代方案。
由于你正在伪造一个函数调用,你需要一个地址,以便在这个即将执行的函数偏移完成后返回。这可以通过获取一个声明的标签的地址来实现。标签是普通开发者不常使用的功能,它允许你跳转到一个函数的不同区域。在现代编程中使用标签被认为是不好的做法,因为if/for/while循环可以完成同样的事情......但对于这个疯狂的黑客来说不是。
这是对标签RETURNADDRESS的声明。不,你确实需要在标签后面加上分号,因为按照C语言的语法,标签后面必须有一个语句。
是时候用一些可爱的内联汇编来结束这个坏小子了 在标签RETURNADDRESS声明的正上方,添加以下内联程序。
不要害怕,你要写的是AT&T格式的x86_64汇编(苹果的汇编器不喜欢Intel)。那个__volatile__是用来提示编译器不要试图将其优化掉。
你可以认为这有点像C的printf,%0将被returnAddr提供的值所取代。在X86中,返回地址是在进入函数之前被推入堆栈的。如你所知,returnAddr指向这个汇编之后的可执行地址。这就是我们如何伪造一个实际的函数调用!
下面的汇编是从+[UIDebuggingInformationOverlay prepareDebuggingOverlay]的函数序言中复制粘贴的。这让我们可以执行函数的设置,但允许我们跳过可怕的检查。
最后,我们跳到prepareDebuggingOverlay的第27个偏移,在我们已经
设置了我们需要的所有数据和堆栈信息,以避免崩溃。jmp *%1将被解析为jmp'ing到存储在methodOffset的值。最后,那些 "r "字符串是什么?我不会过多地讨论内联汇编的细节,因为我认为你的脑袋可能会因为信息过载而爆炸(想想扫描仪),但你只要知道这是在告诉汇编者,你的汇编可以使用任何寄存器来读取这些值。
跳回第2节,在+load方法中进行swizzling,并在该方法的末尾添加以下一行代码。
建立和运行。点击UIB按钮,执行所需代码,设置UIDebuggingInformationOverlay类,然后在状态栏上进行双击。
我的上帝,你能相信这起作用了吗?
我绝对是隐藏状态栏双击的粉丝,但假设你想完全通过代码来实现这个目标。下面是你可以做的。
打开ViewController.swift。在文件的顶部添加。
import UIKit.UIGestureRecognizerSubclass
这将让你设置UIGestureRecognizer的状态(默认头文件只允许对状态变量进行只读访问)。
一旦完成,将overlayButtonTapped(_ sender: Any)中的代码扩充为如下。
最终构建并运行。点击按钮,看看会发生什么。轰隆隆。
这当然不是解决这个问题的唯一方法。我选择了组装的选项,因为它很有趣。但也有更干净的方法。
例如,prepareDebuggingOverlaySwizzled方法可以采用以下方法,这样就不需要使用内联汇编。
还有其他公布的方法。例如,看看@ian_mcdowell的实现https://gist.github.com/IMcD23/1fda47126429df43cc989d02c1c5e4a0 ,它用更少的代码完成了整个动作。
疯狂的一章,是吗?在这一章中,你潜入内存,改变了dispatch_once_t令牌以及内存中的布尔运算,建立了一个POC UIDebuggingInformationOverlay,与iOS 12兼容,同时绕过了苹果新引入的检查,防止你使用这个类。
然后,你使用Objective-C的方法swizzling来执行同样的动作,以及只钩住原始方法的一部分,绕过了几个短路检查。
这就是为什么逆向工程Objective-C是如此有趣,因为你可以钩住那些在你没有源代码的私有代码中悄悄调用的方法,并进行修改或监视它在做什么。
在经历了这个残酷的章节之后,你还有精力吗?这套swizzled代码不会在ARM64设备上工作。你需要看一下汇编,并为该架构执行其他的操作,可能是通过一个预处理程序宏。
哦,还记得我说过UIDebuggingInformationOverlay完全可以做成一个LLDB脚本,在模拟器和iOS 12的实际设备上都能兼容吗?嗯,这就是了。
https://github.com/DerekSelander/LLDB/blob/master/lldb_commands/overlaydbg.py
上一章 | 目录 | 下一章 |
---|