前言
本文翻译自Custom LLDB Commands in Practice
翻译的不对的地方还请多多包涵指正,谢谢~
自定义LLDB命令实战
欢迎来到一篇来源于我们新书《Apple调试进阶&逆向工程》灵感的特别版文章~本文将展示书中的一些非常棒的工具做成的最终产品结果。
本文你将使用一些自定义的LLDB调试命令和脚本来探索SpringBoard
应用,在理解这些脚本上会给一些帮助。
什么,你说SpringBoard
是啥?嗯,它是iOS的主屏幕应用,负责启动iOS应用,Siri解释你说的语言,查看通知和小工具栏等等。
通过本篇教程,你将探索在SpringBoard
背后的一些逻辑,利用调试脚本来为你做繁重的工作。
开始吧
在开始探索之旅前你需要有一点需要注意的配置工作。
这里下载启动包。这包含了一个你将要安装到电脑的LLDB命令和脚本的文件夹。
LLDB通过加载在电脑上搜索几个预定义的位置的内容。其中一个地址就在~/.lldbinit
。(若没发现该隐藏文件,就在~/
目录下主动创建)
使用你最喜欢的文本编辑器并打开这个文件。为了这个特定的例子,我仅仅使用了一个简单的终端文本编辑器nano
,但你可以自由地使用觉得顺手的。
-
使用
Finder
,打开你刚下载的包含lldb_commands
的文件夹。保持Finder
开着直到你将一个文件拖到终端窗口。在终端内,使用nano或者你的文本编辑器打开~/.lldbinit
nano ~/.lldbinit
然后按下回车键。
-
在nano或者你的文本编辑器,如下编辑
command script import
保证在文本后面有一个空格,因为你将从
Finder
窗口把内容的地址加进来。 通过
Finder
窗口,打开lldb_commands
文件夹,然后找到名叫dslldb.py
的文件。把它拖到终端窗口中。(译者注:这步的操作其实很简单就是将文件的地址放在~/.lldbinit
文件内容command script import
的后面)保存
~/.lldbinit
文件并关闭编辑器。对于nano来说,你需要按下Ctrl + O
来保存,按下Ctrl + X
退出。
整体总结下,下面就是你需要该做的样子:
dslldb.py
文件将搜索所有在同一个文件夹的所有Python脚本并在LLDB启动的时候将它们加载到LLDB。另外,它会找到所有的.txt
文件并加载文件内的命令。我们在书内对这个过程深入介绍了,但现在就让我们享受用这些命令能做的事情吧。
测试命令可用性
在新的终端窗口内,输入如下命令:
lldb
这样会在终端内启动一个空的LLDB会话,现在输入如下命令:
help search
这将参考帮助文档显示你新添加的叫search
的命令。假设一切都进行得很好,你会获得关于该命令的帮助文本。
如果你得到以下信息:
(lldb) help search
error: 'search' is not a known command.
Try 'help' to see a current list of commands.
Try 'apropos search' for a list of related commands.
Try 'type lookup search' for information on types, methods, functions, modules, etc.
这意味着LLDB命令没有被成功加载,保证你LLDB命令的文件夹路径中没有空格且不存在引号。
若一切正常,你可以访问以下一些命令:
search:根据某一个特定类遍历所有在对上的指针。而且能够通过特定的模块(比如 UIKit)或者一些条件来过滤对象。
lookup:执行正则搜索查找类,函数,或者方法。
msl:为一个特定的指针的最后一次创建或者开辟获取堆栈追踪信息。
methods:Dump所有
NSOjbect
子类的方法(仅iOS)。ivars:Dump所有
NSOjbect
子类实例对象所有的实例变量。
这些只是LLDB能做的一小部分,你可以使用SpringBoard
探索更多神奇的命令。
注:如想了解我最新最棒的命令,可检出这个地址。无论何时我需要一个命令,都会创建它并将它放到那个仓库里。你可能会发现一些令人惊奇的东西~
你不需要终端了,可以自由的关掉它并打开我们的XCode~
玩转SprintBoard
我经常想看看开发人员在产出的过程中如何做到的。通过探索别人已经做好的,我可以学到它们的实现方式并且自己写出更好的代码。
遗憾的是,Apple不会开放任何它们自己程序的开源代码,因此需要通过其他方式学习Apple是如何设计程序的。iOS模拟器提供了iOS程序的几个功能示例,我使用LLDB通过它们来侦查出这些程序的实现方式。
很多人似乎认为普通的调试和逆向工程应用使用了不同的调试技巧。我非常赞同这个观点。逆向工程某人的应用能极大挑战你的调试技巧,这就是我为什么一直鼓励大家通过逆向工程来调试。如果你能够快速的找到你感兴趣的东西而不用去读源码的话,想象一下当你分配一个任务找到你程序的一个bug的时候会多么的快~
嵌入到SprintBoard
使用LLDB使连接任何一个电脑上的应用成为可能(只要你禁用了Rootless机制)。幸运的是,你没必要禁用Rootless机制就可以把LLDB命令嵌入到iOS模拟器应用上。
这意味着你可以使用Xcode附加到SpringBoard并使用所有你顺手的命令。
打开任何一个Xcode工程---是的,任何一个。你并不是要编译这个应用,而是使用已经存在的窗口探索SpringBoard程序。
打开模拟器,跳到Xcode。在Debug按钮里,点击 Attach to Process,选择 SprintBoard。
给LLDB和Xcode一些时间让其附加到SpringBoard。成功后,你会看到一个暂停按钮出现在Xcode LLDB 工作面板上。通过点击暂停按钮来暂停这个进程。
你可能需要通过点击Command + Shift + Y
触发显示该面板。一旦SpringBoard程序暂停后,在LLDB里输入:
(lldb) dclass
这会dump所有在SpringBoard中可用的单个Objective-C类。如你所见,有很多类...
我知道很多人现在只关心Swift,对探索Objective-C类不感兴趣,但是Swift很大程度依赖于Objective-C并且在写书时,SpringBoard进程并没有任何Swift类。你可通过输入
dclass -f SwiftObject
搜索纯Swift类,或者输入dclass -r \\.
通过查找类名中的点好搜索继承于NSObject
的Swift子类。如果你希望学习背后的逻辑,请看书中21章:“Script Bridging with SBValue & Language Contexts”
为什么不过滤一下 dclass
命令的结果呢?Dump 所有 UIView
子类是可行的:
(lldb) dclass -f UIView -m SpringBoard
这将限制你的搜索SpringBoard的可执行程序中UIView
的子类。
仍然有很多 UIView
s。过滤search的结果仅展示类名中包含大小写不敏感的“image”词汇。
(lldb) dclass -f UIView -m SpringBoard -r (?i)image
这将仅展示类名中包含大小写不敏感的“image”词汇的类名,且这些类都是在SpringBoard的可执行文件内实现的,且是UIView
的子类。是不是很赞?
这个奇怪的(?i)
是啥?在《Advanced Apple Debugging & Reverse Engineering》中,你将学习Objective-C和Swift的函数签名且怎样执行更智能的正则来搜索你感性的代码。
你会得到以下类信息:
Dumping all classes in SpringBoard, with filter: UIView
************************************************************
SBDeckSwitcherIconImageContainerView
SBSwitcherSnapshotImageView
SBIconImageView
SBStarkIconImageView
SBLiveIconImageView
SBClockApplicationIconImageView
SBFolderIconImageView
SBIconImageCrossfadeView
SBIconImageFolderCrossfadeView
SBIconImageAppCrossfadeView
SBIconImageAppLowQualityCrossfadeView
SBDarkeningImageView
SBCornerAnimatingImageView
SBAutoPurgingImageView
SBImageAlertView
从输出来看,让我们来看看SBIconImageView
类。Dump SBIconImageView
类所有方法和属性:
(lldb) methods SBIconImageView
你会得到一些相似的以下片段:
<SBIconImageView: 0x10b472258>:
in SBIconImageView:
Class Methods:
+ (id) viewMap; (0x10aef017d)
+ (unsigned long) viewMap:(id)arg1 maxRecycledViewsOfClass:(Class)arg2; (0x10aef023c)
+ (id) windowForRecycledViewsInViewMap:(id)arg1; (0x10aef0249)
+ (void) recycleIconImageView:(id)arg1; (0x10aef02a0)
+ (id) dequeueRecycledIconImageViewOfClass:(Class)arg1; (0x10aef0312)
+ (double) cornerRadius; (0x10aeef0e1)
这不仅会 Dump 下类方法和实例方法,且能Dump类属性并输出代码所在的内存地址。
如你希望在代码中私下里使用SBIconImageView
类,可以使用dclass
为该类创建一个Objective-C头文件。在LLDB中输入以下命令:
(lldb) dclass -p SBIconImageView
该命令会生成一个头文件,你可以将它拉入到你的App工程内并使用它。
为了能够在代码中调用
SBIconImageView
类,你需要加在合适的实现了该类的动态链接库。在书中,这些内容在第十五章写明了-- “Hooking & Executing Code with dlopen & dlsym”
让我们回到SBIconImageView
类,搜索到所有正在内存中的该类对象是不是很棒?OK,使用 search
命令,你可以动态地搜到在堆栈中所有该类的实例。在LLDB中,输入:
(lldb) search SBIconImageView
你会看到类似以下片段的输出:
(lldb) search SBIconImageView
<__NSArrayM 0x618000858270>(
<SBIconImageView: 0x7ff6ad7492f0; frame = (-1 -1; 62 62); userInteractionEnabled = NO; layer = <CALayer: 0x6100002226a0>>,
<SBIconImageView: 0x7ff6b0a78e30; frame = (-1 -1; 62 62); userInteractionEnabled = NO; layer = <CALayer: 0x608000225520>>,
<SBIconImageView: 0x7ff6ad743d90; frame = (-1 -1; 62 62); userInteractionEnabled = NO; layer = <CALayer: 0x610000221700>>,
棒极了,但这些实例在哪儿?你可以很遍历所有这些实例且能使用search
命令对他们执行自定义的动作。输入:
(lldb) search SBIconImageView -p '[obj setHidden:YES]'
回到模拟器,你会看到你刚刚做的事情:
你能准确猜出SBIconImageView
类做什么的吗?!通过显示所有的SBIconImageView
实例来撤销你刚刚做的事情。
(lldb) search SBIconImageView -p '[obj setHidden:NO]'
搜索命令很不错,但它返回的是所有屏幕应用的结果---太多了。如果你只想要找到代表Messages
(信息)应用的SBIconImageView
实例呢?
使用methods
命令,你可以搜索你感兴趣的能帮你唯一标识特定SBIconImageView
实例的代码。
例如SBIconImageView
类有一个属性叫icon
,它持有一个叫SBApplicationIcon
的类(不同的SDK可能类不一样)。Dump 所有这个类的方法:
(lldb) methods SBApplicationIcon
这个方法内部有一个叫displayName
的属性。你可以使用这个知识点并通过displayName
来快速找到特定的SBApplicationIcon
实例!
在LLDB输入:
(lldb) search SBIconImageView -c '[[[obj icon] displayName] containsString:@"Messages"]'
这将返回一个displayName
包含"Messages"的SBApplicationIcon
实例。你会得到如下类似结果:
<__NSArrayM 0x618000e5dac0>(
<SBIconImageView: 0x7fb7b567e020; frame = (-1 -1; 62 62); userInteractionEnabled = NO; layer = <CALayer: 0x61000023a660>>
)
拷贝SBApplicationIcon
实例指针。我的例子中,它是0x7fb7b567e020,但你的地址应该是不一样的。通过tv
命令来隐藏这个界面:
(lldb) tv 0x7fb7b567e020
Message应用的图片现在应该消失了:
现在你发现它啦~ 找到这个实例所有的属性值:
(lldb) ivars 0x7fb7b567e020
ivars
命令和methods
一样也是从已经编译成iOS可执行文件的代码构建出来的。你只是在调试的过程中以应用的方法使用此代码。你可以在书中第七章“Image”学到找到这些代码的方法。
我们来最后一个命令 --- lookup
--- 你会再书中第22章“SB Examples, Improved Lookup”创建的。该命令会根据正则表达式搜索所有可执行文件的代码。
在LLDB中输入:
(lldb) lookup Test
这样你会看到很多代码,实际上可以使用--summary
选项来简化:
(lldb) lookup Test -s
你会看到如下类似片段:
1 hits in: AssistantServices
39 hits in: ChatKit
9 hits in: FrontBoard
5 hits in: VideoToolbox
28 hits in: CoreData
7 hits in: MPUFoundation
5 hits in: CoreDuet
2 hits in: BaseBoardUI
7 hits in: MediaServices
5 hits in: PassKitCore
11 hits in: MusicLibrary
16 hits in: Foundation
6 hits in: Sharing
2 hits in: libsqlite3.dylib
8 hits in: PhotoLibrary
如果希望只搜索BaseBoardUI
模块的信息呢?可以基于一个模块来使用lookup命令过滤搜索结果。
(lldb) lookup Test -m BaseBoardUI
****************************************************
2 hits in: BaseBoardUI
****************************************************
-[UIView(BaseBoardUI) bs_isHitTestingDisabled]
-[UIView(BaseBoardUI) bs_setHitTestingDisabled:]
这意味着我可以对在SpringBoard应用内的所有 UIView
使用这些代码!例如,我可以输入 po [[UIApp keyWindow] bs_isHitTestingDisabled]
命令打印出属性值。
不在输入内的是基于SpringBoard应用的所有代码。这也可以理解,因为可执行代码被剥离了(译者注:应用自定义代码和引用的模块,框架剥离开),你看不到调试的符号信息。对于Framework则不同,因为它们需要记录这些信息,当加载时,它能知道准确的地址。
不能使用lookup
命令来搜索可执行文件实在是让我有些难过...
等下你猜?你可以的
输入一下命令:
(lldb) lookup Test -X
这个命令将利用Objective-C的运行时来执行正则搜索,而不是使用DWARF调试信息。
如你所见,有很多在最终的SpringBoard应用的测试代码。试试以下命令:
(lldb) po [[SBTestDataProvider sharedInstance] publish]
一旦你通过点击继续按钮或者在LLDB中输入continue
命令恢复应用后,你会看到一个弹窗~
是的!通知哦~
那么这是不是一篇很有意思的调试课呢~
何去何从
你也看到了,自定义调试命令有强大的能力。《Advanced Apple Debugging & Reverse Engineering》书能让你的调试能力有很大的飞跃。
如果你喜欢本篇文章,可以购买书籍《Advanced Apple Debugging & Reverse Engineering》。
以下是书的一部分内容介绍:
- 开始:关于LLDB及其大量的命令和选项
- Python能力:使用LLDB的Python模块创建强大自定义的调试命令从而窥探和提高现有程序
- 理解汇编:真正理解汇编层面代码是如何工作的,以及怎样在内存中探索这些代码
- Ptrace和Friends:学习如何利用ptrace,dlopen和dlsym来hook C和Swift函数探索没有源码的代码
- 脚本桥接:扩展调试器让它几乎做任何你想做的事,学习如何在脚本中传递可选参数或参数
- DTrace:使用DTrace深入探索函数获取大量的进程信息
- 等等。。。