自定义LLDB命令实战

1,711 阅读12分钟
原文链接: www.jianshu.com

前言

本文翻译自Custom LLDB Commands in Practice
翻译的不对的地方还请多多包涵指正,谢谢~

自定义LLDB命令实战

LLDB: 拥有此技能, 还有什么不能!
LLDB: 拥有此技能, 还有什么不能!

欢迎来到一篇来源于我们新书《Apple调试进阶&逆向工程》灵感的特别版文章~本文将展示书中的一些非常棒的工具做成的最终产品结果。

本文你将使用一些自定义的LLDB调试命令和脚本来探索SpringBoard应用,在理解这些脚本上会给一些帮助。

什么,你说SpringBoard是啥?嗯,它是iOS的主屏幕应用,负责启动iOS应用,Siri解释你说的语言,查看通知和小工具栏等等。

通过本篇教程,你将探索在SpringBoard背后的一些逻辑,利用调试脚本来为你做繁重的工作。

开始吧

在开始探索之旅前你需要有一点需要注意的配置工作。

这里下载启动包。这包含了一个你将要安装到电脑的LLDB命令和脚本的文件夹。

LLDB通过加载在电脑上搜索几个预定义的位置的内容。其中一个地址就在~/.lldbinit。(若没发现该隐藏文件,就在~/目录下主动创建)

使用你最喜欢的文本编辑器并打开这个文件。为了这个特定的例子,我仅仅使用了一个简单的终端文本编辑器nano,但你可以自由地使用觉得顺手的。

  1. 使用Finder,打开你刚下载的包含lldb_commands的文件夹。保持Finder开着直到你将一个文件拖到终端窗口。在终端内,使用nano或者你的文本编辑器打开~/.lldbinit

    nano ~/.lldbinit
    

    然后按下回车键。



  2. 在nano或者你的文本编辑器,如下编辑

    command script import 
    

    保证在文本后面有一个空格,因为你将从Finder窗口把内容的地址加进来。

  3. 通过Finder窗口,打开lldb_commands文件夹,然后找到名叫dslldb.py的文件。把它拖到终端窗口中。(译者注:这步的操作其实很简单就是将文件的地址放在~/.lldbinit文件内容command script import的后面)

  4. 保存~/.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的子类。

仍然有很多 UIViews。过滤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深入探索函数获取大量的进程信息
  • 等等。。。