只用打火机就能获得 root 权限吗?

admin 2024年10月11日13:27:10评论26 views字数 7237阅读24分7秒阅读模式

剧透警告:是的。

只用打火机就能获得 root 权限吗?他们不想让你知道你已经拥有了精英黑客工具

在编写漏洞利用程序之前,您需要一个漏洞。如果没有漏洞,我们就必须发挥创造力 — 这时故障注入就派上用场了。故障注入可以采用多种形式,包括软件控制的数据损坏、电源故障、时钟故障、电磁脉冲、激光等等。

硬件故障注入通常需要专门(且昂贵)的设备。成本源于对故障注入时间和位置的高精度要求。有许多勇敢的尝试来降低成本,其中值得注意的项目包括基于 RP2040 的PicoEMP,一直到“大众激光故障注入”。(RP2040 因其低成本与“PIO”外设相结合而大量出现,PIO 外设可以以严格的时序和延迟执行 I/O)

不久前,我读到一篇文章,介绍使用与电感器耦合的压电 烧烤点火器作为电磁故障注入 (EMFI) 的低成本工具,我被深深吸引住了。我想知道,这种原始工具能发挥多大作用?当时,我能想到的最好的办法是利用DFA利用 Arduino 上运行的 AES 软件实现— 它成功了!

但我并不完全满意。我想开发一些更“真实”的东西,但暂时没有主意。

时间快进到几周前,任天堂 Switch 2 即将发布。我们预计 Switch 2 将运行与 Switch 1 基本相同的系统软件,而且我们已经没有软件漏洞了。因此,我受到启发,重新提高了硬件开发技能,并重新审视了我对低预算 EMFI 的想法。

测试对象

和任何有自尊心的黑客一样,我拥有一堆破烂的笔记本电脑。我选了一台三星 S3520,配备英特尔 i3-2310M CPU 和 1GB DDR3 RAM。它是 2011 年制造的,足够新,可以轻松运行轻量级桌面 Linux 发行版(我选择了 Arch),但也足够糟糕,所以我不担心它会变砖。

我的目标是编写一个基于注入的硬件故障起作用的本地权限提升漏洞。

我认为笔记本电脑最容易受到物理攻击的部分是连接 DRAM 内存和系统其余部分的 DDR 总线。

如果您曾经看过笔记本电脑内存模块 (SODIMM),您会注意到它有很多引脚。其中包括 64 个“DQ”引脚(编号为 DQ0 至 DQ63),它们可以双向(读取或写入)传输数据位。我想,如果我可以在其中一个引脚上注入故障,我就可以做一些有趣的事情。

经过一番折腾之后,我最终确定了硬件设置如下:

只用打火机就能获得 root 权限吗?如果我没算错的话,这对应的是引脚 67,也就是 DQ26

它只是一个电阻(15 欧姆)和一根焊接到 DQ26 的电线。电线就像一根天线,可以拾取附近的任何电磁干扰并将其直接转储到数据总线上。电阻(可能完全没有必要)只是为了确保干扰不会大到干扰内存的正常运行——我只希望故障按需发生,而不是一直发生。

只用打火机就能获得 root 权限吗?忽略随机的电工胶带;这台笔记本电脑已经经历了很多。

我发现在天线附近点击普通压电打火机(不需要电感线圈)足以可靠地引发内存错误,如 memtest 中所示。请注意,显示的错误都与位 29 被翻转相对应。

为什么当我焊接到 DQ26 时是 29 位?老实说,我不太确定;要么是我数错了针脚,要么是我的笔记本电脑主板交换了一些数据线。据我所知,交换数据线是允许的(它可以使信号路由更容易)。

我们无法控制何时注入故障(以手指的反应速度为分辨率),但无论何时发生故障,我们都可以相当确定它总是会翻转任何特定 64 位读取或写入的相同位。

利用 CPython 中的位翻转

作为起点,我想尝试为 CPython 编写一个“沙盒逃逸”漏洞。这纯粹是学术性的,因为 CPython 甚至没有沙盒化,你可以直接执行os.system("/bin/sh"),但我需要一些容易上手的东西,而且我已经熟悉了 CPython 的内部结构。我对这个漏洞的解释会有点粗略,因为具体细节并不那么有趣,我想传达的是总体策略。

为了利用 CPython,我实际上使用了焊接到 DQ7 的电线,而不是之前图示的 DQ26,原因很快就会变得更加明显。

CPython 对象存在于垃圾收集堆中。对象有一个包含其引用计数的标头,然后是指向其类型对象的指针,后面是其他类型特定的字段。我们特别感兴趣的对象类型有两种,bytes和bytearray。bytes对象是不可变的,bytearray对象是可变的。

对象bytes有一个长度字段,后面是数据本身(作为同一堆分配的一部分)。

对象bytearray有一个长度字段,后跟指向实际数据存储缓冲区的指针。

我的漏洞利用策略的核心思想是实例化一个bytes包含虚假bytearray结构的对象。虚假bytearray对象只是数据,我们无法用它做任何事情,但如果我们诱骗 CPython 为我们提供指向这个虚假对象的引用(指针),那么我们就可以构造一个任意内存读/写原语(因为我们自己选择了bytearray的长度和指针字段)。

那么我们如何获得对伪造对象的引用呢?回想一下,我们最初的“漏洞利用原语”是翻转 64 位字的第 7 位的能力。这相当于加上或减去 128(27) 指针。如果我们的伪造bytearray对象位于bytes对象内偏移量 +128 字节的位置,那么对指向该对象的指针进行故障攻击bytes会将其转换为指向精心制作的对象bytearray(概率为 50%)。

所以最大的问题是,我们如何才能破坏这个特定的指针,而不是其他一切?如果我们不小心破坏了一些重要的数据,我们可能会让整个操作系统崩溃,这显然是不好的。

需要记住的是,我们是在干扰内存总线,而不是内存内容(就像Rowhammer之类的程序一样)。我们只会干扰读取和写入操作,“静止”的数据基本是安全的。这里的解决方案是对我们想要干扰的指针的内存访问进行垃圾处理。如果 99% 的总线活动都充斥着可利用的操作,那么随机定时的故障(理论上)有 99% 的机会落在我们想要的某个地方。

如果我们在循环中读取同一个指针,几乎什么也不会发生。这是因为 CPU 缓存了数据以避免不必要的 DRAM 访问(缓存速度很快,而 DRAM 相对较慢且延迟较高)。

我的解决方案是使用对同一对象的引用填充一个大数组(大于此 CPU 的 3MiB 缓存)。然后,我可以在循环中按顺序访问数组项,强制 CPU 每次从 DRAM 中获取它们,并检查它们的值是否发生变化。内部循环如下所示:

1 
2
3
4
5
6
7
# "victim" is the prepared `bytes` object described earlier
spray = (victim,) * 0x100_0000 # I actually use a tuple instead of an array, same idea

for obj in spray:
if obj is not victim: # under non-glitchy conditions, this is always false
print("Found corrupted ptr!")
assert(type(obj) is bytearray)

大多数情况下这不会起作用,所以整个过程都是在另一个大循环内完成的,直到它起作用(或者直到系统崩溃🙃)。

Python 的is关键字在这里派上了用场,它本质上是一个指针比较操作,允许我们检查指针是否发生变化。可视化内存中的对象,它看起来像这样:

只用打火机就能获得 root 权限吗?

“故障”指针以红色显示,现在它可以访问伪造的字节数组对象。故障本身可能发生在写入或读取期间,无论哪种方式,最终结果都相同。漏洞的其余部分并不特别有趣;我设置了一个可重复的读/写原语,然后制作了一个跳转到 shellcode 的 Function 对象。您可以在此处查看完整源代码。该脚本还有一个选项(TESTING变量),可以通过软件诱导模拟位翻转,这对于在没有任何硬件设置的情况下进行测试非常有用。

利用 Linux 中的位翻转

热身完毕后,是时候进行适当的安全边界跨越了。我们能从非特权 Linux 用户变成 root 吗?首先我们需要了解三个核心概念:

  • 内存缓存(我之前提到过)

  • 虚拟内存和页表

  • 转换后备缓冲区 (TLB)

内存缓存

正如我之前提到的,DRAM 速度较慢且延迟较高。因此,CPU 具有片上缓存,速度更快。这些缓存具有多个层级(L1、L2、L3),具有不同的大小/位置/延迟权衡,但就我们的目的而言,我们只需关注 L3 缓存(最大的层级,在本例中为 3MiB)。如果数据当前驻留在缓存中(“缓存命中”),则 CPU 无需访问 DRAM 即可读取它。另一方面,如果出现“缓存未命中”,则 CPU 将不得不访问 DRAM。

从缓存角度来看,最小的内存单位是“缓存行”,在我的笔记本电脑上,缓存行是一个 64 字节的块。这意味着,即使您读取一个未缓存的字节,也会发生整个 64 字节的 DRAM 读取操作。您可能还记得,DRAM 数据总线本身只有 64位宽,这意味着读取发生在 8 次连续访问的“突发”中。

CPU 用来决定将哪些数据保留在缓存中以及何时“驱逐”这些数据的精确策略实际上是供应商专有的秘密。但作为合理的近似,我们可以将其建模为 LRU 缓存:最近最少使用的缓存行首先被驱逐。

虚拟内存

在过去,像MOS 6502这样的简单 CPU有一个“平面”地址空间。如果您的程序尝试从地址 读取0xcafe,那么 CPU 会将 16 位地址总线引脚物理设置为0xcafe( 0b1100101011111110) 并从该位置读回一个字节。除了诸如银行切换 之类的硬件技巧外,您请求的地址就是您从中获取数据的地址。简单至极!

只用打火机就能获得 root 权限吗?我的 6502 电脑,很多年前制造的(USB-C 端口是当代添加的)。请注意 D0-D7 数据引脚和 A0-A15 地址引脚。

快进几年,我们想要在 CPU 上同时运行多个程序。此外,每个程序都应该被欺骗,相信它拥有整个地址空间。这很有用,原因有很多,包括它可以阻止一个进程破坏另一个进程的内存(无论是意外还是其他原因)。

在现代,我们称此技巧为虚拟内存。虚拟内存意味着程序尝试访问的地址(虚拟地址)与底层物理地址空间之间存在一个间接层。每个进程都可以拥有自己的虚拟->物理映射,从而使不同的进程彼此隔离。

在 x86-64(以及大多数其他现代架构)上,这种间接访问是使用分页概念实现的。

虚拟地址空间被分成 4KiB页,页表的树形层次结构决定了 CPU(特别是MMU)如何解码虚拟地址并将其映射到物理页上。在这个平台上有 4 层页表。正式的 4 层都有不同的名称,但我将它们称为“3 级”到“0 级”,其中 3 级是树的根。页表本身是一个 4KiB 页,包含 512 个页表条目 (PTE) 的数组,每个都是 64 位结构。PTE 要么指向下一级页表的物理地址(对于 3 级到 1 级的情况),要么指向“目标”页的物理地址(对于 0 级的情况)。

根页表的物理地址存储在CR3 CPU寄存器中。

PTE 本身具有以下布局(图表来自 osdev wiki)

只用打火机就能获得 root 权限吗?

我们唯一需要关心的是中间的地址部分。屏蔽所有标志位后,就剩下物理内存地址(因为页面是 4K 对齐的,所以低位始终为零)。

如果想要更好的解释以及一些图表,请查看《用 Rust 编写操作系统》这篇文章(请注意,他们将级别从 4 编号为 1,这可能更为常规😅)。

翻译后备缓冲区

如果遍历页表来解析虚拟地址的过程听起来很昂贵,那是因为它确实如此。这就是TLB 的作用所在。它是 CPU 内部的一个专用硬件,可以高效地缓存虚拟到物理的页面映射。TLB 的大小是有限的,我实际上并不知道我的笔记本电脑的大小,但据我所知,它大约有 1024 个条目(请注意,每个条目对应一个整个页面大小的映射)。

漏洞利用策略

我的漏洞利用策略受到了 Mark Seaborn 的Rowhammer 漏洞利用元素的启发。主要目标是将我们自己的进程的页表映射到用户可访问的内存中。一旦我们拥有了它,我们就可以修改其中的 PTE,以授予我们访问任意物理内存的权限,这实际上是王国的钥匙。

我的策略不是试图控制物理内存中结构的布局(我想这是物理内存版的堆风水吧?),而是用 0 级页表填充(也称为“喷洒”)尽可能多的物理内存。实际上,我填充了恰好 50% 的物理内存。

喷射完成后,我会进入循环,尝试以绕过 TLB 的方式访问一堆 R/W 映射(因为映射数量超出了 TLB 大小),每次访问时都强制进行页表遍历。我想在遍历过程中使内存总线发生故障,以破坏 0 级 PTE 读取的第 29 位。如果我很幸运(几率约为 50%),故障将偏移 PTE 指向的物理地址,使其指向我们之前喷射的 0 级页表之一。

理论上,这应该适用于 29(对应于 512MiB 偏移)和 12(对应于 4KiB 偏移)之间任何位位置的位翻转。重要的是 PTE 最终指向“其他地方”,并且由于我们已经用可利用的页表填充了约 50% 的物理内存,因此我们有很大的成功几率。因此,如果您可以产生足够强的电磁干扰(尽管这样整个系统崩溃甚至变砖的几率会更高),那么焊接天线线可能不是完全必要的。

以下是故障如何影响页表的直观图示:

只用打火机就能获得 root 权限吗?

此图中的每个块代表一个 4K 物理内存页。它们在内存中的位置或多或少是随机的,与漏洞利用逻辑无关。重要的是指向什么。故障的 PTE(红色)应该指向 R/W 页,但现在它指向另一个 0 级页表,提供对它的访问,就像它是常规的 R/W 页一样。实际上,这些 0 级页面还有数千个,故障的 PTE 最终可能指向其中任何一个,但我只能在图表上放这么多。

那么我该如何喷洒这么多的 0 级页表呢?

首先,我创建一个memfd,这是 Linux 的一个相对现代的功能。它的作用与/dev/shm/Mark Seaborn 的漏洞利用中的文件相同,但完全不需要接触文件系统。然后我使用mmap系统调用多次将同一缓冲区映射到内存中。我使用该MAP_FIXED选项强制每个映射在虚拟内存中按 2MiB 对齐,这保证每次都会创建一个新的 0 级页表。Linux 有一个 ~216每个进程允许的映射(VMA,虚拟内存区域)数量受到限制,因此我将每个映射的长度设为 32MiB。这意味着每个映射都会生成 16 个 0 级页表。虽然每个映射占用 32MiB 的虚拟内存空间,但 PTE 都指向完全相同的底层物理页面。每个映射的物理内存成本仅由 0 级页表组成,因此我可以随意喷射它们,直到内存已满。

正如我之前所说,我们尝试循环访问 R/W 映射,等待故障发生。我们可以检测到故障,因为会返回一个意外的值,如果成功,则数据应该看起来像 PTE。如果是这样,我们现在可以对页表进行 R/W 访问。下一步是找出此页表对应的虚拟地址。我通过修改 PTE 以指向物理地址 0(这是一个任意选择,实际上任何地址都应该有效)然后再次扫描 R/W 映射以查看哪个映射发生了变化(如果有)。

MMU 不会立即“注意到”对 PTE 的编辑,因为虚拟->物理地址映射由 TLB 缓存。每次我们更改它时,都需要刷新 TLB。没有从用户空间直接执行此操作的方法(据我所知?)所以我只是循环访问几千个 R/W 映射,强制 TLB 填充新值并逐出旧值。

此时,我们拥有对所有物理内存的完全读/写访问权限!从此时起,我们可以使用许多策略,我再次从 Rowhammer 漏洞中获得了灵感。我以只读模式(我没有写权限!)打开/usr/bin/su可执行文件(即setuid root),并 mmap 它的第一页。然后,我扫描所有物理内存,直到找到同一页。找到物理页面后,我拥有对它的完全写访问权限,我用自己的小型(<4KiB)ELF程序替换它,该程序会生成一个 root shell。这有效地毒害了 Linux 的页面缓存。

下次有人(我)尝试调用该su二进制文件时,Linux 知道它已经在内存中拥有第一页,因此不会再次尝试从磁盘读取它。因此,它会重新使用该缓存页面,并开始执行我们注入的 ELF。游戏结束!

我注入的 ELF 还会刷新页面缓存(echo 1 > /proc/sys/vm/drop_caches),因此下次有人调用su它时它就可以再次正常运行。

我的完整漏洞利用源代码可以在这里找到。

以下是一个演示视频(抱歉,有点模糊):

这次运行我运气特别好,通常需要多次点击打火机才能找到一个好的故障。没有显示之前几次导致整个系统崩溃的尝试!我不确定整体漏洞利用可靠性是多少,我还没有尝试严格测量它。当笔记本电脑的屏幕关闭并且我通过 SSH 登录时,感觉它大约是 50%。但是当我在演示中的图形 shell 中时,它的可靠性可能更接近 20%。该系统具有集成显卡,因此 GPU 的内存访问可能会干扰漏洞利用。还有各种后台服务在运行(pipewire、sshd、systemd 等),并且交换也已启用。我希望它是一个相当逼真的桌面 Linux 环境,禁用所有这些东西可能会提高可靠性。

如果笔记本电脑安装了更多的 RAM,我就能够用页表填充更高比例的 RAM,这也应该会增加整体漏洞利用的可靠性。

实际用途

尽管我的 Linux LPE 很酷,但我已经拥有了那台笔记本电脑的 root 权限,因为它是我的。我们还能用它做更多“有用”的事情吗?

我不太喜欢 PC 游戏(更像是任天堂的粉丝),但每当看到“反作弊”软件使用 TPM 等技术来限制在系统其余部分运行的软件时,我都会感到恼火。也许可靠的 EMFI Windows LPE 可以让游戏玩家重新控制他们的 PC,而不会干扰 TPM 认证状态。

想象一下未来“游戏 RAM”棒上配备 RP2040 来自动化整个漏洞利用(当然,也驱动 RGB LED)。

Android 设备和 SafetyNet/Play Integrity 检查也有类似的情况,尽管将有故障的改装芯片安装到手机中会更具挑战性。

思考

从概念层面上讲,我早就了解页表和 TLB。但即使在进行低级性能优化时,这些知识对我来说也并不重要(另一方面,缓存却一直很重要!)但在这次攻击中,所有这些都绝对重要,终于测试了这些理论知识,这让我非常满意。

实际上看到并与维持虚拟记忆幻觉的结构进行交互感觉有点像逃离矩阵。

未决问题

  • 它可以在 DDR4、DDR5 上运行吗?(我看不出有什么理由不行!)

  • 它能在 ARM 上运行吗?(同样)

  • 不同类型的 ECC 在多大程度上可以缓解这种情况?(特别是 DDR5 Link-ECC)

  • 以电子方式触发类似故障的最简单方法是什么?(例如,使用 RP2040)

  • 您能使用它来摆脱虚拟机管理程序吗?

  • 我可以用它来编写 Webkit 漏洞利用程序吗?

  • 我可以用它来编写 Nintendo Switch 内核漏洞吗?

我将在未来继续研究这些问题——敬请关注!

原文始发于微信公众号(独眼情报):只用打火机就能获得 root 权限吗?

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年10月11日13:27:10
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   只用打火机就能获得 root 权限吗?https://cn-sec.com/archives/3253623.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息