Browser Exploitation Primer

An Introduction

By M411K

What You Will Learn

This blog aims to:

  • Give you a landscape view of the browser exploitation realm.
  • Give you some pointers on how to get started in browser exploitation.

Prerequisites

Before reading this, it’s gonna be good to:

  • Have some low level knowledge (e.g previously coded in C/C++).
  • Know how to code in JavaScript is also a plus.

Why Would I Learn This?

Incentives motivate work

So why would you consider browser hacking, why would you even bother to learn how that black box works?

Let me give you a couple of reasons:

  • It puts bread on the table, i.e you can get up to $250,000 bounties while doing this kind of stuffπŸ’°.
  • It can even help you while doing you’re day to day programming β€” yes while you’re writing the next JS framework! β€”, knowing how things work at a bare metal level, can really you even with the most abstract languages, case in point, is there a difference between these two loops performance wise?
for (let i = 0; i < n; ++i) {
  for (let j = 0; j < n; ++j) {
    for (let k = 0; k < n; ++k) {
      C[i][j] += A[i][k] * B[k][j];
    }
  }
}
for (let k = 0; k < n; ++k) {
  for (let j = 0; j < n; ++j) {
    for (let i = 0; i < n; ++i) {
      C[i][j] += A[i][k] * B[k][j];
    }
  }
}
  • Teaches you how to handle complexity, embrace abstraction, and cope with a high probability of failure.
  • β€œAesthetically pleasing” - Alisa Esage, popping a shell is always fun to see, but there is something about popping a shell by just entering a website in the address bar.
πŸ“Œ I’m a newbie myself, so take the information below with a grain of salt

How Browsers Work?

Browsers can be thought of as almost independent machines, i.e. they run programs (aka websites), render UIs, handle different users, etc. This might even have been the motivation behind projects like Chromium OS and Firefox OS (now discontinued), though they didn’t succeedβ€”after all, you can’t just overtake well-established, operating systems overnight anyways πŸ€·β€β™‚οΈ.

Nonetheless, browsers are incredibly complex and beautifully engineered pieces of software, and their architecture is worth exploring.

We’ll be focusing on the Chromium browser (since I’ve had some experience with its low-level implementation, particularly its JavaScript engine-but let’s not get ahead of ourselves).

A picture diagram is worth a hundred words, so here’s a diagram outlining the major components of Chromium’s architecture, I used the three dots to abstract away the scary IPC (Inter-process communication woo woo πŸ‘»), which we won’t be discussing for now:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                        Chromium Browser                      β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚  β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚  β”‚   Renderer  β”‚  β”‚    GPU Process        β”‚ β”‚
β”‚  β”‚ |   ...   | β”‚  β”‚  (Blink +   β”‚  β”‚   (Graphics           β”‚ β”‚
β”‚  β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚  β”‚       V8)   β”‚  β”‚     Acceleration)     β”‚ β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚             β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”              β”‚
β”‚             β”‚    Network   β”‚    β”‚  Storage    β”‚              β”‚
β”‚             β”‚  (Net Stack) β”‚    β”‚ (Cache,     β”‚              β”‚
β”‚             β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚  Cookies)   β”‚              β”‚
β”‚                                 β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

We will be discussing the renderer process, in particular, the V8 component.

Now How Does V8 Work?

Why are we looking at the V8 component specifically you ask? because it’s the most interesting one, from an attacker perspective, it’s where a whole language (Javascript) gets evaluated, and JIT-ed, and the thing about JIT compilation is that you can’t avoid memory corruption by just employing a memory safe language (e.g Rust), it’s code generation logic that might lead to such vulnerabilities, not the code itself (at least not as frequently cmiiw), that’s why the V8 team, keeps using C++ as of today.

Here is a diagram illustrating a general view of the V8 pipeline workflow:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                       V8 JavaScript Engine                  β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚  β”‚     Parser            β”‚ ─► β”‚     Abstract Syntax Tree  β”‚ β”‚
β”‚  β”‚ (Converts JS to AST)  β”‚    β”‚         (AST)             β”‚ β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚                                   β–Ό                         β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚  β”‚     Ignition          β”‚ ◄─ β”‚     Bytecode Generator    β”‚ β”‚
β”‚  β”‚  (Interpreter)        β”‚    β”‚ (Converts AST to Bytecode)β”‚ β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”          β–Ό                         β”‚
β”‚  β”Œβ”€β”€β”˜ Maglev/Sparkplug └─┐    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚  β”‚  Turbo(Fan/Shaft)/... β”‚ ◄─ β”‚    Profiler (Hot Code)    β”‚ β”‚
β”‚  β”‚ (Optimizing Compiler) β”‚    β”‚   (Monitors Execution)    β”‚ β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚                                                             β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚  β”‚     Orinoco           β”‚ ◄─ β”‚     Garbage Collector     β”‚ β”‚
β”‚  β”‚ (Memory Management)   β”‚    β”‚ (Heap Cleanup)            β”‚ β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Okay, I guess, but as we know it’s the case with Javascript, β€œEverything is an Object”—at least mostly, so how are objects are stored in the memory?

Well, as the saying goes β€œThings are known by their opposites”, so let’s see how is a low level language: C, differ from an abstract language: Javascript:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
|                       | C (Low-Level)                | JavaScript (Abstract)        |
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€+──────────────────────────────+───────────────────────────────
| Memory Management     | Manual (malloc/free)         | Automatic (Garbage Collected)|
|                       | Uses glibc heap              | Uses V8 heap                 |
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€+──────────────────────────────+───────────────────────────────
| Object Representation | Structs saved as-is in       | Objects are complex          |
|                       | contiguous memory            | spec-defined[^1] structures  |
|                       | (e.g., `struct {int x;}`)    | (e.g., hidden classes, maps) |
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€+──────────────────────────────+───────────────────────────────
| Execution Model       | Compiled to native machine   | [JIT-compiled] (e.g., V8     |
|                       | code (no runtime)            | Ignition/TurboFan)           |
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€+──────────────────────────────+───────────────────────────────
| Type System           | Static (fixed at compile     | Dynamic (types inferred      |
|                       | time)                        | at runtime)                  |
└───────────────────────+──────────────────────────────+β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
[^1]: https://262.ecma-international.org/

Let’s look at an example of an array of floats:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  JS Code          β”‚       β”‚            V8 Heap                      β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€       β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ const arr =       β”‚       |  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚
β”‚   [1.1, 1.2, 1.3];│────────▢ β”‚         JSArray (arr)           β”‚    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜       β”‚  β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€    β”‚
                            β”‚  β”‚ - HiddenClass (Map)             β”‚    β”‚
                            β”‚  β”‚ - ElementsKind: DOUBLE_ELEMENTS β”‚    β”‚
                            β”‚  β”‚ - Length: 3                     β”‚    β”‚
                            β”‚  β”‚ - Elements   ───────────────────┼──┐ |
                            β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚ β”‚
                            β”‚                                       β”‚ β”‚
                            β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚ β”‚
                            β”‚  β”‚       FixedDoubleArray          β”‚β—€β”€β”˜ β”‚
                            β”‚  β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€    β”‚
                            β”‚  β”‚ - [0]: 1.1                      β”‚    β”‚
                            β”‚  β”‚ - [1]: 1.2                      β”‚    β”‚
                            β”‚  β”‚ - [2]: 1.3                      β”‚    β”‚
                            β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚
                            β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Pointer Compression

Let’s talk about pointer compression, because that’s what enabled (or at least in my understanding helped) with pointer sandboxing that we will be discussing later.

Well you might be used to 64 bits addresses, but in V8 heap, they announced (around 2018 IIRC) pointer compression, which is basically just saving the lower 32-bits of each pointer (offset), while the higher 4-bytes are saved to a register (r14 on x86), for fast access, so the pseudo-code for β€œuncompressing” a β€œsandbox” pointer can be stated as this: full_pointer = base_address | (compressed_pointer & 0xFFFFFFFF).

Pointer Tagging

There is also the concept of pointer tagging, to distinguish between Smi (immediate small integer) and a HeapObject (anything allocated in the V8 heap inherits from this), this is very simple, the LSB bit can be either 0 (Smi) or 1 (HeapObject).

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ 32-bit Compressed Pointer  β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ [31........1][0]           β”‚
β”‚   Offset     Tag           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Careful reader will recognize that this poses that V8 heap will be limited to 4GB, which is true, but fortunately V8 has already a 2GB/4GB limit even before this mechanism has been deployed.

The Optimization Pipeline

Overview

Before learning how V8 bugs gets introduced, having an idea about it’s optimization pipeline.

Let’s look at an example at how a javascript function gets optimized down to machine code:

 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
 β”‚    JavaScript Source      β”‚
 β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
 β”‚  function calc(a, b) {    β”‚
 β”‚    const sum = a + b;     β”‚
 β”‚    const prod = a * b;    β”‚
 β”‚    return sum;            β”‚
 β”‚  }                        β”‚
 β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
              β–Ό
 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
 β”‚      Bytecode (Ignition)     β”‚    β”‚     Initial IR[^1] (TurboFan) β”‚
 β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€    β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
 β”‚  Ldar a1                     β”‚    β”‚  t1 = Load a                  β”‚
 β”‚  Add a0, [0]                 β”‚    β”‚  t2 = Load b                  β”‚
 β”‚  Star0                       β”‚    β”‚  t3 = Add t1, t2              β”‚
 β”‚  Ldar a1                     β”‚    β”‚  Store sum = t3               β”‚
 β”‚  Mul a0, [1]                 β”‚ ─► β”‚  t4 = Load a                  β”‚
 β”‚  Star1                       β”‚    β”‚  t5 = Load b                  β”‚
 β”‚  Ldar r0                     β”‚    β”‚  t6 = Mul t4, t5              β”‚
 β”‚  Return                      β”‚    β”‚  Store prod = t6              β”‚
 β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚  t7 = Load sum                β”‚
                                     β”‚  Return t7                    β”‚
                                     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                              β–Ό
                          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                          β”‚      Optimized IR (After DCE[^2])    β”‚
                          β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
                          β”‚  t1 = Load a                         β”‚
                          β”‚  t2 = Load b                         β”‚
                          β”‚  t3 = Add t1, t2                     β”‚
                          β”‚  Return t3                           β”‚
                          β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                              β–Ό
                          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                          β”‚        Final Machine Code            β”‚
                          β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
                          β”‚  mov rax, [a]                        β”‚
                          β”‚  add rax, [b]                        β”‚
                          β”‚  ret                                 β”‚
                          β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
[^1]: Intermediate representation
[^2]: Dead-code elimination

Ignition’s bytecode isn’t currently in our interest, but it’s quite self explanatory once you know that ignition’s machine is register-based, with an accumulator register, and that Lda/Sta Loads/Stores to that accumulator, ai registers holds the arguments passed to the function.

Other forms of optimizations can also happen, let’s recall a couple of those:

Common subexpression elimination

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Original IR          β”‚    β”‚  Optimized IR         β”‚
β”‚                       β”‚    β”‚                       β”‚
β”‚  t1 = Load a          β”‚    β”‚  t1 = Load a          β”‚
β”‚  t2 = Load b          │───►│  t2 = Load b          β”‚
β”‚  t3 = Add t1, t2      β”‚    β”‚  t3 = Add t1, t2      β”‚
β”‚  t4 = Load a          β”‚    β”‚  t4 = t1              β”‚  // Reuse t1
β”‚  t5 = Load b          β”‚    β”‚  t5 = t2              β”‚  // Reuse t2
β”‚  t6 = Add t4, t5      β”‚    β”‚  t6 = t3              β”‚  // Reuse t3
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Inline expansion

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Caller Function      β”‚    β”‚  Inlined IR           β”‚
β”‚                       β”‚    β”‚                       β”‚
β”‚  t1 = Call foo(a, b)  │───►│  t1 = Add a, b        β”‚  // foo() inlined
β”‚  ...                  β”‚    β”‚  ...                  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Loop-invariant code motion

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Original Loop        β”‚    β”‚  Optimized Loop       β”‚
β”‚                       β”‚    β”‚                       β”‚
β”‚  while (i < n) {      β”‚    β”‚  t1 = Load x          β”‚  // Hoisted
β”‚    t1 = Load x        │───►│  while (i < n) {      β”‚
β”‚    t2 = Add t1, i     β”‚    β”‚    t2 = Add t1, i     β”‚
β”‚  }                    β”‚    β”‚  }                    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

When It Breaks

πŸ’‘ Performance is the enemy of security

JIT Typer Bugs

Typer bugs are a class of vulnerabilities that arise from incorrect type or range analysis during JIT optimization. V8 predicts variable types and value ranges to enable optimizations. If it makes wrong assumptions, it can lead to eliminated security checks.

Let’s see an example of these typer bugs.

function f(x) {
  var arr = [1.1, 1.2];
  var y = 0; // y0
  if (x == "foo") y = 1; // y1
  // y2 = phi(y0, y1)
  y = y + 1; // y3 = y2 + 1
  return arr[y];
}

The comments represent how the variables are gonna be assigned in the SSA (Static single-assignment form) form which is used by Turbofan, variables can be assigned only once in the said form.

The Phi function merges two or multiple possibilities, choosing one of them depending on the previous control flow.

Below is a CFG (Control-flow graph) in an SSA form.

              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
              β”‚    x  == "foo"     β”‚
              β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜
                     β”‚      β”‚
                Yes  β”‚      β”‚ No
                     β”‚      β”‚
       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜      └─────────┐
       β”‚                              β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”              β”Œβ”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  y0 = 0       β”‚              β”‚  y1 = 1        β”‚
β”‚  Range(0, 0)  β”‚              β”‚  Range(1, 1)   β”‚
β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜              β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
       β”‚                               β”‚
       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                        β–Ό
            β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
            β”‚  y2 = Phi(y0, y1)      β”‚
            β”‚  Range(0, 1)           β”‚
            β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                        β–Ό
            β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
            β”‚  y3 = SpecSafeAdd(y2, 1) β”‚
            β”‚  Range(1, 2)             β”‚
            β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

y3 type (Range(1, 2)) is perfectly correct, but let’s assume SpecSafeAdd is erroneous, i.e., it updates the Range minimum but not the maximum, below is the updated node:

                       ...
                        β–Ό
            β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
            β”‚  y3 = SpecSafeAdd(y2, 1) β”‚
            β”‚  Range(1, 1)             β”‚
            β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Now we have a problem, which is that y3.Max is assumed to be 1 , which is in the range of the arr , this means when the function is compiled, when we invoke the function with x == β€œfoo” , y will have a value of 2 and at the return, we will get arr[2] effectively having an out-of-bound read.

What about out-of-bound write?

function f(x, w) {
  var arr = [1.1, 1.2];
  var y = 0;
  if (x == "foo") y = 1;
  y = y + 1;
  arr[y] = w;
}

Yeah.

Exploitation

So we have an out-of-bound r/w, it’s eventually just a byte/few bytes access, we need more reach, and more power, to have exactly that, in browser exploitation it’s very common after you get a memory corruption vulnerability, to develop two primitives: addrof/fakeobj .

addrof

The goal of this primitive β€” practically a function β€” is that you pass it an object, and you get it’s address of that object inside the V8 heap.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚      JavaScript           β”‚         β”‚        V8 Heap           β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€         β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                           β”‚         β”‚                          β”‚
β”‚  let obj = {x: 42};  ────────0x158f...β”€β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚                           β”‚         β”‚  β”‚  JSObject (obj)   β”‚   β”‚
β”‚  let addr = addrof(obj);  β”‚         β”‚  β”‚ - map: 0x...      β”‚   β”‚
β”‚                           β”‚         β”‚  β”‚ - properties: ... β”‚   β”‚
β”‚  // addr = 0x158f00040321 |         β”‚  β”‚ - elements: ...   β”‚   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜         β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
                                      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

fakeobj

The goal of this primitive is to pass it an address, that you already have setup-ed a valid object that array and it will return to that object in the javascript world, and you’ll ultimately have control over that object’s backing store (e.g elements address of an array), thus having control where to write/read in all of the V8 heap.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚        JavaScript               β”‚         β”‚            V8 Heap                 β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€         β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                 β”‚         β”‚                                    β”‚
β”‚  var arr = [1.1, 1.1,         ─── 0x158f...β”€β”€β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”             β”‚
β”‚             map<<4|properties,  β”‚         β”‚  β”‚  JSArray (arr)    β”‚             β”‚
β”‚             length<<4|elements] β”‚         β”‚  β”‚ - map: 0x...      β”‚             β”‚
β”‚                                 β”‚         β”‚  β”‚ - properties: ... β”‚             β”‚
β”‚  var addr = addrof(arr);        β”‚         β”‚β”Œβ”€β”€ - elements: ...   β”‚             β”‚
β”‚  // addr = 0x158f00040321       β”‚         β”‚β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜             β”‚
β”‚                                 β”‚         β”‚β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚  fake_arr = fakeobj(addr +    ─────────────┴──  Fake JSArray (arr's elements)β”‚ β”‚
β”‚                         0x...)  β”‚         β”‚  β”‚ - map: 0x...                  β”‚ β”‚
β”‚                                 β”‚         β”‚  β”‚ - properties: ...             β”‚ β”‚
β”‚                                 β”‚         β”‚β”Œβ”€β”€ - elements: ...               β”‚ β”‚
β”‚                                 β”‚         β”‚β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚                                 β”‚         β”‚β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚                                 β”‚         β”‚β”‚   β”‚  WasmTrustedInstanceData  β”‚   β”‚
β”‚  fake_arr[0] ──────────────────────────────┴──── - jump_table_start: 0x... β”‚   β”‚
β”‚                                 β”‚         β”‚    β”‚           (wasm rwx page) β”‚   β”‚
β”‚                                 β”‚         β”‚    β”‚                           β”‚   β”‚
β”‚                                 β”‚         β”‚    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

With these two primitives in you’re tool belts, it’s just a matter of writing you’re shell-code, to say, a WASM rwx page, et voila!

You’re Starter Pack

When doing V8 research, debugging is a must, this is my goto docker image:

FROM ubuntu:22.04 as build

RUN apt-get update && apt-get -y upgrade
RUN apt-get install -yq --no-install-recommends build-essential git ca-certificates python3-pkgconfig curl python3

RUN git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git /opt/depot_tools
ENV PATH="/opt/depot_tools:${PATH}"

RUN mkdir /build

RUN cd /build && fetch v8 && cd v8 && git checkout 247d42b64689fe03fea5949373b3f0d0daa81375 && gclient sync

RUN cd /build/v8 && gn gen out/release --args='is_debug=false target_cpu="x64" v8_symbol_level=2 v8_enable_backtrace=true v8_enable_dissembler=true v8_enable_object_print=true v8_enable_verify_heap=true v8_enable_sandbox=true' && \
    autoninja -C out/release d8

The V8 Cage

V8 Team POV

It turns out that making things more complicated can also be a way of solving problemsβ€”and that’s exactly the case with this new security feature introduced by the V8 team. The core idea is to β€˜remove any unboxed pointers in the V8 heap’ and instead rely on indices into lookup tables that contain the actual unsandboxed pointers.

Let’s see how is that relevant to the most two important components that are traditionally used in the exploitation chain.

Functionβ€”Wise

Previously, JSFunction objects contained relative pointers to CodeDataContainer objects, which in turn held raw pointers to JIT-compiled code - enabling JIT spraying attacks. The sandbox now replaces raw pointers with indices into a trusted code pointer table (outside the V8 heap), effectively eliminating1 these kinds of attacks.

Before

 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
 β”‚       V8 Sandbox        β”‚
 β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
 β”‚β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”β”‚      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
 β”‚β”‚      JSFunction       β”‚β”‚      β”‚         Code          β”‚
 β”‚β”‚    (V8 Heap rw-)      │──────►│  (JIT-compiled r-x)   β”‚
 β”‚β”‚                       β”‚β”‚      β”‚                       β”‚
 β”‚β”‚  - [[Code]]:          β”‚β”‚      β”‚  push    rbp          β”‚
 β”‚β”‚    *Direct pointer*   β”‚β”‚      β”‚  mov     rbp, rsp     β”‚
 β”‚β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜β”‚      β”‚  sub     rsp, 0xc0    β”‚
 β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

After

 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
 β”‚       V8 Sandbox        β”‚
 β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
 β”‚β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”β”‚      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
 β”‚β”‚      JSFunction       β”‚β”‚      β”‚  Code Pointer Table   β”‚       β”‚         Code          β”‚
 β”‚β”‚    (V8 Heap rw-)      β”‚β”‚      β”‚  (in trusted space)   │──────►│  (JIT-compiled r-x)   β”‚
 β”‚β”‚                       β”‚β”‚      β”‚                       β”‚       β”‚                       β”‚
 β”‚β”‚  - [[Code]]:          β”‚β”‚      β”‚  Entry 0: 0xABC123    β”‚       β”‚  push    rbp          β”‚
 β”‚β”‚    *Index into table* │──────►│  Entry 1: 0xDEF456    β”‚       β”‚  mov     rbp, rsp     β”‚
 β”‚β”‚    (e.g., "Entry 1")  β”‚β”‚      β”‚  Entry 2: 0xGHI789    β”‚       β”‚  sub     rsp, 0xc0    β”‚
 β”‚β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜β”‚      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
 β”‚                         β”‚
 β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

WASMβ€”Wise

WASM instances also used to hold relative pointers to WasmTrustedInstanceData, which in turn hold an unsandboxed pointer to the WASM rwx page, but now it hold external table indices.

Before

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                          V8 Sandbox                         β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚   WASM Module         β”‚       β”‚   WASM Instance       β”‚  β”‚
β”‚  β”‚                       │──────►│                       β”‚  β”‚
β”‚  β”‚  - Exported Functions β”‚       β”‚  - CodeTable:         β”‚  β”‚
β”‚  β”‚   (Sandboxed Pointer) β”‚       β”‚    *Raw pointers*     β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                                             |               β”‚
β”‚                                             |               β”‚
└─────────────────────────────────────────────|β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                              β–Ό
   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
   β”‚              RWX Memory (Outside Sandbox)             β”‚
   β”‚           - JIT-compiled WASM instructions            β”‚
   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

After

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                          V8 Sandbox                         β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚   WASM Module         β”‚       β”‚   WASM Instance       β”‚  β”‚
β”‚  β”‚                       │──────►│                       β”‚  β”‚
β”‚  β”‚  - Exported Functions β”‚       β”‚  - CodeTable:         β”‚  β”‚
β”‚  β”‚   (Sandboxed Pointer) β”‚       β”‚    *Indices*          β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                                             |               β”‚
β”‚                                             |               β”‚
└─────────────────────────────────────────────|β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                              β–Ό
   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
   β”‚               Trusted Pointer Table (External)        β”‚
   β”‚  - Entry 0: 0x1B15ABC123 (Tag + Address)              β”‚
   β”‚  - Entry 1: 0x1B15DEF456                              β”‚
   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                   |
                   β–Ό
   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
   β”‚                     RWX Memory                        β”‚
   β”‚           - JIT-compiled WASM instructions            β”‚
   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

With these mitigations in place, exploitation is yet another step away.

DYIs

Hacking TVs?

Most modern TVs come with a browser. For example, the TV in our room has an ancient one β€” yes, 2016 is ancient in browser terms. So we can practice our newly learned skills on products like these.

πŸ’‘ Basically, you can practice on anything with a browserβ€”a Nintendo, TV, phone, you name it.

Doing Some 🎩

Of course, I’m joking, but surprisingly, it’s not very common for many headless browsers out there to get updated. Case in point: here’s the User-Agent of a third-party service (we found it being used by one of the multimillion-user platforms out there) endpoint bot:

image

Going Further

We’ve just scratched the surface of the iceberg, if you want to go further into this area of hacking, try doing this pwn.college dojo (that’s how I started), and if you’re looking for some resources, I’ve been collecting some V8 resources, you can check it out, other than that, until the next time!

  1. β€œeliminating” is a stretch.Β 

Share: X (Twitter) LinkedIn VK