JITSploitation I:挖掘“非标准的”JIT漏洞
本系列文章由三篇组成,重点介绍在现代Web浏览器中挖掘和利用JavaScript引擎漏洞过程中所面临的各种技术挑战,并对当前的漏洞利用缓解措施进行评估。本文涉及的漏洞为CVE-2020-9802,该漏洞已经在iOS 13.5中得到了修复;而针对该漏洞缓解措施的绕过漏洞CVE-2020-9870和CVE-2020-9910,也已经在iOS 13.6中得到了相应的修复。
这篇文章是关于Safari渲染器漏洞系列的第三篇。其中,我们在第一篇中讨论了存在于JSC中的一个JIT编译器漏洞,第二篇展示了如何绕过各种缓解措施,利用该漏洞获得一个可靠的读/写原语。在本文中,我们将为读者概述iOS 13上的WebKit中存在的各种代码执行漏洞缓解措施,以及相应的绕过方法。
iOS JIT安全加固的历史
在浏览器漏洞利用的“上古”时代,在渲染器进程中拥有读写能力的攻击者只需将任意shellcode写入rwx JIT区域就大功告成了。
2016年,WebKit部署了第一个基于软件漏洞利用缓解措施:“Bulletproof JIT”。它的工作原理是将JIT区域映射两次,一次以r-x权限执行,一次以rw-权限写入。可写映射被放置在内存中的一个秘密位置,之后,bulletproof JIT机制将依靠一个包含jit_memcpy函数的--x区域,将给定的数据复制到可写的JIT映射中,而不透露其秘密地址。然而,由于缺乏CFI,这种缓解措施很容易被绕过,例如使用ROP技术。此外,如果攻击者能够通过某种手段泄露可写映射的位置,他们就可以轻松地将自己的shellcode写入其中。
在iPhone X推出前后,苹果公司又增加了硬件辅助的缓解措施,即APRR和PAC,使得iOS的JIT加固措施变得更加强大。这些将在后面加以讨论。要想更全面地了解各种iOS漏洞缓解措施,感兴趣的读者可以参考Siguza的演讲“Evolution of iOS mitigations”。
APRR
虽然这个缩写词的扩展名在苹果公司之外还不为人所知,但它的功能却是广为人知的。抛开技术细节不谈(对于细节感兴趣的读者可以参考Siguza关于APRR的博客文章),APRR背后的目标本质上是启用每个线程的页面权限。这是通过用专用CPU寄存器将页表条目权限映射到它们的真实权限来实现的。这样,页面权限基本上就变成了APRR寄存器的索引,而APRR寄存器现在保存着实际的页面权限。作为一个简化的示例,请考虑以下APRR映射:
这样设置APRR寄存器后,就能有效地执行严格的W^X策略:任何页面都无法同时具有可写和可执行权限。
在WebKit中,APRR现在用来保护JIT区域:JIT区域的页表权限是rwx,但W^X是强制执行的,具体如上图所示。因此,JIT区域实际上的权限是r-x,因此,试图直接写入将触发一个segfault故障。由于JIT区域经常执行写入操作(例如当新代码被编译或现有代码被更新时),因此,有必要改变该区域的权限。实际上,它是通过一个专门的“unlock”函数来完成的,该函数将索引7处的APRR寄存器的值(对应页表权限rwx)改为rw-。但是,这种情况只发生在即将把数据复制到JIT区域的线程上,而该区域对所有其他线程的权限仍为r-x。这样可以防止攻击者在JIT编译器线程解锁该区域时与之发生竞争。下面是负责将代码复制到JIT区域的executeJITMemcpy函数源代码,需要注意的是,这里已经进行了简化处理。
ALWAYS_INLINE void* performJITMemcpy(void *dst, const void *src, size_t n) { os_thread_self_restrict_rwx_to_rw(); memcpy(dst, src, n); os_thread_self_restrict_rwx_to_rx(); return dst; }
另外,请注意ALWAYS_INLINE的用法,它迫使这个函数在每个调用点都内联该函数。关于这一点,我们稍后再谈。
APRR本身是相当容易被绕过的,主要有两种类型的绕过方式:
1. 通过ROPing、JOPing等技术进入performJITMemcpy函数(或真正的内联函数),然后将任意代码复制到JIT区域。
2. 由于编译器先将机器代码放入一个临时的堆缓冲区中,之后再复制到JIT区域中,攻击者就有可能在复制之前破坏堆上的机器代码。
下面,我们开始介绍PAC。
PAC
PAC是指针认证码(Pointer Authentication Codes)的缩写,是另一种硬件特性,它允许将密码签名存储在指针的其他未使用的高位中。这已经成为许多研究的主题,例如Brandon Azad的一篇文章就对此进行了深入的介绍。启用PAC特性后,每个代码指针都必须有一个有效的签名,在将控制流传输到它之前要检查该签名。由于PAC密钥保存在寄存器中,因此,攻击者是无法访问它们的,所以,也就无法伪造有效指针了。
因此,PAC可立即阻止攻击者执行上述第一种攻击方式。此外,由于performJITMemcpy函数被标记为ALWAYS_INLINE,因此,不会存在任何指向该函数的现有函数指针,从而使得攻击者可以使用受控参数来调用该函数。
上面所说的第二种攻击方式需要额外的工作来进行防御。该问题主要在LinkBuffer::CopyCompactAndLinkCode函数内部,该函数负责将先前汇编的机器代码复制(并链接以及可能压缩)到JIT区域中。如果攻击者能够在该函数将机器代码复制到JIT区域之前损坏包含机器代码的堆缓冲区,那么攻击者将获得任意代码执行权限。通过在汇编过程中对机器代码计算基于PAC的散列值,然后在复制过程中重新计算并验证该散列值,就可以缓解这种攻击造成的伤害。通过这种方式,可以确保汇编器发出的任何东西也被复制到JIT区域中,而不需要进行任何修改。虽然有可能诱骗编译器发出某种程度的可控代码(稍后将详细介绍),但由于汇编器只支持有限的一组指令,因此,通常就无法执行任意指令了。
小结
APRR和PAC共同实现了以下功能:
1. JIT区域被有效地映射为r-x权限,并且只在极短的时间内被“unlocked”,并且只能在JIT代码更新时对单个线程进行解锁。这就防止了攻击者直接写入JIT区域的行为;
2. PAC用于强制执行CFI,从而防止攻击者进行经典的代码重用攻击,如ROP、JOP等。同时,也禁止了直接调用executeJITMemcpy函数,因为它总是内联到它的调用方中的;
3. 在将JIT代码复制到JIT区域之前,PAC用于确保发出的JIT代码的完整性。
这是本研究项目最后一部分的起点。本篇文章的剩余部分将讨论不同的绕过方法。
绕过JIT加固技术
接下来介绍的各种攻击最终都是为了获得对程序执行流的控制权,这种控制权足以实现第二阶段的利用(很可能是某种形式的沙盒逃逸)。这很可能是攻击者通常要实现的目标。然而,应该记住的是,如果没有类似站点隔离之类的措施,在渲染器进程中具有内存读/写能力的攻击者通常能够构建UXSS攻击,从而获得对各种网络凭证和会话的访问权限,甚至可能通过web worker实现攻击的持久性。这些问题在过去已经得到了证明,因此,这里就不再进一步讨论了。
无需借助Shellcode的漏洞利用方法
首先,需要注意的是,攻击者不一定需要执行shellcode。例如,如果能够滥用ObjectiveC和JavaScriptCore的运行时,就能直接通过JavaScript执行任意函数和系统调用。然后,这又可以用于实现利用链的下一阶段,这样,就无需绕过JIT加固措施了。这一点已经得到证明,因此,在本项目期间没有对此做进一步的研究。
类似地,虽然JIT的最终输出(机器代码)会受到PAC的保护,但它的中间输出(特别是各种IRS-DFG、B3和AIR)和其他支持数据结构是没有任何保护措施的,因此,它们很容易受到攻击者的操纵。因此,一种可能的方法是破坏JIT的IR代码,以便欺骗编译器生成对带有受控参数的任意函数的调用。这可能会获得一个与上面的权限非常相似的原语,即能够执行受控的系统调用,因此,在本研究中没有对此做进一步的探讨。
竞态条件
竞态条件问题似乎在PAC+APRR边界一带相当普遍。作为示例,下面是一个相当典型的performJITMemcpy调用,在本例中是为了在JIT生成的代码中重新修改指针大小的立即值:
int buffer[4]; buffer[0] = moveWideImediate(Datasize_64, MoveWideOp_Z, 0, getHalfword(value, 0), rd); buffer[1] = moveWideImediate(Datasize_64, MoveWideOp_K, 1, getHalfword(value, 1), rd); buffer[2] = moveWideImediate(Datasize_64, MoveWideOp_K, 2, getHalfword(value, 2), rd); if (NUMBER_OF_ADDRESS_ENCODING_INSTRUCTIONS > 3) buffer[3] = moveWideImediate(Datasize_64, MoveWideOp_K, 3, getHalfword(value, 3), rd); performJITMemcpy(address, buffer, sizeof(int) * 4);
在这里,加载立即数所需的机器指令首先被发射到堆栈分配的缓冲区中,随后通过performJITMemcpy复制到JIT区域。因此,如果另一个线程设法在堆栈分配的缓冲区被复制到JIT区域之前破坏它,攻击者将获得任意代码执行能力。然而,这里的竞争时间窗口非常小,竞争失败可能会导致使用中的栈内存被破坏,从而导致崩溃。这段代码还存在另一个理论上的缺陷:如果NUMBER_OF_ADDRESS_ENCODING_INSTRUCTIONS小于4,那么它就会把未初始化的栈内存复制到JIT区域……)。
最终,我决定从这个研究项目中排除那些不能安全输掉的竞态条件,因为可以说,一个迫使攻击者冒着进程崩溃的风险去赢得竞争的缓解措施,在某些方面算是按照预期工作的。
不受保护的代码指针
另一种可能的攻击方式出现在PAC被错误使用的情况下,例如:
1. 原始指针的签名放置到了攻击者可以破坏的地方;
2. 对于无符号函数指针的调用可以被攻击者控制。
通过对汇编代码的静态分析,可以发现这种情况。虽然我最初想使用binary ninja的各种IL,因为它们支持各种数据流分析,但由于缺乏对PAC指令的支持,这就使得这项工作变得更加困难,因此,我选择了一个非常简单的IDAPython脚本,它会输出以PAC签名指令结尾的指令序列,如PACIZA。当在DyldSharedCache镜像上运行时,该脚本的输出将达到数千行:
libz.1:__text:0x1b6ba1444 ADRL X16, sub_1B6BA9434; PACIZA X16
这个“gadget”本质上是取一个常量(sub_1B6BA9434的地址),并使用一个密钥和0的上下文对其进行签名。因此,对于攻击者来说,并不会对它感兴趣,因为签名的值是无法控制的。在过滤掉这种明显安全的代码片段后,是一个经常出现的代码模式,具体如下所示:
ADRP X16, #_pow_ptr_3@PAGE LDR X16, [X16,#_pow_ptr_3@PAGEOFF] PACIZA X16
这段代码从一个可写内存页面加载一个原始指针,然后,使用PACIZA指令对其进行签名。因此,攻击者可以通过覆盖内存中的原始指针来绕过PAC,然后,通过某种方式让这段代码执行。由此看来,貌似每当一个来自不同编译单元的函数作为指针引用而不是直接被调用时,编译器就会发出这个易受攻击的代码。这个特殊的代码片段是以下C++代码在JavaScriptCore中对应的机器代码:
LValue Output::doublePow(LValue xOperand, LValue yOperand) { double (*powDouble)(double, double) = pow; return callWithoutSideEffects(B3::Double, powDouble, xOperand, yOperand); }
当一个已知对双浮点值进行操作的Math.pow调用被优化时,JIT编译器就会使用这个函数。在这种情况下,编译器会发出对C pow函数的调用,所以,会加载这个函数,并对其地址进行签名。由于编译器存在缺陷,导入的函数指针会被放到一个可写的内存区域,并且也没有对其提供PAC保护。这样的话,这个问题的PoC就很简单了。
// offset from iOS 13.4.1, iPhone Xs let powImportAddr = Add(jscBase, 0x34e1d570); memory.writePtr(powImportAddr, new Int64('0x41414141')); function trigger(x) { return Math.pow(x, 13.37); } for (let i = 0; i < 10000000; i++) { trigger(i + 0.1); }
这将导致崩溃,并且PC=0x41414141,这说明PAC已经被绕过。
之后,我们用稍加修改的IDAPython脚本搜索第二种类型的漏洞,即调用未受保护的指针,也得到了一个有趣的代码片段。
MOV W9, #0x6770 ADRP X16, #___chkstk_darwin_ptr_19@PAGE LDR X16, [X16,#___chkstk_darwin_ptr_19@PAGEOFF] BLR X16
这段代码在许多大型函数的开头都能找到,它分支到__chkstk_darwin函数,该函数很可能用于负责在堆栈溢出时防止一个巨大的栈帧“跳过”堆栈保护页。不过,由于某些原因,该函数的指针是从一个可写的内存区域加载的,而且也没有受到PAC的保护。因此,这可能存在任意代码执行漏洞,具体如下代码片段所示。
// offset from iOS 13.4.1, iPhone Xs let __chkstk_darwin_ptr = Add(jscBase, 0x34e1d430); memory.writePtr(__chkstk_darwin_ptr, new Int64('0x42424242')); // Just need to trigger FTL compilation now, we'll crash in FTL::lowerDFGToB3 function foo(x) { return Math.pow(x, 13.37); } for (let i = 0; i < 10000000; i++) { foo(i + 0.1); }
之所以会出现这种情况,是因为几乎所有具有大型栈帧的函数中都广泛使用了__chkstk_darwin,而其中一个函数,即FTL::lowerDFGToB3,在JIT编译过程中将被执行。
这两个问题已经报告给苹果,随后在7月15日的iOS 13.6中得到修复,并被赋予编号CVE-2020-9870。用于查找这些gadgets的IDAPython脚本也可以在issue #_2044的报告中找到。
操控Mach Messages
与Project Zero团队成员Brandon Azad的聊天过程中,我受到了一个启发:这个绕过方法背后的想法,就是在mach message结构通过mach_msg系统调用发送出去之前破坏它。在iOS和macOS系统上,许多内核接口、整个IOKIT驱动接口以及几乎所有的用户空间IPC都是通过mach messages来实现的,这就使得它变成了一个强大的攻击原语。例如,通过改变内存保护或重新映射页面,可以使用与虚拟内存相关的mach系统调用来绕过PAC或APRR。另外,控制mach messages将再次允许利用JavaScript实现第2阶段的漏洞利用代码,除非它需要执行BSD syscalls的能力。
一个简单但不完美的方法是:用Frida脚本来钩住mach_msg函数,然后根据其调用堆栈来不断复制它的调用,从而找到发送mach messages的代码。当然,这种方法并不完美,因为它会错过那些在正常操作中很少执行的代码路径,但实现起来却非常快。在WebKit渲染器进程中执行上述操作,找到了以下几组对mach_msg的相关调用:
· 与浏览器进程进行的IPC通信
· XPC与其他系统进程之间的通信
· Mach对内核的系统调用
最终,所有这些情况似乎都是竞态条件问题,因为构建的mach message大部分都是立即发送出去的,而没有在内存中停留一段时间(最好是攻击者可以控制的),而在此期间它可能会被破坏。由于在这些情况下竞争失败会导致堆(在IPC和XPC通信的情况下)或栈(在mach系统调用的情况下)损坏,竞争很可能无法安全地重复,因此这些情况无法满足可靠的绕过技术的要求。
滥用信号处理程序
PAC(像许多其他缓解措施一样)依赖于令进程崩溃以阻止攻击者。因此,一个可能的目标是能够中断崩溃进程的信号处理机制。
WebKit的渲染器进程内部提供了信号处理机制,以进行JavaScriptCore优化。例如,JSC为WASM代码提供了执行模式,在这种模式下,所有的边界检查都被省略了,但WASM堆后有一个32GB的防护区域。由于WASM内存访问使用32位索引,如果WASM中发生无效访问,就会访问一个特定的保护页,从而引起一个segfault故障,从而导致WASM信号处理程序被运行。该处理程序将重新匹配WASM代码,这样故障线程将在恢复时引发一个JavaScript异常。
实际上,WebKit中的异常处理是基于mach异常处理基础架构,而不是基于UNIX信号处理设施的。下面,我们简单介绍一下它的工作原理。
1. 当某个渲染器线程发生异常时,GCD工作线程会被内核唤醒来处理异常;
2. 线程会执行mach_msg_server_once,从内核获取描述异常的mach message,为回复消息分配内存空间,然后将两者都传递给处理函数;
3. _Xmach_exception_raise_state_identity是一个自动生成的MIG函数,是异常消息的注册处理函数。它将对输入的mach message进行分析,从而提取出崩溃时的寄存器内容等值,然后执行“真正的”处理函数;
4. 之后,catch_mach_exception_raise_state会遍历一个注册处理程序的链接列表(比如WASM故障处理程序),并执行其中的每一个处理程序,同时也会传递给它们可以修改的输出寄存器状态。同时,该函数会根据其中一个处理程序是否处理了异常,来返回KERN_SUCCESS或KERN_FAILURE;
5. 在_Xmach_exception_raise_state_identity中,使用返回值和输出寄存器状态来填充回复消息;
6. 最后,mach_msg_server_once向内核发送回复消息,并将控制权返回给GCD;
7. 如果返回值是KERN_SUCCESS,内核将通过输出寄存器状态恢复崩溃的线程,否则将终止它。
这个过程可以通过下面的图形直观地展现出来。
因此,攻击者可以通过下列步骤来发动攻击:
1. 单一链接的处理程序列表被破坏并变成一个循环。这是有可能的,因为与处理程序函数指针相反,列表元素的下一个指针是不受PAC的保护的。
2. 通过一个单独的线程制造访问违规。这将导致GCD线程“卡”在catch_mach_exception_raise_state中,从而进入无限循环。
3. 受攻击者控制的线程现在可以搜索所有线程栈(它们在内存中连续分配),寻找catch_mach_exception_raise_state的返回地址。一旦找到,就可以访问回复的mach message,因为指向它的指针被溢出到堆栈上。然后,攻击者可以直接操纵该回复消息。特别是,现在可以设置新的寄存器状态(PC除外,因为PC受PAC保护)和返回值了,以指出异常是否被处理。
4. 栈上溢出的指针被替换成其他的指针,以使_Xmach_exception_raise_state_identity将信号处理程序的实际返回值(将是KERN_FAILURE)写入不同的内存位置,而它的调用者,即mach_msg_server_once将把攻击者控制的回复消息发回内核。
5. 由于线程修复了处理程序列表,导致处理程序线程跳出循环,并从catch_mach_exception_raise_state返回。这时,内核将收到一个完全处于攻击者控制之下的回复消息,这时,系统将恢复崩溃的线程,并开始使用攻击者控制的寄存器(和堆栈)上下文。
这是一个功能非常强大的漏洞利用原语,基本上可以构建一个小型的“调试器”,能够中断程序中的大部分数据访问,并且能够在这些点上任意地更改执行上下文。这样的话,攻击者就能够通过多种方式绕过PAC和/或APRR,例如:
1. 破坏AssemblerBuffer,使任意指令被LinkBuffer复制到JIT区域。这将导致计算出的哈希值不匹配,链接器崩溃,但这只发生在指令被复制之后,并且可以轻松捕获崩溃
2. 在向LinkBuffer::CopyCompactAndLinkCode中的JIT区域进行写入时崩溃(通过损坏之前的目标指针),并更改源寄存器的内容,以便在使用原始指令进行哈希值计算的同时将任意指令写入JIT区域。
3. 在LinkBuffer::copyCompactAndLinkCode期间崩溃,然后,在其他地方继续执行。这将使JIT区域对该线程来说是可写的(尽管不是可执行的)。
4. 暴力破解一个PAC代码(例如,通过反复访问、崩溃,然后修改PAC保护指针),然后JOP到一个内联JITMemcpy的函数中。
在pwn.js文件中公布的概念验证性质的漏洞利用代码中,可以找到一个简单的PoC,演示这个技术的运行机制。它通过破坏一个受PAC保护的缓冲区指针,在其访问过程中捕获异常,然后修改保存原始指针的寄存器并恢复执行,从而利用TypedArrays的“调试器”成功绕过PAC防御机制。
这个问题已经报告给了苹果公司。报告后仅6天,苹果就修复了这个问题:在JavaScript引擎初始化时初始化信号处理程序,然后将保存信号处理程序的内存区域标记为只读。这样就可以防止攻击者修改列表了。7月15日,苹果向使用iOS 13.6的用户发送了补丁程序,该问题分配的漏洞编号为CVE-2020-9910。
漏洞的变种
这种漏洞模式比较模糊,而且也与信号处理没有密切的关系。作为一个例子,考虑以下来自LinkBuffer::copyCompactAndLinkCode的代码:
if (verifyUncompactedHash.finalHash() != expectedFinalHash) { dataLogLn("Hashes don't match: ", ...); dataLogLn("Crashing!"); CRASH(); }
如果在链接和复制汇编的代码期间,JSC确定机器代码被损坏(比如哈希值不匹配),则执行该代码。这里的问题是,攻击者可能会破坏数据,从而导致dataLogLn无限阻塞,例如破坏锁或使某个循环无法退出。在这种情况下,攻击者控制的机器代码将复制到JIT区域中,然后,攻击者就可以在另一个线程中执行,而不必担心与CRASH()的竞争中失败。这个潜在的变体很可能是在最初的问题报告给Apple后不久就被他们发现的,然后在WebKit的commit E87946B7A8进行了修复。
另一个例子是,JSC在遇到错误的PAC签名(WebKit的PtrTag机制是基于PAC的),并在执行CRASH()之前先调用了以下函数,则表明攻击者破坏了关键数据:
void reportBadTag(const void* ptr, PtrTag expectedTag) { dataLog("PtrTag ASSERTION FAILED on pointer ", RawPointer(ptr), ", actual tag = ", tagForPtr(ptr)); ... }
实际上,tagForPtr调用会遍历一个链表:
static const char* tagForPtr(const void* ptr) { PtrTagLookup* lookup = s_ptrTagLookup; while (lookup) { const char* tagName = lookup->tagForPtr(ptr); if (tagName) return tagName; lookup = lookup->next; } ...
因此,通过将该列表转换为循环,就可防止由于PAC失效而引起崩溃。反过来,这又允许对PAC进行蛮力破解,或者泄漏有效签名的任意指针,具体如该变体的报告中所述。该变体作为Project Zero issue#2042的变体报告给苹果,然后在commits 13E30EC7A5和DB8B3982F2中得到了修复。
最后,这个漏洞甚至可能存在“更广泛的”变体:如果有代码在执行可以被攻击者阻止的操作(如循环或锁操作)之前,将敏感值(如验证后的原始指针)暂时泄漏到堆栈中,那么,攻击者可能会在不必赢得比赛的情况下破坏敏感数据。不过在整个研究过程中,没有发现这种情况。
小结
从本质上讲,在检测到故障条件之后、在进程最终终止之前执行的每一段代码,都应该被视为攻击面:如果攻击者能够使这段代码阻塞,那么他们就有可能“获胜”。对于信号处理,情况会变得更加复杂,因为像下面的代码:
if (security_failure) { CRASH(); }
其实等价于:
if (security_failure) { signal_handler(); CRASH(); }
因此,理想情况下,信号处理将完全从关键进程中移除,或者至少限制在较少的信号上。总而言之,这些修复措施的实施速度意味着苹果致力于将PAC(和APRR)作为一种严重的安全缓解措施。
结束语
这篇文章讨论了绕过WebKit的JIT安全加固措施的多种方法。虽然有些方法没有成功,或者由于各种原因没有进一步尝试,但发现了两个以前不为人知(至少是未曾公开的……)的安全漏洞,以及多个变种,通过它们可以稳定的绕过各种安全防御措施。这些漏洞已经报告给了苹果公司,随后在iOS 13.6中得到了相应的修复,它们的漏洞编号分别为CVE-2020-9870和CVE-2020-9910。
总的来说,要想找到一个合适的漏洞,并用它绕过各种漏洞缓解措施,将是一个相当耗时的过程。不过,要记住,这种努力有很大一部分都是攻击者的一次性成本,也就是说,只是在第一次开发漏洞利用代码时需要付出的成本。之后,攻击很可能会将以前的大部分利用工作重复用于后续漏洞利用过程。另一方面,苹果还将缓解措施的绕过等同于漏洞对待,一旦报告就迅速给予了修复,并为其分配了CVE编号。很高兴看到苹果迅速修复漏洞缓解措施绕过问题的决心,希望他们今后继续这样做。
虽然在可预见的未来,逻辑漏洞可能会导致沙箱逃逸,并且基本上不受漏洞缓解技术的影响,但似乎有一点可以肯定:一个典型的漏洞链仍然需要执行渲染器shellcode(或至少是大致相当的过程)。由于这很可能需要某种形式的内存损坏,因此,在加强沙盒保护的同时,开发和维护不同层次的内存损坏缓解措施,通常来说是非常值得做的事情。
本文翻译自:https://googleprojectzero.blogspot.com/2020/09/jitsploitation-three.html如若转载,请注明原文地址: