本文由 简悦 SimpRead 转码, 原文地址 mas.owasp.org
iOS 逆向工程可谓喜忧参半。一方面,用 Objective-C 和 Swift 编程的应用程序可以 ......
逆向工程∥
iOS 逆向工程喜忧参半。一方面,用 Objective-C 和 Swift 编程的应用程序可以很好地反汇编。在 Objective-C 中,对象方法通过称为 "选择器 "的动态函数指针调用,这些指针在运行时通过名称解析。运行时名称解析的优势在于,这些名称需要在最终二进制文件中保持不变,从而使反汇编更具可读性。遗憾的是,这也意味着反汇编器中无法提供方法间的直接交叉引用,因此构建流程图具有挑战性。
在本指南中,我们将介绍静态和动态分析以及工具。在本章中,我们将引用 OWASP UnCrackable Apps for iOS,如果你打算学习这些示例,请从 MASTG 软件库中下载。
反汇编和反编译∥
由于 Objective-C 和 Swift 从根本上不同,编写应用程序的编程语言会影响逆向工程的可能性。例如,Objective-C 允许在运行时更改方法调用。这使得钩入其他应用程序函数(Cycript和其他逆向工程工具大量使用的技术)变得容易。这种 "method swizzling "在 Swift 中的实现方式并不相同,这种差异使得这种技术在 Swift 中比在 Objective-C 中更难执行。
在 iOS 上,所有应用程序代码(包括 Swift 和 Objective-C)都编译为机器代码(如 ARM)。因此,分析 iOS 应用程序需要反汇编器。
如果要反汇编 App Store 中的应用程序,请先移除 Fairplay DRM。iOS 基本安全测试 "一章中的"获取应用程序二进制文件 "一节解释了如何获取应用程序二进制文件。
在本节中,术语 "应用程序二进制文件 "是指应用程序捆绑包中包含编译代码的 Macho-O 文件,不应与应用程序捆绑包 - IPA 文件混淆。有关 IPA 文件组成的更多详情,请参阅 "iOS 基本安全测试 "一章中的"探索应用程序包 "一节。
使用 IDA Pro 反汇编∥
如果您有 IDA Pro 的许可证,也可以使用 IDA Pro 分析应用程序二进制文件。
不幸的是,免费版 IDA 不支持 ARM 处理器类型。
要开始分析,只需在 IDA Pro 中打开应用程序二进制文件即可。
打开文件后,IDA Pro 将执行自动分析,这可能需要一段时间,具体取决于二进制文件的大小。自动分析完成后,您可以在IDA View(反汇编)窗口中浏览反汇编,并在Functions窗口中探索功能,两者均如下图所示。
普通的 IDA Pro 许可证默认情况下不包含反编译器,而且还需要额外的 Hex-Rays 反编译器许可证,价格昂贵。相比之下,Ghidra 内置的免费反编译器功能强大,是逆向工程的理想选择。
如果您有普通的 IDA Pro 许可证而不想购买 Hex-Rays 反编译器,您可以通过安装 IDA Pro 的 GhIDA 插件 来使用 Ghidra 的反编译器。
本章的大部分内容适用于以 Objective-C 编写的应用程序或具有桥接类型的应用程序,桥接类型是指同时兼容 Swift 和 Objective-C 的类型。大多数与 Objective-C 兼容的工具的 Swift 兼容性都在不断改进。例如,Frida 支持 Swift 绑定。
静态分析∥
静态分析 iOS 应用程序的首选方法是使用原始 Xcode 项目文件。理想情况下,您可以编译和调试应用程序,以快速识别源代码中的任何潜在问题。
在无法访问原始源代码的情况下对 iOS 应用程序进行黑盒分析需要逆向工程。例如,iOS 应用程序没有反编译器(尽管大多数商业和开放源代码反汇编器可以提供二进制文件的伪源代码视图),因此深入检查需要读取汇编代码。
基本信息收集∥
在本节中,我们将了解一些使用静态分析收集给定应用程序基本信息的方法和工具。
应用程序二进制文件∥
您可以使用 class-dump 获取应用程序源代码中有关方法的信息。下面的示例使用 Damn Vulnerable iOS App 来演示这一点。我们的二进制文件是所谓的胖二进制文件,这意味着它可以在 32 位和 64 位平台上执行:
解压缩应用程序并运行 otool:
unzip DamnVulnerableiOSApp.ipa
cd Payload/DamnVulnerableIOSApp.app
otool -hv DamnVulnerableIOSApp
输出结果如下
DamnVulnerableIOSApp (architecture armv7):
Mach header
magic cputype cpusubtype caps filetype ncmds sizeofcmds flags
MH_MAGIC ARM V7 0x00 EXECUTE 33 3684 NOUNDEFS DYLDLINK TWOLEVEL PIE
DamnVulnerableIOSApp (architecture arm64):
Mach header
magic cputype cpusubtype caps filetype ncmds sizeofcmds flags
MH_MAGIC_64 ARM64 ALL 0x00 EXECUTE 33 4192 NOUNDEFS DYLDLINK TWOLEVEL PIE
注意架构: armv7"(32 位)和 "arm64"(64 位)。胖二进制文件的这种设计允许在不同架构上部署应用程序。要使用 class-dump 分析应用程序,我们必须创建一个所谓的瘦二进制文件,其中只包含一种架构:
lipo -thin armv7 DamnVulnerableIOSApp -output DVIA32
然后我们就可以继续执行类转储了:
iOS8-jailbreak:~ root# class-dump DVIA32
@interface FlurryUtil : ./DVIA/DVIA/DamnVulnerableIOSApp/DamnVulnerableIOSApp/YapDatabase/Extensions/Views/Internal/
{
}
+ (BOOL)appIsCracked;
+ (BOOL)deviceIsJailbroken;
注意加号,它表示这是一个返回 BOOL 类型的类方法。减号表示这是一个实例方法。请参阅后面的章节,了解它们之间的实际区别。
一些商用反汇编程序(如 Hopper)会自动执行这些步骤,你可以看到反汇编后的二进制文件和类信息。
下面的命令正在列出共享库:
otool -L <binary>
检索字符串∥
在分析二进制文件时,字符串总是一个很好的起点,因为它们提供了相关代码的上下文。例如,"密码图生成失败 "这样的错误日志字符串会提示我们,相邻代码可能负责生成密码图。
要从 iOS 二进制文件中提取字符串,可以使用 Ghidra 或 Cutter 等图形用户界面工具,也可以使用基于 CLI 的工具,如 strings Unix 工具(strings \<path\_to\_binary>)或 radare2 的 rabin2(rabin2 -zz <path_to_binary>)。使用基于 CLI 的工具时,可以利用其他工具,如 grep(例如与正则表达式结合使用)来进一步过滤和分析结果。
交叉引用∥
Ghidra 可用于分析 iOS 二进制文件,并通过右键单击所需函数并选择 Show References to 获取交叉引用。
API 使用∥
iOS 平台为应用程序中的常用功能提供了许多内置库,例如加密、蓝牙、NFC、网络和定位库。确定应用程序中是否存在这些库可以为我们提供有关其底层工作的宝贵信息。
例如,如果应用程序导入了 "CC_SHA256 "函数,则表明该应用程序将使用 SHA256 算法执行某种散列操作。有关如何分析 iOS 密码 API 的更多信息,请参阅"iOS 密码 API "一节。
同样,上述方法也可用于确定应用程序使用蓝牙的位置和方式。例如,使用蓝牙通道执行通信的应用程序必须使用核心蓝牙框架的函数,如 CBCentralManager 或 connect。使用 iOS 蓝牙文档,您可以确定关键函数,并围绕这些函数导入开始分析。
网络通信∥
你可能会遇到的大多数应用程序都会连接到远程端点。即使在执行任何动态分析(如流量捕获和分析)之前,也可以通过枚举应用程序应与之通信的域来获取一些初始输入或入口点。
通常情况下,这些域会以字符串的形式存在于应用程序的二进制文件中。我们可以通过检索字符串(如上所述)或使用 Ghidra 等工具检查字符串来提取域。后一种方法有一个明显的优势:它可以为你提供上下文信息,因为通过检查交叉引用,你可以看到每个域是在哪种上下文中使用的。
从这里开始,您就可以利用这些信息获得更多有用的信息,例如,您可以将域名与固定证书进行匹配,或对域名进行进一步侦查,以了解更多目标环境信息。
安全连接的实施和验证是一个复杂的过程,需要考虑很多方面。例如,许多应用程序使用 HTTP 以外的其他协议,如 XMPP 或纯 TCP 数据包,或执行证书锁定以阻止 MITM 攻击。
请记住,在大多数情况下,仅使用静态分析是不够的,与动态分析相比,静态分析的效率可能会非常低,而动态分析的结果要可靠得多(例如使用拦截代理)。在本节中,我们只是触及了皮毛,因此请参阅 "iOS 基本安全测试 "一章中的"基本网络监控/嗅探 "一节,并查看"iOS 网络通信 "一章中的测试用例,以获取更多信息。
手动(反向)代码审查∥
审查拆解的 Objective-C 和 Swift 代码∥
在本节中,我们将手动探索 iOS 应用程序的二进制代码,并对其执行静态分析。手动分析是一个缓慢的过程,需要极大的耐心。良好的手动分析可以使动态分析更加成功。
执行静态分析没有硬性规定,但有一些经验法则可用于系统地进行手动分析:
- 了解被评估应用程序的工作原理--应用程序的目标以及在输入错误时的表现。
- 探索应用程序二进制文件中的各种字符串,这对发现应用程序中有趣的功能和可能的错误处理逻辑很有帮助。
- 查找名称与我们的目标相关的函数和类。
- 最后,找到进入应用程序的各种入口点,并从那里开始探索应用程序。
本节讨论的技术是通用的,适用于任何分析工具。
Objective-C∥
除了在"反汇编和反编译 "一节中学到的技术外,本节还需要对Objective-C 运行时有一定的了解。例如,"_objc_msgSend "或"_objc_release "等函数对 Objective-C 运行时具有特殊意义。
我们将使用 UnCrackable App for iOS Level 1,它的目标很简单,就是找到隐藏在二进制文件某处的 secret string。该应用程序只有一个主屏幕,用户可以通过在提供的文本字段中输入自定义字符串进行交互。
当用户输入错误字符串时,应用程序会弹出 "验证失败 "的消息。
您可以记下弹出窗口中显示的字符串,因为这可能有助于搜索处理输入并做出决定的代码。幸运的是,该应用程序的复杂性和交互都很简单,这对我们的逆转工作是个好兆头。
本节的静态分析将使用 Ghidra 9.0.4。Ghidra 9.1_beta 自动分析有一个错误,无法显示 Objective-C 类。
我们可以先在 Ghidra 中打开二进制文件,检查其中的字符串。一开始,列出的字符串可能会让人应接不暇,但只要积累了一些反转 Objective-C 代码的经验,就能学会如何过滤和丢弃那些没有实际帮助或不相关的字符串。例如,下图所示的字符串就是为 Objective-C 运行时生成的。其他字符串在某些情况下可能会有帮助,例如包含符号(函数名、类名等)的字符串,我们将在执行静态分析以检查是否使用了某些特定函数时使用它们。
如果我们继续仔细分析,就会发现 "Verification Failed(验证失败)"字符串,它用于在输入错误时弹出。如果跟踪该字符串的交叉引用(Xrefs),就会找到 ViewController 类的 buttonClick 函数。我们将在本节的稍后部分研究buttonClick函数。在进一步检查应用程序中的其他字符串时,只有少数几个字符串看起来可能是 hidden flag 的候选字符串。您也可以尝试验证一下。
接下来,我们有两条路可走。我们可以开始分析上述步骤中确定的 buttonClick 函数,或者从各个入口点开始分析应用程序。在现实世界中,大多数情况下你会选择第一种途径,但从学习的角度来看,本节我们将选择后一种途径。
iOS 应用程序会调用 iOS 运行时提供的不同预定义函数,这取决于它在应用程序生命周期中的状态。这些函数被称为应用程序的入口点。例如
- 应用程序首次启动时调用
[AppDelegate application:didFinishLaunchingWithOptions:]。 - 当应用程序从非活动状态转为活动状态时,会调用
[AppDelegate applicationDidBecomeActive:]。
许多应用程序都会在这些部分执行关键代码,因此它们通常是系统跟踪代码的良好起点。
在分析完 AppDelegate 类中的所有函数后,我们可以得出结论:没有相关代码。上述函数中没有任何代码,这就提出了一个问题--应用程序的初始化代码是从哪里调用的?
幸运的是,当前应用程序的代码库很小,我们可以在符号树视图中找到另一个ViewController类。在这个类中,函数 viewDidLoad 看起来很有趣。如果查看viewDidLoad的文档,就会发现它还可以用于对视图执行额外的初始化。
如果我们对该函数进行反编译,会发现一些有趣的现象。例如,第 31 行调用了一个本地函数,第 27-29 行初始化了一个标签,并将 "setHidden "标志设置为 1。你可以记下这些观察结果,并继续探索该类中的其他函数。为简洁起见,探索函数的其他部分留作读者练习。
在第一步中,我们观察到应用程序只有在按下用户界面按钮时才会验证输入字符串。因此,分析 buttonClick 函数是一个显而易见的目标。如前所述,该函数也包含我们在弹出窗口中看到的字符串。在第 29 行,我们根据 isEqualString(第 23 行保存在 uVar1 中的输出)的结果做出了决定。比较的输入来自文本输入框(来自用户)和 label 的值。因此,我们可以假定隐藏标记存储在该标签中。
现在,我们已经跟踪了完整的流程,并掌握了有关应用程序流程的所有信息。我们还得出结论,隐藏标记存在于文本标签中,为了确定标签的值,我们需要重新访问 viewDidLoad 函数,并了解在已识别的本地函数中发生了什么。对本地函数的分析将在"查看已反汇编的本地代码 "中讨论。
审查反汇编本地代码∥
分析反汇编本地代码需要充分了解底层平台使用的调用约定和指令。在本节中,我们将研究 ARM64 本机代码的反汇编。要了解 ARM 架构,Azeria Labs Tutorials 提供的 Introduction to ARM Assembly Basics是一个很好的起点。以下是我们将在本节中使用的内容的快速摘要:
- 在 ARM64 中,寄存器的大小为 64 位,称为 Xn,其中 n 是 0 到 31 之间的数字。如果使用寄存器的低位(LSB)32 位,则称为 Wn。
- 函数的输入参数通过 X0-X7 寄存器传递。
- 函数的返回值通过 X0 寄存器传递。
- 加载(LDR)和存储(STR)指令用于从寄存器读取或写入内存。
- B、BL、BLX 是用于调用函数的分支指令。
如上所述,Objective-C 代码也可以编译成本地二进制代码,但分析 C/C++ 本地代码可能更具挑战性。在 Objective-C 中存在各种符号(尤其是函数名),这有助于理解代码。在上一节中,我们已经了解到,"setText"、"isEqualStrings "等函数名可以帮助我们快速理解代码的语义。在 C/C++ 本地代码中,如果所有二进制文件都被剥离,那么可以帮助我们分析代码的符号就会非常少甚至没有。
反编译器可以帮助我们分析本地代码,但应谨慎使用。现代反编译器非常复杂,在它们用来反编译代码的许多技术中,有几种是基于启发式的。基于启发式的技术不一定总能给出正确的结果,确定给定本地函数的输入参数数就是一个例子。在反编译器的辅助下,掌握分析反汇编代码的知识可以减少分析本地代码时出错的几率。
我们将分析上一节中在 viewDidLoad 函数中确定的本地函数。该函数位于偏移量 0x1000080d4。该函数的返回值用于setText标签的函数调用。该文本用于与用户输入进行比较。因此,我们可以确定该函数将返回一个字符串或等价文本。
在反汇编函数时,我们首先可以看到函数没有输入。整个函数都没有读取寄存器 X0-X7。此外,该函数还多次调用其他函数,如 0x100008158、0x10000dbf0 等。
与这些函数调用相对应的指令如下。分支指令 bl 用于调用位于 0x100008158 的函数。
1000080f0 1a 00 00 94 bl FUN_100008158
1000080f4 60 02 00 39 strb w0,[x19]=>DAT_10000dbf0
函数的返回值(在 W0 中找到)被存储到寄存器 X19 中的地址(strb将一个字节存储到寄存器中的地址)。我们可以看到其他函数调用的相同模式,返回值存储在 X19 寄存器中,每次偏移量都比前一次函数调用多一个。这种行为与每次填充字符串数组的每个索引有关。每次返回值都被写入该字符串数组的一个索引。这样的调用共有 11 次,根据目前的证据,我们可以推测隐藏标志的长度为 11。在反汇编即将结束时,函数返回了该字符串数组的地址。
100008148 E0 03 13 aa mov x0=>DAT_10000dbf0,x19
要确定隐藏标志的值,我们需要知道上面确定的每个后续函数调用的返回值。在分析函数 0x100006fb4 时,我们可以发现这个函数比我们之前分析的函数要大得多、复杂得多。函数图对分析复杂函数非常有帮助,因为它有助于更好地理解函数的控制流。在 Ghidra 中,点击子菜单中的显示函数图图标即可获得函数图。
完全手动分析所有本地函数将耗费大量时间,而且可能不是最明智的方法。在这种情况下,强烈建议使用动态分析方法。例如,通过使用挂钩或简单调试程序等技术,我们可以轻松确定返回值。通常情况下,使用动态分析方法是个好主意,然后再回过头来手动分析反馈循环中的函数。这样就能同时从两种方法中获益,同时节省时间和精力。动态分析技术在"动态分析 "一节中讨论。
自动静态分析∥
目前有几款用于分析 iOS 应用程序的自动化工具,其中大部分是商业工具。免费开源工具 MobSF 和 objection 具有一些静态和动态分析功能。其他工具列于"测试工具" 章节的 "静态源代码分析 "部分。
不要回避使用自动扫描仪进行分析--它们可以帮助你挑选低垂的果实,让你专注于更有趣的分析方面,如业务逻辑。请记住,静态分析仪可能会产生假阳性和假阴性;请务必仔细查看分析结果。
动态分析∥
越狱设备的生活很轻松:您不仅可以轻松获得对设备的访问权限,由于没有代码签名,您还可以使用更强大的动态分析技术。在 iOS 上,大多数动态分析工具都基于 Cydia Substrate(一个用于开发运行时补丁的框架)或 Frida(一个动态自省工具)。对于基本的 API 监控,你可以不了解 Substrate 或 Frida 工作原理的所有细节,只需使用现有的 API 监控工具即可。
非越狱设备上的动态分析∥
如果无法访问越狱设备,可以为目标应用程序打上补丁并重新打包,以便在启动时加载动态库(例如,Frida 小工具,以便使用 Frida 和 objection 等相关工具进行动态测试)。这样,您就可以对应用程序进行检测,并完成动态分析所需的一切操作(当然,这种方法无法突破沙盒)。不过,这种技术只有在应用程序二进制文件未经过 FairPlay 加密(即从 App Store 获取)的情况下才有效。
自动重新打包∥
反对 自动重新打包应用程序的过程。你可以在官方维基页面上找到详尽的文档。
对于大多数用例来说,使用异议的重新打包功能就足够了。不过,在某些复杂情况下,你可能需要更精细的控制或更可定制的重新打包流程。在这种情况下,您可以在"手动重新打包"中阅读有关重新打包和辞职流程的详细说明。
Manual Repackaging∥
由于 Apple 的配置和代码签名系统令人困惑,因此重新签名应用程序比想象中更具挑战性。除非配置文件和代码签名头完全正确,否则 iOS 不会运行应用程序。这需要学习许多概念--证书类型、捆绑 ID、应用程序 ID、团队标识符,以及 Apple 的构建工具如何将它们连接起来。让操作系统运行未通过默认方法(Xcode)构建的二进制文件是一个令人生畏的过程。
我们将使用 optool、苹果的构建工具和一些 shell 命令。我们的方法受到 Vincent Tan's Swizzler project 的启发。NCC 小组介绍了另一种重新打包方法。
要重现下面列出的步骤,请从 OWASP Mobile Testing Guide 资源库下载 UnCrackable iOS App Level 1。我们的目标是让 UnCrackable 应用程序在启动过程中加载 FridaGadget.dylib,这样我们就能用 Frida 对应用程序进行检测。
请注意,以下步骤仅适用于 macOS,因为 Xcode 仅适用于 macOS。
获取开发人员配置文件和证书∥
供应配置文件是一个由 Apple 签名的 plist 文件,它将你的代码签名证书添加到一个或多个设备上可接受的证书列表中。换句话说,这代表苹果明确允许你的应用程序因某些原因运行,例如在选定设备上调试(开发配置文件)。供应配置文件还包括授予应用程序的 entitlements。certificate 包含你用来签名的私钥。
根据你是否注册为 iOS 开发者,你可以通过以下方式之一获得证书和供应配置文件:
使用 iOS 开发者账户:
如果你以前用 Xcode 开发并部署过 iOS 应用程序,那么你已经安装了自己的代码签名证书。使用 security 命令(仅限 macOS)列出你的签名身份:
$ security find-identity -v
1) 61FA3547E0AF42A11E233F6A2B255E6B6AF262CE "iPhone Distribution: Company Name Ltd."
2) 8004380F331DCA22CC1B47FB1A805890AE41C938 "iPhone Developer: Bernhard Müller (RV852WND79)"
登录 Apple Developer 门户发布新的 App ID,然后发布并下载配置文件。App ID 是一个由两部分组成的字符串:一个由 Apple 提供的团队 ID 和一个捆绑 ID 搜索字符串,您可以将其设置为任意值,如 com.example.myapp。请注意,您可以使用单个 App ID 重新签署多个应用程序。确保创建的是 development 配置文件,而不是 distribution 配置文件,以便调试应用程序。
在下面的示例中,我使用了与公司开发团队相关联的签名身份。我为这些示例创建了应用程序 ID "sg.vp.repackaged "和供应配置文件 "AwesomeRepackaging"。我最终创建了 "AwesomeRepackaging.mobileprovision "文件,请在下面的 shell 命令中用你自己的文件名替换它。
使用常规 Apple ID:
即使你不是付费开发者,苹果也会为你提供免费的开发配置文件。您可以通过 Xcode 和常规 Apple 帐户获取配置文件:只需创建一个空的 iOS 项目,然后从应用程序容器中提取 embedded.mobileprovision,该容器位于您主目录的 Xcode 子目录中:~/Library/Developer/Xcode/DerivedData/<ProjectName>/Build/Products/Debug-iphoneos/<ProjectName>.app/。NCC 博客文章 "无需越狱的 iOS 仪器"详细解释了这一过程。
获得配置文件后,可以使用 security命令检查其内容。你会在配置文件中找到授予应用程序的权限,以及允许使用的证书和设备。代码签名需要这些内容,因此请将它们提取到一个单独的 plist 文件中,如下所示。查看文件内容,确保一切符合预期。
$ security cms -D -i AwesomeRepackaging.mobileprovision > profile.plist
$ /usr/libexec/PlistBuddy -x -c 'Print :Entitlements' profile.plist > entitlements.plist
$ cat entitlements.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>application-identifier</key>
<string>LRUD9L355Y.sg.vantagepoint.repackage</string>
<key>com.apple.developer.team-identifier</key>
<string>LRUD9L355Y</string>
<key>get-task-allow</key>
<true/>
<key>keychain-access-groups</key>
<array>
<string>LRUD9L355Y.*</string>
</array>
</dict>
</plist>
请注意应用程序标识符,它是团队 ID (LRUD9L355Y) 和捆绑 ID (sg.vantagepoint.repackage) 的组合。此供应配置文件仅对具有此应用程序 ID 的应用程序有效。get-task-allow "键也很重要:设置为 "true "时,其他进程(如调试服务器)可被允许附加到应用程序(因此,在分发配置文件中应设置为 "false")。
基本信息收集∥
在 iOS 上,收集有关运行中进程或应用程序的基本信息可能比 Android 稍具挑战性。在 Android(或任何基于 Linux 的操作系统)上,进程信息是通过 procfs 以可读取文本文件的形式公开的。因此,在 root 设备上可以通过解析这些文本文件获得目标进程的任何信息。相比之下,iOS 上没有与 procfs 相对应的文件。此外,在 iOS 上,许多用于探索进程信息的标准 UNIX 命令行工具(如 lsof 和 vmmap)都被移除,以减少固件的大小。
在本节中,我们将学习如何使用 lsof 等命令行工具收集 iOS 上的进程信息。由于 iOS 系统默认情况下没有这些工具,因此我们需要通过其他方法安装它们。例如,可以使用 Cydia安装 lsof(该可执行文件并非最新版本,但仍能达到我们的目的)。
打开文件∥
lsof是一个功能强大的命令,可提供有关运行进程的大量信息。它可以提供所有打开文件的列表,包括流、网络文件或普通文件。在不带任何选项的情况下调用 lsof 命令时,它将列出属于系统中所有活动进程的所有打开文件;而在使用 -c <进程名>或 -p <pid> 标记的情况下调用时,它将返回指定进程的打开文件列表。man page详细介绍了其他各种选项。
在运行 PID 为 2828 的 iOS 应用程序中使用 lsof,可以列出各种打开的文件,如下所示。
iPhone:~ root# lsof -p 2828
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
iOweApp 2828 mobile cwd DIR 1,2 864 2 /
iOweApp 2828 mobile txt REG 1,3 206144 189774 /private/var/containers/Bundle/Application/F390A491-3524-40EA-B3F8-6C1FA105A23A/iOweApp.app/iOweApp
iOweApp 2828 mobile txt REG 1,3 5492 213230 /private/var/mobile/Containers/Data/Application/5AB3E437-9E2D-4F04-BD2B-972F6055699E/tmp/com.apple.dyld/iOweApp-6346DC276FE6865055F1194368EC73CC72E4C5224537F7F23DF19314CF6FD8AA.closure
iOweApp 2828 mobile txt REG 1,3 30628 212198 /private/var/preferences/Logging/.plist-cache.vqXhr1EE
iOweApp 2828 mobile txt REG 1,2 50080 234433 /usr/lib/libobjc-trampolines.dylib
iOweApp 2828 mobile txt REG 1,2 344204 74185 /System/Library/Fonts/AppFonts/ChalkboardSE.ttc
iOweApp 2828 mobile txt REG 1,2 664848 234595 /usr/lib/dyld
...
已加载的本地库∥
您可以使用 objection 中的 list_frameworks 命令列出代表框架的所有应用程序捆绑包。
...itudehacks.DVIAswiftv2.develop on (iPhone: 13.2.3) [usb] # ios bundles list_frameworks
Executable Bundle Version Path
-------------- ----------------------------------------- --------- -------------------------------------------
Bolts org.cocoapods.Bolts 1.9.0 ...8/DVIA-v2.app/Frameworks/Bolts.framework
RealmSwift org.cocoapods.RealmSwift 4.1.1 ...A-v2.app/Frameworks/RealmSwift.framework
...ystem/Library/Frameworks/IOKit.framework
...
打开连接∥
当使用选项 -i 时调用 lsof 命令,它会给出设备上所有活动进程的开放网络端口列表。要获取特定进程的开放网络端口列表,可使用 lsof -i -a -p <pid> 命令,其中 -a (AND) 选项用于过滤。下面显示的是 PID 1 的过滤输出。
iPhone:~ root# lsof -i -a -p 1
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
launchd 1 root 27u IPv6 0x69c2ce210efdc023 0t0 TCP *:ssh (LISTEN)
launchd 1 root 28u IPv6 0x69c2ce210efdc023 0t0 TCP *:ssh (LISTEN)
launchd 1 root 29u IPv4 0x69c2ce210eeaef53 0t0 TCP *:ssh (LISTEN)
launchd 1 root 30u IPv4 0x69c2ce210eeaef53 0t0 TCP *:ssh (LISTEN)
launchd 1 root 31u IPv4 0x69c2ce211253b90b 0t0 TCP 192.168.1.12:ssh->192.168.1.8:62684 (ESTABLISHED)
launchd 1 root 42u IPv4 0x69c2ce211253b90b 0t0 TCP 192.168.1.12:ssh->192.168.1.8:62684 (ESTABLISHED)
沙盒检测∥
在 iOS 上,每个应用程序都有一个沙盒文件夹来存储数据。根据 iOS 安全模型,其他应用程序无法访问应用程序的沙盒文件夹。此外,用户不能直接访问 iOS 文件系统,因此无法浏览或提取文件系统中的数据。在 iOS <8.3 中,有一些应用程序可以用来浏览设备的文件系统,如 iExplorer 和 iFunBox,但在最近的 iOS 版本(>8.3)中,沙盒规则更加严格,这些应用程序已无法使用。因此,如果需要访问文件系统,只能在越狱设备上进行。作为越狱过程的一部分,应用程序的沙盒保护会被禁用,因此可以轻松访问沙盒文件夹。
应用程序沙盒文件夹的内容已在 iOS 基本安全测试一章的"访问应用程序数据目录 "中讨论过。本章概述了文件夹结构以及应分析的目录。
调试∥
LLDB 等 iOS 调试器使用它来附加、步进或继续进程,但却不能使用它来读取或写入内存(缺少所有 PT_READ_* 和 PT_WRITE* 请求)。相反,它们必须获得所谓的马赫任务端口(通过调用带有目标进程 ID 的task_for_pid),然后使用马赫 IPC 接口 API 函数来执行暂停目标进程、读/写寄存器状态(thread_get_state/thread_set_state)和虚拟内存(mach_vm_read/mach_vm_write)等操作。
更多信息,请参阅 GitHub 上的 LLVM 项目,其中包含 LLDB 的源代码,以及《Mac OS X 和 iOS Internals》的第 5 章和第 13 章: To the Apple's Core"[#levin]中的第 5 章和第 13 章,以及 "The Mac Hacker's Handbook"[#miller]中的第 4 章 "Tracing and Debugging"。
使用 LLDB 调试∥
Xcode 安装的默认 debugserver 可执行文件不能用于附加到任意进程(通常仅用于调试使用 Xcode 部署的自行开发的应用程序)。要调试第三方应用程序,必须在 debugserver 可执行文件中添加 task_for_pid-allow 权限,这样调试器进程就可以调用 task_for_pid 来获取目标 Mach 任务端口(如前所述)。一个简单的方法是在随 Xcode 提供的 debugserver 二进制文件中添加权限。
要获取可执行文件,请加载以下 DMG 镜像:
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/<target-iOS-version>/DeveloperDiskImage.dmg
你会在挂载卷的 /usr/bin/ 目录中找到 debugserver 可执行文件。将其复制到一个临时目录,然后创建一个名为 entitlements.plist 的文件,内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/ PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.springboard.debugapplications</key>
<true/>
<key>run-unsigned-code</key>
<true/>
<key>get-task-allow</key>
<true/>
<key>task_for_pid-allow</key>
<true/>
</dict>
</plist>
使用编码设计应用权限:
codesign -s - --entitlements entitlements.plist -f debugserver
将修改后的二进制文件复制到测试设备上的任意目录。以下示例使用 usbmuxd 通过 USB 转发本地端口。
iproxy 2222 22
scp -P 2222 debugserver root@localhost:/tmp/
注:在 iOS 12 及更高版本中,使用以下步骤签署从 XCode 镜像中获取的 debugserver 二进制文件。
-
通过 scp 将 debugserver 二进制文件复制到设备,例如 /tmp 文件夹。
-
通过 SSH 连接到设备,创建名为 entitlements.xml 的文件,内容如下:
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>platform-application</key>
<true/>
<key>com.apple.private.security.no-container</key>
<true/>
<key>com.apple.private.skip-library-validation</key>
<true/>
<key>com.apple.backboardd.debugapplications</key>
<true/>
<key>com.apple.backboardd.launchapplications</key>
<true/>
<key>com.apple.diagnosticd.diagnostic</key>
<true/>
<key>com.apple.frontboard.debugapplications</key>
<true/>
<key>com.apple.frontboard.launchapplications</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
<key>com.apple.springboard.debugapplications</key>
<true/>
<key>com.apple.system-task-ports</key>
<true/>
<key>get-task-allow</key>
<true/>
<key>run-unsigned-code</key>
<true/>
<key>task_for_pid-allow</key>
<true/>
</dict>
</plist>
- 键入以下命令签署 debugserver 二进制文件:
ldid -Sentitlements.xml debugserver
- 通过以下命令验证 debugserver 二进制文件是否可以执行:
./debugserver
现在,你可以将 debugserver 附加到设备上运行的任何进程。
VP-iPhone-18:/tmp root# ./debugserver *:1234 -a 2670
debugserver-@(#)PROGRAM:debugserver PROJECT:debugserver-320.2.89
for armv7.
Attaching to process 2670...
使用以下命令,您可以通过运行在目标设备上的 debugserver 启动应用程序:
debugserver -x backboard \*:1234 /Applications/MobileSMS.app/MobileSMS
附加到已运行的应用程序:
debugserver \*:1234 -a "MobileSMS"
现在您可以从主机连接到 iOS 设备:
(lldb) process connect connect://<ip-of-ios-device>:1234
输入 image list 会显示主可执行文件和所有依赖库的列表。
调试发布应用程序∥
在上一节中,我们了解了如何使用 LLDB 在 iOS 设备上设置调试环境。在本节中,我们将利用这些信息学习如何调试第三方发布应用程序。我们将继续使用 UnCrackable App for iOS Level 1 并使用调试器解决它。
与调试构建不同,为发布构建编译的代码经过优化,以实现最高性能和最小二进制构建大小。作为一般的最佳做法,发布版编译会去掉大部分调试符号,这就增加了逆向工程和调试二进制文件的复杂性。
由于调试符号的缺失,反向跟踪输出中缺少了符号名,因此无法通过函数名来设置断点。幸运的是,调试器也支持直接在内存地址上设置断点。在本节中,我们将进一步学习如何设置断点,最终解决 crackme 的难题。
在使用内存地址设置断点之前,我们需要做一些基础工作。这需要确定两个偏移量:
- 断点偏移: 我们要设置断点的代码的偏移地址。这个地址可以通过像 Ghidra 这样的反汇编器对代码进行静态分析来获得。
- ASLR 移位偏移: 当前进程的 ASLR 移位偏移量。由于 ASLR 偏移量是在应用程序的每个新实例中随机生成的,因此必须为每个调试会话单独获取。这可以通过调试器本身确定。
iOS 是一个现代操作系统,采用了多种技术来减少代码执行攻击,其中一种技术就是地址空间随机化布局(ASLR)。每次执行新的应用程序时,系统都会生成一个随机的 ASLR 移位偏移量,不同进程的数据结构都会根据该偏移量进行移位。
调试器最终使用的断点地址是上述两个地址的总和(断点偏移 + ASLR 移位偏移)。这种方法假定反汇编器和 iOS 使用的映像基址(稍后讨论)是相同的,这在大多数情况下是正确的。
当二进制文件在 Ghidra 等反汇编器中打开时,它会通过模拟相应操作系统的加载器来加载二进制文件。加载二进制文件的地址称为_image base address_。二进制文件中的所有代码和符号都可以使用从该映像基址偏移的常量地址来寻址。在 Ghidra 中,可以通过确定 Mach-O 文件的起始地址来获得映像基地址。在本例中,它是 0x100000000。
根据我们之前在"手动(反向)代码审查 "部分对无法破解的 1 级应用程序的分析,隐藏字符串的值存储在一个设置了hidden标志的标签中。在反汇编中,该标签的文本值存储在寄存器 X21 中,通过 mov 从 X0 存储,偏移量为 0x100004520。这就是我们的 断点偏移。
对于第二个地址,我们需要确定给定进程的 ASLR 移位偏移量。可以使用 LLDB 命令 "image list -o -f "来确定 ASLR 偏移量。输出结果如下面的截图所示。
在输出中,第一列包含图像的序列号([X]),第二列包含随机生成的 ASLR 偏移量,第三列包含图像的完整路径,最后,括号中的内容显示了将 ASLR 偏移量添加到原始图像基地址后的图像基地址(0x100000000 + 0x70000 = 0x100070000)。您会发现图像基地址 0x100000000 与 Ghidra 中的相同。现在,要获得代码位置的有效内存地址,我们只需在 Ghidra 中确定的地址上加上 ASLR 偏移量即可。设置断点的有效地址将是 0x100004520 + 0x70000 = 0x100074520。可以使用命令 b 0x100074520 设置断点。
在上述输出中,你可能还会注意到许多列为图像的路径并不指向 iOS 设备上的文件系统。相反,它们指向运行 LLDB 的主机上的某个位置。这些图像是系统库,其调试符号可在主机上使用,以帮助应用程序开发和调试(作为 Xcode iOS SDK 的一部分)。因此,您可以直接使用函数名对这些库设置断点。
设置断点并运行应用程序后,一旦断点被击中,执行就会停止。现在,您可以访问并探索进程的当前状态。在本例中,通过之前的静态分析,我们知道寄存器 X0 中包含隐藏的字符串,因此让我们来探索它。在 LLDB 中,您可以使用 po (print object) 命令打印 Objective-C 对象。
瞧,在静态分析和调试器的帮助下,破解问题就可以轻松解决了。LLDB 实现了大量功能,包括更改寄存器的值、更改进程内存中的值,甚至使用 Python 脚本自动执行任务。
苹果官方推荐使用 LLDB 进行调试,但在 iOS 上也可以使用 GDB。只要将 LLDB 的特定命令更改为 GDB 命令,上述讨论的技术也适用于使用 GDB 进行调试。
跟踪∥
跟踪涉及记录程序的执行信息。与 Android 不同的是,可用于跟踪 iOS 应用程序各个方面的选项非常有限。在本节中,我们将主要依赖 Frida 等工具来执行跟踪。
方法跟踪∥
拦截 Objective-C 方法是一种有用的 iOS 安全测试技术。例如,您可能会对数据存储操作或网络请求感兴趣。在下面的示例中,我们将编写一个简单的跟踪器,用于记录通过 iOS 标准 HTTP API 发出的 HTTP(S) 请求。我们还将向你展示如何将跟踪器注入 Safari 网页浏览器。
在下面的示例中,我们将假设你使用的是越狱设备。如果不是这种情况,首先需要按照 重新打包和重新签名一节中概述的步骤重新打包 Safari 应用程序。
Frida 附带一个函数跟踪工具 frida-trace。frida-trace 通过 -m 标志接受 Objective-C 方法。你也可以向它传递通配符,例如,如果给定了 -[NSURL*],frida-trace就会自动在所有 NSURL 类选择器上安装钩子。我们将用它来大致了解用户打开 URL 时 Safari 调用了哪些库函数。
在设备上运行 Safari,并确保设备已通过 USB 连接。然后启动 frida-trace 如下:
$ frida-trace -U -m "-[NSURL *]" Safari
Instrumenting functions...
-[NSURL isMusicStoreURL]: Loaded handler at "/Users/berndt/Desktop/__handlers__/__NSURL_isMusicStoreURL_.js"
-[NSURL isAppStoreURL]: Loaded handler at "/Users/berndt/Desktop/__handlers__/__NSURL_isAppStoreURL_.js"
(...)
Started tracing 248 functions. Press Ctrl+C to stop.
接下来,在 Safari 中导航到一个新网站。你应该会在 frida-trace 控制台上看到跟踪到的函数调用。请注意,调用 initWithURL: 方法是为了初始化一个新的 URL 请求对象。
/* TID 0xc07 */
20313 ms -[NSURLRequest _initWithCFURLRequest:0x1043bca30 ]
20313 ms -[NSURLRequest URL]
(...)
21324 ms -[NSURLRequest initWithURL:0x106388b00 ]
21324 ms | -[NSURLRequest initWithURL:0x106388b00 cachePolicy:0x0 timeoutInterval:0x106388b80
本地库跟踪∥
正如本章前面所讨论的,iOS 应用程序也可以包含本地代码(C/C++ 代码),也可以使用 frida-trace CLI 对其进行跟踪。例如,可以通过运行以下命令跟踪对 open 函数的调用:
frida-trace -U -i "open" sg.vp.UnCrackable1
使用 Frida 跟踪本地代码的整体方法和进一步改进与 Android"跟踪 "一节中讨论的方法类似。
遗憾的是,目前还没有诸如 strace 或 ftrace 等工具可用于跟踪 iOS 应用程序的系统调用或函数调用。只有 DTrace 是一款功能强大、用途广泛的跟踪工具,但它只适用于 MacOS,不适用于 iOS。
基于仿真的分析∥
iOS 模拟器¶
Apple 在 Xcode 中提供了一个模拟器应用程序,可为 iPhone、iPad 或 Apple Watch 提供 真实的 iOS 设备外观 用户界面。它允许您在开发过程中快速创建应用程序原型并测试调试构建,但实际上它并不是模拟器。模拟器和仿真器的区别在"基于仿真的动态分析 "一节中已有论述。
在开发和调试应用程序时,Xcode 工具链会生成 x86 代码,这些代码可以在 iOS 模拟器中执行。但是,在发布版本时,只会生成 ARM 代码(与 iOS 模拟器不兼容)。这就是为什么从 Apple App Store 下载的应用程序无法在 iOS 模拟器上进行任何应用程序分析的原因。
Corellium¶
Corellium 是一款商业工具,可提供运行实际 iOS 固件的虚拟 iOS 设备,是有史以来唯一公开的 iOS 模拟器。由于它是一款专有产品,因此有关其实现的信息并不多。Corellium 没有社区许可证,因此我们不会详细介绍其使用方法。
Corellium 允许你启动一个设备(无论是否越狱)的多个实例,这些实例可以作为本地设备访问(通过简单的 VPN 配置)。它还能拍摄和恢复设备状态快照,并为设备提供方便的网络外壳。最后,也是最重要的一点,由于它的 "模拟器 "性质,你可以执行从苹果应用商店下载的应用程序,从而可以对真正的 iOS(越狱)设备进行任何类型的应用程序分析。
请注意,要在 Corellium 设备上安装 IPA,必须使用有效的苹果开发者证书进行未加密和签名。更多信息请参阅 此处。
二进制分析∥
使用二进制分析框架进行二进制分析的介绍已在 Android 的"动态分析 "一节中讨论过。我们建议您重温这部分内容,温故知新。
对于 Android,我们使用 Angr 的符号执行引擎来解决一个难题。在本节中,我们将首先使用 Unicorn 解决 UnCrackable App for iOS Level 1 挑战,然后重新使用 Angr 二进制分析框架来分析挑战,但我们将使用其具体执行(或动态执行)功能来代替符号执行。
独角兽∥
Unicorn 是一个轻量级、多体系结构 CPU 仿真器框架,基于 QEMU,并超越它增加了专门用于 CPU 仿真的有用功能。Unicorn 提供了执行处理器指令所需的基本架构。在本节中,我们将使用Unicorn 的 Python 绑定来解决UnCrackable App for iOS Level 1挑战。
要充分发挥 Unicorn 的威力,我们需要实现所有必要的基础设施,而这些基础设施一般都可以从操作系统中获得,例如二进制加载器、链接器和其他依赖项,或者使用其他更高级别的框架,例如 Qiling,它利用 Unicorn 仿真 CPU 指令,但能理解操作系统的上下文。不过,对于这个非常本地化的挑战来说,这样做是多余的,因为只需执行二进制文件的一小部分就足够了。
在"查看已反汇编的本地代码 "部分进行手动分析时,我们确定地址为 0x1000080d4 的函数负责动态生成秘密字符串。正如我们即将看到的那样,所有必要的代码几乎都包含在二进制文件中,因此这是使用像 Unicorn 这样的 CPU 仿真器的完美场景。
如果我们分析一下该函数和随后的函数调用,就会发现它并不依赖于任何外部库,也不执行任何系统调用。对函数的唯一外部访问发生在地址 0x1000080f4 处,其中一个值被存储到地址 0x10000dbf0,该地址映射到"__data "部分。
因此,为了正确模拟这部分代码,除了 __text 部分(包含指令)外,我们还需要加载 __data 部分。
要使用 Unicorn 解决这一难题,我们将执行以下步骤:
- 运行
lipo -thin arm64 <app_binary> -output uncrackable.arm64(也可使用 ARMv7),获取 ARM64 版本的二进制文件。 - 从二进制文件中提取
__text和__data部分。 - 创建并映射用作堆栈内存的内存。
- 创建内存并加载
__text和__data部分。 - 通过提供开始和结束地址执行二进制文件。
- 最后,转储函数的返回值,在本例中就是我们的秘密字符串。
为了从 Mach-O 二进制文件中提取 __text 和 __data 部分的内容,我们将使用 LIEF,它提供了一个方便的抽象概念来操作多种可执行文件格式。在将这些部分加载到内存之前,我们需要确定它们的基地址,例如使用 Ghidra、Radare2 或 IDA Pro。
从上表中,我们将使用 __text 和 __data部分的基地址分别为 0x10000432c 和 0x10000d3e8 将它们加载到内存中。
为 Unicorn 分配内存时,内存地址应为 4k 页面对齐,分配的大小应为 1024 的倍数。
下面的脚本模拟了位于 0x1000080d4 的函数,并转储了秘密字符串:
import lief
from unicorn import *
from unicorn.arm64_const import *
# --- Extract __text and __data section content from the binary ---
binary = lief.parse("uncrackable.arm64")
text_section = binary.get_section("__text")
text_content = text_section.content
data_section = binary.get_section("__data")
data_content = data_section.content
# --- Setup Unicorn for ARM64 execution ---
arch = "arm64le"
emu = Uc(UC_ARCH_ARM64, UC_MODE_ARM)
# --- Create Stack memory ---
addr = 0x40000000
size = 1024*1024
emu.mem_map(addr, size)
emu.reg_write(UC_ARM64_REG_SP, addr + size - 1)
# --- Load text section --
base_addr = 0x100000000
tmp_len = 1024*1024
text_section_load_addr = 0x10000432c
emu.mem_map(base_addr, tmp_len)
emu.mem_write(text_section_load_addr, bytes(text_content))
# --- Load data section ---
data_section_load_addr = 0x10000d3e8
emu.mem_write(data_section_load_addr, bytes(data_content))
# --- Hack for stack_chk_guard ---
# without this will throw invalid memory read at 0x0
emu.mem_map(0x0, 1024)
emu.mem_write(0x0, b"00")
# --- Execute from 0x1000080d4 to 0x100008154 ---
emu.emu_start(0x1000080d4, 0x100008154)
ret_value = emu.reg_read(UC_ARM64_REG_X0)
# --- Dump return value ---
print(emu.mem_read(ret_value, 11))
你可能会注意到在地址 0x0 处有一个额外的内存分配,这是一个简单的绕过
stack_chk_guard检查的方法。如果不这样做,就会出现读取内存无效的错误,二进制文件无法执行。有了这个漏洞,程序将访问 0x0 的值,并将其用于stack_chk_guard检查。
总之,使用 Unicorn 确实需要在执行二进制文件前进行一些额外设置,但一旦完成,该工具就能帮助深入了解二进制文件。它提供了执行完整二进制文件或有限部分二进制文件的灵活性。Unicorn 还提供了应用程序接口,可将钩子附加到执行过程中。使用这些钩子,你可以在执行过程中的任何时刻观察程序的状态,甚至可以操作寄存器或变量值,强行探索程序中的其他执行分支。在 Unicorn 中运行二进制程序的另一个好处是,你不必担心各种检查,如 root/jailbreak 检测或调试器检测等。
Angr¶
Angr是一款用途非常广泛的工具,提供多种技术来促进二进制分析,同时支持各种文件格式和硬件指令集。
Angr 中的 Mach-O 后端并不完善,但在我们的案例中却运行得很好。
在手动分析审查已反汇编的本地代码 "部分中的代码时,我们发现进行进一步手动分析非常麻烦。偏移量 0x1000080d4 处的函数被确定为包含秘密字符串的最终目标。
如果我们重新查看该函数,就会发现它涉及多个子函数调用,有趣的是,这些函数都不依赖于其他库调用或系统调用。这是使用 Angr 具体执行引擎的完美案例。请按照以下步骤解决这一难题:
- 运行
lipo -thin arm64 <app_binary> -output uncrackable.arm64获取 ARM64 版本的二进制文件(也可使用 ARMv7)。 - 通过加载上述二进制文件创建 Angr
Project。 - 通过传递要执行函数的地址,获取一个
callable对象。摘自 Angr 文档 "可调用 "是二进制文件中函数的代表,可以像本地 python 函数一样与之交互。 - 将上述
callable对象传递给具体执行引擎,本例中的具体执行引擎是claripy.backends.concrete。 - 访问内存,并从上述函数返回的指针中提取字符串。
import angr
import claripy
def solve():
# Load the binary by creating angr project.
project = angr.Project('uncrackable.arm64')
# Pass the address of the function to the callable
func = project.factory.callable(0x1000080d4)
# Get the return value of the function
ptr_secret_string = claripy.backends.concrete.convert(func()).value
print("Address of the pointer to the secret string: " + hex(ptr_secret_string))
# Extract the value from the pointer to the secret string
secret_string = func.result_state.mem[ptr_secret_string].string.concrete
print(f"Secret String: {secret_string}")
solve()
上面,Angr 在其具体执行引擎提供的执行环境中执行了 ARM64 代码。从内存中访问结果就像在真实设备上执行程序一样。这个案例就是一个很好的例子,二进制分析框架使我们能够对二进制文件进行全面分析,即使没有运行该文件所需的专用设备。
篡改和运行时工具∥
补丁、重新打包和重新签名∥
是时候认真对待了!如你所知,IPA 文件实际上是 ZIP 压缩包,因此你可以使用任何 ZIP 工具来解压压缩包。
unzip UnCrackable-Level1.ipa
补丁示例: 安装 Frida 小工具∥
如果您想在非越狱设备上使用 Frida,则需要包含 FridaGadget.dylib。先下载它:
curl -O <https://build.frida.re/frida/ios/lib/FridaGadget.dylib>
将 FridaGadget.dylib 复制到应用程序目录,然后使用 optool 为 "UnCrackable Level 1 "二进制文件添加加载命令。
$ unzip UnCrackable-Level1.ipa
$ cp FridaGadget.dylib Payload/UnCrackable\ Level\ 1.app/
$ optool install -c load -p "@executable_path/FridaGadget.dylib" -t Payload/UnCrackable\ Level\ 1.app/UnCrackable\ Level\ 1
Found FAT Header
Found thin header...
Found thin header...
Inserting a LC_LOAD_DYLIB command for architecture: arm
Successfully inserted a LC_LOAD_DYLIB command for arm
Inserting a LC_LOAD_DYLIB command for architecture: arm64
Successfully inserted a LC_LOAD_DYLIB command for arm64
Writing executable to Payload/UnCrackable Level 1.app/UnCrackable Level 1...
补丁示例: 使应用程序可调试∥
默认情况下,Apple App Store 上的应用程序不可调试。要调试 iOS 应用程序,必须启用 "get-task-allow "权限。该权限允许其他进程(如调试器)附加到应用程序。Xcode 不会在分发配置文件中添加 "get-task-allow "权限;它只会被列入白名单并添加到开发配置文件中。
因此,要调试从 App Store 获取的 iOS 应用程序,需要使用带有 "get-task-allow "权限的开发供应配置文件对其重新签名。下一节将讨论如何重新签名应用程序。
重新打包和重新签名∥
当然,篡改应用程序会使主可执行文件的代码签名失效,因此无法在非越狱设备上运行。你需要替换供应配置文件,并用配置文件中列出的证书签署主可执行文件和你所包含的文件(如 FridaGadget.dylib)。
首先,将我们自己的配置文件添加到软件包中:
cp AwesomeRepackaging.mobileprovision Payload/UnCrackable\ Level\ 1.app/embedded.mobileprovision
接下来,我们需要确保 Info.plist 中的绑定 ID 与配置文件中指定的一致,因为在签名过程中,代码设计工具将从 Info.plist 中读取绑定 ID;错误的值将导致签名无效。
/usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier sg.vantagepoint.repackage" Payload/UnCrackable\ Level\ 1.app/Info.plist
最后,我们使用代码设计工具重新签署两个二进制文件。你需要使用_你自己_的签名身份(在本例中为 8004380F331DCA22CC1B47FB1A805890AE41C938),你可以通过执行命令 security find-identity -v 输出该身份。
$ rm -rf Payload/UnCrackable\ Level\ 1.app/_CodeSignature
$ /usr/bin/codesign --force --sign 8004380F331DCA22CC1B47FB1A805890AE41C938 Payload/UnCrackable\ Level\ 1.app/FridaGadget.dylib
Payload/UnCrackable Level 1.app/FridaGadget.dylib: replacing existing signature
entitlements.plist 是你为空 iOS 项目创建的文件。
$ /usr/bin/codesign --force --sign 8004380F331DCA22CC1B47FB1A805890AE41C938 --entitlements entitlements.plist Payload/UnCrackable\ Level\ 1.app/UnCrackable\ Level\ 1
Payload/UnCrackable Level 1.app/UnCrackable Level 1: replacing existing signature
现在你应该可以运行修改后的应用程序了。使用 ios-deploy 在设备上部署和运行应用程序:
ios-deploy --debug --bundle Payload/UnCrackable\ Level\ 1.app/
如果一切顺利,应用程序应在调试模式下启动并附加 LLDB。然后,Frida 也应能连接到应用程序。您可以通过 frida-ps 命令来验证:
$ frida-ps -U
PID Name
--- ------
499 Gadget
出错时(通常会出错),最可能的原因是供应配置文件和代码签名头不匹配。阅读官方文档有助于了解代码签名过程。Apple 的权限故障排除页面也是一个有用的资源。
补丁 React Native 应用程序∥
如果已使用 React Native框架进行开发,则主应用程序代码位于Payload/[APP].app/main.jsbundle文件中。该文件包含 JavaScript 代码。大多数情况下,该文件中的 JavaScript 代码都经过了最小化处理。使用工具 JStillery,可以重试该文件的人类可读版本,从而进行代码分析。与在线版本相比,CLI 版本的 JStillery和本地服务器更为可取,因为后者会向第三方披露源代码。
从 iOS 10 开始,应用程序压缩包会在安装时解压到"/private/var/containers/Bundle/Application/[GUID]/[APP].app "文件夹中,因此可以在此位置修改 JavaScript 应用程序主文件。
要确定应用程序文件夹的确切位置,可以使用工具 ipainstaller:
- 使用命令
ipainstaller -l列出设备上安装的应用程序。从输出列表中获取目标应用程序的名称。 - 使用命令
ipainstaller -i [APP_NAME]显示目标应用程序的相关信息,包括安装位置和数据文件夹位置。 - 获取以
Application:开头的行中引用的路径。
使用以下方法修补 JavaScript 文件:
- 导航到应用程序文件夹。
- 将文件
Payload/[APP].app/main.jsbundle的内容复制到临时文件。 - 使用
JStillery美化临时文件的内容并对其进行解密。 - 确定临时文件中应打补丁的代码并打补丁。
- 将 打补丁的代码 放在一行中,并复制到原始的
Payload/[APP].app/main.jsbundle文件中。 - 关闭并重新启动应用程序。
动态仪器∥
信息收集∥
在本节中,我们将学习如何使用 Frida 获取运行中应用程序的信息。
获取加载的类及其方法∥
在 Frida REPL Objective-C 运行时中,"ObjC "命令可用于访问运行应用程序中的信息。在 ObjC 命令中,函数 enumerateLoadedClasses 列出了给定应用程序已加载的类。
$ frida -U -f com.iOweApp
[iPhone::com.iOweApp]-> ObjC.enumerateLoadedClasses()
{
"/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation": [
"__NSBlockVariable__",
"__NSGlobalBlock__",
"__NSFinalizingBlock__",
"__NSAutoBlock__",
"__NSMallocBlock__",
"__NSStackBlock__"
],
"/private/var/containers/Bundle/Application/F390A491-3524-40EA-B3F8-6C1FA105A23A/iOweApp.app/iOweApp": [
"JailbreakDetection",
"CriticalLogic",
"ViewController",
"AppDelegate"
]
}
使用 ObjC.classes.<classname>.$ownMethods 可以列出每个类中声明的方法。
[iPhone::com.iOweApp]-> ObjC.classes.JailbreakDetection.$ownMethods
[
"+ isJailbroken"
]
[iPhone::com.iOweApp]-> ObjC.classes.CriticalLogic.$ownMethods
[
"+ doSha256:",
"- a:",
"- AES128Operation:data:key:iv:",
"- coreLogic",
"- bat",
"- b:",
"- hexString:"
]
获取已加载的库∥
在 Frida REPL 中,可以使用 Process 命令获取与进程相关的信息。在 Process 命令中,函数 enumerateModules 列出了加载到进程内存中的库。
[iPhone::com.iOweApp]-> Process.enumerateModules()
[
{
"base": "0x10008c000",
"name": "iOweApp",
"path": "/private/var/containers/Bundle/Application/F390A491-3524-40EA-B3F8-6C1FA105A23A/iOweApp.app/iOweApp",
"size": 49152
},
{
"base": "0x1a1c82000",
"name": "Foundation",
"path": "/System/Library/Frameworks/Foundation.framework/Foundation",
"size": 2859008
},
{
"base": "0x1a16f4000",
"name": "libobjc.A.dylib",
"path": "/usr/lib/libobjc.A.dylib",
"size": 200704
},
...
同样,还可以获取与各种线程相关的信息。
Process.enumerateThreads()
[
{
"context": {
...
},
"id": 1287,
"state": "waiting"
},
...
进程 "命令提供了多个函数,可根据需要加以利用。一些有用的函数包括 findModuleByAddress, findModuleByName 和 enumerateRanges 等。
方法挂钩∥
Frida¶
在"方法跟踪"一节中,我们在 Safari 浏览网站时使用了 frida-trace,发现 initWithURL: 方法被调用来初始化一个新的 URL 请求对象。我们可以在 Apple Developer Website上查找此方法的声明:
- (instancetype)initWithURL:(NSURL *)url;
利用这些信息,我们可以编写一个 Frida 脚本,拦截 initWithURL: 方法并打印传递给该方法的 URL。完整脚本如下。请务必阅读代码和内联注释,以了解其中的内容。
import sys
import frida
# JavaScript to be injected
frida_code = """
// Obtain a reference to the initWithURL: method of the NSURLRequest class
var URL = ObjC.classes.NSURLRequest["- initWithURL:"];
// Intercept the method
Interceptor.attach(URL.implementation, {
onEnter: function(args) {
// Get a handle on NSString
var NSString = ObjC.classes.NSString;
// Obtain a reference to the NSLog function, and use it to print the URL value
// args[2] refers to the first method argument (NSURL *url)
var NSLog = new NativeFunction(Module.findExportByName('Foundation', 'NSLog'), 'void', ['pointer', '...']);
// We should always initialize an autorelease pool before interacting with Objective-C APIs
var pool = ObjC.classes.NSAutoreleasePool.alloc().init();
try {
// Creates a JS binding given a NativePointer.
var myNSURL = new ObjC.Object(args[2]);
// Create an immutable ObjC string object from a JS string object.
var str_url = NSString.stringWithString_(myNSURL.toString());
// Call the iOS NSLog function to print the URL to the iOS device logs
NSLog(str_url);
// Use Frida's console.log to print the URL to your terminal
console.log(str_url);
} finally {
pool.release();
}
}
});
"""
process = frida.get_usb_device().attach("Safari")
script = process.create_script(frida_code)
script.load()
sys.stdin.read()
在 iOS 设备上启动 Safari。在连接的主机上运行上述 Python 脚本并打开设备日志(如 "iOS 基本安全测试 "一章中的 "监控系统日志 "一节所述)。尝试在 Safari 中打开一个新 URL,例如 github.com/OWASP/owasp…;您应该会在日志和终端中看到 Frida 的输出。
当然,本例只是 Frida 的其中一个示例。要充分发挥该工具的潜力,你应该学会使用它的 JavaScript API。Frida 网站的文档部分提供了在 iOS 上使用 Frida 的教程 和示例。
流程探索∥
测试应用程序时,进程探索可让测试人员深入了解应用程序的进程内存。它可通过运行时仪器实现,并允许执行以下任务:
- 检索内存地图和加载的库。
- 搜索某些数据的出现。
- 搜索后,获取内存映射中某个偏移的位置。
- 执行内存转储,离线检查或逆向工程二进制数据。
- 在二进制文件或框架运行时对其进行逆向工程。
正如你所看到的,这些任务都是辅助性的和/或被动的,它们会帮助我们收集数据和信息,为其他技术提供支持。因此,它们通常与方法挂钩等其他技术结合使用。
在以下章节中,您将使用 r2frida直接从应用程序运行时获取信息。首先,打开一个 r2frida 会话到目标应用程序(例如 iGoat-Swift),该应用程序应在 iPhone 上运行(通过 USB 连接)。使用以下命令:
r2 frida://usb//iGoat-Swift
Memory Maps and Inspection∥
运行 \dm 可以获取应用程序的内存映射:
[0x00000000]> \dm
0x0000000100b7c000 - 0x0000000100de0000 r-x /private/var/containers/Bundle/Application/3ADAF47D-A734-49FA-B274-FBCA66589E67/iGoat-Swift.app/iGoat-Swift
0x0000000100de0000 - 0x0000000100e68000 rw- /private/var/containers/Bundle/Application/3ADAF47D-A734-49FA-B274-FBCA66589E67/iGoat-Swift.app/iGoat-Swift
0x0000000100e68000 - 0x0000000100e97000 r-- /private/var/containers/Bundle/Application/3ADAF47D-A734-49FA-B274-FBCA66589E67/iGoat-Swift.app/iGoat-Swift
...
0x0000000100ea8000 - 0x0000000100eb0000 rw-
0x0000000100eb0000 - 0x0000000100eb4000 r--
0x0000000100eb4000 - 0x0000000100eb8000 r-x /usr/lib/TweakInject.dylib
0x0000000100eb8000 - 0x0000000100ebc000 rw- /usr/lib/TweakInject.dylib
0x0000000100ebc000 - 0x0000000100ec0000 r-- /usr/lib/TweakInject.dylib
0x0000000100f60000 - 0x00000001012dc000 r-x /private/var/containers/Bundle/Application/3ADAF47D-A734-49FA-B274-FBCA66589E67/iGoat-Swift.app/Frameworks/Realm.framework/Realm
在搜索或探索应用程序内存时,您可以随时验证当前偏移在内存映射中的位置。你可以简单地运行 /dm.,而不用在列表中记录和搜索内存地址。你可以在下面的 "内存搜索 "部分找到一个例子。
如果你只对应用程序加载的模块(二进制文件和库)感兴趣,可以使用命令 \il 列出所有模块:
[0x00000000]> \il
0x0000000100b7c000 iGoat-Swift
0x0000000100eb4000 TweakInject.dylib
0x00000001862c0000 SystemConfiguration
0x00000001847c0000 libc++.1.dylib
0x0000000185ed9000 Foundation
0x000000018483c000 libobjc.A.dylib
0x00000001847be000 libSystem.B.dylib
0x0000000185b77000 CFNetwork
0x0000000187d64000 CoreData
0x00000001854b4000 CoreFoundation
0x00000001861d3000 Security
0x000000018ea1d000 UIKit
0x0000000100f60000 Realm
如你所料,你可以将库的地址与内存映射相关联:例如,主应用程序二进制文件 iGoat-Swift 位于 0x0000000100b7c000,而 Realm Framework 位于 0x000000010000f60000。
你也可以使用 objection 来显示相同的信息。
$ objection --gadget OWASP.iGoat-Swift explore
OWASP.iGoat-Swift on (iPhone: 11.1.2) [usb] # memory list modules
Save the output by adding `--json modules.json` to this command
Name Base Size Path
-------------------------------- ----------- -------------------- ------------------------------------------------------------------------------
iGoat-Swift 0x100b7c000 2506752 (2.4 MiB) /var/containers/Bundle/Application/3ADAF47D-A734-49FA-B274-FBCA66589E67/iGo...
TweakInject.dylib 0x100eb4000 16384 (16.0 KiB) /usr/lib/TweakInject.dylib
SystemConfiguration 0x1862c0000 446464 (436.0 KiB) /System/Library/Frameworks/SystemConfiguration.framework/SystemConfiguratio...
libc++.1.dylib 0x1847c0000 368640 (360.0 KiB) /usr/lib/libc++.1.dylib
内存搜索∥
内存搜索是一种非常有用的技术,可用于测试应用程序内存中可能存在的敏感数据。
请参阅 r2frida 的搜索命令帮助 (\/?),了解搜索命令并获取选项列表。下面显示的只是其中的一部分:
[0x00000000]> \/?
/ search
/j search json
/w search wide
/wj search wide json
/x search hex
/xj search hex json
...
你可以使用搜索设置 \e~search 来调整搜索。例如, \e search.quiet=true; 将只打印结果并隐藏搜索进度:
[0x00000000]> \e~search
e search.in=perm:r--
e search.quiet=false
现在,我们继续使用默认设置,专注于字符串搜索。在第一个例子中,你可以从搜索你知道应该位于应用程序主二进制文件中的东西开始:
\[0x00000000]> / iGoat
搜索 5 个字节: 69 47 6f 61 74
在 \[0x0000000100b7c000-0x0000000100de0000] 中搜索 5 个字节
...
命中 509
0x100d7d332 hit2\_0 iGoat\_Swift24StringAnalysisExerciseVCC
0x100d7d3b2 hit2\_1 iGoat\_Swift28BrokenCryptographyExerciseVCC
0x100d7d442 hit2\_2 iGoat\_Swift23BackgroundingExerciseVCC
0x100d7d4b2 hit2\_3 iGoat\_Swift9AboutCellC
0x100d7d522 hit2\_4 iGoat\_Swift12FadeAnimatorV
现在点击第一下,找到它并检查你在内存地图中的当前位置:
[0x00000000]> s 0x100d7d332
[0x100d7d332]> \dm.
0x0000000100b7c000 - 0x0000000100de0000 r-x /private/var/containers/Bundle/Application/3ADAF47D-A734-49FA-B274-FBCA66589E67/iGoat-Swift.app/iGoat-Swift
不出所料,你正位于主 iGoat-Swift 二进制文件区域(r-x,读取和执行)。在上一节中,你看到主二进制文件位于 0x0000000100b7c000 和 0x0000000100e97000 之间。
现在,在第二个示例中,你可以搜索应用程序二进制文件或任何加载库中都没有的内容,通常是用户输入。打开 iGoat-Swift 应用程序,在菜单中导航到 Authentication -> Remote Authentication -> Start。在那里你会发现一个可以覆盖的密码字段。写入字符串 "owasp-mstg",但先不要点击登录。执行以下两个步骤。
[0x00000000]> \/ owasp-mstg
hits: 1
0x1c06619c0 hit3_0 owasp-mstg
事实上,可以在地址 0x1c06619c0 找到字符串。查找 s 到那里,然后用 \dm. 获取当前内存区域。
[0x100d7d332]> s 0x1c06619c0
[0x1c06619c0]> \dm.
0x00000001c0000000 - 0x00000001c8000000 rw-
现在你知道字符串位于内存映射的 rw- (读写)区域。
此外,您还可以搜索字符串的宽版本 (/w)的出现,并再次检查其内存区域:
这一次,我们运行
\dm.命令,搜索与 globhit5_*匹配的所有@@命中。
[0x00000000]> /w owasp-mstg
Searching 20 bytes: 6f 00 77 00 61 00 73 00 70 00 2d 00 6d 00 73 00 74 00 67 00
Searching 20 bytes in [0x0000000100708000-0x000000010096c000]
...
hits: 2
0x1020d1280 hit5_0 6f0077006100730070002d006d00730074006700
0x1030c9c85 hit5_1 6f0077006100730070002d006d00730074006700
[0x00000000]> \dm.@@ hit5_*
0x0000000102000000 - 0x0000000102100000 rw-
0x0000000103084000 - 0x00000001030cc000 rw-
它们处于不同的 rw- 区域。请注意,搜索字符串的宽版本有时是找到它们的唯一方法,这在下一节中将会看到。
内存搜索非常有用,可以快速了解某些数据是否位于主程序二进制文件、共享库或其他区域。您还可以用它来测试程序在内存中保存数据的行为。例如,您可以继续前面的示例,这次点击 "登录 "并再次搜索数据的出现。此外,您还可以检查登录完成后是否仍能在内存中找到这些字符串,以验证这些_敏感数据_在使用后是否会从内存中清除。
内存转储∥
您可以使用 objection 和 Fridump 转储应用程序的进程内存。要在非越狱设备上使用这些工具,必须用 FridaGadget.dylib 重新打包 iOS 应用程序并重新签名。有关此过程的详细说明,请参阅"非越狱设备上的动态分析 "一节。要在越狱手机上使用这些工具,只需安装并运行 frida-server。
有了反对意见,就可以使用 "memory dump all "命令转储设备上运行进程的所有内存。
$ objection explore
iPhone on (iPhone: 10.3.1) [usb] # memory dump all /Users/foo/memory_iOS/memory
Dumping 768.0 KiB from base: 0x1ad200000 [####################################] 100%
Memory dumped to file: /Users/foo/memory_iOS/memory
或者,你也可以使用 Fridump。首先,你需要输入要转储的应用程序的名称,可以使用 frida-ps 获取。
$ frida-ps -U
PID Name
---- ------
1026 Gadget
然后,在 Fridump 中指定应用程序名称。
$ python3 fridump.py -u Gadget -s
Current Directory: /Users/foo/PentestTools/iOS/fridump
Output directory is set to: /Users/foo/PentestTools/iOS/fridump/dump
Creating directory...
Starting Memory dump...
Progress: [##################################################] 100.0% Complete
Running strings on all files:
Progress: [##################################################] 100.0% Complete
Finished! Press Ctrl+C
添加 -s 标志后,所有字符串都会从转储的原始内存文件中提取出来,并添加到文件 strings.txt 中,该文件保存在 Fridump 的转储目录中。
在这两种情况下,如果在 radare2 中打开文件,就可以使用它的搜索命令 (/)。请注意,首先我们进行标准字符串搜索,但没有成功,接下来我们搜索 wide string,成功找到了字符串 "owasp-mstg"。
$ r2 memory_ios
[0x00000000]> / owasp-mstg
Searching 10 bytes in [0x0-0x628c000]
hits: 0
[0x00000000]> /w owasp-mstg
Searching 20 bytes in [0x0-0x628c000]
hits: 1
0x0036f800 hit4_0 6f0077006100730070002d006d00730074006700
接下来,我们可以使用 s 0x0036f800 或 s hit4_0 查找它的地址,然后使用 psw(代表 print string wide)或使用 px 打印它的原始十六进制值:
[0x0036f800]> psw
owasp-mstg
[0x0036f800]> px 48
- offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
0x0036f800 6f00 7700 6100 7300 7000 2d00 6d00 7300 o.w.a.s.p.-.m.s.
0x0036f810 7400 6700 0000 0000 0000 0000 0000 0000 t.g.............
0x0036f820 0000 0000 0000 0000 0000 0000 0000 0000 ................
注意,要使用 strings 命令找到这个字符串,必须使用 -e 标志指定编码,本例中的 l 表示 16 位 little-endian 字符。
$ strings -e l memory_ios | grep owasp-mstg
owasp-mstg
运行时逆向工程∥
运行时逆向工程可以看作是逆向工程的即时版本,在这种版本中,你不需要将二进制数据传输到主机上。而是直接从应用程序的内存中进行分析。
我们将继续使用 iGoat-Swift 应用程序,用 r2frida r2 frida://usb//iGoat-Swift打开会话,然后使用 \i 命令开始显示目标二进制信息:
[0x00000000]> \i
arch arm
bits 64
os darwin
pid 2166
uid 501
objc true
runtime V8
java false
cylang true
pageSize 16384
pointerSize 8
codeSigningPolicy optional
isDebuggerAttached false
cwd /
使用 \is <lib> 搜索某个模块的所有符号,例如 \is libboringssl.dylib。
下面的命令对包含 "aes"(~+aes)的符号进行不区分大小写的搜索(grep)。
[0x00000000]> \is libboringssl.dylib~+aes
0x1863d6ed8 s EVP_aes_128_cbc
0x1863d6ee4 s EVP_aes_192_cbc
0x1863d6ef0 s EVP_aes_256_cbc
0x1863d6f14 s EVP_has_aes_hardware
0x1863d6f1c s aes_init_key
0x1863d728c s aes_cipher
0x0 u ccaes_cbc_decrypt_mode
0x0 u ccaes_cbc_encrypt_mode
...
或者你也可以查看导入/导出。例如
- 列出主二进制文件的所有导入:
\ii iGoat-Swift. - 列出 libc++.1.dylib 库的导出:
usr/lib/libc++.1.dylib.
对于大的二进制文件,建议通过添加
~..将输出导入内部 less 程序,即\ii iGoat-Swift~...(如果不这样做,对于这个二进制文件,你会得到近 5000 行打印到你的终端)。
接下来你可能要看的是类:
[0x00000000]> \ic~+passcode
PSPasscodeField
_UITextFieldPasscodeCutoutBackground
UIPasscodeField
PasscodeFieldCell
...
列表类字段:
[0x19687256c]> \ic UIPasscodeField
0x000000018eec6680 - becomeFirstResponder
0x000000018eec5d78 - appendString:
0x000000018eec6650 - canBecomeFirstResponder
0x000000018eec6700 - isFirstResponder
0x000000018eec6a60 - hitTest:forEvent:
0x000000018eec5384 - setKeyboardType:
0x000000018eec5c8c - setStringValue:
0x000000018eec5c64 - stringValue
...
假设您对 0x000000018eec5c8c - setStringValue: 感兴趣。您可以使用 s 0x000000018eec5c8c 查找该地址,分析函数 af 并打印 10 行反汇编内容 pd 10:
[0x18eec5c8c]> pd 10
╭ (fcn) fcn.18eec5c8c 35
│ fcn.18eec5c8c (int32_t arg1, int32_t arg3);
│ bp: 0 (vars 0, args 0)
│ sp: 0 (vars 0, args 0)
│ rg: 2 (vars 0, args 2)
│ 0x18eec5c8c f657bd not byte [rdi - 0x43] ; arg1
│ 0x18eec5c8f a9f44f01a9 test eax, 0xa9014ff4
│ 0x18eec5c94 fd std
│ ╭─< 0x18eec5c95 7b02 jnp 0x18eec5c99
│ │ 0x18eec5c97 a9fd830091 test eax, 0x910083fd
│ 0x18eec5c9c f30300 add eax, dword [rax]
│ 0x18eec5c9f aa stosb byte [rdi], al
│ ╭─< 0x18eec5ca0 e003 loopne 0x18eec5ca5
│ │ 0x18eec5ca2 02aa9b494197 add ch, byte [rdx - 0x68beb665] ; arg3
╰ 0x18eec5ca8 f4 hlt
最后,与其在内存中搜索字符串,不如从某个二进制文件中获取字符串并进行过滤,就像使用 radare2 的 offline 方法一样。为此,你必须找到二进制文件,然后运行 \iz 命令。
建议使用关键字
~<keyword>/~+<keyword>进行过滤,以减少终端输出。如果只想查看所有结果,也可以将它们导入内部的\iz~...。
[0x00000000]> \il~iGoa
0x00000001006b8000 iGoat-Swift
[0x00000000]> s 0x00000001006b8000
[0x1006b8000]> \iz
Reading 2.390625MB ...
Do you want to print 8568 lines? (y/N) N
[0x1006b8000]> \iz~+hill
Reading 2.390625MB ...
[0x1006b8000]> \iz~+pass
Reading 2.390625MB ...
0x00000001006b93ed "passwordTextField"
0x00000001006bb11a "11iGoat_Swift20KeychainPasswordItemV0C5ErrorO"
0x00000001006bb164 "unexpectedPasswordData"
0x00000001006d3f62 "Error reading password from keychain - "
0x00000001006d40f2 "Incorrect Password"
0x00000001006d4112 "Enter the correct password"
0x00000001006d4632 "T@"UITextField",N,W,VpasswordField"
0x00000001006d46f2 "CREATE TABLE IF NOT EXISTS creds (id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT, password TEXT);"
0x00000001006d4792 "INSERT INTO creds(username, password) VALUES(?, ?)"
要了解更多信息,请参阅 r2frida wiki。
参考资料?
- Apple 的权限疑难解答 - developer.apple.com/library/con…
- 苹果的代码签名 - developer.apple.com/support/cod…
- Cycript 手册 - www.cycript.org/manual/
- 无需越狱的 iOS 工具 - www.nccgroup.trust/au/about-us…
- Frida iOS 教程 - www.frida.re/docs/ios/
- Frida iOS 示例 - www.frida.re/docs/exampl…
- r2frida Wiki - github.com/enovella/r2…
- [#miller] - Charlie Miller,Dino Dai Zovi。iOS 黑客手册》。Wiley, 2012 - www.wiley.com/en-us/iOS+H…
- [#levin] Jonathan Levin. Mac OS X 和 iOS 内部: To the Apple's Core. Wiley, 2013 - newosxbook.com/MOXiI.pdf
资源∥
内部∥
- OWASP UnCrackable Apps for iOS
- objection
- UnCrackable App for iOS Level 1
- security
- class-dump
- iGoat-Swift
- iOS Cryptographic APIs
- iOS Network Communication
- ios-deploy
- objection
- optool
- otool
- r2frida