0% found this document useful (0 votes)
136 views

Offensivecon 22 Attacking Javascript Engines

The document discusses the architecture of JavaScript engines, including how they use an interpreter to compile code into bytecode, a just-in-time (JIT) compiler to optimize bytecode into machine code at runtime, and a garbage collector to manage memory. It also examines how JavaScript engines represent objects and properties internally using concepts like hidden classes and property arrays to optimize property access. The document suggests this internal representation of objects and properties in JavaScript engines can be leveraged for attacks.

Uploaded by

Ankur Agarwal
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
136 views

Offensivecon 22 Attacking Javascript Engines

The document discusses the architecture of JavaScript engines, including how they use an interpreter to compile code into bytecode, a just-in-time (JIT) compiler to optimize bytecode into machine code at runtime, and a garbage collector to manage memory. It also examines how JavaScript engines represent objects and properties internally using concepts like hidden classes and property arrays to optimize property access. The document suggests this internal representation of objects and properties in JavaScript engines can be leveraged for attacks.

Uploaded by

Ankur Agarwal
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 75

V8 / Chromium / Chrome

Attacking JavaScript
Engines in 2022
Samuel Groß (@5aelo), Amy Burnett (@itszn13)

JavaScriptCore / WebKit / Safari

Spidermonkey / Gecko / Firefox


Basic JavaScript

let v = 0x1337;
// typeof(v) == “number”
v: 0x1337
Basic JavaScript

“foobar”

let v = 0x1337;
// typeof(v) == “number”
v: <pointer>
v = "foobar";
// typeof(v) == “string”
Basic JavaScript

let v = 0x1337;
// typeof(v) == “number”
v: <pointer>
v = "foobar";
// typeof(v) == “string”
v = {a: 42, b: 43};
// typeof(v) == “object” JSObject
???
Basic JavaScript JSObject 1
----------------------- HiddenClass 1
Type: <pointer> -----------------------
let o1 = {a: 42, b: 43}; Extra: null Type: PlainObject
Slot 0: 42 .a: inline slot 0
console.log(o1.a); Slot 1: 43 .b: inline slot 1
Basic JavaScript JSObject 1
----------------------- HiddenClass 1
Type: <pointer> -----------------------
let o1 = {a: 42, b: 43}; Extra: null Type: PlainObject
Slot 0: 42 .a: inline slot 0
console.log(o1.a); Slot 1: 43 .b: inline slot 1

let o2 = {a: 13, b: 37};

JSObject 2
-----------------------
Type: <pointer>
Extra: <pointer>
Slot 0: 13
Slot 1: 37
Basic JavaScript JSObject 1
----------------------- HiddenClass 1
Type: <pointer> -----------------------
let o1 = {a: 42, b: 43}; Extra: null Type: PlainObject
Slot 0: 42 .a: inline slot 0
console.log(o1.a); Slot 1: 43 .b: inline slot 1

let o2 = {a: 13, b: 37};

o2.c = o1;

JSObject 2
-----------------------
Type: <???>
Extra: <???>
Slot 0: 13
Slot 1: 37
Basic JavaScript JSObject 1
----------------------- HiddenClass 1
Type: <pointer> -----------------------
let o1 = {a: 42, b: 43}; Extra: null Type: PlainObject
Slot 0: 42 .a: inline slot 0
console.log(o1.a); Slot 1: 43 .b: inline slot 1

let o2 = {a: 13, b: 37}; HiddenClass 2


-----------------------
o2.c = o1; Type: PlainObject
.a: inline slot 0
.b: inline slot 1
JSObject 2 .c: out-of-line slot 0
-----------------------
Type: <pointer>
Extra: <???>
Slot 0: 13
Slot 1: 37
Basic JavaScript JSObject 1
----------------------- HiddenClass 1
Type: <pointer> -----------------------
let o1 = {a: 42, b: 43}; Extra: null Type: PlainObject
Slot 0: 42 .a: inline slot 0
console.log(o1.a); Slot 1: 43 .b: inline slot 1

let o2 = {a: 13, b: 37}; HiddenClass 2


-----------------------
o2.c = o1; Type: PlainObject
.a: inline slot 0
.b: inline slot 1
JSObject 2 .c: out-of-line slot 0
-----------------------
Type: <pointer> PropertyArray 1
Extra: <pointer> -----------------------
Slot 0: 13 Slot 0: <pointer>
Slot 1: 37 Slot 1: null
Slot 2: null
Basic JavaScript JSObject 1
----------------------- HiddenClass 1
Type: <pointer> -----------------------
let o1 = {a: 42, b: 43}; Extra: null Type: PlainObject
Slot 0: 42 .a: inline slot 0
console.log(o1.a); Slot 1: 43 .b: inline slot 1

let o2 = {a: 13, b: 37};


HiddenClass 3
o2.c = o1; -----------------------
Type: PlainObject
.b: inline slot 1
delete o2.a; .c: out-of-line slot 0
JSObject 2
-----------------------
Type: <pointer> PropertyArray 1
Extra: <pointer> -----------------------
Slot 0: <deleted> Slot 0: <pointer>
Slot 1: 37 Slot 1: null
Slot 2: null
Interpreter

Bytecode
Compiler

Runtime
(objects, globals, constructors,
functions, methods, …)

JIT
Compiler(s)

Garbage
Wasm Collector
Compiler(s) (GC)
function main() {
console.log(“Hello World!”);
}
main();

Interpreter

Bytecode
Compiler

Runtime
(objects, globals, constructors,
functions, methods, …)

JIT
Compiler(s)

Garbage
Wasm Collector
Compiler(s) (GC)
function main() {
console.log(“Hello World!”);
}
main();

Interpreter

Bytecode
Compiler

Runtime
(objects, globals, constructors,
functions, methods, …)

Bytecode
LdaGlobal
Star1
JIT LdaNamedProperty
Star0
Compiler(s)

Garbage
Wasm Collector
Compiler(s) (GC)
function main() {
console.log(“Hello World!”);
}
main();

Interpreter

Bytecode
Compiler

Runtime
(objects, globals, constructors,
functions, methods, …)

Bytecode
LdaGlobal
Star1
JIT LdaNamedProperty
Star0
Compiler(s)

Garbage
Wasm Collector
Compiler(s) (GC)
function main() {
console.log(“Hello World!”);
}
main();

Interpreter

Bytecode
Compiler

Runtime
(objects, globals, constructors,
functions, methods, …)

Bytecode Runtime State


LdaGlobal
Star1
JIT LdaNamedProperty
Star0
Compiler(s)

Garbage
Wasm Collector
Compiler(s) (GC)
Interpreter

Bytecode
Compiler

Runtime
(objects, globals, constructors,
functions, methods, …)

Bytecode Runtime State


LdaGlobal
Star1
JIT LdaNamedProperty
Star0
Compiler(s)

Garbage
Wasm Collector
Compiler(s) (GC)
Interpreter

Bytecode
Compiler

Runtime
(objects, globals, constructors,
functions, methods, …)

Bytecode Runtime State


Machine Code
LdaGlobal
Star1
JIT LdaNamedProperty
add x3, x28, x3
Star0
ldr x4, [x26, #376]
Compiler(s) cmp w3, w4
b.eq #+0x23c
ldur w5, [x3, #-1]

Garbage
Wasm Collector
Compiler(s) (GC)
Interpreter

Bytecode
Compiler

Runtime
(objects, globals, constructors,
functions, methods, …)

Bytecode Runtime State


Machine Code
LdaGlobal
Star1
JIT LdaNamedProperty
add x3, x28, x3
Star0
ldr x4, [x26, #376]
Compiler(s) cmp w3, w4
b.eq #+0x23c
ldur w5, [x3, #-1]

Garbage
Wasm Collector
Compiler(s) (GC)
Interpreter

Bytecode
Compiler

Runtime
(objects, globals, constructors,
functions, methods, …)

Bytecode
LdaGlobal
Star1
LdaNamedProperty Machine Code
Star0
JIT
add x3, x28, x3
Compiler(s) ldr x4, [x26, #376]
cmp w3, w4
b.eq #+0x23c
ldur w5, [x3, #-1]

(func
(param $lhs i32)
(param $rhs i32)
(result i32)
Garbage
local.get $lhs
local.get $rhs Wasm Collector
i32.add))
Compiler(s) (GC)
JIT Compilation
A (Hypothetical) JIT Optimization Example
const W = 64, H = 64;
const bmp = new Uint8Array(W * H);
function set(p, v) {
if (p.x < 0 || p.x >= W ||
p.y < 0 || p.y >= H) {
throw "invalid point";
}
bmp[p.x * W + p.y] = v;
}
Example: “Training” the JIT
const W = 64, H = 64;
const bmp = new Uint8Array(W * H);
function set(p, v) {
if (p.x < 0 || p.x >= W || // "Train" the JIT
for (let i = 0; i < 10000; i++) {
p.y < 0 || p.y >= H) { set({x: 1, y: 2}, 3);
throw "invalid point"; }

}
bmp[p.x * W + p.y] = v;
}
Example: Bytecode Parsing
const W = 64, H = 64; x1 = LoadProperty p, ‘x’
const bmp = new Uint8Array(W * H); GotoIf .throwException, x1 < 0
function set(p, v) { x2 = LoadProperty p, ‘x’
if (p.x < 0 || p.x >= W ||
GotoIf .throwException, x2 >= 64
p.y < 0 || p.y >= H) {
throw "invalid point";
}
bmp[p.x * W + p.y] = v;
}
Example: Speculation + Lowering
const W = 64, H = 64; CheckType p, ObjType1
const bmp = new Uint8Array(W * H); x1 = LoadField p, +8
function set(p, v) { GotoIf .throwException, x1 < 0
if (p.x < 0 || p.x >= W ||
CheckType p, ObjType1
p.y < 0 || p.y >= H) {
x2 = LoadField p, +8
throw "invalid point";
GotoIf .throwException, x2 >= 64
}
bmp[p.x * W + p.y] = v;
}
Example: Speculation + Lowering
const W = 64, H = 64; CheckType p, ObjType1
const bmp = new Uint8Array(W * H); x1 = LoadField p, +8
function set(p, v) { GotoIf .throwException, x1 < 0
if (p.x < 0 || p.x >= W ||
CheckType p, ObjType1
p.y < 0 || p.y >= H) {
x2 = LoadField p, +8
throw "invalid point";
GotoIf .throwException, x2 >= 64
}
bmp[p.x * W + p.y] = v;
}
Example: Redundancy Elimination
const W = 64, H = 64; CheckType p, ObjType1
const bmp = new Uint8Array(W * H); x1 = LoadField p, +8
function set(p, v) { GotoIf .throwException, x1 < 0
if (p.x < 0 || p.x >= W ||
CheckType p, ObjType1
p.y < 0 || p.y >= H) {
x2 = LoadField p, +8
throw "invalid point";
GotoIf .throwException, x1 >= 64
}
bmp[p.x * W + p.y] = v;
}
Example: Bytecode Parsing
const W = 64, H = 64; W = LoadGlobal ‘W’
const bmp = new Uint8Array(W * H); i1 = Mul x, W
function set(p, v) { i2 = Add i1, y
if (p.x < 0 || p.x >= W ||
bmp = LoadGlobal ‘bmp’
p.y < 0 || p.y >= H) {
StoreElememt bmp, i2, v
throw "invalid point";
}
bmp[p.x * W + p.y] = v;
}
Example: Constant Folding + Lowering
const W = 64, H = 64; i1 = IntegerMul x, 64
const bmp = new Uint8Array(W * H); i2 = IntegerAdd i1, y
function set(p, v) { CheckBounds i2, 4096
if (p.x < 0 || p.x >= W ||
CheckType v, Uint8
p.y < 0 || p.y >= H) {
StoreUint8Array bmp, i2, v
throw "invalid point";
}
bmp[p.x * W + p.y] = v;
}
Example: Range Analysis + Bounds Check Elimination
const W = 64, H = 64; // x = Range [0, 64)
const bmp = new Uint8Array(W * H); // y = Range [0, 64)
function set(p, v) { i1 = IntegerMul x, 64
if (p.x < 0 || p.x >= W ||
// i1 = Range [0, 4033)
p.y < 0 || p.y >= H) {
i2 = IntegerAdd i1, y
throw "invalid point";
// i2 = Range [0, 4096)
}
CheckBounds i2, 4096
bmp[p.x * W + p.y] = v;
...
}
Example: Final JIT IR Code
const W = 64, H = 64; CheckType p, ObjType1
const bmp = new Uint8Array(W * H); x = LoadField p, +8
function set(p, v) { y = LoadField p, +16
if (p.x < 0 || p.x >= W ||
GotoIf .throwException x < 0 || ...
p.y < 0 || p.y >= H) {
i1 = IntegerMul x, 64
throw "invalid point";
i2 = IntegerAdd i1, y
}
CheckType v, Uint8
bmp[p.x * W + p.y] = v;
StoreUint8Array bmp, i2, v
}
JIT Compilation (simplified) Static Analysis of
Input Code

Runtime State of
Feedback from Past Various Objects
Executions

Optimization
(mostly remove unnecessary stuff, but also
e.g. move things out of loops, …)

Optimized
Unoptimized Speculation
(insert type checks based Machine
Bytecode on feedback)
Code

Lowering
(convert higher-level IR to lower-level IR,
ultimately to machine code)
A (Hypothetical) JIT Bug Example
function replace(a, cond, v) {

let i = a.findIndex(cond);

a[i] = v;

let a = [0, 1, 2, 3, 4, 5];

replace(a, (e) => e == 3, 42);

// a == [0, 1, 2, 42, 4, 5];


A (Hypothetical) JIT Bug Example
function replace(a, cond, v) { CheckType a, ArrType1

let i = a.findIndex(cond); i = Call Runtime_FindIndex(a, cond)

a[i] = v; CheckBounds a, i

} StoreArray a, i, v

let a = [0, 1, 2, 3, 4, 5];

replace(a, (e) => e == 3, 42);

// a == [0, 1, 2, 42, 4, 5];


A (Hypothetical) JIT Bug Example
function replace(a, cond, v) { CheckType a, ArrType1

let i = a.findIndex(cond); i = Call Runtime_FindIndex(a, cond)

a[i] = v; // i = Range [0, a.length - 1)

} CheckBounds a, i

let a = [0, 1, 2, 3, 4, 5]; StoreArray a, i, v

replace(a, (e) => e == 3, 42);

// a == [0, 1, 2, 42, 4, 5];


A (Hypothetical) JIT Bug Example
function replace(a, cond, v) { CheckType a, ArrType1

let i = a.findIndex(cond); i = Call Runtime_FindIndex(a, cond)

a[i] = v; // i = Range [0, a.length - 1)

} CheckBounds a, i

let a = [0, 1, 2, 3, 4, 5]; StoreArray a, i, v

replace(a, (e) => false, 42);


A (Hypothetical) JIT Bug Example
function replace(a, cond, v) { CheckType a, ArrType1

let i = a.findIndex(cond); i = Call Runtime_FindIndex(a, cond)

a[i] = v; // i = Range [0, a.length - 1)

} CheckBounds a, i

let a = [0, 1, 2, 3, 4, 5]; StoreArray a, i, v

replace(a, (e) => false, 42);


A (Hypothetical) JIT Bug Example
function replace(a, cond, v) { CheckType a, ArrType1

let i = a.findIndex(cond); i = Call Runtime_FindIndex(a, cond)

a[i] = v; // i = Range [-1, a.length - 1)

} Check i >= 0

StoreArray a, i, v
A (Hypothetical) JIT Bug Example
function replace(a, cond, v) { CheckType a, ArrType1

let i = a.findIndex(cond); i = Call Runtime_FindIndex(a, cond)

a[i] = v; // i = Range [-1, a.length - 1)

} Check i >= 0

let a = [0, 1, 2, 3, 4, 5]; StoreArray a, i, v

replace(a, (e) => {

a.length = 0; return true;

}, 42);
A (Hypothetical) JIT Bug Example
function replace(a, cond, v) { CheckType a, ArrType1

let i = a.findIndex(cond); i = Call Runtime_FindIndex(a, cond)

a[i] = v; // i = Range [-1, a.length - 1)

} Check i >= 0

let a = [0, 1, 2, 3, 4, 5]; StoreArray a, i, v

replace(a, (e) => {

a.length = 0; return true;

}, 42);
Optimization Register
Analysis Allocation
CVE-2019-8623 CVE-2018-12386
Other
CVE-2019-8518
“Breaks” Type Spatial
Safety LICM Memory
(loop-invariant code
motion) Safety

GVN
Type-Check (global value numbering)
BCE
Elimination CVE-2019-9810 (bounds-check
elimination)
CVE-2019-17026 Alias CVE-2017-2547 Array Length
Analysis Computation
Type CSE
(common subexpression
Inference Runtime elimination) Range
CVE-2018-4233
CVE-2018-17463 State Analysis
CVE-2019-11707 CVE-2019-8506 crbug 762874 (2017)
CVE-2020-6418 CVE-2021-30551 crbug 880207 (2018)
CVE-2021-30561C “Pureness” CVE-2019-13764
VE-2021-30632 Analysis
CVE-2020-9802 Pattern
Matching
Temporal Write Barrier
CVE-2021-30598
CVE-2021-30599
Memory Lowering
Elision GC
Safety Modelling
CVE-2021-21220
CVE-2019-4442
CVE-2019-8622
Optimization

Analysis

Other
“Breaks” Type Spatial
Safety Memory
Safety

Temporal
Memory
Safety
Optimization

Analysis

Other
“Breaks” Type Spatial
Safety Memory
Safety

BCE
(bounds-check
elimination)
CVE-2017-2547

Range
Analysis
crbug 762874 (2017)
crbug 880207 (2018)
CVE-2019-13764

Temporal
Memory
Safety
Optimization

Analysis

Other
“Breaks” Type Spatial
Safety Memory
Safety

Type-Check BCE
Elimination (bounds-check
elimination)

Type
Inference Range
CVE-2018-4233
CVE-2018-17463 CheckType o, ObjType1 Analysis
crbug 762874 (2017)
CVE-2019-11707 ... crbug 880207 (2018)
CVE-2020-6418
CheckType o, ObjType1 CVE-2019-13764

Temporal
Memory
Safety
Optimization

Analysis

Other
“Breaks” Type Spatial
Safety Memory
Safety

GVN
Type-Check (global value numbering)
BCE
Elimination CVE-2019-9810 (bounds-check
elimination)
CVE-2019-17026 Alias
Analysis
Type
Inference Range
CVE-2018-4233
CVE-2018-17463 let tmp1 = x + y; Analysis
crbug 762874 (2017)
CVE-2019-11707 ... crbug 880207 (2018)
CVE-2020-6418
let tmp2 = x + y; tmp1; CVE-2019-13764

Temporal
Memory
Safety
Optimization

Analysis

Other
“Breaks” Type Spatial
Safety Memory
Safety

GVN
Type-Check (global value numbering)
BCE
Elimination CVE-2019-9810 (bounds-check
elimination)
CVE-2019-17026 Alias
Analysis
Type
Inference Range
CVE-2018-4233
CVE-2018-17463
Analysis
CVE-2019-11707 crbug 762874 (2017)
CVE-2020-6418 crbug 880207 (2018)
CVE-2019-13764

Temporal Write Barrier


Memory Elision GC
Safety Modelling
CVE-2019-4442
CVE-2019-8622
Optimization Register
Analysis Allocation
CVE-2019-8623 CVE-2018-12386
Other
CVE-2019-8518
“Breaks” Type Spatial
Safety LICM Memory
(loop-invariant code
motion) Safety

GVN
Type-Check (global value numbering)
BCE
Elimination CVE-2019-9810 (bounds-check
elimination)
CVE-2019-17026 Alias CVE-2017-2547
Analysis
Type
Inference Range
CVE-2018-4233 const x = 42; Analysis
CVE-2018-17463
CVE-2019-11707
for (let i = 0; i < 100; i++) { crbug 762874 (2017)
CVE-2020-6418 ...; crbug 880207 (2018)
CVE-2019-13764
a[x] = 1337;
}

Temporal Write Barrier


Memory Elision GC
Safety Modelling
CVE-2019-4442
CVE-2019-8622
Optimization Register
Analysis Allocation
CVE-2019-8623 CVE-2018-12386
Other
CVE-2019-8518
“Breaks” Type Spatial
Safety LICM Memory
(loop-invariant code
motion) Safety

GVN
Type-Check (global value numbering)
BCE
Elimination CVE-2019-9810 (bounds-check
elimination)
CVE-2019-17026 Alias CVE-2017-2547 Array Length
Analysis Computation
Type CSE
(common subexpression
Inference elimination) Range
CVE-2018-4233
CVE-2018-17463
Analysis
CVE-2019-11707 crbug 762874 (2017)
CVE-2020-6418 crbug 880207 (2018)
“Pureness” CVE-2019-13764
Analysis
CVE-2020-9802 Pattern
Matching
Temporal Write Barrier
CVE-2021-30598
CVE-2021-30599
Memory Lowering
Elision GC
Safety Modelling
CVE-2021-21220
CVE-2019-4442
CVE-2019-8622
Optimization Register
Analysis Allocation
CVE-2019-8623 CVE-2018-12386
Other
CVE-2019-8518
“Breaks” Type Spatial
Safety LICM Memory
(loop-invariant code
motion) Safety

GVN
Type-Check (global value numbering)
BCE
Elimination CVE-2019-9810 (bounds-check
elimination)
CVE-2019-17026 Alias CVE-2017-2547 Array Length
Analysis Computation
Type CSE
(common subexpression
Inference Runtime elimination) Range
CVE-2018-4233
CVE-2018-17463 State Analysis
CVE-2019-11707 CVE-2019-8506 crbug 762874 (2017)
CVE-2020-6418 CVE-2021-30551 crbug 880207 (2018)
CVE-2021-30561C “Pureness” CVE-2019-13764
VE-2021-30632 Analysis
CVE-2020-9802 Pattern
Matching
Temporal Write Barrier
CVE-2021-30598
CVE-2021-30599
Memory Lowering
Elision GC
Safety Modelling
CVE-2021-21220
CVE-2019-4442
CVE-2019-8622
Optimization Register
Analysis Allocation
CVE-2019-8623 CVE-2018-12386
Other
CVE-2019-8518
“Breaks” Type Spatial
Safety LICM Memory
(loop-invariant code
motion) Safety

GVN
Type-Check (global value numbering)
BCE
Elimination CVE-2019-9810 (bounds-check
elimination)
CVE-2019-17026 Alias CVE-2017-2547 Array Length
Analysis Computation
Type CSE
(common subexpression
Inference Runtime elimination) Range
CVE-2018-4233
CVE-2018-17463 State Analysis
CVE-2019-11707 CVE-2019-8506 crbug 762874 (2017)
CVE-2020-6418 CVE-2021-30551 crbug 880207 (2018)
CVE-2021-30561C “Pureness” CVE-2019-13764
VE-2021-30632 Analysis
CVE-2020-9802 Pattern
Matching
Temporal Write Barrier
CVE-2021-30598
CVE-2021-30599
Memory Lowering
Elision GC
Safety Modelling
CVE-2021-21220
CVE-2019-4442
CVE-2019-8622
Type Spatial
Safety Memory
Safety

Exploitation
- Choose (arbitrary) victim type Exploitation
- Choose (arbitrary) target type - Choose (arbitrary) victim array
- Choose (arbitrary) operation - Choose (arbitrary) OOB index
- Trigger bug to confuse objects - Choose read or write access
- Trigger bug to corrupt memory

Exploitation
Temporal - Choose (arbitrary) victim type
Memory - Choose (arbitrary) replacement type
Safety - Trigger bug and GC to cause UaF
JS Outside JIT
Interpreter

Bytecode CVE-2021-30517
Compiler CVE-2021-38001

CVE-2022-0102
Runtime
(objects, globals, constructors,
functions, methods, …)

CVE-2021-1789
CVE-2021-21225
● Plenty of complexity elsewhere CVE-2021-38003

JIT ● Few bug patterns, many “1-off” bugs


Compiler(s)

See prev. slides :)

Garbage
Wasm Collector
Compiler(s) (GC)
CVE-2021-37975
CVE-2021-30734
Exploitation & Mitigations
Exploit Flow Circa 2016

Create Fake
JSObject JIT
Overwrite
Arbitrary Memory
“Shellcode”
Bug Read & Write
Sandbox
Corrupt Escape
Existing
JSObject
What About Classical Mitigations?

- ASLR: Usually easy to construct a leak via type confusion or


OOB (second bug not required)
- DEP/NX: JIT provides easy ways to map shellcode
- Stack Cookies: Most JS bugs are heap based

Can OOB read and write with same bug

Victim Array | 0 | 1 | 2 | ... Other Data


"Modern" Mitigations
When most people think of modern mitigations they think of Control Flow Integrity (CFI)

Armv8.3+: Pointer Authentication (PAC) and Branch Target Identification (BTI)


Intel: Shadow Stack and Control-flow Enforcement Technology (CET)
Windows: Control Flow Guard (CFG)
Shell
Func2 Code /
ROP

Func1
Func3
"Modern" Mitigations - Not Quite There Yet
When most people think of modern mitigations they think of Control Flow Integrity (CFI)

Armv8.3+: Pointer Authentication (PAC) and Branch Target Identification (BTI)

Intel: Shadow Stack and Control-flow Enforcement Technology (CET)

Windows: Control Flow Guard (CFG)

JSC supports PAC


V8 does not yet have full support for CET, CFG, or PAC
Pointer Authentication
Newer iOS devices and M1 Macbooks benefit from Armv8's Pointer Authentication

- PAC*: signs the pointer, writes cryptographic signature to upper bits


- AUT*: verifies the pointer

Mostly used to protect code pointers, but may be used for data as well

0x00007fc75ae25b20 0xa9b6414141414141

0xa9b67fc75ae25b20 0x8000414141414141
Bypassing Pointer Authentication
PAC bypasses can be considered similar to bugs; ie patched quickly if disclosed
Example Bypass Methods
- Pointer Forgery: Writable memory which later gets signed [ref]
- Swap or use signed pointers which lack context
Writable Mem
ADRP X16, #_pow_ptr_3@PAGE Arbitrary
LDR X16, [X16,#_pow_ptr_3@PAGEOFF] 0x41414141 Write
0x41414141
PACIZA X16 Primitive
0x66c0000041414141

Additionally, V8 currently supports PAC, but not in JITed code [ref]


"Scripted" Code Execution
If you can't get arbitrary asm code, you may be able to call existing functionality

Build control flow with manipulated calls / actions made from JavaScript

Required sandbox escape functionality usually already exists!

Good Example: ObjectiveC Selector Calls [ref][ref]

Function
JS Bound Corrupted Using Data
corrupted_web_obj.do_action() API Web Api for IPC /
Object Data ObjC Call
The Rise Of Data-Only Attacks
On PAC devices and as CFI rolls out, shellcode/rop exec is becoming harder…

However, this is usually not the endgame of a JS exploit

Exploits may attempt to attack cross-process data integerty / confidentiality

- Corrupt IPC data / messages / state to exploit a sandbox bug


- Read sensitive data stored within the process itself

These attacks do not rely on code exec, only memory read and write
Exploitation Tricks: Winning Races With Linked Lists
A lot of data attacks become races: Either

- You complete the write in time


- You smash some other data and crash…

We can abuse linked list structures to stall this race [ref]

Wait until Perform


program hits write to
♾ loop target data

Repair linked list and


Corrupt link list to
allow thread to resume
form a cycle
Attacking Cross-Origin
We have control of all the data in
the compromised process

- Force the process to load


sensitive data
- Inject JavaScript into other
website -> hijack session
- Abuse persistent data features
in other websites [ref]
Mitigating Cross Origin Attacks
Chrome and Firefox have enabled
"Site Isolation"

- Iframes are in separate


processes
- Requests and access enforced
by the network IPC
“Shellcode”
Multiple Endgames Sandbox
Escape

“Scripted”
Create Fake Sandbox
JSObject Escape

Arbitrary Memory
Bug Read & Write

Corrupt Data-Only
Existing Sandbox
JSObject Escape

Renderer
Data
Exfiltration
Mitigating Arbitrary Read / Write
Arbitrary read/write is a very powerful primitive

Thus, vendors are creating mitigations to make it more difficult

Pointer Caging - Code restricts pointers to specific regions of memory


JavaScriptCore's GigaCage
GigaCage prevents pointers being used to corrupt sensitive memory

class JSArrayBufferView {
using VectorPtr = CagedPtr<Gigacage::Primitive, void, tagCagedPtr>;
VectorPtr m_vector;
}

CagedPtr forces all pointer accesses to remain in a specific "GigaCage" region


JavaScriptCore's GigaCage
Is GigaCage Effective?
Required to protect a "vulnerable" pointer:

- Explicit caged typing of the pointer


- Correct uncaging implementation when accessing (such as in the JIT)

There are a lot of objects and a lot of pointers

- Attackers just need to find single uncaged pointer they can r/w from
- This is made easier by faking object state
Is GigaCage Effective?
Current easiest method: make a fake JSArray…[ref]
JSArray

JSCell (Type Info) Properties

Array Length Non Zero


Butterfly (Data Ptr)
Elements Target Data
… For R/W

Slightly limited R/W, but allows corrupting more complex structures elsewhere
Moving Towards A Heap Sandbox
Attackers will continue to find objects with corruptible pointers

Why not constrain the entire JS Heap?

- JavaScript manages many "external pointers" to browser memory


Moving Towards A Heap Sandbox
Attackers will continue to find objects with corruptible pointers

Why not constrain the entire JS Heap?

- JavaScript manages many "external pointers" to browser memory

Solution: Hold these pointers outside the heap and reference with index #
Future V8 Heap Sandbox
All JS objects confined to sandbox memory
All other sensitive memory is outside:
- External pointers (and type) in table
- JIT compiler structures and code
- Any reference to other memory

Exploit now relies on unsound behavior of


external objects and code it has handles to
(similar to a sandbox escape…)
“Shellcode”
Exploit Flow Today Sandbox
Escape

“Scripted”
Sandbox
Fake Own Escape
JSObject
Arbitrary Memory
Read & Write
Bug
Data-Only
Corrupt
Sandbox
Existing
Escape
JSObject

Limited Memory Renderer


Read & Write Data
Exfiltration
What Have We Learned
Fewer bug classes, instead more “1-off” bugs, more complex JIT bugs

No significant changes to “early” exploitation phase (Same primitives available)

Current mitigations are not fully effective or applied evenly

Future mitigations seem more promising! (But still not bulletproof)

You might also like