iOS ARM64 上的堆溢出:堆整理,释放后使用(第 3 部分)

原文链接:https://www.inversecos.com/2022/07/heap-overflows-on-ios-arm64-heap.html

欢迎回到我的iOS arm64开发系列的第3部分!

如果您错过了本系列中的博客,请查看下面的^_^
初学者如何对iOS应用程序进行逆向工程和修补:第1部分
逆向和利用 iOS 二进制文件指南第 2 部分:ARM64 ROP 链

介绍
我们已经在 ARM64 上经历了 iOS 挂钩、缓冲区溢出和简单的 ROP 链。现在是时候讨论堆溢出和利用释放后使用(UAF)错误了。本博客的目的是向您展示如何利用UAF错误并将其转化为“恶意”内容。我将逐步引导您完成以下操作:

  • 如何识别 UAF 错误
  • 如何静态分析二进制文件以找出如何执行利用
  • 堆溢出逻辑
  • 堆梳理
  • 充分利用

与往常一样,我们将使用开源工具来执行此操作,我已经编译,签名并上传了我们将用于本练习的练习二进制文件。我还为那些想要阅读它的人上传了源代码。所有这些都可供您在我的Github上下载:

从 GitHub 下载练习二进制“moneymachine” 这里

本博客分为五个部分:

  • 高级演练步骤
  • 练习二进制简介
  • 使用 Radare2 对二进制文件进行静态分析
  • ARM64 堆溢出
  • 资源

高级步骤

这些是我们将在这项工作中采取的高级别步骤,我将详细解释每一个步骤。如果细节不是你的东西,那么只需偷看我的YouTube视频:

  1. 通过 SFTP 将二进制文件上传到越狱的 iOS 设备到 /var/mobile 目录。我正在运行一个名为“honeypot”的越狱iOS 14设备
  2. 通过玩转程序查找 UAF 漏洞
  3. 使用 R2 静态分析二进制文件,以找到我们可以操作的任何有意义的函数
  4. 使用 LLDB 在 iOS 设备上设置远程调试会话
  5. 调试二进制文件并在内存分配后设置断点,以获取对象将分配到的堆地址
  6. 通过填充堆区域来执行堆整理,直到清空所有可用列表,并且唯一要写入的“free gap”是堆上的初始释放对象
  7. 执行堆溢出
  8. 调用函数调用以触发负载

练习二进制简介

这个二进制文件被称为“moneymachine”,它是一个MACH-O 64位二进制文件。它的灵感来自互联网和我所有在玩Runscape长大的奇怪朋友!哈哈。二进制文件的名称参考了100 gecs标志性的模因歌曲“赚钱机器”。

无论如何,当您下载二进制文件并运行二进制文件时,有6个选项可供选择(如下图所示)。

这个练习的目标是利用堆溢出并进行堆整理,直到你可以买到传奇物品!此二进制文件中编码了一个嵌入式的释放后使用漏洞。请注意,无需禁用任何安全控制即可使用此漏洞利用程序。;)

当你选择[2]时,程序将调用“malloc”,并在堆上分配内存用于你的任务(在这种情况下,是哥布林外交)。“quest”是一个结构 – 一个对象,它存储任务的名称以及一个函数指针,该函数指向列出所有可用任务的函数[3]。

对 [5] 的调用将调用 ‘free’ 函数,该函数将释放堆上分配的对象。

但是,此对象尚未为 null,这意味着数据仍驻留在堆上 – 这就是为什么当您在对象释放后调用 [3] 时,它仍将返回所有可用的任务。这里的目标是从一个破碎的旅行者变成一个富有的旅行者。

然后,在我们利用赚钱机器之后,我们将变得足够富有,成为世界上最大,最宏伟的剑的拥有者。为此,您必须执行堆溢出,堆整理,并确定我们需要覆盖堆上的函数指针的机密函数。

使用 R2 和 LLDB 进行静态分析

让我们看一下二进制文件并执行一些静态分析UwU

1. 检查程序中的所有功能

为此,您可以使用Hopper,Radare2(https://github.com/radareorg/radare2) – 无论您想使用什么拆装机。我将使用 radare2 来执行此步骤。您需要做的就是运行以下命令:

  • r2 <二进制名称>
  • aaa – 触发 r2 来分析标志、函数调用、字节等
  • afl – 列出所有函数

从下面的屏幕截图中,您可以看到一些标准C函数和三个非标准函数以紫色突出显示。

2.查看buyItems功能以查看其功能

本练习的目的是弄清楚您如何负担得起购买传奇物品 – 因此,根据此函数的名称,我们可能应该优先考虑它以进行分析。希望它可以帮助我们理解如何在此二进制文件中购买物品背后的逻辑。要检查此函数,请键入:

  • s sym._buyItems
  • pdf

在代码中,我们可以看到比较发生,导致两次跳跃。让我把这个汇编分解成简单的英语:

  • 首先,我们注意到有一个名为“sym._money”的变量,我们可以猜测变量可能正在存储我们有多少黄金。
  • “sym._money”的值存储在 w8 寄存器中
  • 该函数将 999999 的值移动到 w9 寄存器中(以紫色突出显示)。这在十六进制中表示为0XF423F。
  • 在w9和w8之间进行了比较(将我们当前的货币与999999的价值进行比较)
  • 该程序分支到两个不同的位置,具体取决于我们拥有的金额是否大于或等于999999(b.ge),否则它会跳到其他地方

我们真的不需要分析代码的其余部分来理解为什么这是有用的。很可能在这一点上,由于我们被0金币打破了,我们将无法购买传奇物品。传奇物品的成本也可能999999。

我将二进制文件拉入 Hopper(因为它在视觉上比 r2 更好地显示字符串),以根据比较向您展示函数分支的位置。第一个位置是,如果比较失败,我们只有不到999999黄金 – 它会打印“你没有足够的钱”。

第二个位置显示,如果您确实拥有999999或更多的黄金会发生什么。它打印“获得的物品”。

现在有了这些知识,很明显我们需要找到一种方法来获得更多的钱来利用这个程序。

3. 分析 makeItRain 函数

接下来,让我们看一下 makeItRain 函数,看看它的作用。正如您在下图中看到的那样 – 在函数序幕之后,它再次与“money”变量(sym._money)交互。

  • 将sym._money的值存储在 w8 寄存器中
  • 将0xF4240的值存储在 w10 寄存器中,该值对应于值“1000,000”
  • 将 w10 寄存器中的值添加到 w8 寄存器
  • 打印一些东西

由此我们可以推断出,这个函数将“货币”的价值增加了1,000,000。

4. 分析其他功能

查看程序中调用的其他函数,我们可以看到对这些函数的引用:

  • malloc – 某些东西正在堆上分配内存
  • free – 有些东西正在堆上被释放
  • fread/fopen – 正在从文件中读取某些内容

5. 查找释放后使用的错误

当我们运行程序时,我们会注意到一些有趣的东西。当我们在分配新任务[2]之前调用[3]以获取可用任务时,会发生分段错误。分段错误在我们获得新任务后不会发生[2]。

但是,在使用选项 [5] 释放任务后,我们调用 [3] 个可用任务,数据仍会被引用。这是释放后使用 bug 的前提,其中数据仍驻留在堆上,并且内存地址仍被另一个函数引用。

如果我们看一下源代码,我们可以在下面看到,释放堆上的quest对象不包括将数据清空!正是这个错误导致了释放后使用的错误。

堆溢出堆整理

现在我们已经静态分析了二进制文件,我们已经知道了我们应该如何继续利用:

  • 使用选项 [2] 在堆上分配新任务
  • 释放堆上的新对象 [5]
  • 通过调用 [4] 从文件导入项目来读取文件中的数据
  • 使用文件中的数据覆盖原始任务堆对象
  • 使用 makeItRain() 的地址覆盖堆上对 listQuests() 的函数调用
  • 通过调用 [3] 可用任务触发免费后使用
  • 通过调用 [1] 购买传奇物品来查看漏洞利用是否成功

要执行这些步骤,我们需要收集一些信息并确保一些事情:

  • 获取任务分配到的堆地址
  • 获取选项 [4] 将文件内容写入的堆地址
  • 确保两个堆地址相同,否则执行堆整理

1.远程连接到使用LLDB运行二进制文件的iOS

在 iOS 设备上,确保打开两个终端会话,一个正在运行二进制文件,另一个用于启动调试服务器会话,将其附加到正在运行的进程。将用于执行远程分析的工作站的 IP 地址传递到调试服务器

在工作站上,打开 LLDB 并连接到正在运行的进程。

2.拆卸主要功能

我们要做的第一件事是分析主函数。分析 main 函数的目的是让我们了解它在做什么,并找出我们应该在哪里设置一些断点。为此,您只需键入以下命令:

  • disass -n main

请注意,您的所有地址都将与我:)

3. 设置断点,以便确定我们在堆上写入的位置

malloc 分配到的堆地址将在函数调用 malloc 后立即推送到 ARM64 中的 x0 寄存器中。因此,通过在调用malloc后立即中断并检查寄存器,您将能够提取我们正在写入^_^的堆地址

因此,在此阶段,我们将滚动浏览 main 函数并向下复制将设置断点的地址。第一个断点发生在程序的早期(可能是对 [2] New Quest 的调用完成的地方)。

第二个 malloc 在读取文件“items.txt”的内容并将其存储到堆上之后,稍后在 main 函数中。

我们还将在调用“free”函数后立即设置一个断点。这样我们就可以仔细检查这个UAF是否存在。如果存在 UAF 错误,则在调用 free 后,堆上的数据仍将保留,因为它不会被清空。

要设置断点,只需运行以下命令:

4. 命中第一个断点并从第一个 malloc 收集堆的地址

由于程序执行当前已暂停,我们需要继续该过程,然后选择 [2] 来分配新的任务。这应该立即在第一个 malloc 处命中我们的第一个断点:

现在,我们需要检查寄存器并拉取堆的地址。这可以通过键入“寄存器读取”来完成。正如你在下面看到的,我们的任务“哥布林外交”将被分配到的堆地址将位于地址“0x0000000104b06ce0”。

现在查看堆地址,您会发现堆上没有太多内容。这在以后比较将数据写入堆时会发生什么时非常重要。

5. 通过调用 [5] 释放堆分配

通过键入“进程继续”继续进程,然后调用选项 [5] 以释放堆上分配的数据,从而继续执行进程。

6. 检查堆上的数据

通过键入“进程中断”来暂停执行,然后观察写入堆的内容。正如您在下面看到的,在突出显示的紫色区域中填充了数据。正如预期的那样,写入的数据对应于六边形的“哥布林外交”,然后是0x104b06d00的listQuests()函数的地址。这里需要注意的重要一点是,即使堆已被释放,数据仍然驻留在这里,而不是被空!这是 bug 的前提。

为了真正证明我的观点,堆上的值与十六进制的“哥布林外交”完全相关,但以小端格式:)

7. 准备有效负载以覆盖堆

为了准备有效负载,我们需要将数据放入“items.txt”文件中,然后再调用对选项[4]的调用以从文件中读取条目。让我们以此为契机来计算偏移量,我们需要用 makeItRain() 地址覆盖函数指针地址。

将所有数据管道化到items.txt文件之后。然后,我将其SFTP放入与iOS设备上的二进制文件相同的目录中。

8. 获取有效负载堆地址并检查它是否与第一个堆地址相同

现在,让我们触发有效负载,并查看此新数据将在堆上写入的位置。我们通过调用选项 [4] 来执行此操作。

这将命中我们的第二个 malloc 断点。我们将执行相同的步骤,调用“寄存器读取”以从 x0 寄存器中提取新的堆地址。此堆地址是0x0000000102906080,它与0x0000000104b06ce0的第一个堆地址不同。

9.堆梳理或堆风水技术

在更高版本的 iOS 上,即使堆上分配的两个对象大小相同,第二个对象也可能不会分配到与第一个释放对象相同的位置。这是由于堆管理器管理堆上的可用空间的方式造成的。关于这是如何工作的一个很好的解释是在Azeria Labs博客(https://azeria-labs.com/grooming-the-ios-kernel-heap/)。

它的工作原理是,在堆上,可能存在几个间隙,新的malloc’d对象将被写入其中。我们的目标是继续用数据填补这些空白,以增加我们的数据被写入堆上攻击者释放区域的机会。为此,您只需继续喷洒堆并用对象填充所有可用间隙,直到我们的对象写入所需的堆部分。

在下图中,这是我们当前堆状态的*VERY*粗略表示。我简化了这一点,所以它更容易理解。基本上,我们有自由的间隙,我们释放的任务对象就坐在堆上。但是,我们的项目.txt被写入堆上另一个释放的间隙,位于不同的地址。

因此,要执行堆整理,我们需要填充堆,直到区域中只有释放的区域可以容纳malloc’d物品的大小.txt对象是原始释放的任务对象地址。

因此,在我们的程序示例中,我们将继续调用[4]导入项目调用malloc,直到我们得到我们的项目.txt分配给剩余区域中唯一的间隙 – 与释放的quest对象相同的地址。这个程序是一个更简单的例子,因为我对它进行了编码,以便两个对象的大小相同。

10. 再次调用 [4] 导入项目

您可能需要调用 [4] 的次数会有所不同,但您需要继续该过程并继续调用它并检查 x0 寄存器,直到我们在堆上命中与0x0000000104b06ce0处的原始对象相同的地址。正如您在程序的第二次迭代中看到的那样,我们已经将第二个malloc设置为相同的地址 – 0x0000000104b06ce0。

此时,我们将点击“进程继续”以允许填充堆。从下面的屏幕截图中可以看出,我们已经用十六进制表示“AAAAAAAABBBBBBBBCCCCCDDDDDDDDDEEEEEEEEFFFFFFFF”覆盖了堆。

以红色突出显示的是“EEEEEEEE”的十六进制。这是我们分配任务数据时“listQuests()”驻留在原始堆上的位置。这意味着当我们编辑有效负载时,我们需要将其替换为“makeItRain()”函数的地址。

充分利用

现在我们知道我们需要将有效负载中的EEEEEEEE替换为makeItRain()函数的地址。我们已准备好执行完整的漏洞利用。让我们得到它!

步骤 1:重新运行二进制文件

我们将开始对正在运行的二进制文件进行全新调试。因此,只需再次加载二进制文件,然后通过远程工作站连接到该二进制文件,即可执行该漏洞利用。

步骤 2:重复堆溢出部分中的步骤 2-4

再一次,我们需要在对malloc的两次调用之后的地址上设置断点,这样我们才能确保写入相同的堆地址。对于实际利用,您可以忽略在“free”函数调用之后设置的第三个断点。如果堆地址不同,只要记住你可能需要堆梳理,我很少看到它直接击中相同的地址。

步骤 3:获取 makeItRain() 函数的运行时地址

我们需要更改有效负载,以在运行时将 EEEEEEEE 替换为 makeItRain 函数的地址。因此,我们需要反汇编此函数并获取0x1028637bc的输入地址。

要设置断点,您只需运行:

  • br s -a 0x1028637bc

步骤 4:更换负载

目前,我们的有效载荷如下所示:

\x41\x41\x41\x41\x41\x41\x41\x41\x42\x42\x42\x42\x42\x42\x42\x42\x43\x43\x43\x43\x43\x43\x43\x43\x44\x44\x44\x44\x44\x44\x44\x44\x45\x45\x45\x45\x45\x45\x45\x46\x46\x46\x46\x46\x46\x46\x46\x46\x46\x46\x46\x46\x46\x46\x46\x46\x46\x46

我们需要将 \x45\x45\x45\x45\x45\x45\x45\x45\x45 部分替换为 makeItRain 0x1028637bc地址的小字节序版本。

这会将有效负载转换为:

\x41\x41\x41\x41\x41\x41\x41\x41\x42\x42\x42\x42\x42\x42\x42\x42\x43\x43\x43\x43\x43\x43\x43\x44\x44\x44\x44\x44\x44\x44\x44\x44\xbc\x37\x86\x02\x01\x00\x00\x46\x46\x46\x46\x46\x46\x46\x46\x46\x46\x46\x46\x46\x46\x46\x46\x46\x46

步骤 5:从items.txt中读取

在读取文件之前,请确保已分配新任务 [2] 并释放它 [5]。然后调用 [4] 导入项目,检查堆地址是否相同。如果没有,那么你需要堆喷,直到它是。

步骤 6:检查堆和堆上的有效负载

我喜欢中断该过程并确保地址正确写入堆。您可以通过运行以下命令来执行此操作:

  • 进程中断
  • x/64 0x0000000102a04510

从下面的屏幕截图中可以看出,我们已经成功地用makeItRain()函数覆盖了堆的函数调用区域。

第7步:让它下雨宝贝!

现在是时候触发漏洞利用了。为此,您只需要调用[3]可用任务函数,它就会跳转到makeItRain()地址。正如您在下面看到的,这是成功的!

现在要做的就是尝试获得我们的传奇物品UwU。正如您在下面看到的,这是一个成功!堆溢出,UAF 滥用与堆梳理完成 !

其他资源

我注意到没有多少资源专门用于arm64。arm32上有一些,但大多数开发文章都集中在x86上。我建议您进一步研究,请查看以下资源T_T

阿泽里亚实验室:https://azeria-labs.com/heap-overflows-and-the-ios-kernel-heap/

高难度黑客:https://highaltitudehacks.com/2020/09/06/arm64-reversing-and-exploitation-part-2-use-after-free/

比利·埃利斯:https://www.youtube.com/watch?v=L8Ya7fBgEzU&ab_channel=BillyEllis

LiveOverflow(请注意,这是在 x86 上):https://www.youtube.com/watch?v=TfJrU95q1J4&ab_channel=LiveOverflow

发表评论

您的电子邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部