跳过内容

MASTG-TECH-0076: 审查反汇编的 Objective-C 和 Swift 代码

在本节中,我们将手动探索 iOS 应用程序的二进制代码,并对其执行静态分析。手动分析可能是一个缓慢的过程,需要极大的耐心。一次好的手动分析可以使动态分析更加成功。

没有针对执行静态分析的硬性规定,但是可以使用一些经验法则来系统地进行手动分析

  • 了解正在评估的应用程序的工作方式 - 应用程序的目标以及在输入错误的情况下其行为方式。
  • 探索应用程序二进制文件中存在的各种字符串,这可能会非常有帮助,例如在发现应用程序中有趣的函数和可能的错误处理逻辑时。
  • 查找具有与我们的目标相关名称的函数和类。
  • 最后,找到进入应用程序的各种入口点,并从那里开始探索应用程序。

本节中讨论的技术是通用的,并且适用于与分析所使用的工具无关的情况。

Objective-C

为了有效地审查反汇编的本机代码,重要的是要基本了解 Objective-C 运行时。函数(如 _objc_msgSend_objc_release)在 Objective-C 运行时中尤为重要。

除了您在 反汇编本机代码 中学到的知识外,我们将使用 iOS UnCrackable L1 应用这些概念。此应用程序的目标是找到隐藏在其二进制文件中的密钥字符串。

该应用程序具有一个简单的主屏幕,允许用户通过将自定义字符串输入到提供的文本字段中进行交互。我们的目标是对应用程序进行逆向工程,以找到隐藏的密钥字符串。

当用户输入错误的字符串时,应用程序会显示一个弹出窗口,其中包含“验证失败”消息。

您可以记录弹出窗口中显示的字符串,因为这在搜索处理输入和做出决定的代码时可能会有所帮助。幸运的是,此应用程序的复杂性和交互非常简单,这有利于我们的逆向工程。

在本节中,我们将使用 Ghidra 9.0.4 进行静态分析。Ghidra 9.1_beta 自动分析存在错误,并且不显示 Objective-C 类。

我们可以通过在 Ghidra 中打开二进制文件来开始检查二进制文件中存在的字符串。最初,列出的字符串可能会让人不知所措,但是随着逆向 Objective-C 代码的一些经验,您将学习如何过滤和丢弃那些不是真正有用或相关的字符串。例如,以下屏幕截图中显示的那些是为 Objective-C 运行时生成的。在某些情况下,其他字符串可能会有所帮助,例如包含符号(函数名称,类名称等)的字符串,我们将在执行静态分析以检查是否正在使用某些特定函数时使用它们。

如果我们继续进行仔细的分析,我们可以发现字符串“验证失败”,该字符串用于给出错误输入时的弹出窗口。如果跟踪此字符串的交叉引用(Xrefs),您将到达 ViewController 类的 buttonClick 函数。我们将在本节后面介绍 buttonClick 函数。当进一步检查应用程序中的其他字符串时,只有少数几个字符串看起来像是隐藏标志的可能候选者。您可以尝试它们并进行验证。

展望未来,我们有两种选择。我们可以从分析以上步骤中确定的 buttonClick 函数开始,也可以从各种入口点开始分析应用程序。在现实世界中,大多数时候您会选择第一种方法,但是从学习的角度来看,在本节中我们将采用后一种方法。

iOS 应用程序会根据 应用程序生命周期 中的状态调用 iOS 运行时提供的不同预定义函数。这些函数被称为应用程序的入口点。例如

  • 首次启动应用程序时,会调用 [AppDelegate application:didFinishLaunchingWithOptions:]
  • 当应用程序从非活动状态变为活动状态时,将调用 [AppDelegate applicationDidBecomeActive:]

许多应用程序在这些部分中执行关键代码,因此它们通常是系统地跟踪代码的一个好的起点。

一旦我们完成了对 AppDelegate 类中所有函数的分析,我们可以得出结论,不存在任何相关的代码。以上函数中缺少任何代码提出了一个问题 - 从哪里调用应用程序的初始化代码?

幸运的是,当前应用程序具有一个小型代码库,我们可以在 符号树 视图中找到另一个 ViewController 类。在此类中,函数 viewDidLoad 函数看起来很有趣。如果您检查 viewDidLoad 的文档,则可以看到它也可以用于对视图执行其他初始化。

如果我们检查此函数的反编译,则发生了一些有趣的事情。例如,在第 31 行调用了本机函数,并且使用第 27-29 行中设置为 1 的 setHidden 标志初始化了标签。您可以记录这些观察结果,并继续浏览此类中的其他函数。为简洁起见,探索该函数的其他部分留给读者练习。

在我们的第一步中,我们观察到应用程序仅在按下 UI 按钮时才验证输入字符串。因此,分析 buttonClick 函数是一个显而易见的目标。如前所述,此函数还包含我们在弹出窗口中看到的字符串。在第 29 行做出决定,该决定基于 isEqualString 的结果(输出保存在第 23 行的 uVar1 中)。比较的输入来自文本输入字段(来自用户)和 label 的值。因此,我们可以假设隐藏标志存储在该标签中。

现在,我们已经遵循了完整的流程,并拥有了有关应用程序流程的所有信息。我们还得出了结论,即隐藏标志存在于文本标签中,为了确定标签的值,我们需要重新访问 viewDidLoad 函数,并了解已标识的本机函数中发生了什么。本机函数的分析将在 审查反汇编的本机代码 中讨论。