,(www.ipfs8.vip)是FiLecoin致力服务于使用FiLecoin存储和检索数据的官方权威平台。IPFS官网实时更新FiLecoin(FIL)行情、当前FiLecoin(FIL)矿池、FiLecoin(FIL)收益数据、各类FiLecoin(FIL)矿机出售信息。并开放FiLecoin(FIL)交易所、IPFS云矿机、IPFS矿机出售、租用、招商等业务。
在这篇文章中,我们将为读者深入先容JavaScriptCore的WebAssembly子系统中的一个平安破绽,JavaScriptCore是WebKit和Apple Safari浏览器中的JavaScript引擎。需要说明的是,这个破绽已在Safari 14.1.1中获得了修复。现实上,这个破绽是通过源代码审查发现的,并在Pwn2Own 2021中用于实现远程代码执行。在下一篇文章中,我们将详细先容内核模式的沙箱逃逸手艺。
下面是我们在Pwn2Own 2021时代乐成行使该破绽的一个演示视频:
https://blog.ret2.io/assets/img/p2o_2021_rce_demo.mp4
WebAssembly概述
WebAssembly,又称为wasm,是一种使用二进制示意法的类汇编语言,主要用于Web环境。与高度动态和庞大的JavaScript范式相比,WebAssembly显得异常精练。WebAssembly仅有四种原始值类型(32/64位的整数和浮点数),以及一个相对较小的指令集,这些指令都是在栈式机(stack machine)上运行的。
像许多汇编语言一样,wasm可以用人类可读的文本名堂手工编写。然而,现实天下的wasm应用程序通常是用编译器构建的。现在,越来越多的高级语言最先支持编译为wasm语言,像Emscripten这样的项目也最先支持基于LLVM的随便语言。
WebAssembly LLInt
当JavaScriptCore运行传统的JavaScript代码时,它行使了四级执行模式,以逐步优化代码。当引擎以为一个函数是“热”函数(若是该函数经常被挪用的话,就会泛起这种情形)的时刻,或者包罗一个迭代次数足够多的循环的时刻,引擎就会将这个函数通报到下一个层级,以举行更深入的优化处置。
wasm执行流水线接纳了类似的方式,不外它具有三个层级。对WebAssembly模块举行剖析后发生字节码,将提供应一个注释器:wasm llint(低级注释器)。在llint之后,尚有两个JIT(just-in-time)编译器:BBQ(Build Bytecode Quickly,快速构建字节码)编译器和OMG(Optimized-Machine code Generator,优化型机械码天生器)编译器。
三级wasm执行流水线
在本文中,我们只关注llint注释器;稀奇是剖析历程和字节码天生历程。对于wasm模块的代码段中的每个函数,都市举行响应的剖析处置,并一次性天生响应的字节码。同时,剖析器的逻辑是通用的,并凭证卖力代码天生的上下文工具举行模板化。对于llint来说,这个工具就是一个天生字节码的LLIntGenerator工具。
剖析历程与代码天生
剖析器将卖力验证函数的有用性,这将涉及所有客栈操作和控制流分支的类型检查。Wasm函数都具有异常结构化的控制流,并以组织块的形式(可以是一个通用块,一个循环块,或一个if条件块)泛起。块是嵌套的,而分支指令只能针对一个外围块(enclosing block)。从剖析器的角度来看,每个块都有自己的表达式客栈,与外围块的表达式客栈是离开的。通过多值规范,每个块可以具有参数类型和返回类型的署名。参数从当前表达式客栈中弹出,并用作新块客栈的初始值;当分支出块时,返回值将被压入外围块的客栈。
FunctionParser会跟踪控制栈和表达式栈上的类型。而LLIntGenerator则跟踪种种元数据,包罗当前的整体客栈巨细(对各个块的客栈巨细举行汇总)和整个剖析历程中泛起的最大客栈巨细。当前的客栈巨细有助于将抽象的客栈位置酿成内陆的客栈偏移,而客栈巨细的最大值将决议在函数序言中保留若干客栈空间。
让我们看一下一些简朴的wasm函数的例子。现实上,这个函数不需要参数,并返回一个32位的整数。下面展示的是剖析器/天生器在剖析指令之前的状态。
凭证挪用老例的划定,优先选用寄存器来通报参数,然后才选用客栈举行通报。llint会为所有可能的参数寄存器保留客栈槽,而不管函数是否接受那么多参数。在x86_64架构上,有2个挪用方保留的寄存器,6个参数GPR和8个参数FPR,这就是为什么m_stackSize从16最先的缘故原由。
m_expressionStack跟踪注释器客栈上的类型(而不是值;这里只是剖析,不是执行)。在这个例子中,压入了一个i32:
然后,再压入一个i32:
xor指令将弹出前两个i32操作数,并压入一个i32效果值:
现在来看看一个使用块的例子。这个函数不需要参数,并返回一个64位的整数。
初始状态:
一个i32值被压入,从而增添了当前客栈的巨细:
当我们到达这个块时,系统将确立一个新的表达式客栈,并将所有参数(在本例中是一个i32值)从当前客栈中弹出并压入新的客栈中。然后,一个控制客栈条目被确立,并指向当前客栈(内层块的客栈),新客栈成为当前客栈。当前的客栈巨细不会发生改变。
转换指令将从客栈中弹出i32,并压入一个i64值:
当这个块竣事时,其返回类型被移到内层的客栈上,控制条目被弹出,内层的客栈成为当前客栈(在某种意义上讲,控制客栈就是一个客栈的客栈):
破绽剖析
m_maxStackSize字段的用途,就是纪录函数执行历程中所需的最大客栈槽数。因此,它会经常举行更新,主要是在每次向表达式客栈压入数据时:
ExpressionType push(NoConsistencyCheckTag) { m_maxStackSize = std::max(m_maxStackSize, ++m_stackSize); return virtualRegisterForLocal(m_stackSize - 1); }
当剖析完成后,举行需要的处置,以知足客栈对齐要求(16字节对齐),并将其存储到天生的FunctionCodeBlock的m_numCalleeLocals字段中:
std::unique_ptr { ... m_codeBlock->m_numCalleeLocals = WTF::roundUpToMultipleOf(stackAlignmentRegisters(), m_maxStackSize); ... }
当函数被现实挪用时,llint序言将使用m_numCalleeLocals来确定栈帧巨细(即sub rsp, 0x...),以及它是否大到足以触发客栈溢出异常:
macro wasmPrologue(codeBlockGetter, codeBlockSetter, loadWasmInstance) ... , Get new sp in ws1 and check stack height. loadi Wasm::FunctionCodeBlock::m_numCalleeLocals[ws0], ws1 lshiftp 3, ws1 addp maxFrameExtentForSlowPathCall, ws1 subp cfr, ws1, ws1 bpa ws1, cfr, .stackOverflow bpbeq Wasm::Instance::m_cachedStackLimit[wasmInstance], ws1, .stackHeightOK .stackOverflow: throwException(StackOverflow) .stackHeightOK: move ws1, sp ...
只要压入操作的次数足够多,m_maxStackSize最终将被设置为UINT_MAX,或者0xffffffff。当剖析完成后,LLIntGenerator::finalize会将最大客栈尺寸向上舍入,以便对齐:将0xffffffff向上舍入到2的倍数会引起整数溢出,从而酿成0。
欢迎进入AllbetGmaing下载(www.aLLbetgame.us),欧博官网是欧博集团的官方网站。欧博官网开放Allbet注册、Allbe代理、Allbet电脑客户端、Allbet手机版下载等业务。
这将导致m_numCalleeLocals的值为0,而这个值决议了函数序言时代的栈帧巨细。因此,挪用该函数时,现实上并没有为栈帧分配任何空间,也不会触发客栈溢出异常。该函数现实上可以随意使用客栈槽,而llint以为没有需要为栈帧分配内存空间……
触发破绽
为了触发这个破绽,我们需要确立一个wasm函数,执行约莫2^32次压入操作。固然,不清扫尚有触发该破绽的其他方式,但最终照样需要借助于滥用多值规范和剖析器对不能达代码的处置方式。
多值规范允许块具有随便数目的返回值,而JavaScriptCore并没有划定响应的上限。也就是说,我们能够确立具有大量返回值的块。
对于执行不到的代码,剖析器会举行一些异常基本的剖析,以确定代码到底是不能达的,照样僵尸代码。例如,一个显式的不能达操作码或一个无条件分支使厥后面的代码(在统一块内)不能达。当具有不能达代码的块竣事时,天生器的操作就似乎该块名堂优越一样,并将声明的返回类型压入内层的客栈。
在某些情形下,这可能是必须的行为:if-else的一个分支抛出不能达异常,而另一个分支的行为正常。另外,以返回值类型检查失败为由拒绝函数为无效是错误的,由于异常无论若何都市从函数中跳出来。
无论若何,我们可以用以下模式滥用这种行为:
;; "real" code we want to execute can be placed here block [signature with N ret values] unreachable end ;; the unreachable block ends, N types are pushed onto the stack ;; parsing continues as if the subsequent code was reachable
这使得我们可以用很少的现实代码将相当多的值压入剖析器的表达式客栈中。
为此,我们首先想到的做法就是直接将这种模式串联起来,从而实现N次压入操作,然则这种做法现实上是不能行的,由于表达式客栈是用WTF::Vector实现的,它提供了一个32位的长度字段,同时,还会对换整长度的操作举行适当的检查,以确保分配的内存长度不跨越32位。该向量(vector)的元素是TypedExpression工具,其长度为8,这意味着客栈巨细的上限为2^32 / 8 = 2^29 = 0x20000000。另外,调整长度时,也未必严酷根据2的n次幂来举行调整,以是,现实的上限还要小一些。
为领会决这个问题,我们可以使用嵌套块,由于在前面的例子中看到,每个嵌套块都市有自己的表达式客栈,也就是自己的向量。
;; "real" code we want to execute can be placed here block [signature with N ret values] unreachable end ;; current stack has N values, maximum is N block ;; new block has an empty expression stack block [signature with N ret values] unreachable end ;; current stack has N values, maximum is 2N block ... end end
通过使每个块都具有0x10000000个返回值,并嵌套16个这样的块,我们可以将m_maxStackSize设置为0xffffffff,一旦剖析完成就会发生溢出。
每个向量将占用约莫2GB空间,共有16个向量,以是,它们总共占用32GB内存空间。这可能看起来有点不切现实,但依附macOS在压缩内存方面的魔力,分配和使用所有这些内存约莫只需要2.5分钟(固然,详细时间会因硬件而异),这完全在Pwn2Own的时间限制内,即每次举行破绽行使的时长为5分钟。
实现信息泄露
若是将m_numCalleeLocals设置为0,那么,执行wasm函数时,llint就不会对栈帧举行递减操作,这样的话,将导致以下客栈结构:
| ... | | loc1 | | loc0 | | callee-saved 1 | | callee-saved 0 | rsp, rbp -> | previous rbp | | return address |
正如之前简朴提过的,loc0到loc13将由6个GPR和8个FPR组成,这些都是挪用老例指定的潜在参数,以是为了接见loc0和loc1,我们需要接受两个i64参数。
在llint中,某些操作被指定为慢速路径,并通过挪用内陆C++代码来实现这些操作。在慢速路径处置历程中发生的任何内陆客栈压入操作,都有可能笼罩被挪用方保留的寄存器和内陆寄存器,详细如上图所示。我们的目的,就是选择一个这样的慢速路径,使其挪用将用代码地址和客栈地址笼罩loc0和loc1。然后,在从慢速路径返回时,我们可以“正常”使用内陆变量来对泄露的数据举行运算。
在这里,我们将挪用slow_path_wasm_out_of_line_jump_target,由于“Out-of-line”跳转目的适用于偏移量过大,无法直接用字节码名堂编码的分支。在我们的例子中,只要偏移量不低于0x80,就能知足我们的要求:
block ;; branch out of block ;; an unconditional `br 0` will not work as the filler would be dead code i32.const 1 br_if 0 ;; filler code here... ;; such that the offset from the above branch ;; to the end of the block is >= 0x80 end
上述代码模式将执行对slow_path_wasm_out_of_line_jump_target的内陆挪用,详细如下所示:
现在,在loc0中有一个返回地址,它将指向JavaScriptCore dylib,同时,在loc1中有一个客栈地址,为我们提供了实现远程代码执行所需的信息泄露功效。
固然,也许其他的慢速路径处置程序也能很好地事情;我们之以是选择这个路径,是由于它异常简朴,换句话说,行使它实现的exploit在差其余WebKit版本上正常事情的可能性要更大一些。
绕过防护页面机制
记着,我们可以执行的函数并没有为任何基于客栈的操作分配栈帧。例如,一个压入操作可能会在rbp-0x40处写入本机客栈,而随后的压入操作则可能在rbp-0x48处执行写入操作,以此类推,这里并没有响应的约束。以是,从理论上说,这个函数应该能够对界外客栈槽(有大的负偏移量,例如rbp-0x10000)执行写入操作。这样的话,我们就能够笼罩当前客栈下面的任何内存。
这一点在主线程的上下文中没有太大的辅助,由于主线程的客栈下面并没有举行任何映射(至少,缺乏可靠和已知的偏移量)。然而,线程的客栈是在专用虚拟内存区域中延续递增的地址上延续分配的。例如:
STACK GUARD 70000b255000-70000b256000 [ 4K ] ---/rwx stack guard for thread 1 Stack 70000b256000-70000b2d8000 [ 520K ] rw-/rwx thread 1 STACK GUARD 70000b2d8000-70000b2d9000 [ 4K ] ---/rwx stack guard for thread 2 Stack 70000b2d9000-70000b35b000 [ 520K ] rw-/rwx thread 2
假设有问题的wasm函数在线程2中执行,那么,线程1的客栈就会成为损坏的目的。现在,行使该破绽的唯一障碍就是内存的防护页……幸运的是,llint在原始优化方面尚有许多小窍门可资行使。
当压入一个常量值时,天生器现实上并没有发出指令将常量值写入客栈槽。相反,它将常数添加到一个“常数池”中,随后针对该客栈槽的任何读取操作,都将从常数池而不是客栈中获取响应的数据。任何对客栈槽的写入操作,现实上也都是对这个常数池执行写入操作。对于某些控制流来说,常量也可以被“详细化()”(显式地将常量值写入客栈),然则通已往控制流来制止这种情形也不是什么难事。
为了阐释这一点,请看下面的代码:
i32.const 1 i32.const 2 i32.const 3 i32.add
在执行上述代码的历程中,写入本机客栈的唯一值是5,即3+2的盘算效果。
这种行为将使我们能够通过压入大量无用的常量来轻松绕过防护页面。
ROP
通过笼罩受害线程客栈上的值(这是一种不太常见的浏览器破绽行使手艺),我们可以立刻获得ROP。这样做的利益是,既不需要逐步确立越来越壮大的原语,也不需要addrof或fakeobj,只需要一个过时的ropchain即可。
由于我们泄露的指针存储在内陆文件中,因此,对应的gadget将如下所示:
local.get 0 ;; JavaScriptCore dylib address i64.const i64.add ;; the addition will write the gadget to the stack
同样的,对目的客栈地址举行写入操作时,可以将local.get 1用作基址。写入常数时,可以用0举行逐位或运算来完成。
为了执行shellcode,需要让ropchain执行一些异常主要的事情。在启用SIP的情形下,只有在用mmap确立页面时指定了一个特殊的标志MAP_JIT(0x800),才允许对该页面举行rwx珍爱。由于线程客栈没有用这个标志举行映射,以是,我们无法直接为客栈上shellcode设置响应的珍爱权限并返回到这些代码所在地址。
相反,我们将使用ExecutableAllocator::allocate函数在现有的rwx JIT区域中保留一个地址,并通过memcpy将我们的shellcode复制到该地址处,然后返回到该地址处。通常情形下,第一阶段的shellcode只是一个简短的stub,用来下载一个更大的第二阶段的shellcode,例如,实现沙盒逃逸的exploit。
综上所述,若是把所有的代码片断放在一起,最终将获得如下所示的wasm函数:
;; take 2 i64 args which will become our leaks ;; note that args are referenced as locals 0 and 1 (func $foo (param i64 i64) ;; cause an out of line jump to populate leaks block i32.const 1 br_if 0 ;; filler ... end ;; subtract offset to JavaScriptCore dylib base local.get 0 i64.const i64.sub local.set 0 ;; and similarly as needed for the stack address ;; push a ton of constants to hop over the guard page i64.const 0 i64.const 0 i64.const 0 ;; and so on ... ;; prepend a "ROP sled" so we dont need to be spot-on with the stack offset local.get 0 i64.const i64.add ;; repeat to write the sled... ;; write the ropchain local.get 0 i64.const i64.add ;; and so on, calling ExecutableAllocator::allocate to reserve rwx space ;; then copy the shellcode and return to it ;; append code to overflow m_maxStackSize block block unreachable end block block unreachable end ;; and so on ... end end )
小结
在Safari 14.1.1中,整数溢露马脚已被修复,分配的CVE ID为CVE-2021-30734。该补丁行使天生用具有校验功效的算术操作来处置客栈长度的运算。
需要说明的是,该exploit的源代码仅限于教育用途,读者可以从这里下载。
在浏览器的渲染器历程中实现了随便代码执行后,典型的破绽行使链的下一步,就是实现某种形式的沙盒逃逸,为此,通常需要行使具有更高权限的历程或内核。在接下来的文章中,我们将先容若何行使内核驱动程序来实现随便的内核代码执行。
本文翻译自:https://blog.ret2.io/2021/06/02/pwn2own-2021-jsc-exploit/
网友评论
4条评论USDT钱包(www.usdt8.vip)
回复皇冠线上开户(www.huangguan.us)
回复KUALA LUMPUR: Bintai Kinden Corp Bhd is partnering Marafie Co to supply piping materials to oil and gas (O&G) related companies in Saudi Arabia.嗯,还能继续看
皇冠信用网开户(www.hg9988.vip)
回复2、山药去皮切块,放在盐水里防止变黑。芡实清洗干净浸泡一下即可。一定一定要看这个!
免费足球推荐
回复贵州俱乐部方面表示,尊重中国足协的决定,不准备进一步上诉,毕竟有些困难不是单凭俱乐部可以解决的;对于球员欠薪,他们将会和有关部门一起协调妥善解决。gogogo,评论走起