首页
社区
课程
招聘
[原创]强网杯2023Final-D8利用分析——从越界读到任意代码执行(CVE-2023-4427)
发表于: 2024-3-6 12:46 12599

[原创]强网杯2023Final-D8利用分析——从越界读到任意代码执行(CVE-2023-4427)

2024-3-6 12:46
12599

# 前言 Introduction


在2024年1月举行的强网杯决赛中遇到了以 `CVE-2023-4427` 为题所出的一道题目,该漏洞相关信息均以披露,在比赛期间我也完成了本地的利用测试,但可惜的是,我的利用只在我本地的设备上成功运行,尽管在官方提供的虚拟机下也能成功利用,但却没能完成展示机的利用,尽管目前笔者并不清楚其原因,但斗胆在此讨论该漏洞的相关利用思路,如果有师傅解决了该问题,还请务必与我联系,感激不尽:(附一张本地虚拟机成功的图片)




> 这是一台全新的、刚刚通过 ovf 导入的虚拟机,它在我本地成功了,但奇怪的是,它在其他队友们的设备,以及展示机上都未能成功,目前笔者暂时不清楚确切的原因。

> 如果您了解相关信息,欢迎联系:`tokameine@gmail.com`,感激不尽!


> 顺便,久违的撰写了 V8 相关的内容,姑且将其作为[Chaos-me-JavaScript-V8](https://github.com/ErodedElk/Chaos-me-JavaScript-V8)的第八章吧QWQ

# 调试环境搭建 Environment


在题目的目录下可以找到相关的版本:


```shell

V8 version 12.2.149

d8> 

```


得到版本以后,按照标准流程构建即可,详细内容请参考 [Chapter1-环境配置](https://github.com/ErodedElk/Chaos-me-JavaScript-V8/blob/master/Chapter1-%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE.md),本文仅做简述。

## 配置depot_tools


```sh

cd ~

git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git

export PATH=~/depot_tools:$PATH

```


## 获取源代码


```sh

mkdir v8

cd v8

fetch v8

cd v8

git checkout 12.2.149

gclient sync -D

```


## 安装依赖


```sh

./build/install-build-deps.sh

```


## 补丁


```sh

git apply diff.patch

```


这里把补丁一起贴一下:


```diff

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;

```

## 编译


```sh

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

```

## 注意事项


这里简单解释一下为什么笔者另外编译了一个调试版本的二进制文件。


在我们调试 d8 的时候往往都需要使用到官方提供的 gdb 插件,这个插件能够帮助我们解析某个地址处的对象的所有相关成员,由于笔者一般记不清各种对象的成员偏移,所以往往会使用这种方法来查找对象的各个成员的偏移地址。而该插件的原理是直接调用了调试版二进制文件下的一个函数,该函数在 `is_debug=true` 的时候才会被编译出来,因此特地编译一个调试版用来对照,方便我们在调试 Release 版的时候清楚我们需要哪些数据。


# 漏洞成因分析


## 前置知识 Prerequisite Knowledge


> 其实各大分析文章都讲的差不多了,但这里为了内容完整,笔者还是再次引述或依托一部分自己的理解造一次轮子吧。


### In-object property 对象内属性


在构造对象时,一个对象的属性会被储存在对象本身的 `property` 中,如图:




我们通过如下代码来验证:


```js

obj={};

obj.a=1;

obj.b=2;

obj.c=3;

obj.d=4;

obj.e=5;

%DebugPrint(obj);

%SystemBreak()

```


得到对应对象:


```sh

DebugPrint: 0x1a05001c9475: [JS_OBJECT_TYPE]

 - map: 0x1a05000d9d21 <Map[28](HOLEY_ELEMENTS)> [FastProperties]

 - prototype: 0x1a05000c4be9 <Object map = 0x1a05000c4225>

 - elements: 0x1a05000006cd <FixedArray[0]> [HOLEY_ELEMENTS]

 - properties: 0x1a05000006cd <FixedArray[0]>

 - All own properties (excluding elements): {

    0x1a0500002a21: [String] in ReadOnlySpace: #a: 1 (const data field 0), location: in-object

    0x1a0500002a31: [String] in ReadOnlySpace: #b: 2 (const data field 1), location: in-object

    0x1a0500002a41: [String] in ReadOnlySpace: #c: 3 (const data field 2), location: in-object

    0x1a0500002a51: [String] in ReadOnlySpace: #d: 4 (const data field 3), location: in-object

 }

```


```sh

pwndbg> x/16wx 0x1a05001c9475-1

0x1a05001c9474: 0x000d9d21 0x000006cd 0x000006cd 0x00000002

0x1a05001c9484: 0x00000004 0x00000006 0x00000008 0x0000062d

```


可以发现,各属性对应的值均被储存在对象的特定偏移处。


不过请注意,如果属性的数量超过了 4 个,多出来的部分并不会再储存在内存连续的位置,而是为 `properties` 成员创建额外的数组对象,将多余的部分储存在该对象中,例如:


```js

obj={};

obj.a=1;

obj.b=2;

obj.c=3;

obj.d=4;

obj.e=4;

%DebugPrint(obj);

%SystemBreak()

```


```sh

DebugPrint: 0x937001c947d: [JS_OBJECT_TYPE]

 - map: 0x0937000d9d51 <Map[28](HOLEY_ELEMENTS)> [FastProperties]

 - prototype: 0x0937000c4be9 <Object map = 0x937000c4225>

 - elements: 0x0937000006cd <FixedArray[0]> [HOLEY_ELEMENTS]

 - properties: 0x0937001c959d <PropertyArray[3]>

 - All own properties (excluding elements): {

    0x93700002a21: [String] in ReadOnlySpace: #a: 1 (const data field 0), location: in-object

    0x93700002a31: [String] in ReadOnlySpace: #b: 2 (const data field 1), location: in-object

    0x93700002a41: [String] in ReadOnlySpace: #c: 3 (const data field 2), location: in-object

    0x93700002a51: [String] in ReadOnlySpace: #d: 4 (const data field 3), location: in-object

    0x93700002a61: [String] in ReadOnlySpace: #e: 4 (const data field 4), location: properties[0]

 }

```


```sh

pwndbg> job 0x0937001c959d

warning: Could not find DWO CU obj/v8_base_without_compiler/objects-printer.dwo(0x3c4788f05463b6bd) referenced by CU at offset 0x15e0 [in module /home/tokameine/Desktop/qwfin/debug-qwb/libv8.so]

0x937001c959d: [PropertyArray]

 - map: 0x093700000941 <Map(PROPERTY_ARRAY_TYPE)>

 - length: 3

 - hash: 0

           0: 4

         1-2: 0x093700000061 <undefined>

```


可以注意到,数量少于等于 4 时,`properties` 对应的类型是 `FixedArray`,而超过之后则变成`PropertyArray` ,并储存多余的部分。



### DescriptorArray 描述符数组


对于对象的每个属性,都需要有一个地方用于存储该属性相关的信息,该对象即为 `DescriptorArray` ,他位于对象的 `map` 成员下的 `instance descriptors` :


```sh

DebugPrint: 0x937001c947d: [JS_OBJECT_TYPE]

 - map: 0x0937000d9d51 <Map[28](HOLEY_ELEMENTS)> [FastProperties]

 - prototype: 0x0937000c4be9 <Object map = 0x937000c4225>

 - elements: 0x0937000006cd <FixedArray[0]> [HOLEY_ELEMENTS]

 - properties: 0x0937001c959d <PropertyArray[3]>

 - All own properties (excluding elements): {

    0x93700002a21: [String] in ReadOnlySpace: #a: 1 (const data field 0), location: in-object

    0x93700002a31: [String] in ReadOnlySpace: #b: 2 (const data field 1), location: in-object

    0x93700002a41: [String] in ReadOnlySpace: #c: 3 (const data field 2), location: in-object

    0x93700002a51: [String] in ReadOnlySpace: #d: 4 (const data field 3), location: in-object

    0x93700002a61: [String] in ReadOnlySpace: #e: 4 (const data field 4), location: properties[0]

 }

0x937000d9d51: [Map] in OldSpace

 - map: 0x0937000c3d01 <MetaMap (0x0937000c3d51 <NativeContext[285]>)>

 - type: JS_OBJECT_TYPE

 - instance size: 28

 - inobject properties: 4

 - unused property fields: 2

 - elements kind: HOLEY_ELEMENTS

 - enum length: invalid

 - stable_map

 - back pointer: 0x0937000d9d29 <Map[28](HOLEY_ELEMENTS)>

 - prototype_validity cell: 0x0937000d9cd1 <Cell value= 0>

 - instance descriptors (own) #5: 0x0937001c9551 <DescriptorArray[5]>

 - prototype: 0x0937000c4be9 <Object map = 0x937000c4225>

 - constructor: 0x0937000c472d <JSFunction Object (sfi = 0x937003367e5)>

 - dependent code: 0x0937000006dd <Other heap object (WEAK_ARRAY_LIST_TYPE)>

 - construction counter: 0


pwndbg> job 0x0937001c9551

0x937001c9551: [DescriptorArray]

 - map: 0x09370000062d <Map(DESCRIPTOR_ARRAY_TYPE)>

 - enum_cache: empty

 - nof slack descriptors: 0

 - nof descriptors: 5

 - raw gc state: mc epoch 0, marked 0, delta 0

  [0]: 0x93700002a21: [String] in ReadOnlySpace: #a (const data field 0:s, p: 4, attrs: [WEC]) @ Any

  [1]: 0x93700002a31: [String] in ReadOnlySpace: #b (const data field 1:s, p: 3, attrs: [WEC]) @ Any

  [2]: 0x93700002a41: [String] in ReadOnlySpace: #c (const data field 2:s, p: 2, attrs: [WEC]) @ Any

  [3]: 0x93700002a51: [String] in ReadOnlySpace: #d (const data field 3:s, p: 1, attrs: [WEC]) @ Any

  [4]: 0x93700002a61: [String] in ReadOnlySpace: #e (const data field 4:s, p: 0, attrs: [WEC]) @ Any

```


当有不同的对象据由相似的属性时,他们会共用这些描述符,例如:


```js

obj1={};

obj1.a=1;

obj1.b=2;

obj2={};

obj2.a=1;

obj2.b=2;

obj2.c=3;

%DebugPrint(obj1);

%DebugPrint(obj2);

```


```sh

DebugPrint: 0xe1e001c94a5: [JS_OBJECT_TYPE]

 - map: 0x0e1e000d9ce5 <Map[28](HOLEY_ELEMENTS)> [FastProperties]

 - prototype: 0x0e1e000c4be9 <Object map = 0xe1e000c4225>

 - elements: 0x0e1e000006cd <FixedArray[0]> [HOLEY_ELEMENTS]

 - properties: 0x0e1e000006cd <FixedArray[0]>

 - All own properties (excluding elements): {

    0xe1e00002a21: [String] in ReadOnlySpace: #a: 1 (const data field 0), location: in-object

    0xe1e00002a31: [String] in ReadOnlySpace: #b: 2 (const data field 1), location: in-object


DebugPrint: 0xe1e001c9505: [JS_OBJECT_TYPE]

 - map: 0x0e1e000d9d21 <Map[28](HOLEY_ELEMENTS)> [FastProperties]

 - prototype: 0x0e1e000c4be9 <Object map = 0xe1e000c4225>

 - elements: 0x0e1e000006cd <FixedArray[0]> [HOLEY_ELEMENTS]

 - properties: 0x0e1e000006cd <FixedArray[0]>

 - All own properties (excluding elements): {

    0xe1e00002a21: [String] in ReadOnlySpace: #a: 1 (const data field 0), location: in-object

    0xe1e00002a31: [String] in ReadOnlySpace: #b: 2 (const data field 1), location: in-object

    0xe1e00002a41: [String] in ReadOnlySpace: #c: 3 (const data field 2), location: in-object

```


可以看到,`obj1` 和 `obj2` 的 `a` 和 `b` 属性共享了同一个描述符。


而描述符的布局如下:


```c

// A DescriptorArray is a custom array that holds instance descriptors.

// It has the following layout:

//   Header:

//     [16:0  bits]: number_of_all_descriptors (including slack)

//     [32:16 bits]: number_of_descriptors

//     [48:32 bits]: raw_number_of_marked_descriptors (used by GC)

//     [64:48 bits]: alignment filler

//     [kEnumCacheOffset]: enum cache

//   Elements:

//     [kHeaderSize + 0]: first key (and internalized String)

//     [kHeaderSize + 1]: first descriptor details (see PropertyDetails)

//     [kHeaderSize + 2]: first value for constants / Smi(1) when not used

//   Slack:

//     [kHeaderSize + number of descriptors * 3]: start of slack

// The "value" fields store either values or field types. A field type is either

// FieldType::None(), FieldType::Any() or a weak reference to a Map. All other

// references are strong.

```


> 我们暂且还不太关心具体的结构。

### enum cache 枚举缓存


通过上文方式声明的属性称之为 `fast property`,这里笔者就以 `快速属性` 代称。在查找快速属性时,如果对象没有被更改,那么就会为其生成枚举缓存,例如通过 `Object.getOwnPropertyNames` 或者 `for-in` 语法。



这里的缓存只记录了相关的 `key` 和索引,不记录具体的值。 


```js

obj={};

obj.a=1;

for (let key in obj) { } 

%DebugPrint(obj);

%SystemBreak();

```


```sh

DebugPrint: 0xf61001c9475: [JS_OBJECT_TYPE]

 - map: 0x0f61000d9c85 <Map[28](HOLEY_ELEMENTS)> [FastProperties]

 - prototype: 0x0f61000c4be9 <Object map = 0xf61000c4225>

 - elements: 0x0f61000006cd <FixedArray[0]> [HOLEY_ELEMENTS]

 - properties: 0x0f61000006cd <FixedArray[0]>

 - All own properties (excluding elements): {

    0xf6100002a21: [String] in ReadOnlySpace: #a: 1 (const data field 0), location: in-object

 }

0xf61000d9c85: [Map] in OldSpace

 - map: 0x0f61000c3d01 <MetaMap (0x0f61000c3d51 <NativeContext[285]>)>

 - type: JS_OBJECT_TYPE

 - instance size: 28

 - inobject properties: 4

 - unused property fields: 3

 - elements kind: HOLEY_ELEMENTS

 - enum length: 1

 - stable_map

 - back pointer: 0x0f61000c4a1d <Map[28](HOLEY_ELEMENTS)>

 - prototype_validity cell: 0x0f61000d9ccd <Cell value= 0>

 - instance descriptors (own) #1: 0x0f61001c9491 <DescriptorArray[1]>

 - prototype: 0x0f61000c4be9 <Object map = 0xf61000c4225>

 - constructor: 0x0f61000c472d <JSFunction Object (sfi = 0xf61003367e5)>

 - dependent code: 0x0f61000006dd <Other heap object (WEAK_ARRAY_LIST_TYPE)>

 - construction counter: 0

```


查一下相关对象:


```sh

pwndbg> job 0x0f61001c9491

warning: Could not find DWO CU obj/v8_base_without_compiler/objects-printer.dwo(0x3c4788f05463b6bd) referenced by CU at offset 0x15e0 [in module /home/tokameine/Desktop/qwfin/debug-qwb/libv8.so]

0xf61001c9491: [DescriptorArray]

 - map: 0x0f610000062d <Map(DESCRIPTOR_ARRAY_TYPE)>

 - enum_cache: 1

   - keys: 0x0f61000d9cd5 <FixedArray[1]>

   - indices: 0x0f61000d9ce1 <FixedArray[1]>

 - nof slack descriptors: 0

 - nof descriptors: 1

 - raw gc state: mc epoch 0, marked 0, delta 0

  [0]: 0xf6100002a21: [String] in ReadOnlySpace: #a (const data field 0:s, p: 0, attrs: [WEC]) @ Any

pwndbg> job 0x0f61000d9ce1

0xf61000d9ce1: [FixedArray] in OldSpace

 - map: 0x0f6100000565 <Map(FIXED_ARRAY_TYPE)>

 - length: 1

           0: 0

pwndbg> job 0x0f61000d9cd5

0xf61000d9cd5: [FixedArray] in OldSpace

 - map: 0x0f6100000565 <Map(FIXED_ARRAY_TYPE)>

 - length: 1

           0: 0x0f6100002a21 <String[1]: #a>

```


在对代码完成 `ReduceJSLoadPropertyWithEnumeratedKey` 优化以后,将会使用该缓存来访问相应属性。对应 SON 中的表现为,将 `JSLoadProperty` 节点优化为 `LoadFieldByIndex`。


相关代码:


```cpp

// js-native-context-specialization.cc


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);

  JSForInNextNode name(NodeProperties::GetValueInput(node, 1));

  Node* effect = NodeProperties::GetEffectInput(node);


 ...

}

```


```cpp

// keys.cc


MaybeHandle<FixedArray> FastKeyAccumulator::GetKeys(

    GetKeysConversion keys_conversion) {

  // TODO(v8:9401): We should extend the fast path of KeyAccumulator::GetKeys to

  // also use fast path even when filter = SKIP_SYMBOLS. We used to pass wrong

  // filter to use fast path in cases where we tried to verify all properties

  // are enumerable. However these checks weren't correct and passing the wrong

  // filter led to wrong behaviour.

  printf("[+] FastKeyAccumulator::GetKeys\n");

  if (filter_ == ENUMERABLE_STRINGS) {

    Handle<FixedArray> keys;

    if (GetKeysFast(keys_conversion).ToHandle(&keys)) {  // 반복문일경우 빠른 읽기로 key를 가져옵니다.

      return keys;

    }

    if (isolate_->has_pending_exception()) return MaybeHandle<FixedArray>();

  }


  if (try_prototype_info_cache_) {

    return GetKeysWithPrototypeInfoCache(keys_conversion);

  }

  return GetKeysSlow(keys_conversion);

}



MaybeHandle<FixedArray> FastKeyAccumulator::GetKeysFast(

    GetKeysConversion keys_conversion) {

  printf("[+] FastKeyAccumulator::GetKeysFast\n");

  bool own_only = has_empty_prototype_ || mode_ == KeyCollectionMode::kOwnOnly;

  Map map = receiver_->map();

  if (!own_only || map.IsCustomElementsReceiverMap()) {

    return MaybeHandle<FixedArray>();

  }


  // From this point on we are certain to only collect own keys.

  DCHECK(receiver_->IsJSObject());

  Handle<JSObject> object = Handle<JSObject>::cast(receiver_);


  // Do not try to use the enum-cache for dict-mode objects.

  if (map.is_dictionary_map()) {

    return GetOwnKeysWithElements<false>(isolate_, object, keys_conversion,

                                         skip_indices_);

  }

  int enum_length = receiver_->map().EnumLength();

  if (enum_length == kInvalidEnumCacheSentinel) {

    Handle<FixedArray> keys;

    // Try initializing the enum cache and return own properties.

    if (GetOwnKeysWithUninitializedEnumLength().ToHandle(&keys)) { // enum cache가 생성되지 않으면 enum length는 0 

      if (v8_flags.trace_for_in_enumerate) {

        PrintF("| strings=%d symbols=0 elements=0 || prototypes>=1 ||\n",

               keys->length());

      }

      is_receiver_simple_enum_ =

          object->map().EnumLength() != kInvalidEnumCacheSentinel;

      return keys;

    }

  }

  // The properties-only case failed because there were probably elements on the

  // receiver.

  return GetOwnKeysWithElements<true>(isolate_, object, keys_conversion,

                                      skip_indices_);

}


MaybeHandle<FixedArray>

FastKeyAccumulator::GetOwnKeysWithUninitializedEnumLength() {

  printf("[+] FastKeyAccumulator::GetOwnKeysWithUninitializedEnumLength\n");

  Handle<JSObject> object = Handle<JSObject>::cast(receiver_);

  // Uninitialized enum length

  Map map = object->map();

  if (object->elements() != ReadOnlyRoots(isolate_).empty_fixed_array() &&

      object->elements() !=

          ReadOnlyRoots(isolate_).empty_slow_element_dictionary()) {

    // Assume that there are elements.

    return MaybeHandle<FixedArray>();

  }

  int number_of_own_descriptors = map.NumberOfOwnDescriptors();

  if (number_of_own_descriptors == 0) {

    map.SetEnumLength(0);

    return isolate_->factory()->empty_fixed_array();

  }

  // We have no elements but possibly enumerable property keys, hence we can

  // directly initialize the enum cache.

  Handle<FixedArray> keys = GetFastEnumPropertyKeys(isolate_, object); // in-object는 peroperties에 없기 때문에 가져온 array는 비어있음

  if (is_for_in_) return keys;

  // Do not leak the enum cache as it might end up as an elements backing store.

  return isolate_->factory()->CopyFixedArray(keys);

}


// 최종적으로 fastproperty에 대한 접근과 cache를 생성합니다.

// static

Handle<FixedArray> FastKeyAccumulator::InitializeFastPropertyEnumCache(

    Isolate* isolate, Handle<Map> map, int enum_length,

    AllocationType allocation) {

  printf("[+] FastKeyAccumulator::InitializeFastPropertyEnumCache\n");

  DCHECK_EQ(kInvalidEnumCacheSentinel, map->EnumLength());

  DCHECK_GT(enum_length, 0);

  DCHECK_EQ(enum_length, map->NumberOfEnumerableProperties());

  DCHECK(!map->is_dictionary_map());


  Handle<DescriptorArray> descriptors =

      Handle<DescriptorArray>(map->instance_descriptors(isolate), isolate);


  // The enum cache should have been a hit if the number of enumerable

  // properties is fewer than what's already in the cache.

  DCHECK_LT(descriptors->enum_cache().keys().length(), enum_length);

  isolate->counters()->enum_cache_misses()->Increment();


  // Create the keys array.

  int index = 0;

  bool fields_only = true;

  Handle<FixedArray> keys =

      isolate->factory()->NewFixedArray(enum_length, allocation);

  for (InternalIndex i : map->IterateOwnDescriptors()) {

    DisallowGarbageCollection no_gc;

    PropertyDetails details = descriptors->GetDetails(i);

    if (details.IsDontEnum()) continue;

    Object key = descriptors->GetKey(i);

    if (key.IsSymbol()) continue;

    keys->set(index, key);

    if (details.location() != PropertyLocation::kField) fields_only = false;

    index++;

  }

  DCHECK_EQ(index, keys->length());


  // Optionally also create the indices array.

  Handle<FixedArray> indices = isolate->factory()->empty_fixed_array();

  if (fields_only) {

    indices = isolate->factory()->NewFixedArray(enum_length, allocation);

    index = 0;

    DisallowGarbageCollection no_gc;

    Tagged<Map> raw_map = *map;

    Tagged<FixedArray> raw_indices = *indices;

    Tagged<DescriptorArray> raw_descriptors = *descriptors;

    for (InternalIndex i : raw_map->IterateOwnDescriptors()) {

      PropertyDetails details = raw_descriptors->GetDetails(i);

      if (details.IsDontEnum()) continue;

      Object key = raw_descriptors->GetKey(i);

      if (key.IsSymbol()) continue;

      DCHECK_EQ(PropertyKind::kData, details.kind());

      DCHECK_EQ(PropertyLocation::kField, details.location());

      FieldIndex field_index = FieldIndex::ForDetails(raw_map, details);

      raw_indices->set(index, Smi::FromInt(field_index.GetLoadByFieldIndex()));

      index++;

    }

    DCHECK_EQ(index, indices->length());

  }


  DescriptorArray::InitializeOrChangeEnumCache(descriptors, isolate, keys,

                                               indices, allocation); // enum cache생성

  if (map->OnlyHasSimpleProperties()) map->SetEnumLength(enum_length);

  return keys;

}

```


```cpp

// objects.cc


void DescriptorArray::InitializeOrChangeEnumCache(

    Handle<DescriptorArray> descriptors, Isolate* isolate,

    Handle<FixedArray> keys, Handle<FixedArray> indices,

    AllocationType allocation_if_initialize) {

  printf("[+] DescriptorArray::InitializeOrChangeEnumCache\n");

  EnumCache enum_cache = descriptors->enum_cache();

  if (enum_cache == ReadOnlyRoots(isolate).empty_enum_cache()) {

    enum_cache = *isolate->factory()->NewEnumCache(keys, indices,

                                                   allocation_if_initialize);

    descriptors->set_enum_cache(enum_cache);

  } else {

    enum_cache.set_keys(*keys);

    enum_cache.set_indices(*indices);

  }

  enum_cache.Print();

}

```

## 漏洞成因


修复该漏洞的补丁内容如下:


```diff

diff --git a/src/objects/map-updater.cc b/src/objects/map-updater.cc

index 8b2e7f3..568df12 100644

--- a/src/objects/map-updater.cc

+++ b/src/objects/map-updater.cc

@@ -12,6 +12,7 @@

 #include "src/handles/handles.h"

 #include "src/heap/parked-scope-inl.h"

 #include "src/objects/field-type.h"

+#include "src/objects/keys.h"

 #include "src/objects/objects-inl.h"

 #include "src/objects/objects.h"

 #include "src/objects/property-details.h"

@@ -1037,6 +1038,13 @@

   // 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;

```


根据注释,补丁的大致用意是:如果某个对象需要更新描述符,那么如果这个被更新的描述符已经构建了枚举缓存,那么就要为新的描述符也重新构建枚举缓存。


反过来说,这意味着对于补丁以前的解释器来说,在它对某个对象更新其描述符时,不会为新描述符重新生成枚举缓存,也就是说,**新的描述符会使用旧的枚举缓存**。


其中的一个补丁:


```diff

diff --git a/src/objects/map-updater.cc b/src/objects/map-updater.cc

index 7a864d9..9c20491 100644

--- a/src/objects/map-updater.cc

+++ b/src/objects/map-updater.cc

@@ -1040,7 +1040,8 @@

   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) {

+  if (old_descriptors_->enum_cache()->keys()->length() > 0 &&

+      new_map->NumberOfEnumerableProperties() > 0) {

     FastKeyAccumulator::InitializeFastPropertyEnumCache(

         isolate_, new_map, new_map->NumberOfEnumerableProperties());

   }

```


```sh

[runtime] Don't try to create empty enum cache.


When copying maps and the new map has no enumerable properties we

should not try to initialize an enum cache.


This happens if the deprecation is due to making the only property in

a map non enumerable.


Bug: chromium:1472317, chromium:1470668

Change-Id: I7a6af63e50dc30592e2caacce0caccfb31f534cf

Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/4775581

Reviewed-by: Tobias Tebbi <tebbi@chromium.org>

Commit-Queue: Olivier Flückiger <olivf@chromium.org>

Cr-Commit-Position: refs/heads/main@{#89534}

```


大致意思就是说,如果新对象没有快速属性,不要为它创建空的枚举缓存。


不妨用一个简单的例子来看看具体的现象是什么:


```js

const object1 = {};

object1.a = 1;

const object2 = {};

object2.a = 2;

object2.b = 3;

const object3 = {};

object3.a = 4;

object3.b = 5;

object3.c = 6;


for (let key in object2) { }


%DebugPrint(object2);

%DebugPrint(object3);

%SystemBreak();

object3.c = 1.1;

% DebugPrint(object2);

%SystemBreak();

```


首先创建了三个对象,其中 `object2` 和 `object3` 能够共享描述符:


```sh

DebugPrint: 0x123a001c959d: [JS_OBJECT_TYPE]

 - map: 0x123a000d9d01 <Map[28](HOLEY_ELEMENTS)> [FastProperties]

 - prototype: 0x123a000c4be9 <Object map = 0x123a000c4225>

 - elements: 0x123a000006cd <FixedArray[0]> [HOLEY_ELEMENTS]

 - properties: 0x123a000006cd <FixedArray[0]>

 - All own properties (excluding elements): {

    0x123a00002a21: [String] in ReadOnlySpace: #a: 2 (const data field 0), location: in-object

    0x123a00002a31: [String] in ReadOnlySpace: #b: 3 (const data field 1), location: in-object

 }

0x123a000d9d01: [Map] in OldSpace

 - map: 0x123a000c3d01 <MetaMap (0x123a000c3d51 <NativeContext[285]>)>

 - type: JS_OBJECT_TYPE

 - instance size: 28

 - inobject properties: 4

 - unused property fields: 2

 - elements kind: HOLEY_ELEMENTS

 - enum length: 2

 - back pointer: 0x123a000d9cb1 <Map[28](HOLEY_ELEMENTS)>

 - prototype_validity cell: 0x123a000d9cf9 <Cell value= 0>

 - instance descriptors #2: 0x123a001c95fd <DescriptorArray[3]>

 - transitions #1: 0x123a000d9d29 <Map[28](HOLEY_ELEMENTS)>

     0x123a00002a41: [String] in ReadOnlySpace: #c: (transition to (const data field, attrs: [WEC]) @ Any) -> 0x123a000d9d29 <Map[28](HOLEY_ELEMENTS)>

 - prototype: 0x123a000c4be9 <Object map = 0x123a000c4225>

 - constructor: 0x123a000c472d <JSFunction Object (sfi = 0x123a003367e5)>

 - dependent code: 0x123a000006dd <Other heap object (WEAK_ARRAY_LIST_TYPE)>

 - construction counter: 0


DebugPrint: 0x123a001c95e1: [JS_OBJECT_TYPE]

 - map: 0x123a000d9d29 <Map[28](HOLEY_ELEMENTS)> [FastProperties]

 - prototype: 0x123a000c4be9 <Object map = 0x123a000c4225>

 - elements: 0x123a000006cd <FixedArray[0]> [HOLEY_ELEMENTS]

 - properties: 0x123a000006cd <FixedArray[0]>

 - All own properties (excluding elements): {

    0x123a00002a21: [String] in ReadOnlySpace: #a: 4 (const data field 0), location: in-object

    0x123a00002a31: [String] in ReadOnlySpace: #b: 5 (const data field 1), location: in-object

    0x123a00002a41: [String] in ReadOnlySpace: #c: 6 (const data field 2), location: in-object

 }

0x123a000d9d29: [Map] in OldSpace

 - map: 0x123a000c3d01 <MetaMap (0x123a000c3d51 <NativeContext[285]>)>

 - type: JS_OBJECT_TYPE

 - instance size: 28

 - inobject properties: 4

 - unused property fields: 1

 - elements kind: HOLEY_ELEMENTS

 - enum length: invalid

 - stable_map

 - back pointer: 0x123a000d9d01 <Map[28](HOLEY_ELEMENTS)>

 - prototype_validity cell: 0x123a000d9cf9 <Cell value= 0>

 - instance descriptors (own) #3: 0x123a001c95fd <DescriptorArray[3]>

 - prototype: 0x123a000c4be9 <Object map = 0x123a000c4225>

 - constructor: 0x123a000c472d <JSFunction Object (sfi = 0x123a003367e5)>

 - dependent code: 0x123a000006dd <Other heap object (WEAK_ARRAY_LIST_TYPE)>

 - construction counter: 0


```


```sh

pwndbg> job 0x123a001c95fd

warning: Could not find DWO CU obj/v8_base_without_compiler/objects-printer.dwo(0x3c4788f05463b6bd) referenced by CU at offset 0x15e0 [in module /home/tokameine/Desktop/qwfin/debug-qwb/libv8.so]

0x123a001c95fd: [DescriptorArray]

 - map: 0x123a0000062d <Map(DESCRIPTOR_ARRAY_TYPE)>

 - enum_cache: 2

   - keys: 0x123a000d9d51 <FixedArray[2]>

   - indices: 0x123a000d9d61 <FixedArray[2]>

 - nof slack descriptors: 0

 - nof descriptors: 3

 - raw gc state: mc epoch 0, marked 0, delta 0

  [0]: 0x123a00002a21: [String] in ReadOnlySpace: #a (const data field 0:s, p: 2, attrs: [WEC]) @ Any

  [1]: 0x123a00002a31: [String] in ReadOnlySpace: #b (const data field 1:s, p: 1, attrs: [WEC]) @ Any

  [2]: 0x123a00002a41: [String] in ReadOnlySpace: #c (const data field 2:s, p: 0, attrs: [WEC]) @ Any

```


可以看到,两个对象共享了 `instance descriptors` ,因此会据有相同的描述符。


而当对 `object3` 的属性做额外的赋值时,将会改变这一现状,因为类型的变化,因此需要重新更新这两个描述符:


```sh

DebugPrint: 0x123a001c959d: [JS_OBJECT_TYPE]

 - map: 0x123a000d9d01 <Map[28](HOLEY_ELEMENTS)> [FastProperties]

 - prototype: 0x123a000c4be9 <Object map = 0x123a000c4225>

 - elements: 0x123a000006cd <FixedArray[0]> [HOLEY_ELEMENTS]

 - properties: 0x123a000006cd <FixedArray[0]>

 - All own properties (excluding elements): {

    0x123a00002a21: [String] in ReadOnlySpace: #a: 2 (const data field 0), location: in-object

    0x123a00002a31: [String] in ReadOnlySpace: #b: 3 (const data field 1), location: in-object

 }

0x123a000d9d01: [Map] in OldSpace

 - map: 0x123a000c3d01 <MetaMap (0x123a000c3d51 <NativeContext[285]>)>

 - type: JS_OBJECT_TYPE

 - instance size: 28

 - inobject properties: 4

 - unused property fields: 2

 - elements kind: HOLEY_ELEMENTS

 - enum length: invalid

 - back pointer: 0x123a000d9cb1 <Map[28](HOLEY_ELEMENTS)>


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 4
支持
分享
最新回复 (8)
雪    币: 8642
活跃值: (6743)
能力值: ( LV12,RANK:449 )
在线值:
发帖
回帖
粉丝
2
嘶,发帖的时候没注意到不是markdown编辑,但是发完再想修改就没办法用markdown了吗?长文实在没办法手动改......
2024-3-6 12:48
0
雪    币: 3380
活跃值: (30966)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
感谢分享
2024-3-7 09:38
1
雪    币: 50161
活跃值: (20550)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
4
Tokameine 嘶,发帖的时候没注意到不是markdown编辑,但是发完再想修改就没办法用markdown了吗?长文实在没办法手动改......
只能重发了,发完了就不能切换了
2024-3-28 11:58
0
雪    币: 6008
活跃值: (2690)
能力值: ( LV9,RANK:250 )
在线值:
发帖
回帖
粉丝
5
师傅,我写了个比较稳定的利用,就是利用通用堆喷大对象,这样伪造 map 时就不需要 map 的地址,这样就避免了修改代码,map 地址偏移改变的情况。并且其实不需要实现任意偏移读,调试可以发现,其 offset 为一个 map(这里我有点忘了)的地址偏移,其值在我的环境上是 0x6a4,使用我们只需要在后面堆喷就行了,这样就有很大概率可以命中,这样也可以防止对象偏移改变的情况。所以最后成功的关键就在于堆喷是否成功了,其机会还是蛮大的,我写在CSDN上了https://blog.csdn.net/qq_61670993/article/details/137133853(我当时也参考了你的文章....,感恩)
2024-4-13 00:05
0
雪    币: 6008
活跃值: (2690)
能力值: ( LV9,RANK:250 )
在线值:
发帖
回帖
粉丝
6
当然,仅仅是个人感觉更加稳定一些,具体问题可以探讨
2024-4-13 00:07
0
雪    币: 8642
活跃值: (6743)
能力值: ( LV12,RANK:449 )
在线值:
发帖
回帖
粉丝
7
XiaozaYa 师傅,我写了个比较稳定的利用,就是利用通用堆喷大对象,这样伪造 map 时就不需要 map 的地址,这样就避免了修改代码,map 地址偏移改变的情况。并且其实不需要实现任意偏移读,调试可以发现,其 o ...
感谢分享,当时做题时确实没想到堆喷这件事,之后有时间或许会再复现一次,若有问题希望还能再和师傅交流QWQ
2024-4-13 01:44
0
雪    币: 264
活跃值: (684)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
8
Tokameine 嘶,发帖的时候没注意到不是markdown编辑,但是发完再想修改就没办法用markdown了吗?长文实在没办法手动改......
论坛日记-发帖指南
https://bbs.kanxue.com/thread-283114.htm
2024-8-27 13:49
0
雪    币: 8642
活跃值: (6743)
能力值: ( LV12,RANK:449 )
在线值:
发帖
回帖
粉丝
9
zZhouQing 论坛日记-发帖指南 https://bbs.kanxue.com/thread-283114.htm
挺好的,只可惜我已经懒得弄了......
2024-8-27 23:02
1
游客
登录 | 注册 方可回帖
返回
//