前言
大家好,我是本源
由于现在不够CB,改不了名字。emmm,大家觉得我写的好的记得给个评分支持下,让我尽快凑够买改名卡的CB......
最近这段时间在小肩膀那边学习了浏览器定制的相关课程,感受颇深,老师经常和我们讲学习逆向要努力勤奋。时刻保持利他之心。切勿骄傲自满,技术是要多分享交流才能获得提升,深受启发。
特地来分享一篇关于爬虫定制的浏览器相关的文章。如有不足,请指点~
1.为什么自动化爬取数据?自动化爬取有什么困境?
💡 Tips:为什么要用自动化来爬取数据?直接走协议不香吗?
网络爬虫要获取数据,最好最快的方法,自然是使用协议来进行模拟登录,获取数据等行为。但是现如今的网页端反爬虫措施越来越完善,爬虫和反爬虫的对抗已经进入了白热化,甚至可以说一天不学习就会落后很多。
相比于协议的反爬虫力度来说,自动化的压力要小很多,自动化来爬取数据速度只是比人手动快一些,比协议要慢的多。这也说明自动化爬虫对网站的伤害比较小。如果网络爬虫的要求仅仅是完成一些重复性工作,或者说只是爬取到数据就好,爬慢一点也无妨,使用自动化确实是一个好的方案(虽然爬虫界各大佬对自动化的鄙视是表露在外的)。
Python爬虫在进行自动化的时候,最常用的是selenium,因为不少网站会检测selenium的一些特征。更进一步,如果不使用selenium,而使用其他的自动化工具,也可以检测webdriver之类的自动化特征,或者干脆就看看你的JS操作的isTrust,如果是手动出发的就是True,JS触发的就是False。
此外,还有浏览器的指纹检测,将一台机器上相对稳定不变的参数作为指纹搜集,比如canvas,webgl,webAudio,SSL/TLS指纹等等,举一个浏览器指纹检测的例子https://gongjux.com/fingerprint/。
面对自动化反爬,许多人的处理方案就是使用油猴脚本,通过Hook来对一些参数获取进行劫持替换,我们接下来采取一种更底层的方式,直接取修改chromium浏览器的C++层代码。
当编译好一个自己修改过的chromium浏览器,并随机了指纹,可以采用两种方案进行数据爬取:
-
使用playwright进行浏览器驱动和自动化。
-
使用Windows的API进行浏览器的自动化,借助Fiddler之类的工具转发数据到本地服务。
但今天讲的文章里,我们主要探究如何自己定制修改chromium浏览器。
特别鸣谢:感谢如意老师给予的指导
2.chromium浏览器的源码该如何编译?
💡 Tips:由于chromium的源码在不停地更新换代,所以如果按照网上的经验贴通常是无法编译成功的,所以最好的方式就是通过谷歌的官方文档进行编译。
官方文档地址:chromium编译官方文档 。
记住,你现在可以编译,不代表你以后也可以用这套环境编译最新的源码。别人可以编译,不代表你也能够编译。一切以官方文档为标准 。
编译chromium源码的系统要求:
-
64位的电脑,而且至少有8G的内存,16G以上更好,多多益善。
-
只编译chrome,需要至少100G的NTFS格式的磁盘存储空间,如果要整体编译,需要至少200G以上的存储空间。
-
比较新的版本的visual studio,至少vs2017以及之后的版本。
-
Windows 10或者更新的操作系统。
满足以上条件就可以愉快地开始编译了。
(1)visual studio的配置
Chromium 需要Visual Studio 2017 (>=15.7.2) 才能构建,但最好使用Visual Studio 2019 (>=16.0.0)。本次编译示范使用的是vs2019的社区版。
这里必须要勾选<span class="ne-text">使用C++的桌面开发</span>
其中的<span class="ne-text">MFC/ATL</span>
组件。
此外,需要额外安装SDK10.0.20348.0。下载链接在这里:SDK下载地址。
安装SDK的时候,Debugging Tools是必须的,保险起见,还是全部勾选吧:
(2)安装depot_tools
下载depot_tools压缩包并解压,并将其配置到环境变量的开头,由于其中会涉及到Python环境变量,所以务必将它移动到最开始,以防Python环境变量配置错误。
配置环境变量的方式:控制面板→系统和安全→系统→高级系统设置:
配置成功后,到对应的depot_tools的目录之下,使用管理员方式打开CMD,运行命令<span class="ne-text">gclient</span>
,这一步可能需要科学上网,务必保证你可以科学上网。
(3)环境变量
接下来,需要在环境变量中新增以下键值对,其中的GYP_MSVS_OVERRIDE_PATH和vs2019_install填写你自己的vs路径,GYP_MSVS_VERSION是对应的vs版本,WINDOWSSDKDIR是你的SDK安装的路径。
http_proxy和https_proxy是你的科学上网相关的代理。
复制代码 隐藏代码DEPOT_TOOLS_WIN_TOOLCHAIN=0
GYP_GENERATORS=msvs-ninja,ninja
GYP_MSVS_OVERRIDE_PATH=E:vs2019
GYP_MSVS_VERSION=2019
vs2019_install=E:vs2019
WINDOWSSDKDIR=C:Program Files (x86)Windows Kits10
http_proxy=127.0.0.1:7890
https_proxy=127.0.0.1:7890
(4)chromium源码拉取和编译
配置了这么多,终于到了源码拉取的部分,但是这也是最耗费时间的部分。
复制代码 隐藏代码mkdir chromium && cd chromium # 任意创建一个目录接收源码fetch chromium --no-history # 拉取最新源码,不带--no-history则拉取完整源码gclient sync --no-history # 因为网络缘故断开,使用这一句重新同步源码
源码拉取完毕之后,当前目录下会多出一个src目录,源码就位于其中。
复制代码 隐藏代码cd src #进入目录gn gen --ide=vs outDefault #在outDefault目录下生成工程文件autoninja -C outbuild mini_installer#编译源码,编译后生成chrome.exe和一个chrome.7z压缩文件#如果不带mini_installer将会进行整体编译,将近10w个文件,非常耗时
编译完成后,进入outDefault目录下:
到此为止,chromium最新版源码编译成功。
3.如何抹除自动化特征
💡 Tips:对于小白来说,修改chromium底层源码最简单的方式就是善用搜索。
总体来说,要定位修改的源码,一共分三步走。
-
知晓自己要修改的东西是什么。
-
定位源码中需要修改的地方。
-
修改源代码并回编译(回编译速度会很快,因为是增量的,不是重头开始编译)。
拿AiBot这个网站来说,网站地址:指纹检测网站
如果要绕过一般的自动化检测网站,最迫切的就是要抹除webdriver这一特征检测,因为检测一般都是检测 window.navigator.webdriver 属性,明白了要抹除的是webdriver,第一步就已经完成了。
不过,一定会有问在杠,比如他知道可以使用其他方法,比如selenium调用execute_cdp_cmd 的方式抹除自动化特征云云,我知道,我确实知道,我也可以把要执行的抹除特征的JS代码放在这里:
复制代码 隐藏代码/*!
* Note: Auto-generated, do not update manually.
* Generated by: https://github.com/berstend/puppeteer-extra/tree/master/packages/extract-stealth-evasions
* Generated on: Sun, 13 Feb 2022 12:56:05 GMT
* License: MIT
*/(({_utilsFns: _utilsFns, _mainFunction: _mainFunction, _args: _args}) => {
const utils = Object.fromEntries(Object.entries(_utilsFns).map((([key, value]) => [key, eval(value)])));
utils.init(), eval(_mainFunction)(utils, ..._args)
})({
_utilsFns: {
init: "() => {n utils.preloadCache()n}",
stripProxyFromErrors: "(handler = {}) => {n const newHandler = {n setPrototypeOf: function (target, proto) {n if (proto === null)n throw new TypeError('Cannot convert object to primitive value')n if (Object.getPrototypeOf(target) === Object.getPrototypeOf(proto)) {n throw new TypeError('Cyclic __proto__ value')n }n return Reflect.setPrototypeOf(target, proto)n }n }n // We wrap each trap in the handler in a try/catch and modify the error stack if they thrown const traps = Object.getOwnPropertyNames(handler)n traps.forEach(trap => {n newHandler[trap] = function () {n try {n // Forward the call to the defined proxy handlern return handler[trap].apply(this, arguments || [])n } catch (err) {n // Stack traces differ per browser, we only support chromium based ones currentlyn if (!err || !err.stack || !err.stack.includes(`at `)) {n throw errn }nn // When something throws within one of our traps the Proxy will show up in error stacksn // An earlier implementation of this code would simply strip lines with a blacklist,n // but it makes sense to be more surgical here and only remove lines related to our Proxy.n // We try to use a known "anchor" line for that and strip it with everything above it.n // If the anchor line cannot be found for some reason we fall back to our blacklist approach.nn const stripWithBlacklist = (stack, stripFirstLine = true) => {n const blacklist = [n `at Reflect.${trap} `, // e.g. Reflect.get or Reflect.applyn `at Object.${trap} `, // e.g. Object.get or Object.applyn `at Object.newHandler.<computed> [as ${trap}] ` // caused by this very wrapper :-)n ]n return (n err.stackn .split('\n')n // Always remove the first (file) line in the stack (guaranteed to be our proxy)n .filter((line, index) => !(index === 1 && stripFirstLine))n // Check if the line starts with one of our blacklisted stringsn .filter(line => !blacklist.some(bl => line.trim().startsWith(bl)))n .join('\n')n )n }nn const stripWithAnchor = (stack, anchor) => {n const stackArr = stack.split('\n')n anchor = anchor || `at Object.newHandler.<computed> [as ${trap}] ` // Known first Proxy line in chromiumn const anchorIndex = stackArr.findIndex(line =>n line.trim().startsWith(anchor)n )n if (anchorIndex === -1) {n return false // 404, anchor not foundn }n // Strip everything from the top until we reach the anchor linen // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)n stackArr.splice(1, anchorIndex)n return stackArr.join('\n')n }nn // Special cases due to our nested toString proxiesn err.stack = err.stack.replace(n 'at Object.toString (',n 'at Function.toString ('n )n if ((err.stack || '').includes('at Function.toString (')) {n err.stack = stripWithBlacklist(err.stack, false)n throw errn }nn // Try using the anchor method, fallback to blacklist if necessaryn err.stack = stripWithAnchor(err.stack) || stripWithBlacklist(err.stack)nn throw err // Re-throw our now sanitized errorn }n }n })n return newHandlern}",
stripErrorWithAnchor: "(err, anchor) => {n const stackArr = err.stack.split('\n')n const anchorIndex = stackArr.findIndex(line => line.trim().startsWith(anchor))n if (anchorIndex === -1) {n return err // 404, anchor not foundn }n // Strip everything from the top until we reach the anchor line (remove anchor line as well)n // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)n stackArr.splice(1, anchorIndex)n err.stack = stackArr.join('\n')n return errn}",
replaceProperty: "(obj, propName, descriptorOverrides = {}) => {n return Object.defineProperty(obj, propName, {n // Copy over the existing descriptors (writable, enumerable, configurable, etc)n ...(Object.getOwnPropertyDescriptor(obj, propName) || {}),n // Add our overrides (e.g. value, get())n ...descriptorOverridesn })n}",
preloadCache: "() => {n if (utils.cache) {n returnn }n utils.cache = {n // Used in our proxiesn Reflect: {n get: Reflect.get.bind(Reflect),n apply: Reflect.apply.bind(Reflect)n },n // Used in `makeNativeString`n nativeToStringStr: Function.toString + '' // => `function toString() { [native code] }`n }n}",
makeNativeString: "(name = '') => {n return utils.cache.nativeToStringStr.replace('toString', name || '')n}",
patchToString: "(obj, str = '') => {n const handler = {n apply: function (target, ctx) {n // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + ""`n if (ctx === Function.prototype.toString) {n return utils.makeNativeString('toString')n }n // `toString` targeted at our proxied Object detectedn if (ctx === obj) {n // We either return the optional string verbatim or derive the most desired result automaticallyn return str || utils.makeNativeString(obj.name)n }n // Check if the toString protype of the context is the same as the global prototype,n // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test casen const hasSameProto = Object.getPrototypeOf(n Function.prototype.toStringn ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtinsn if (!hasSameProto) {n // Pass the call on to the local Function.prototype.toString insteadn return ctx.toString()n }n return target.call(ctx)n }n }nn const toStringProxy = new Proxy(n Function.prototype.toString,n utils.stripProxyFromErrors(handler)n )n utils.replaceProperty(Function.prototype, 'toString', {n value: toStringProxyn })n}",
patchToStringNested: "(obj = {}) => {n return utils.execRecursively(obj, ['function'], utils.patchToString)n}",
redirectToString: "(proxyObj, originalObj) => {n const handler = {n apply: function (target, ctx) {n // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + ""`n if (ctx === Function.prototype.toString) {n return utils.makeNativeString('toString')n }nn // `toString` targeted at our proxied Object detectedn if (ctx === proxyObj) {n const fallback = () =>n originalObj && originalObj.namen ? utils.makeNativeString(originalObj.name)n : utils.makeNativeString(proxyObj.name)nn // Return the toString representation of our original object if possiblen return originalObj + '' || fallback()n }nn if (typeof ctx === 'undefined' || ctx === null) {n return target.call(ctx)n }nn // Check if the toString protype of the context is the same as the global prototype,n // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test casen const hasSameProto = Object.getPrototypeOf(n Function.prototype.toStringn ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtinsn if (!hasSameProto) {n // Pass the call on to the local Function.prototype.toString insteadn return ctx.toString()n }nn return target.call(ctx)n }n }nn const toStringProxy = new Proxy(n Function.prototype.toString,n utils.stripProxyFromErrors(handler)n )n utils.replaceProperty(Function.prototype, 'toString', {n value: toStringProxyn })n}",
replaceWithProxy: "(obj, propName, handler) => {n const originalObj = obj[propName]n const proxyObj = new Proxy(obj[propName], utils.stripProxyFromErrors(handler))nn utils.replaceProperty(obj, propName, { value: proxyObj })n utils.redirectToString(proxyObj, originalObj)nn return truen}",
replaceGetterWithProxy: "(obj, propName, handler) => {n const fn = Object.getOwnPropertyDescriptor(obj, propName).getn const fnStr = fn.toString() // special getter function stringn const proxyObj = new Proxy(fn, utils.stripProxyFromErrors(handler))nn utils.replaceProperty(obj, propName, { get: proxyObj })n utils.patchToString(proxyObj, fnStr)nn return truen}",
mockWithProxy: "(obj, propName, pseudoTarget, handler) => {n const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))nn utils.replaceProperty(obj, propName, { value: proxyObj })n utils.patchToString(proxyObj)nn return truen}",
createProxy: "(pseudoTarget, handler) => {n const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))n utils.patchToString(proxyObj)nn return proxyObjn}",
splitObjPath: "objPath => ({n // Remove last dot entry (property) ==> `HTMLMediaElement.prototype`n objName: objPath.split('.').slice(0, -1).join('.'),n // Extract last dot entry ==> `canPlayType`n propName: objPath.split('.').slice(-1)[0]n})",
replaceObjPathWithProxy: "(objPath, handler) => {n const { objName, propName } = utils.splitObjPath(objPath)n const obj = eval(objName) // eslint-disable-line no-evaln return utils.replaceWithProxy(obj, propName, handler)n}",
execRecursively: "(obj = {}, typeFilter = [], fn) => {n function recurse(obj) {n for (const key in obj) {n if (obj[key] === undefined) {n continuen }n if (obj[key] && typeof obj[key] === 'object') {n recurse(obj[key])n } else {n if (obj[key] && typeFilter.includes(typeof obj[key])) {n fn.call(this, obj[key])n }n }n }n }n recurse(obj)n return objn}",
stringifyFns: "(fnObj = { hello: () => 'world' }) => {n // Object.fromEntries() ponyfill (in 6 lines) - supported only in Node v12+, modern browsers are finen // https://github.com/feross/fromentriesn function fromEntries(iterable) {n return [...iterable].reduce((obj, [key, val]) => {n obj[key] = valn return objn }, {})n }n return (Object.fromEntries || fromEntries)(n Object.entries(fnObj)n .filter(([key, value]) => typeof value === 'function')n .map(([key, value]) => [key, value.toString()]) // eslint-disable-line no-evaln )n}",
materializeFns: "(fnStrObj = { hello: "() => 'world'" }) => {n return Object.fromEntries(n Object.entries(fnStrObj).map(([key, value]) => {n if (value.startsWith('function')) {n // some trickery is needed to make oldschool functions work :-)n return [key, eval(`() => ${value}`)()] // eslint-disable-line no-evaln } else {n // arrow functions just workn return [key, eval(value)] // eslint-disable-line no-evaln }n })n )n}",
makeHandler: "() => ({n // Used by simple `navigator` getter evasionsn getterValue: value => ({n apply(target, ctx, args) {n // Let's fetch the value first, to trigger and escalate potential errorsn // Illegal invocations like `navigator.__proto__.vendor` will throw heren utils.cache.Reflect.apply(...arguments)n return valuen }n })n})" },
_mainFunction: 'utils => {n if (!window.chrome) {n // Use the exact property descriptor found in headful Chromen // fetch it via `Object.getOwnPropertyDescriptor(window, 'chrome')`n Object.defineProperty(window, 'chrome', {n writable: true,n enumerable: true,n configurable: false, // note!n value: {} // We'll extend that latern })n }nn // That means we're running headful and don't need to mock anythingn if ('app' in window.chrome) {n return // Nothing to do heren }nn const makeError = {n ErrorInInvocation: fn => {n const err = new TypeError(`Error in invocation of app.${fn}()`)n return utils.stripErrorWithAnchor(n err,n `at ${fn} (eval at <anonymous>`n )n }n }nn // There's a some static data in that property which doesn't seem to change,n // we should periodically check for updates: `JSON.stringify(window.app, null, 2)`n const STATIC_DATA = JSON.parse(n `n{n "isInstalled": false,n "InstallState": {n "DISABLED": "disabled",n "INSTALLED": "installed",n "NOT_INSTALLED": "not_installed"n },n "RunningState": {n "CANNOT_RUN": "cannot_run",n "READY_TO_RUN": "ready_to_run",n "RUNNING": "running"n }n}n `.trim()n )nn window.chrome.app = {n ...STATIC_DATA,nn get isInstalled() {n return falsen },nn getDetails: function getDetails() {n if (arguments.length) {n throw makeError.ErrorInInvocation(`getDetails`)n }n return nulln },n getIsInstalled: function getDetails() {n if (arguments.length) {n throw makeError.ErrorInInvocation(`getIsInstalled`)n }n return falsen },n runningState: function getDetails() {n if (arguments.length) {n throw makeError.ErrorInInvocation(`runningState`)n }n return 'cannot_run'n }n }n utils.patchToStringNested(window.chrome.app)n }',
_args: []
}), (({_utilsFns: _utilsFns, _mainFunction: _mainFunction, _args: _args}) => {
const utils = Object.fromEntries(Object.entries(_utilsFns).map((([key, value]) => [key, eval(value)])));
utils.init(), eval(_mainFunction)(utils, ..._args)
})({
_utilsFns: {
init: "() => {n utils.preloadCache()n}",
stripProxyFromErrors: "(handler = {}) => {n const newHandler = {n setPrototypeOf: function (target, proto) {n if (proto === null)n throw new TypeError('Cannot convert object to primitive value')n if (Object.getPrototypeOf(target) === Object.getPrototypeOf(proto)) {n throw new TypeError('Cyclic __proto__ value')n }n return Reflect.setPrototypeOf(target, proto)n }n }n // We wrap each trap in the handler in a try/catch and modify the error stack if they thrown const traps = Object.getOwnPropertyNames(handler)n traps.forEach(trap => {n newHandler[trap] = function () {n try {n // Forward the call to the defined proxy handlern return handler[trap].apply(this, arguments || [])n } catch (err) {n // Stack traces differ per browser, we only support chromium based ones currentlyn if (!err || !err.stack || !err.stack.includes(`at `)) {n throw errn }nn // When something throws within one of our traps the Proxy will show up in error stacksn // An earlier implementation of this code would simply strip lines with a blacklist,n // but it makes sense to be more surgical here and only remove lines related to our Proxy.n // We try to use a known "anchor" line for that and strip it with everything above it.n // If the anchor line cannot be found for some reason we fall back to our blacklist approach.nn const stripWithBlacklist = (stack, stripFirstLine = true) => {n const blacklist = [n `at Reflect.${trap} `, // e.g. Reflect.get or Reflect.applyn `at Object.${trap} `, // e.g. Object.get or Object.applyn `at Object.newHandler.<computed> [as ${trap}] ` // caused by this very wrapper :-)n ]n return (n err.stackn .split('\n')n // Always remove the first (file) line in the stack (guaranteed to be our proxy)n .filter((line, index) => !(index === 1 && stripFirstLine))n // Check if the line starts with one of our blacklisted stringsn .filter(line => !blacklist.some(bl => line.trim().startsWith(bl)))n .join('\n')n )n }nn const stripWithAnchor = (stack, anchor) => {n const stackArr = stack.split('\n')n anchor = anchor || `at Object.newHandler.<computed> [as ${trap}] ` // Known first Proxy line in chromiumn const anchorIndex = stackArr.findIndex(line =>n line.trim().startsWith(anchor)n )n if (anchorIndex === -1) {n return false // 404, anchor not foundn }n // Strip everything from the top until we reach the anchor linen // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)n stackArr.splice(1, anchorIndex)n return stackArr.join('\n')n }nn // Special cases due to our nested toString proxiesn err.stack = err.stack.replace(n 'at Object.toString (',n 'at Function.toString ('n )n if ((err.stack || '').includes('at Function.toString (')) {n err.stack = stripWithBlacklist(err.stack, false)n throw errn }nn // Try using the anchor method, fallback to blacklist if necessaryn err.stack = stripWithAnchor(err.stack) || stripWithBlacklist(err.stack)nn throw err // Re-throw our now sanitized errorn }n }n })n return newHandlern}",
stripErrorWithAnchor: "(err, anchor) => {n const stackArr = err.stack.split('\n')n const anchorIndex = stackArr.findIndex(line => line.trim().startsWith(anchor))n if (anchorIndex === -1) {n return err // 404, anchor not foundn }n // Strip everything from the top until we reach the anchor line (remove anchor line as well)n // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)n stackArr.splice(1, anchorIndex)n err.stack = stackArr.join('\n')n return errn}",
replaceProperty: "(obj, propName, descriptorOverrides = {}) => {n return Object.defineProperty(obj, propName, {n // Copy over the existing descriptors (writable, enumerable, configurable, etc)n ...(Object.getOwnPropertyDescriptor(obj, propName) || {}),n // Add our overrides (e.g. value, get())n ...descriptorOverridesn })n}",
preloadCache: "() => {n if (utils.cache) {n returnn }n utils.cache = {n // Used in our proxiesn Reflect: {n get: Reflect.get.bind(Reflect),n apply: Reflect.apply.bind(Reflect)n },n // Used in `makeNativeString`n nativeToStringStr: Function.toString + '' // => `function toString() { [native code] }`n }n}",
makeNativeString: "(name = '') => {n return utils.cache.nativeToStringStr.replace('toString', name || '')n}",
patchToString: "(obj, str = '') => {n const handler = {n apply: function (target, ctx) {n // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + ""`n if (ctx === Function.prototype.toString) {n return utils.makeNativeString('toString')n }n // `toString` targeted at our proxied Object detectedn if (ctx === obj) {n // We either return the optional string verbatim or derive the most desired result automaticallyn return str || utils.makeNativeString(obj.name)n }n // Check if the toString protype of the context is the same as the global prototype,n // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test casen const hasSameProto = Object.getPrototypeOf(n Function.prototype.toStringn ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtinsn if (!hasSameProto) {n // Pass the call on to the local Function.prototype.toString insteadn return ctx.toString()n }n return target.call(ctx)n }n }nn const toStringProxy = new Proxy(n Function.prototype.toString,n utils.stripProxyFromErrors(handler)n )n utils.replaceProperty(Function.prototype, 'toString', {n value: toStringProxyn })n}",
patchToStringNested: "(obj = {}) => {n return utils.execRecursively(obj, ['function'], utils.patchToString)n}",
redirectToString: "(proxyObj, originalObj) => {n const handler = {n apply: function (target, ctx) {n // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + ""`n if (ctx === Function.prototype.toString) {n return utils.makeNativeString('toString')n }nn // `toString` targeted at our proxied Object detectedn if (ctx === proxyObj) {n const fallback = () =>n originalObj && originalObj.namen ? utils.makeNativeString(originalObj.name)n : utils.makeNativeString(proxyObj.name)nn // Return the toString representation of our original object if possiblen return originalObj + '' || fallback()n }nn if (typeof ctx === 'undefined' || ctx === null) {n return target.call(ctx)n }nn // Check if the toString protype of the context is the same as the global prototype,n // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test casen const hasSameProto = Object.getPrototypeOf(n Function.prototype.toStringn ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtinsn if (!hasSameProto) {n // Pass the call on to the local Function.prototype.toString insteadn return ctx.toString()n }nn return target.call(ctx)n }n }nn const toStringProxy = new Proxy(n Function.prototype.toString,n utils.stripProxyFromErrors(handler)n )n utils.replaceProperty(Function.prototype, 'toString', {n value: toStringProxyn })n}",
replaceWithProxy: "(obj, propName, handler) => {n const originalObj = obj[propName]n const proxyObj = new Proxy(obj[propName], utils.stripProxyFromErrors(handler))nn utils.replaceProperty(obj, propName, { value: proxyObj })n utils.redirectToString(proxyObj, originalObj)nn return truen}",
replaceGetterWithProxy: "(obj, propName, handler) => {n const fn = Object.getOwnPropertyDescriptor(obj, propName).getn const fnStr = fn.toString() // special getter function stringn const proxyObj = new Proxy(fn, utils.stripProxyFromErrors(handler))nn utils.replaceProperty(obj, propName, { get: proxyObj })n utils.patchToString(proxyObj, fnStr)nn return truen}",
mockWithProxy: "(obj, propName, pseudoTarget, handler) => {n const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))nn utils.replaceProperty(obj, propName, { value: proxyObj })n utils.patchToString(proxyObj)nn return truen}",
createProxy: "(pseudoTarget, handler) => {n const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))n utils.patchToString(proxyObj)nn return proxyObjn}",
splitObjPath: "objPath => ({n // Remove last dot entry (property) ==> `HTMLMediaElement.prototype`n objName: objPath.split('.').slice(0, -1).join('.'),n // Extract last dot entry ==> `canPlayType`n propName: objPath.split('.').slice(-1)[0]n})",
replaceObjPathWithProxy: "(objPath, handler) => {n const { objName, propName } = utils.splitObjPath(objPath)n const obj = eval(objName) // eslint-disable-line no-evaln return utils.replaceWithProxy(obj, propName, handler)n}",
execRecursively: "(obj = {}, typeFilter = [], fn) => {n function recurse(obj) {n for (const key in obj) {n if (obj[key] === undefined) {n continuen }n if (obj[key] && typeof obj[key] === 'object') {n recurse(obj[key])n } else {n if (obj[key] && typeFilter.includes(typeof obj[key])) {n fn.call(this, obj[key])n }n }n }n }n recurse(obj)n return objn}",
stringifyFns: "(fnObj = { hello: () => 'world' }) => {n // Object.fromEntries() ponyfill (in 6 lines) - supported only in Node v12+, modern browsers are finen // https://github.com/feross/fromentriesn function fromEntries(iterable) {n return [...iterable].reduce((obj, [key, val]) => {n obj[key] = valn return objn }, {})n }n return (Object.fromEntries || fromEntries)(n Object.entries(fnObj)n .filter(([key, value]) => typeof value === 'function')n .map(([key, value]) => [key, value.toString()]) // eslint-disable-line no-evaln )n}",
materializeFns: "(fnStrObj = { hello: "() => 'world'" }) => {n return Object.fromEntries(n Object.entries(fnStrObj).map(([key, value]) => {n if (value.startsWith('function')) {n // some trickery is needed to make oldschool functions work :-)n return [key, eval(`() => ${value}`)()] // eslint-disable-line no-evaln } else {n // arrow functions just workn return [key, eval(value)] // eslint-disable-line no-evaln }n })n )n}",
makeHandler: "() => ({n // Used by simple `navigator` getter evasionsn getterValue: value => ({n apply(target, ctx, args) {n // Let's fetch the value first, to trigger and escalate potential errorsn // Illegal invocations like `navigator.__proto__.vendor` will throw heren utils.cache.Reflect.apply(...arguments)n return valuen }n })n})" },
_mainFunction: "utils => {n if (!window.chrome) {n // Use the exact property descriptor found in headful Chromen // fetch it via `Object.getOwnPropertyDescriptor(window, 'chrome')`n Object.defineProperty(window, 'chrome', {n writable: true,n enumerable: true,n configurable: false, // note!n value: {} // We'll extend that latern })n }nn // That means we're running headful and don't need to mock anythingn if ('csi' in window.chrome) {n return // Nothing to do heren }nn // Check that the Navigation Timing API v1 is available, we need thatn if (!window.performance || !window.performance.timing) {n returnn }nn const { timing } = window.performancenn window.chrome.csi = function() {n return {n onloadT: timing.domContentLoadedEventEnd,n startE: timing.navigationStart,n pageT: Date.now() - timing.navigationStart,n tran: 15 // Transition type or somethingn }n }n utils.patchToString(window.chrome.csi)n }",
_args: []
}), (({_utilsFns: _utilsFns, _mainFunction: _mainFunction, _args: _args}) => {
const utils = Object.fromEntries(Object.entries(_utilsFns).map((([key, value]) => [key, eval(value)])));
utils.init(), eval(_mainFunction)(utils, ..._args)
})({
_utilsFns: {
init: "() => {n utils.preloadCache()n}",
stripProxyFromErrors: "(handler = {}) => {n const newHandler = {n setPrototypeOf: function (target, proto) {n if (proto === null)n throw new TypeError('Cannot convert object to primitive value')n if (Object.getPrototypeOf(target) === Object.getPrototypeOf(proto)) {n throw new TypeError('Cyclic __proto__ value')n }n return Reflect.setPrototypeOf(target, proto)n }n }n // We wrap each trap in the handler in a try/catch and modify the error stack if they thrown const traps = Object.getOwnPropertyNames(handler)n traps.forEach(trap => {n newHandler[trap] = function () {n try {n // Forward the call to the defined proxy handlern return handler[trap].apply(this, arguments || [])n } catch (err) {n // Stack traces differ per browser, we only support chromium based ones currentlyn if (!err || !err.stack || !err.stack.includes(`at `)) {n throw errn }nn // When something throws within one of our traps the Proxy will show up in error stacksn // An earlier implementation of this code would simply strip lines with a blacklist,n // but it makes sense to be more surgical here and only remove lines related to our Proxy.n // We try to use a known "anchor" line for that and strip it with everything above it.n // If the anchor line cannot be found for some reason we fall back to our blacklist approach.nn const stripWithBlacklist = (stack, stripFirstLine = true) => {n const blacklist = [n `at Reflect.${trap} `, // e.g. Reflect.get or Reflect.applyn `at Object.${trap} `, // e.g. Object.get or Object.applyn `at Object.newHandler.<computed> [as ${trap}] ` // caused by this very wrapper :-)n ]n return (n err.stackn .split('\n')n // Always remove the first (file) line in the stack (guaranteed to be our proxy)n .filter((line, index) => !(index === 1 && stripFirstLine))n // Check if the line starts with one of our blacklisted stringsn .filter(line => !blacklist.some(bl => line.trim().startsWith(bl)))n .join('\n')n )n }nn const stripWithAnchor = (stack, anchor) => {n const stackArr = stack.split('\n')n anchor = anchor || `at Object.newHandler.<computed> [as ${trap}] ` // Known first Proxy line in chromiumn const anchorIndex = stackArr.findIndex(line =>n line.trim().startsWith(anchor)n )n if (anchorIndex === -1) {n return false // 404, anchor not foundn }n // Strip everything from the top until we reach the anchor linen // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)n stackArr.splice(1, anchorIndex)n return stackArr.join('\n')n }nn // Special cases due to our nested toString proxiesn err.stack = err.stack.replace(n 'at Object.toString (',n 'at Function.toString ('n )n if ((err.stack || '').includes('at Function.toString (')) {n err.stack = stripWithBlacklist(err.stack, false)n throw errn }nn // Try using the anchor method, fallback to blacklist if necessaryn err.stack = stripWithAnchor(err.stack) || stripWithBlacklist(err.stack)nn throw err // Re-throw our now sanitized errorn }n }n })n return newHandlern}",
stripErrorWithAnchor: "(err, anchor) => {n const stackArr = err.stack.split('\n')n const anchorIndex = stackArr.findIndex(line => line.trim().startsWith(anchor))n if (anchorIndex === -1) {n return err // 404, anchor not foundn }n // Strip everything from the top until we reach the anchor line (remove anchor line as well)n // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)n stackArr.splice(1, anchorIndex)n err.stack = stackArr.join('\n')n return errn}",
replaceProperty: "(obj, propName, descriptorOverrides = {}) => {n return Object.defineProperty(obj, propName, {n // Copy over the existing descriptors (writable, enumerable, configurable, etc)n ...(Object.getOwnPropertyDescriptor(obj, propName) || {}),n // Add our overrides (e.g. value, get())n ...descriptorOverridesn })n}",
preloadCache: "() => {n if (utils.cache) {n returnn }n utils.cache = {n // Used in our proxiesn Reflect: {n get: Reflect.get.bind(Reflect),n apply: Reflect.apply.bind(Reflect)n },n // Used in `makeNativeString`n nativeToStringStr: Function.toString + '' // => `function toString() { [native code] }`n }n}",
makeNativeString: "(name = '') => {n return utils.cache.nativeToStringStr.replace('toString', name || '')n}",
patchToString: "(obj, str = '') => {n const handler = {n apply: function (target, ctx) {n // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + ""`n if (ctx === Function.prototype.toString) {n return utils.makeNativeString('toString')n }n // `toString` targeted at our proxied Object detectedn if (ctx === obj) {n // We either return the optional string verbatim or derive the most desired result automaticallyn return str || utils.makeNativeString(obj.name)n }n // Check if the toString protype of the context is the same as the global prototype,n // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test casen const hasSameProto = Object.getPrototypeOf(n Function.prototype.toStringn ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtinsn if (!hasSameProto) {n // Pass the call on to the local Function.prototype.toString insteadn return ctx.toString()n }n return target.call(ctx)n }n }nn const toStringProxy = new Proxy(n Function.prototype.toString,n utils.stripProxyFromErrors(handler)n )n utils.replaceProperty(Function.prototype, 'toString', {n value: toStringProxyn })n}",
patchToStringNested: "(obj = {}) => {n return utils.execRecursively(obj, ['function'], utils.patchToString)n}",
redirectToString: "(proxyObj, originalObj) => {n const handler = {n apply: function (target, ctx) {n // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + ""`n if (ctx === Function.prototype.toString) {n return utils.makeNativeString('toString')n }nn // `toString` targeted at our proxied Object detectedn if (ctx === proxyObj) {n const fallback = () =>n originalObj && originalObj.namen ? utils.makeNativeString(originalObj.name)n : utils.makeNativeString(proxyObj.name)nn // Return the toString representation of our original object if possiblen return originalObj + '' || fallback()n }nn if (typeof ctx === 'undefined' || ctx === null) {n return target.call(ctx)n }nn // Check if the toString protype of the context is the same as the global prototype,n // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test casen const hasSameProto = Object.getPrototypeOf(n Function.prototype.toStringn ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtinsn if (!hasSameProto) {n // Pass the call on to the local Function.prototype.toString insteadn return ctx.toString()n }nn return target.call(ctx)n }n }nn const toStringProxy = new Proxy(n Function.prototype.toString,n utils.stripProxyFromErrors(handler)n )n utils.replaceProperty(Function.prototype, 'toString', {n value: toStringProxyn })n}",
replaceWithProxy: "(obj, propName, handler) => {n const originalObj = obj[propName]n const proxyObj = new Proxy(obj[propName], utils.stripProxyFromErrors(handler))nn utils.replaceProperty(obj, propName, { value: proxyObj })n utils.redirectToString(proxyObj, originalObj)nn return truen}",
replaceGetterWithProxy: "(obj, propName, handler) => {n const fn = Object.getOwnPropertyDescriptor(obj, propName).getn const fnStr = fn.toString() // special getter function stringn const proxyObj = new Proxy(fn, utils.stripProxyFromErrors(handler))nn utils.replaceProperty(obj, propName, { get: proxyObj })n utils.patchToString(proxyObj, fnStr)nn return truen}",
mockWithProxy: "(obj, propName, pseudoTarget, handler) => {n const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))nn utils.replaceProperty(obj, propName, { value: proxyObj })n utils.patchToString(proxyObj)nn return truen}",
createProxy: "(pseudoTarget, handler) => {n const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))n utils.patchToString(proxyObj)nn return proxyObjn}",
splitObjPath: "objPath => ({n // Remove last dot entry (property) ==> `HTMLMediaElement.prototype`n objName: objPath.split('.').slice(0, -1).join('.'),n // Extract last dot entry ==> `canPlayType`n propName: objPath.split('.').slice(-1)[0]n})",
replaceObjPathWithProxy: "(objPath, handler) => {n const { objName, propName } = utils.splitObjPath(objPath)n const obj = eval(objName) // eslint-disable-line no-evaln return utils.replaceWithProxy(obj, propName, handler)n}",
execRecursively: "(obj = {}, typeFilter = [], fn) => {n function recurse(obj) {n for (const key in obj) {n if (obj[key] === undefined) {n continuen }n if (obj[key] && typeof obj[key] === 'object') {n recurse(obj[key])n } else {n if (obj[key] && typeFilter.includes(typeof obj[key])) {n fn.call(this, obj[key])n }n }n }n }n recurse(obj)n return objn}",
stringifyFns: "(fnObj = { hello: () => 'world' }) => {n // Object.fromEntries() ponyfill (in 6 lines) - supported only in Node v12+, modern browsers are finen // https://github.com/feross/fromentriesn function fromEntries(iterable) {n return [...iterable].reduce((obj, [key, val]) => {n obj[key] = valn return objn }, {})n }n return (Object.fromEntries || fromEntries)(n Object.entries(fnObj)n .filter(([key, value]) => typeof value === 'function')n .map(([key, value]) => [key, value.toString()]) // eslint-disable-line no-evaln )n}",
materializeFns: "(fnStrObj = { hello: "() => 'world'" }) => {n return Object.fromEntries(n Object.entries(fnStrObj).map(([key, value]) => {n if (value.startsWith('function')) {n // some trickery is needed to make oldschool functions work :-)n return [key, eval(`() => ${value}`)()] // eslint-disable-line no-evaln } else {n // arrow functions just workn return [key, eval(value)] // eslint-disable-line no-evaln }n })n )n}",
makeHandler: "() => ({n // Used by simple `navigator` getter evasionsn getterValue: value => ({n apply(target, ctx, args) {n // Let's fetch the value first, to trigger and escalate potential errorsn // Illegal invocations like `navigator.__proto__.vendor` will throw heren utils.cache.Reflect.apply(...arguments)n return valuen }n })n})" },
_mainFunction: "(utils, { opts }) => {n if (!window.chrome) {n // Use the exact property descriptor found in headful Chromen // fetch it via `Object.getOwnPropertyDescriptor(window, 'chrome')`n Object.defineProperty(window, 'chrome', {n writable: true,n enumerable: true,n configurable: false, // note!n value: {} // We'll extend that latern })n }nn // That means we're running headful and don't need to mock anythingn if ('loadTimes' in window.chrome) {n return // Nothing to do heren }nn // Check that the Navigation Timing API v1 + v2 is available, we need thatn if (n !window.performance ||n !window.performance.timing ||n !window.PerformancePaintTimingn ) {n returnn }nn const { performance } = windownn // Some stuff is not available on about:blank as it requires a navigation to occur,n // let's harden the code to not fail then:n const ntEntryFallback = {n nextHopProtocol: 'h2',n type: 'other'n }nn // The API exposes some funky info regarding the connectionn const protocolInfo = {n get connectionInfo() {n const ntEntry =n performance.getEntriesByType('navigation')[0] || ntEntryFallbackn return ntEntry.nextHopProtocoln },n get npnNegotiatedProtocol() {n // NPN is deprecated in favor of ALPN, but this implementation returns then // HTTP/2 or HTTP2+QUIC/39 requests negotiated via ALPN.n const ntEntry =n performance.getEntriesByType('navigation')[0] || ntEntryFallbackn return ['h2', 'hq'].includes(ntEntry.nextHopProtocol)n ? ntEntry.nextHopProtocoln : 'unknown'n },n get navigationType() {n const ntEntry =n performance.getEntriesByType('navigation')[0] || ntEntryFallbackn return ntEntry.typen },n get wasAlternateProtocolAvailable() {n // The Alternate-Protocol header is deprecated in favor of Alt-Svcn // (https://www.mnot.net/blog/2016/03/09/alt-svc), so technically thisn // should always return false.n return falsen },n get wasFetchedViaSpdy() {n // SPDY is deprecated in favor of HTTP/2, but this implementation returnsn // true for HTTP/2 or HTTP2+QUIC/39 as well.n const ntEntry =n performance.getEntriesByType('navigation')[0] || ntEntryFallbackn return ['h2', 'hq'].includes(ntEntry.nextHopProtocol)n },n get wasNpnNegotiated() {n // NPN is deprecated in favor of ALPN, but this implementation returns truen // for HTTP/2 or HTTP2+QUIC/39 requests negotiated via ALPN.n const ntEntry =n performance.getEntriesByType('navigation')[0] || ntEntryFallbackn return ['h2', 'hq'].includes(ntEntry.nextHopProtocol)n }n }nn const { timing } = window.performancenn // Truncate number to specific number of decimals, most of the `loadTimes` stuff has 3n function toFixed(num, fixed) {n var re = new RegExp('^-?\\d+(?:.\\d{0,' + (fixed || -1) + '})?')n return num.toString().match(re)[0]n }nn const timingInfo = {n get firstPaintAfterLoadTime() {n // This was never actually implemented and always returns 0.n return 0n },n get requestTime() {n return timing.navigationStart / 1000n },n get startLoadTime() {n return timing.navigationStart / 1000n },n get commitLoadTime() {n return timing.responseStart / 1000n },n get finishDocumentLoadTime() {n return timing.domContentLoadedEventEnd / 1000n },n get finishLoadTime() {n return timing.loadEventEnd / 1000n },n get firstPaintTime() {n const fpEntry = performance.getEntriesByType('paint')[0] || {n startTime: timing.loadEventEnd / 1000 // Fallback if no navigation occured (`about:blank`)n }n return toFixed(n (fpEntry.startTime + performance.timeOrigin) / 1000,n 3n )n }n }nn window.chrome.loadTimes = function() {n return {n ...protocolInfo,n ...timingInfon }n }n utils.patchToString(window.chrome.loadTimes)n }",
_args: [{opts: {}}]
}), (({_utilsFns: _utilsFns, _mainFunction: _mainFunction, _args: _args}) => {
const utils = Object.fromEntries(Object.entries(_utilsFns).map((([key, value]) => [key, eval(value)])));
utils.init(), eval(_mainFunction)(utils, ..._args)
})({
_utilsFns: {
init: "() => {n utils.preloadCache()n}",
stripProxyFromErrors: "(handler = {}) => {n const newHandler = {n setPrototypeOf: function (target, proto) {n if (proto === null)n throw new TypeError('Cannot convert object to primitive value')n if (Object.getPrototypeOf(target) === Object.getPrototypeOf(proto)) {n throw new TypeError('Cyclic __proto__ value')n }n return Reflect.setPrototypeOf(target, proto)n }n }n // We wrap each trap in the handler in a try/catch and modify the error stack if they thrown const traps = Object.getOwnPropertyNames(handler)n traps.forEach(trap => {n newHandler[trap] = function () {n try {n // Forward the call to the defined proxy handlern return handler[trap].apply(this, arguments || [])n } catch (err) {n // Stack traces differ per browser, we only support chromium based ones currentlyn if (!err || !err.stack || !err.stack.includes(`at `)) {n throw errn }nn // When something throws within one of our traps the Proxy will show up in error stacksn // An earlier implementation of this code would simply strip lines with a blacklist,n // but it makes sense to be more surgical here and only remove lines related to our Proxy.n // We try to use a known "anchor" line for that and strip it with everything above it.n // If the anchor line cannot be found for some reason we fall back to our blacklist approach.nn const stripWithBlacklist = (stack, stripFirstLine = true) => {n const blacklist = [n `at Reflect.${trap} `, // e.g. Reflect.get or Reflect.applyn `at Object.${trap} `, // e.g. Object.get or Object.applyn `at Object.newHandler.<computed> [as ${trap}] ` // caused by this very wrapper :-)n ]n return (n err.stackn .split('\n')n // Always remove the first (file) line in the stack (guaranteed to be our proxy)n .filter((line, index) => !(index === 1 && stripFirstLine))n // Check if the line starts with one of our blacklisted stringsn .filter(line => !blacklist.some(bl => line.trim().startsWith(bl)))n .join('\n')n )n }nn const stripWithAnchor = (stack, anchor) => {n const stackArr = stack.split('\n')n anchor = anchor || `at Object.newHandler.<computed> [as ${trap}] ` // Known first Proxy line in chromiumn const anchorIndex = stackArr.findIndex(line =>n line.trim().startsWith(anchor)n )n if (anchorIndex === -1) {n return false // 404, anchor not foundn }n // Strip everything from the top until we reach the anchor linen // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)n stackArr.splice(1, anchorIndex)n return stackArr.join('\n')n }nn // Special cases due to our nested toString proxiesn err.stack = err.stack.replace(n 'at Object.toString (',n 'at Function.toString ('n )n if ((err.stack || '').includes('at Function.toString (')) {n err.stack = stripWithBlacklist(err.stack, false)n throw errn }nn // Try using the anchor method, fallback to blacklist if necessaryn err.stack = stripWithAnchor(err.stack) || stripWithBlacklist(err.stack)nn throw err // Re-throw our now sanitized errorn }n }n })n return newHandlern}",
stripErrorWithAnchor: "(err, anchor) => {n const stackArr = err.stack.split('\n')n const anchorIndex = stackArr.findIndex(line => line.trim().startsWith(anchor))n if (anchorIndex === -1) {n return err // 404, anchor not foundn }n // Strip everything from the top until we reach the anchor line (remove anchor line as well)n // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)n stackArr.splice(1, anchorIndex)n err.stack = stackArr.join('\n')n return errn}",
replaceProperty: "(obj, propName, descriptorOverrides = {}) => {n return Object.defineProperty(obj, propName, {n // Copy over the existing descriptors (writable, enumerable, configurable, etc)n ...(Object.getOwnPropertyDescriptor(obj, propName) || {}),n // Add our overrides (e.g. value, get())n ...descriptorOverridesn })n}",
preloadCache: "() => {n if (utils.cache) {n returnn }n utils.cache = {n // Used in our proxiesn Reflect: {n get: Reflect.get.bind(Reflect),n apply: Reflect.apply.bind(Reflect)n },n // Used in `makeNativeString`n nativeToStringStr: Function.toString + '' // => `function toString() { [native code] }`n }n}",
makeNativeString: "(name = '') => {n return utils.cache.nativeToStringStr.replace('toString', name || '')n}",
patchToString: "(obj, str = '') => {n const handler = {n apply: function (target, ctx) {n // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + ""`n if (ctx === Function.prototype.toString) {n return utils.makeNativeString('toString')n }n // `toString` targeted at our proxied Object detectedn if (ctx === obj) {n // We either return the optional string verbatim or derive the most desired result automaticallyn return str || utils.makeNativeString(obj.name)n }n // Check if the toString protype of the context is the same as the global prototype,n // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test casen const hasSameProto = Object.getPrototypeOf(n Function.prototype.toStringn ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtinsn if (!hasSameProto) {n // Pass the call on to the local Function.prototype.toString insteadn return ctx.toString()n }n return target.call(ctx)n }n }nn const toStringProxy = new Proxy(n Function.prototype.toString,n utils.stripProxyFromErrors(handler)n )n utils.replaceProperty(Function.prototype, 'toString', {n value: toStringProxyn })n}",
patchToStringNested: "(obj = {}) => {n return utils.execRecursively(obj, ['function'], utils.patchToString)n}",
redirectToString: "(proxyObj, originalObj) => {n const handler = {n apply: function (target, ctx) {n // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + ""`n if (ctx === Function.prototype.toString) {n return utils.makeNativeString('toString')n }nn // `toString` targeted at our proxied Object detectedn if (ctx === proxyObj) {n const fallback = () =>n originalObj && originalObj.namen ? utils.makeNativeString(originalObj.name)n : utils.makeNativeString(proxyObj.name)nn // Return the toString representation of our original object if possiblen return originalObj + '' || fallback()n }nn if (typeof ctx === 'undefined' || ctx === null) {n return target.call(ctx)n }nn // Check if the toString protype of the context is the same as the global prototype,n // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test casen const hasSameProto = Object.getPrototypeOf(n Function.prototype.toStringn ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtinsn if (!hasSameProto) {n // Pass the call on to the local Function.prototype.toString insteadn return ctx.toString()n }nn return target.call(ctx)n }n }nn const toStringProxy = new Proxy(n Function.prototype.toString,n utils.stripProxyFromErrors(handler)n )n utils.replaceProperty(Function.prototype, 'toString', {n value: toStringProxyn })n}",
replaceWithProxy: "(obj, propName, handler) => {n const originalObj = obj[propName]n const proxyObj = new Proxy(obj[propName], utils.stripProxyFromErrors(handler))nn utils.replaceProperty(obj, propName, { value: proxyObj })n utils.redirectToString(proxyObj, originalObj)nn return truen}",
replaceGetterWithProxy: "(obj, propName, handler) => {n const fn = Object.getOwnPropertyDescriptor(obj, propName).getn const fnStr = fn.toString() // special getter function stringn const proxyObj = new Proxy(fn, utils.stripProxyFromErrors(handler))nn utils.replaceProperty(obj, propName, { get: proxyObj })n utils.patchToString(proxyObj, fnStr)nn return truen}",
mockWithProxy: "(obj, propName, pseudoTarget, handler) => {n const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))nn utils.replaceProperty(obj, propName, { value: proxyObj })n utils.patchToString(proxyObj)nn return truen}",
createProxy: "(pseudoTarget, handler) => {n const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))n utils.patchToString(proxyObj)nn return proxyObjn}",
splitObjPath: "objPath => ({n // Remove last dot entry (property) ==> `HTMLMediaElement.prototype`n objName: objPath.split('.').slice(0, -1).join('.'),n // Extract last dot entry ==> `canPlayType`n propName: objPath.split('.').slice(-1)[0]n})",
replaceObjPathWithProxy: "(objPath, handler) => {n const { objName, propName } = utils.splitObjPath(objPath)n const obj = eval(objName) // eslint-disable-line no-evaln return utils.replaceWithProxy(obj, propName, handler)n}",
execRecursively: "(obj = {}, typeFilter = [], fn) => {n function recurse(obj) {n for (const key in obj) {n if (obj[key] === undefined) {n continuen }n if (obj[key] && typeof obj[key] === 'object') {n recurse(obj[key])n } else {n if (obj[key] && typeFilter.includes(typeof obj[key])) {n fn.call(this, obj[key])n }n }n }n }n recurse(obj)n return objn}",
stringifyFns: "(fnObj = { hello: () => 'world' }) => {n // Object.fromEntries() ponyfill (in 6 lines) - supported only in Node v12+, modern browsers are finen // https://github.com/feross/fromentriesn function fromEntries(iterable) {n return [...iterable].reduce((obj, [key, val]) => {n obj[key] = valn return objn }, {})n }n return (Object.fromEntries || fromEntries)(n Object.entries(fnObj)n .filter(([key, value]) => typeof value === 'function')n .map(([key, value]) => [key, value.toString()]) // eslint-disable-line no-evaln )n}",
materializeFns: "(fnStrObj = { hello: "() => 'world'" }) => {n return Object.fromEntries(n Object.entries(fnStrObj).map(([key, value]) => {n if (value.startsWith('function')) {n // some trickery is needed to make oldschool functions work :-)n return [key, eval(`() => ${value}`)()] // eslint-disable-line no-evaln } else {n // arrow functions just workn return [key, eval(value)] // eslint-disable-line no-evaln }n })n )n}",
makeHandler: "() => ({n // Used by simple `navigator` getter evasionsn getterValue: value => ({n apply(target, ctx, args) {n // Let's fetch the value first, to trigger and escalate potential errorsn // Illegal invocations like `navigator.__proto__.vendor` will throw heren utils.cache.Reflect.apply(...arguments)n return valuen }n })n})" },
_mainFunction: "(utils, { opts, STATIC_DATA }) => {n if (!window.chrome) {n // Use the exact property descriptor found in headful Chromen // fetch it via `Object.getOwnPropertyDescriptor(window, 'chrome')`n Object.defineProperty(window, 'chrome', {n writable: true,n enumerable: true,n configurable: false, // note!n value: {} // We'll extend that latern })n }nn // That means we're running headful and don't need to mock anythingn const existsAlready = 'runtime' in window.chromen // `chrome.runtime` is only exposed on secure originsn const isNotSecure = !window.location.protocol.startsWith('https')n if (existsAlready || (isNotSecure && !opts.runOnInsecureOrigins)) {n return // Nothing to do heren }nn window.chrome.runtime = {n // There's a bunch of static data in that property which doesn't seem to change,n // we should periodically check for updates: `JSON.stringify(window.chrome.runtime, null, 2)`n ...STATIC_DATA,n // `chrome.runtime.id` is extension related and returns undefined in Chromen get id() {n return undefinedn },n // These two require more sophisticated mocksn connect: null,n sendMessage: nulln }nn const makeCustomRuntimeErrors = (preamble, method, extensionId) => ({n NoMatchingSignature: new TypeError(n preamble + `No matching signature.`n ),n MustSpecifyExtensionID: new TypeError(n preamble +n `${method} called from a webpage must specify an Extension ID (string) for its first argument.`n ),n InvalidExtensionID: new TypeError(n preamble + `Invalid extension id: '${extensionId}'`n )n })nn // Valid Extension IDs are 32 characters in length and use the letter `a` to `p`:n // https://source.chromium.org/chromium/chromium/src/+/master:components/crx_file/id_util.cc;drc=14a055ccb17e8c8d5d437fe080faba4c6f07beac;l=90n const isValidExtensionID = str =>n str.length === 32 && str.toLowerCase().match(/^[a-p]+$/)nn /** Mock `chrome.runtime.sendMessage` */n const sendMessageHandler = {n apply: function(target, ctx, args) {n const [extensionId, options, responseCallback] = args || []nn // Define custom errorsn const errorPreamble = `Error in invocation of runtime.sendMessage(optional string extensionId, any message, optional object options, optional function responseCallback): `n const Errors = makeCustomRuntimeErrors(n errorPreamble,n `chrome.runtime.sendMessage()`,n extensionIdn )nn // Check if the call signature looks okn const noArguments = args.length === 0n const tooManyArguments = args.length > 4n const incorrectOptions = options && typeof options !== 'object'n const incorrectResponseCallback =n responseCallback && typeof responseCallback !== 'function'n if (n noArguments ||n tooManyArguments ||n incorrectOptions ||n incorrectResponseCallbackn ) {n throw Errors.NoMatchingSignaturen }nn // At least 2 arguments are required before we even validate the extension IDn if (args.length < 2) {n throw Errors.MustSpecifyExtensionIDn }nn // Now let's make sure we got a string as extension IDn if (typeof extensionId !== 'string') {n throw Errors.NoMatchingSignaturen }nn if (!isValidExtensionID(extensionId)) {n throw Errors.InvalidExtensionIDn }nn return undefined // Normal behaviorn }n }n utils.mockWithProxy(n window.chrome.runtime,n 'sendMessage',n function sendMessage() {},n sendMessageHandlern )nn /**n * Mock `chrome.runtime.connect`n *n * home.php?mod=space&uid=2660 https://developer.chrome.com/apps/runtime#method-connectn */n const connectHandler = {n apply: function(target, ctx, args) {n const [extensionId, connectInfo] = args || []nn // Define custom errorsn const errorPreamble = `Error in invocation of runtime.connect(optional string extensionId, optional object connectInfo): `n const Errors = makeCustomRuntimeErrors(n errorPreamble,n `chrome.runtime.connect()`,n extensionIdn )nn // Behavior differs a bit from sendMessage:n const noArguments = args.length === 0n const emptyStringArgument = args.length === 1 && extensionId === ''n if (noArguments || emptyStringArgument) {n throw Errors.MustSpecifyExtensionIDn }nn const tooManyArguments = args.length > 2n const incorrectConnectInfoType =n connectInfo && typeof connectInfo !== 'object'nn if (tooManyArguments || incorrectConnectInfoType) {n throw Errors.NoMatchingSignaturen }nn const extensionIdIsString = typeof extensionId === 'string'n if (extensionIdIsString && extensionId === '') {n throw Errors.MustSpecifyExtensionIDn }n if (extensionIdIsString && !isValidExtensionID(extensionId)) {n throw Errors.InvalidExtensionIDn }nn // There's another edge-case here: extensionId is optional so we might find a connectInfo object as first param, which we need to validaten const validateConnectInfo = ci => {n // More than a first param connectInfo as been providedn if (args.length > 1) {n throw Errors.NoMatchingSignaturen }n // An empty connectInfo has been providedn if (Object.keys(ci).length === 0) {n throw Errors.MustSpecifyExtensionIDn }n // Loop over all connectInfo props an check themn Object.entries(ci).forEach(([k, v]) => {n const isExpected = ['name', 'includeTlsChannelId'].includes(k)n if (!isExpected) {n throw new TypeError(n errorPreamble + `Unexpected property: '${k}'.`n )n }n const MismatchError = (propName, expected, found) =>n TypeError(n errorPreamble +n `Error at property '${propName}': Invalid type: expected ${expected}, found ${found}.`n )n if (k === 'name' && typeof v !== 'string') {n throw MismatchError(k, 'string', typeof v)n }n if (k === 'includeTlsChannelId' && typeof v !== 'boolean') {n throw MismatchError(k, 'boolean', typeof v)n }n })n }n if (typeof extensionId === 'object') {n validateConnectInfo(extensionId)n throw Errors.MustSpecifyExtensionIDn }nn // Unfortunately even when the connect fails Chrome will return an object with methods we need to mock as welln return utils.patchToStringNested(makeConnectResponse())n }n }n utils.mockWithProxy(n window.chrome.runtime,n 'connect',n function connect() {},n connectHandlern )nn function makeConnectResponse() {n const onSomething = () => ({n addListener: function addListener() {},n dispatch: function dispatch() {},n hasListener: function hasListener() {},n hasListeners: function hasListeners() {n return falsen },n removeListener: function removeListener() {}n })nn const response = {n name: '',n sender: undefined,n disconnect: function disconnect() {},n onDisconnect: onSomething(),n onMessage: onSomething(),n postMessage: function postMessage() {n if (!arguments.length) {n throw new TypeError(`Insufficient number of arguments.`)n }n throw new Error(`Attempting to use a disconnected port object`)n }n }n return responsen }n }",
_args: [{
opts: {runOnInsecureOrigins: !1},
STATIC_DATA: {
OnInstalledReason: {
CHROME_UPDATE: "chrome_update",
INSTALL: "install",
SHARED_MODULE_UPDATE: "shared_module_update",
UPDATE: "update" },
OnRestartRequiredReason: {APP_UPDATE: "app_update", OS_UPDATE: "os_update", PERIODIC: "periodic"},
PlatformArch: {
ARM: "arm",
ARM64: "arm64",
MIPS: "mips",
MIPS64: "mips64",
X86_32: "x86-32",
X86_64: "x86-64" },
PlatformNaclArch: {ARM: "arm", MIPS: "mips", MIPS64: "mips64", X86_32: "x86-32", X86_64: "x86-64"},
PlatformOs: {ANDROID: "android", CROS: "cros", LINUX: "linux", MAC: "mac", OPENBSD: "openbsd", WIN: "win"},
RequestUpdateCheckStatus: {
NO_UPDATE: "no_update",
THROTTLED: "throttled",
UPDATE_AVAILABLE: "update_available" }
}
}]
}), (({_utilsFns: _utilsFns, _mainFunction: _mainFunction, _args: _args}) => {
const utils = Object.fromEntries(Object.entries(_utilsFns).map((([key, value]) => [key, eval(value)])));
utils.init(), eval(_mainFunction)(utils, ..._args)
})({
_utilsFns: {
init: "() => {n utils.preloadCache()n}",
stripProxyFromErrors: "(handler = {}) => {n const newHandler = {n setPrototypeOf: function (target, proto) {n if (proto === null)n throw new TypeError('Cannot convert object to primitive value')n if (Object.getPrototypeOf(target) === Object.getPrototypeOf(proto)) {n throw new TypeError('Cyclic __proto__ value')n }n return Reflect.setPrototypeOf(target, proto)n }n }n // We wrap each trap in the handler in a try/catch and modify the error stack if they thrown const traps = Object.getOwnPropertyNames(handler)n traps.forEach(trap => {n newHandler[trap] = function () {n try {n // Forward the call to the defined proxy handlern return handler[trap].apply(this, arguments || [])n } catch (err) {n // Stack traces differ per browser, we only support chromium based ones currentlyn if (!err || !err.stack || !err.stack.includes(`at `)) {n throw errn }nn // When something throws within one of our traps the Proxy will show up in error stacksn // An earlier implementation of this code would simply strip lines with a blacklist,n // but it makes sense to be more surgical here and only remove lines related to our Proxy.n // We try to use a known "anchor" line for that and strip it with everything above it.n // If the anchor line cannot be found for some reason we fall back to our blacklist approach.nn const stripWithBlacklist = (stack, stripFirstLine = true) => {n const blacklist = [n `at Reflect.${trap} `, // e.g. Reflect.get or Reflect.applyn `at Object.${trap} `, // e.g. Object.get or Object.applyn `at Object.newHandler.<computed> [as ${trap}] ` // caused by this very wrapper :-)n ]n return (n err.stackn .split('\n')n // Always remove the first (file) line in the stack (guaranteed to be our proxy)n .filter((line, index) => !(index === 1 && stripFirstLine))n // Check if the line starts with one of our blacklisted stringsn .filter(line => !blacklist.some(bl => line.trim().startsWith(bl)))n .join('\n')n )n }nn const stripWithAnchor = (stack, anchor) => {n const stackArr = stack.split('\n')n anchor = anchor || `at Object.newHandler.<computed> [as ${trap}] ` // Known first Proxy line in chromiumn const anchorIndex = stackArr.findIndex(line =>n line.trim().startsWith(anchor)n )n if (anchorIndex === -1) {n return false // 404, anchor not foundn }n // Strip everything from the top until we reach the anchor linen // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)n stackArr.splice(1, anchorIndex)n return stackArr.join('\n')n }nn // Special cases due to our nested toString proxiesn err.stack = err.stack.replace(n 'at Object.toString (',n 'at Function.toString ('n )n if ((err.stack || '').includes('at Function.toString (')) {n err.stack = stripWithBlacklist(err.stack, false)n throw errn }nn // Try using the anchor method, fallback to blacklist if necessaryn err.stack = stripWithAnchor(err.stack) || stripWithBlacklist(err.stack)nn throw err // Re-throw our now sanitized errorn }n }n })n return newHandlern}",
stripErrorWithAnchor: "(err, anchor) => {n const stackArr = err.stack.split('\n')n const anchorIndex = stackArr.findIndex(line => line.trim().startsWith(anchor))n if (anchorIndex === -1) {n return err // 404, anchor not foundn }n // Strip everything from the top until we reach the anchor line (remove anchor line as well)n // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)n stackArr.splice(1, anchorIndex)n err.stack = stackArr.join('\n')n return errn}",
replaceProperty: "(obj, propName, descriptorOverrides = {}) => {n return Object.defineProperty(obj, propName, {n // Copy over the existing descriptors (writable, enumerable, configurable, etc)n ...(Object.getOwnPropertyDescriptor(obj, propName) || {}),n // Add our overrides (e.g. value, get())n ...descriptorOverridesn })n}",
preloadCache: "() => {n if (utils.cache) {n returnn }n utils.cache = {n // Used in our proxiesn Reflect: {n get: Reflect.get.bind(Reflect),n apply: Reflect.apply.bind(Reflect)n },n // Used in `makeNativeString`n nativeToStringStr: Function.toString + '' // => `function toString() { [native code] }`n }n}",
makeNativeString: "(name = '') => {n return utils.cache.nativeToStringStr.replace('toString', name || '')n}",
patchToString: "(obj, str = '') => {n const handler = {n apply: function (target, ctx) {n // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + ""`n if (ctx === Function.prototype.toString) {n return utils.makeNativeString('toString')n }n // `toString` targeted at our proxied Object detectedn if (ctx === obj) {n // We either return the optional string verbatim or derive the most desired result automaticallyn return str || utils.makeNativeString(obj.name)n }n // Check if the toString protype of the context is the same as the global prototype,n // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test casen const hasSameProto = Object.getPrototypeOf(n Function.prototype.toStringn ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtinsn if (!hasSameProto) {n // Pass the call on to the local Function.prototype.toString insteadn return ctx.toString()n }n return target.call(ctx)n }n }nn const toStringProxy = new Proxy(n Function.prototype.toString,n utils.stripProxyFromErrors(handler)n )n utils.replaceProperty(Function.prototype, 'toString', {n value: toStringProxyn })n}",
patchToStringNested: "(obj = {}) => {n return utils.execRecursively(obj, ['function'], utils.patchToString)n}",
redirectToString: "(proxyObj, originalObj) => {n const handler = {n apply: function (target, ctx) {n // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + ""`n if (ctx === Function.prototype.toString) {n return utils.makeNativeString('toString')n }nn // `toString` targeted at our proxied Object detectedn if (ctx === proxyObj) {n const fallback = () =>n originalObj && originalObj.namen ? utils.makeNativeString(originalObj.name)n : utils.makeNativeString(proxyObj.name)nn // Return the toString representation of our original object if possiblen return originalObj + '' || fallback()n }nn if (typeof ctx === 'undefined' || ctx === null) {n return target.call(ctx)n }nn // Check if the toString protype of the context is the same as the global prototype,n // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test casen const hasSameProto = Object.getPrototypeOf(n Function.prototype.toStringn ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtinsn if (!hasSameProto) {n // Pass the call on to the local Function.prototype.toString insteadn return ctx.toString()n }nn return target.call(ctx)n }n }nn const toStringProxy = new Proxy(n Function.prototype.toString,n utils.stripProxyFromErrors(handler)n )n utils.replaceProperty(Function.prototype, 'toString', {n value: toStringProxyn })n}",
replaceWithProxy: "(obj, propName, handler) => {n const originalObj = obj[propName]n const proxyObj = new Proxy(obj[propName], utils.stripProxyFromErrors(handler))nn utils.replaceProperty(obj, propName, { value: proxyObj })n utils.redirectToString(proxyObj, originalObj)nn return truen}",
replaceGetterWithProxy: "(obj, propName, handler) => {n const fn = Object.getOwnPropertyDescriptor(obj, propName).getn const fnStr = fn.toString() // special getter function stringn const proxyObj = new Proxy(fn, utils.stripProxyFromErrors(handler))nn utils.replaceProperty(obj, propName, { get: proxyObj })n utils.patchToString(proxyObj, fnStr)nn return truen}",
mockWithProxy: "(obj, propName, pseudoTarget, handler) => {n const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))nn utils.replaceProperty(obj, propName, { value: proxyObj })n utils.patchToString(proxyObj)nn return truen}",
createProxy: "(pseudoTarget, handler) => {n const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))n utils.patchToString(proxyObj)nn return proxyObjn}",
splitObjPath: "objPath => ({n // Remove last dot entry (property) ==> `HTMLMediaElement.prototype`n objName: objPath.split('.').slice(0, -1).join('.'),n // Extract last dot entry ==> `canPlayType`n propName: objPath.split('.').slice(-1)[0]n})",
replaceObjPathWithProxy: "(objPath, handler) => {n const { objName, propName } = utils.splitObjPath(objPath)n const obj = eval(objName) // eslint-disable-line no-evaln return utils.replaceWithProxy(obj, propName, handler)n}",
execRecursively: "(obj = {}, typeFilter = [], fn) => {n function recurse(obj) {n for (const key in obj) {n if (obj[key] === undefined) {n continuen }n if (obj[key] && typeof obj[key] === 'object') {n recurse(obj[key])n } else {n if (obj[key] && typeFilter.includes(typeof obj[key])) {n fn.call(this, obj[key])n }n }n }n }n recurse(obj)n return objn}",
stringifyFns: "(fnObj = { hello: () => 'world' }) => {n // Object.fromEntries() ponyfill (in 6 lines) - supported only in Node v12+, modern browsers are finen // https://github.com/feross/fromentriesn function fromEntries(iterable) {n return [...iterable].reduce((obj, [key, val]) => {n obj[key] = valn return objn }, {})n }n return (Object.fromEntries || fromEntries)(n Object.entries(fnObj)n .filter(([key, value]) => typeof value === 'function')n .map(([key, value]) => [key, value.toString()]) // eslint-disable-line no-evaln )n}",
materializeFns: "(fnStrObj = { hello: "() => 'world'" }) => {n return Object.fromEntries(n Object.entries(fnStrObj).map(([key, value]) => {n if (value.startsWith('function')) {n // some trickery is needed to make oldschool functions work :-)n return [key, eval(`() => ${value}`)()] // eslint-disable-line no-evaln } else {n // arrow functions just workn return [key, eval(value)] // eslint-disable-line no-evaln }n })n )n}",
makeHandler: "() => ({n // Used by simple `navigator` getter evasionsn getterValue: value => ({n apply(target, ctx, args) {n // Let's fetch the value first, to trigger and escalate potential errorsn // Illegal invocations like `navigator.__proto__.vendor` will throw heren utils.cache.Reflect.apply(...arguments)n return valuen }n })n})" },
_mainFunction: "utils => {n /**n * Input might look funky, we need to normalize it so e.g. whitespace isn't an issue for our spoofing.n *n * @examplen * video/webm; codecs="vp8, vorbis"n * video/mp4; codecs="avc1.42E01E"n * audio/x-m4a;n * audio/ogg; codecs="vorbis"n * home.php?mod=space&uid=952169 {String} argn */n const parseInput = arg => {n const [mime, codecStr] = arg.trim().split(';')n let codecs = []n if (codecStr && codecStr.includes('codecs="')) {n codecs = codecStrn .trim()n .replace(`codecs="`, '')n .replace(`"`, '')n .trim()n .split(',')n .filter(x => !!x)n .map(x => x.trim())n }n return {n mime,n codecStr,n codecsn }n }nn const canPlayType = {n // Intercept certain requestsn apply: function(target, ctx, args) {n if (!args || !args.length) {n return target.apply(ctx, args)n }n const { mime, codecs } = parseInput(args[0])n // This specific mp4 codec is missing in Chromiumn if (mime === 'video/mp4') {n if (codecs.includes('avc1.42E01E')) {n return 'probably'n }n }n // This mimetype is only supported if no codecs are specifiedn if (mime === 'audio/x-m4a' && !codecs.length) {n return 'maybe'n }nn // This mimetype is only supported if no codecs are specifiedn if (mime === 'audio/aac' && !codecs.length) {n return 'probably'n }n // Everything else as usualn return target.apply(ctx, args)n }n }nn /* global HTMLMediaElement */n utils.replaceWithProxy(n HTMLMediaElement.prototype,n 'canPlayType',n canPlayTypen )n }",
_args: []
}), (({_utilsFns: _utilsFns, _mainFunction: _mainFunction, _args: _args}) => {
const utils = Object.fromEntries(Object.entries(_utilsFns).map((([key, value]) => [key, eval(value)])));
utils.init(), eval(_mainFunction)(utils, ..._args)
})({
_utilsFns: {
init: "() => {n utils.preloadCache()n}",
stripProxyFromErrors: "(handler = {}) => {n const newHandler = {n setPrototypeOf: function (target, proto) {n if (proto === null)n throw new TypeError('Cannot convert object to primitive value')n if (Object.getPrototypeOf(target) === Object.getPrototypeOf(proto)) {n throw new TypeError('Cyclic __proto__ value')n }n return Reflect.setPrototypeOf(target, proto)n }n }n // We wrap each trap in the handler in a try/catch and modify the error stack if they thrown const traps = Object.getOwnPropertyNames(handler)n traps.forEach(trap => {n newHandler[trap] = function () {n try {n // Forward the call to the defined proxy handlern return handler[trap].apply(this, arguments || [])n } catch (err) {n // Stack traces differ per browser, we only support chromium based ones currentlyn if (!err || !err.stack || !err.stack.includes(`at `)) {n throw errn }nn // When something throws within one of our traps the Proxy will show up in error stacksn // An earlier implementation of this code would simply strip lines with a blacklist,n // but it makes sense to be more surgical here and only remove lines related to our Proxy.n // We try to use a known "anchor" line for that and strip it with everything above it.n // If the anchor line cannot be found for some reason we fall back to our blacklist approach.nn const stripWithBlacklist = (stack, stripFirstLine = true) => {n const blacklist = [n `at Reflect.${trap} `, // e.g. Reflect.get or Reflect.applyn `at Object.${trap} `, // e.g. Object.get or Object.applyn `at Object.newHandler.<computed> [as ${trap}] ` // caused by this very wrapper :-)n ]n return (n err.stackn .split('\n')n // Always remove the first (file) line in the stack (guaranteed to be our proxy)n .filter((line, index) => !(index === 1 && stripFirstLine))n // Check if the line starts with one of our blacklisted stringsn .filter(line => !blacklist.some(bl => line.trim().startsWith(bl)))n .join('\n')n )n }nn const stripWithAnchor = (stack, anchor) => {n const stackArr = stack.split('\n')n anchor = anchor || `at Object.newHandler.<computed> [as ${trap}] ` // Known first Proxy line in chromiumn const anchorIndex = stackArr.findIndex(line =>n line.trim().startsWith(anchor)n )n if (anchorIndex === -1) {n return false // 404, anchor not foundn }n // Strip everything from the top until we reach the anchor linen // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)n stackArr.splice(1, anchorIndex)n return stackArr.join('\n')n }nn // Special cases due to our nested toString proxiesn err.stack = err.stack.replace(n 'at Object.toString (',n 'at Function.toString ('n )n if ((err.stack || '').includes('at Function.toString (')) {n err.stack = stripWithBlacklist(err.stack, false)n throw errn }nn // Try using the anchor method, fallback to blacklist if necessaryn err.stack = stripWithAnchor(err.stack) || stripWithBlacklist(err.stack)nn throw err // Re-throw our now sanitized errorn }n }n })n return newHandlern}",
stripErrorWithAnchor: "(err, anchor) => {n const stackArr = err.stack.split('\n')n const anchorIndex = stackArr.findIndex(line => line.trim().startsWith(anchor))n if (anchorIndex === -1) {n return err // 404, anchor not foundn }n // Strip everything from the top until we reach the anchor line (remove anchor line as well)n // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)n stackArr.splice(1, anchorIndex)n err.stack = stackArr.join('\n')n return errn}",
replaceProperty: "(obj, propName, descriptorOverrides = {}) => {n return Object.defineProperty(obj, propName, {n // Copy over the existing descriptors (writable, enumerable, configurable, etc)n ...(Object.getOwnPropertyDescriptor(obj, propName) || {}),n // Add our overrides (e.g. value, get())n ...descriptorOverridesn })n}",
preloadCache: "() => {n if (utils.cache) {n returnn }n utils.cache = {n // Used in our proxiesn Reflect: {n get: Reflect.get.bind(Reflect),n apply: Reflect.apply.bind(Reflect)n },n // Used in `makeNativeString`n nativeToStringStr: Function.toString + '' // => `function toString() { [native code] }`n }n}",
makeNativeString: "(name = '') => {n return utils.cache.nativeToStringStr.replace('toString', name || '')n}",
patchToString: "(obj, str = '') => {n const handler = {n apply: function (target, ctx) {n // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + ""`n if (ctx === Function.prototype.toString) {n return utils.makeNativeString('toString')n }n // `toString` targeted at our proxied Object detectedn if (ctx === obj) {n // We either return the optional string verbatim or derive the most desired result automaticallyn return str || utils.makeNativeString(obj.name)n }n // Check if the toString protype of the context is the same as the global prototype,n // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test casen const hasSameProto = Object.getPrototypeOf(n Function.prototype.toStringn ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtinsn if (!hasSameProto) {n // Pass the call on to the local Function.prototype.toString insteadn return ctx.toString()n }n return target.call(ctx)n }n }nn const toStringProxy = new Proxy(n Function.prototype.toString,n utils.stripProxyFromErrors(handler)n )n utils.replaceProperty(Function.prototype, 'toString', {n value: toStringProxyn })n}",
patchToStringNested: "(obj = {}) => {n return utils.execRecursively(obj, ['function'], utils.patchToString)n}",
redirectToString: "(proxyObj, originalObj) => {n const handler = {n apply: function (target, ctx) {n // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + ""`n if (ctx === Function.prototype.toString) {n return utils.makeNativeString('toString')n }nn // `toString` targeted at our proxied Object detectedn if (ctx === proxyObj) {n const fallback = () =>n originalObj && originalObj.namen ? utils.makeNativeString(originalObj.name)n : utils.makeNativeString(proxyObj.name)nn // Return the toString representation of our original object if possiblen return originalObj + '' || fallback()n }nn if (typeof ctx === 'undefined' || ctx === null) {n return target.call(ctx)n }nn // Check if the toString protype of the context is the same as the global prototype,n // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test casen const hasSameProto = Object.getPrototypeOf(n Function.prototype.toStringn ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtinsn if (!hasSameProto) {n // Pass the call on to the local Function.prototype.toString insteadn return ctx.toString()n }nn return target.call(ctx)n }n }nn const toStringProxy = new Proxy(n Function.prototype.toString,n utils.stripProxyFromErrors(handler)n )n utils.replaceProperty(Function.prototype, 'toString', {n value: toStringProxyn })n}",
replaceWithProxy: "(obj, propName, handler) => {n const originalObj = obj[propName]n const proxyObj = new Proxy(obj[propName], utils.stripProxyFromErrors(handler))nn utils.replaceProperty(obj, propName, { value: proxyObj })n utils.redirectToString(proxyObj, originalObj)nn return truen}",
replaceGetterWithProxy: "(obj, propName, handler) => {n const fn = Object.getOwnPropertyDescriptor(obj, propName).getn const fnStr = fn.toString() // special getter function stringn const proxyObj = new Proxy(fn, utils.stripProxyFromErrors(handler))nn utils.replaceProperty(obj, propName, { get: proxyObj })n utils.patchToString(proxyObj, fnStr)nn return truen}",
mockWithProxy: "(obj, propName, pseudoTarget, handler) => {n const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))nn utils.replaceProperty(obj, propName, { value: proxyObj })n utils.patchToString(proxyObj)nn return truen}",
createProxy: "(pseudoTarget, handler) => {n const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))n utils.patchToString(proxyObj)nn return proxyObjn}",
splitObjPath: "objPath => ({n // Remove last dot entry (property) ==> `HTMLMediaElement.prototype`n objName: objPath.split('.').slice(0, -1).join('.'),n // Extract last dot entry ==> `canPlayType`n propName: objPath.split('.').slice(-1)[0]n})",
replaceObjPathWithProxy: "(objPath, handler) => {n const { objName, propName } = utils.splitObjPath(objPath)n const obj = eval(objName) // eslint-disable-line no-evaln return utils.replaceWithProxy(obj, propName, handler)n}",
execRecursively: "(obj = {}, typeFilter = [], fn) => {n function recurse(obj) {n for (const key in obj) {n if (obj[key] === undefined) {n continuen }n if (obj[key] && typeof obj[key] === 'object') {n recurse(obj[key])n } else {n if (obj[key] && typeFilter.includes(typeof obj[key])) {n fn.call(this, obj[key])n }n }n }n }n recurse(obj)n return objn}",
stringifyFns: "(fnObj = { hello: () => 'world' }) => {n // Object.fromEntries() ponyfill (in 6 lines) - supported only in Node v12+, modern browsers are finen // https://github.com/feross/fromentriesn function fromEntries(iterable) {n return [...iterable].reduce((obj, [key, val]) => {n obj[key] = valn return objn }, {})n }n return (Object.fromEntries || fromEntries)(n Object.entries(fnObj)n .filter(([key, value]) => typeof value === 'function')n .map(([key, value]) => [key, value.toString()]) // eslint-disable-line no-evaln )n}",
materializeFns: "(fnStrObj = { hello: "() => 'world'" }) => {n return Object.fromEntries(n Object.entries(fnStrObj).map(([key, value]) => {n if (value.startsWith('function')) {n // some trickery is needed to make oldschool functions work :-)n return [key, eval(`() => ${value}`)()] // eslint-disable-line no-evaln } else {n // arrow functions just workn return [key, eval(value)] // eslint-disable-line no-evaln }n })n )n}",
makeHandler: "() => ({n // Used by simple `navigator` getter evasionsn getterValue: value => ({n apply(target, ctx, args) {n // Let's fetch the value first, to trigger and escalate potential errorsn // Illegal invocations like `navigator.__proto__.vendor` will throw heren utils.cache.Reflect.apply(...arguments)n return valuen }n })n})" },
_mainFunction: "(utils, { opts }) => {n utils.replaceGetterWithProxy(n Object.getPrototypeOf(navigator),n 'hardwareConcurrency',n utils.makeHandler().getterValue(opts.hardwareConcurrency)n )n }",
_args: [{opts: {hardwareConcurrency: 4}}]
}), (({_utilsFns: _utilsFns, _mainFunction: _mainFunction, _args: _args}) => {
const utils = Object.fromEntries(Object.entries(_utilsFns).map((([key, value]) => [key, eval(value)])));
utils.init(), eval(_mainFunction)(utils, ..._args)
})({
_utilsFns: {
init: "() => {n utils.preloadCache()n}",
stripProxyFromErrors: "(handler = {}) => {n const newHandler = {n setPrototypeOf: function (target, proto) {n if (proto === null)n throw new TypeError('Cannot convert object to primitive value')n if (Object.getPrototypeOf(target) === Object.getPrototypeOf(proto)) {n throw new TypeError('Cyclic __proto__ value')n }n return Reflect.setPrototypeOf(target, proto)n }n }n // We wrap each trap in the handler in a try/catch and modify the error stack if they thrown const traps = Object.getOwnPropertyNames(handler)n traps.forEach(trap => {n newHandler[trap] = function () {n try {n // Forward the call to the defined proxy handlern return handler[trap].apply(this, arguments || [])n } catch (err) {n // Stack traces differ per browser, we only support chromium based ones currentlyn if (!err || !err.stack || !err.stack.includes(`at `)) {n throw errn }nn // When something throws within one of our traps the Proxy will show up in error stacksn // An earlier implementation of this code would simply strip lines with a blacklist,n // but it makes sense to be more surgical here and only remove lines related to our Proxy.n // We try to use a known "anchor" line for that and strip it with everything above it.n // If the anchor line cannot be found for some reason we fall back to our blacklist approach.nn const stripWithBlacklist = (stack, stripFirstLine = true) => {n const blacklist = [n `at Reflect.${trap} `, // e.g. Reflect.get or Reflect.applyn `at Object.${trap} `, // e.g. Object.get or Object.applyn `at Object.newHandler.<computed> [as ${trap}] ` // caused by this very wrapper :-)n ]n return (n err.stackn .split('\n')n // Always remove the first (file) line in the stack (guaranteed to be our proxy)n .filter((line, index) => !(index === 1 && stripFirstLine))n // Check if the line starts with one of our blacklisted stringsn .filter(line => !blacklist.some(bl => line.trim().startsWith(bl)))n .join('\n')n )n }nn const stripWithAnchor = (stack, anchor) => {n const stackArr = stack.split('\n')n anchor = anchor || `at Object.newHandler.<computed> [as ${trap}] ` // Known first Proxy line in chromiumn const anchorIndex = stackArr.findIndex(line =>n line.trim().startsWith(anchor)n )n if (anchorIndex === -1) {n return false // 404, anchor not foundn }n // Strip everything from the top until we reach the anchor linen // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)n stackArr.splice(1, anchorIndex)n return stackArr.join('\n')n }nn // Special cases due to our nested toString proxiesn err.stack = err.stack.replace(n 'at Object.toString (',n 'at Function.toString ('n )n if ((err.stack || '').includes('at Function.toString (')) {n err.stack = stripWithBlacklist(err.stack, false)n throw errn }nn // Try using the anchor method, fallback to blacklist if necessaryn err.stack = stripWithAnchor(err.stack) || stripWithBlacklist(err.stack)nn throw err // Re-throw our now sanitized errorn }n }n })n return newHandlern}",
stripErrorWithAnchor: "(err, anchor) => {n const stackArr = err.stack.split('\n')n const anchorIndex = stackArr.findIndex(line => line.trim().startsWith(anchor))n if (anchorIndex === -1) {n return err // 404, anchor not foundn }n // Strip everything from the top until we reach the anchor line (remove anchor line as well)n // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)n stackArr.splice(1, anchorIndex)n err.stack = stackArr.join('\n')n return errn}",
replaceProperty: "(obj, propName, descriptorOverrides = {}) => {n return Object.defineProperty(obj, propName, {n // Copy over the existing descriptors (writable, enumerable, configurable, etc)n ...(Object.getOwnPropertyDescriptor(obj, propName) || {}),n // Add our overrides (e.g. value, get())n ...descriptorOverridesn })n}",
preloadCache: "() => {n if (utils.cache) {n returnn }n utils.cache = {n // Used in our proxiesn Reflect: {n get: Reflect.get.bind(Reflect),n apply: Reflect.apply.bind(Reflect)n },n // Used in `makeNativeString`n nativeToStringStr: Function.toString + '' // => `function toString() { [native code] }`n }n}",
makeNativeString: "(name = '') => {n return utils.cache.nativeToStringStr.replace('toString', name || '')n}",
patchToString: "(obj, str = '') => {n const handler = {n apply: function (target, ctx) {n // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + ""`n if (ctx === Function.prototype.toString) {n return utils.makeNativeString('toString')n }n // `toString` targeted at our proxied Object detectedn if (ctx === obj) {n // We either return the optional string verbatim or derive the most desired result automaticallyn return str || utils.makeNativeString(obj.name)n }n // Check if the toString protype of the context is the same as the global prototype,n // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test casen const hasSameProto = Object.getPrototypeOf(n Function.prototype.toStringn ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtinsn if (!hasSameProto) {n // Pass the call on to the local Function.prototype.toString insteadn return ctx.toString()n }n return target.call(ctx)n }n }nn const toStringProxy = new Proxy(n Function.prototype.toString,n utils.stripProxyFromErrors(handler)n )n utils.replaceProperty(Function.prototype, 'toString', {n value: toStringProxyn })n}",
patchToStringNested: "(obj = {}) => {n return utils.execRecursively(obj, ['function'], utils.patchToString)n}",
redirectToString: "(proxyObj, originalObj) => {n const handler = {n apply: function (target, ctx) {n // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + ""`n if (ctx === Function.prototype.toString) {n return utils.makeNativeString('toString')n }nn // `toString` targeted at our proxied Object detectedn if (ctx === proxyObj) {n const fallback = () =>n originalObj && originalObj.namen ? utils.makeNativeString(originalObj.name)n : utils.makeNativeString(proxyObj.name)nn // Return the toString representation of our original object if possiblen return originalObj + '' || fallback()n }nn if (typeof ctx === 'undefined' || ctx === null) {n return target.call(ctx)n }nn // Check if the toString protype of the context is the same as the global prototype,n // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test casen const hasSameProto = Object.getPrototypeOf(n Function.prototype.toStringn ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtinsn if (!hasSameProto) {n // Pass the call on to the local Function.prototype.toString insteadn return ctx.toString()n }nn return target.call(ctx)n }n }nn const toStringProxy = new Proxy(n Function.prototype.toString,n utils.stripProxyFromErrors(handler)n )n utils.replaceProperty(Function.prototype, 'toString', {n value: toStringProxyn })n}",
replaceWithProxy: "(obj, propName, handler) => {n const originalObj = obj[propName]n const proxyObj = new Proxy(obj[propName], utils.stripProxyFromErrors(handler))nn utils.replaceProperty(obj, propName, { value: proxyObj })n utils.redirectToString(proxyObj, originalObj)nn return truen}",
replaceGetterWithProxy: "(obj, propName, handler) => {n const fn = Object.getOwnPropertyDescriptor(obj, propName).getn const fnStr = fn.toString() // special getter function stringn const proxyObj = new Proxy(fn, utils.stripProxyFromErrors(handler))nn utils.replaceProperty(obj, propName, { get: proxyObj })n utils.patchToString(proxyObj, fnStr)nn return truen}",
mockWithProxy: "(obj, propName, pseudoTarget, handler) => {n const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))nn utils.replaceProperty(obj, propName, { value: proxyObj })n utils.patchToString(proxyObj)nn return truen}",
createProxy: "(pseudoTarget, handler) => {n const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))n utils.patchToString(proxyObj)nn return proxyObjn}",
splitObjPath: "objPath => ({n // Remove last dot entry (property) ==> `HTMLMediaElement.prototype`n objName: objPath.split('.').slice(0, -1).join('.'),n // Extract last dot entry ==> `canPlayType`n propName: objPath.split('.').slice(-1)[0]n})",
replaceObjPathWithProxy: "(objPath, handler) => {n const { objName, propName } = utils.splitObjPath(objPath)n const obj = eval(objName) // eslint-disable-line no-evaln return utils.replaceWithProxy(obj, propName, handler)n}",
execRecursively: "(obj = {}, typeFilter = [], fn) => {n function recurse(obj) {n for (const key in obj) {n if (obj[key] === undefined) {n continuen }n if (obj[key] && typeof obj[key] === 'object') {n recurse(obj[key])n } else {n if (obj[key] && typeFilter.includes(typeof obj[key])) {n fn.call(this, obj[key])n }n }n }n }n recurse(obj)n return objn}",
stringifyFns: "(fnObj = { hello: () => 'world' }) => {n // Object.fromEntries() ponyfill (in 6 lines) - supported only in Node v12+, modern browsers are finen // https://github.com/feross/fromentriesn function fromEntries(iterable) {n return [...iterable].reduce((obj, [key, val]) => {n obj[key] = valn return objn }, {})n }n return (Object.fromEntries || fromEntries)(n Object.entries(fnObj)n .filter(([key, value]) => typeof value === 'function')n .map(([key, value]) => [key, value.toString()]) // eslint-disable-line no-evaln )n}",
materializeFns: "(fnStrObj = { hello: "() => 'world'" }) => {n return Object.fromEntries(n Object.entries(fnStrObj).map(([key, value]) => {n if (value.startsWith('function')) {n // some trickery is needed to make oldschool functions work :-)n return [key, eval(`() => ${value}`)()] // eslint-disable-line no-evaln } else {n // arrow functions just workn return [key, eval(value)] // eslint-disable-line no-evaln }n })n )n}",
makeHandler: "() => ({n // Used by simple `navigator` getter evasionsn getterValue: value => ({n apply(target, ctx, args) {n // Let's fetch the value first, to trigger and escalate potential errorsn // Illegal invocations like `navigator.__proto__.vendor` will throw heren utils.cache.Reflect.apply(...arguments)n return valuen }n })n})" },
_mainFunction: "(utils, { opts }) => {n const languages = opts.languages.lengthn ? opts.languagesn : ['en-US', 'en']n utils.replaceGetterWithProxy(n Object.getPrototypeOf(navigator),n 'languages',n utils.makeHandler().getterValue(Object.freeze([...languages]))n )n }",
_args: [{opts: {languages: []}}]
}), (({_utilsFns: _utilsFns, _mainFunction: _mainFunction, _args: _args}) => {
const utils = Object.fromEntries(Object.entries(_utilsFns).map((([key, value]) => [key, eval(value)])));
utils.init(), eval(_mainFunction)(utils, ..._args)
})({
_utilsFns: {
init: "() => {n utils.preloadCache()n}",
stripProxyFromErrors: "(handler = {}) => {n const newHandler = {n setPrototypeOf: function (target, proto) {n if (proto === null)n throw new TypeError('Cannot convert object to primitive value')n if (Object.getPrototypeOf(target) === Object.getPrototypeOf(proto)) {n throw new TypeError('Cyclic __proto__ value')n }n return Reflect.setPrototypeOf(target, proto)n }n }n // We wrap each trap in the handler in a try/catch and modify the error stack if they thrown const traps = Object.getOwnPropertyNames(handler)n traps.forEach(trap => {n newHandler[trap] = function () {n try {n // Forward the call to the defined proxy handlern return handler[trap].apply(this, arguments || [])n } catch (err) {n // Stack traces differ per browser, we only support chromium based ones currentlyn if (!err || !err.stack || !err.stack.includes(`at `)) {n throw errn }nn // When something throws within one of our traps the Proxy will show up in error stacksn // An earlier implementation of this code would simply strip lines with a blacklist,n // but it makes sense to be more surgical here and only remove lines related to our Proxy.n // We try to use a known "anchor" line for that and strip it with everything above it.n // If the anchor line cannot be found for some reason we fall back to our blacklist approach.nn const stripWithBlacklist = (stack, stripFirstLine = true) => {n const blacklist = [n `at Reflect.${trap} `, // e.g. Reflect.get or Reflect.applyn `at Object.${trap} `, // e.g. Object.get or Object.applyn `at Object.newHandler.<computed> [as ${trap}] ` // caused by this very wrapper :-)n ]n return (n err.stackn .split('\n')n // Always remove the first (file) line in the stack (guaranteed to be our proxy)n .filter((line, index) => !(index === 1 && stripFirstLine))n // Check if the line starts with one of our blacklisted stringsn .filter(line => !blacklist.some(bl => line.trim().startsWith(bl)))n .join('\n')n )n }nn const stripWithAnchor = (stack, anchor) => {n const stackArr = stack.split('\n')n anchor = anchor || `at Object.newHandler.<computed> [as ${trap}] ` // Known first Proxy line in chromiumn const anchorIndex = stackArr.findIndex(line =>n line.trim().startsWith(anchor)n )n if (anchorIndex === -1) {n return false // 404, anchor not foundn }n // Strip everything from the top until we reach the anchor linen // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)n stackArr.splice(1, anchorIndex)n return stackArr.join('\n')n }nn // Special cases due to our nested toString proxiesn err.stack = err.stack.replace(n 'at Object.toString (',n 'at Function.toString ('n )n if ((err.stack || '').includes('at Function.toString (')) {n err.stack = stripWithBlacklist(err.stack, false)n throw errn }nn // Try using the anchor method, fallback to blacklist if necessaryn err.stack = stripWithAnchor(err.stack) || stripWithBlacklist(err.stack)nn throw err // Re-throw our now sanitized errorn }n }n })n return newHandlern}",
stripErrorWithAnchor: "(err, anchor) => {n const stackArr = err.stack.split('\n')n const anchorIndex = stackArr.findIndex(line => line.trim().startsWith(anchor))n if (anchorIndex === -1) {n return err // 404, anchor not foundn }n // Strip everything from the top until we reach the anchor line (remove anchor line as well)n // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)n stackArr.splice(1, anchorIndex)n err.stack = stackArr.join('\n')n return errn}",
replaceProperty: "(obj, propName, descriptorOverrides = {}) => {n return Object.defineProperty(obj, propName, {n // Copy over the existing descriptors (writable, enumerable, configurable, etc)n ...(Object.getOwnPropertyDescriptor(obj, propName) || {}),n // Add our overrides (e.g. value, get())n ...descriptorOverridesn })n}",
preloadCache: "() => {n if (utils.cache) {n returnn }n utils.cache = {n // Used in our proxiesn Reflect: {n get: Reflect.get.bind(Reflect),n apply: Reflect.apply.bind(Reflect)n },n // Used in `makeNativeString`n nativeToStringStr: Function.toString + '' // => `function toString() { [native code] }`n }n}",
makeNativeString: "(name = '') => {n return utils.cache.nativeToStringStr.replace('toString', name || '')n}",
patchToString: "(obj, str = '') => {n const handler = {n apply: function (target, ctx) {n // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + ""`n if (ctx === Function.prototype.toString) {n return utils.makeNativeString('toString')n }n // `toString` targeted at our proxied Object detectedn if (ctx === obj) {n // We either return the optional string verbatim or derive the most desired result automaticallyn return str || utils.makeNativeString(obj.name)n }n // Check if the toString protype of the context is the same as the global prototype,n // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test casen const hasSameProto = Object.getPrototypeOf(n Function.prototype.toStringn ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtinsn if (!hasSameProto) {n // Pass the call on to the local Function.prototype.toString insteadn return ctx.toString()n }n return target.call(ctx)n }n }nn const toStringProxy = new Proxy(n Function.prototype.toString,n utils.stripProxyFromErrors(handler)n )n utils.replaceProperty(Function.prototype, 'toString', {n value: toStringProxyn })n}",
patchToStringNested: "(obj = {}) => {n return utils.execRecursively(obj, ['function'], utils.patchToString)n}",
redirectToString: "(proxyObj, originalObj) => {n const handler = {n apply: function (target, ctx) {n // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + ""`n if (ctx === Function.prototype.toString) {n return utils.makeNativeString('toString')n }nn // `toString` targeted at our proxied Object detectedn if (ctx === proxyObj) {n const fallback = () =>n originalObj && originalObj.namen ? utils.makeNativeString(originalObj.name)n : utils.makeNativeString(proxyObj.name)nn // Return the toString representation of our original object if possiblen return originalObj + '' || fallback()n }nn if (typeof ctx === 'undefined' || ctx === null) {n return target.call(ctx)n }nn // Check if the toString protype of the context is the same as the global prototype,n // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test casen const hasSameProto = Object.getPrototypeOf(n Function.prototype.toStringn ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtinsn if (!hasSameProto) {n // Pass the call on to the local Function.prototype.toString insteadn return ctx.toString()n }nn return target.call(ctx)n }n }nn const toStringProxy = new Proxy(n Function.prototype.toString,n utils.stripProxyFromErrors(handler)n )n utils.replaceProperty(Function.prototype, 'toString', {n value: toStringProxyn })n}",
replaceWithProxy: "(obj, propName, handler) => {n const originalObj = obj[propName]n const proxyObj = new Proxy(obj[propName], utils.stripProxyFromErrors(handler))nn utils.replaceProperty(obj, propName, { value: proxyObj })n utils.redirectToString(proxyObj, originalObj)nn return truen}",
replaceGetterWithProxy: "(obj, propName, handler) => {n const fn = Object.getOwnPropertyDescriptor(obj, propName).getn const fnStr = fn.toString() // special getter function stringn const proxyObj = new Proxy(fn, utils.stripProxyFromErrors(handler))nn utils.replaceProperty(obj, propName, { get: proxyObj })n utils.patchToString(proxyObj, fnStr)nn return truen}",
mockWithProxy: "(obj, propName, pseudoTarget, handler) => {n const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))nn utils.replaceProperty(obj, propName, { value: proxyObj })n utils.patchToString(proxyObj)nn return truen}",
createProxy: "(pseudoTarget, handler) => {n const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))n utils.patchToString(proxyObj)nn return proxyObjn}",
splitObjPath: "objPath => ({n // Remove last dot entry (property) ==> `HTMLMediaElement.prototype`n objName: objPath.split('.').slice(0, -1).join('.'),n // Extract last dot entry ==> `canPlayType`n propName: objPath.split('.').slice(-1)[0]n})",
replaceObjPathWithProxy: "(objPath, handler) => {n const { objName, propName } = utils.splitObjPath(objPath)n const obj = eval(objName) // eslint-disable-line no-evaln return utils.replaceWithProxy(obj, propName, handler)n}",
execRecursively: "(obj = {}, typeFilter = [], fn) => {n function recurse(obj) {n for (const key in obj) {n if (obj[key] === undefined) {n continuen }n if (obj[key] && typeof obj[key] === 'object') {n recurse(obj[key])n } else {n if (obj[key] && typeFilter.includes(typeof obj[key])) {n fn.call(this, obj[key])n }n }n }n }n recurse(obj)n return objn}",
stringifyFns: "(fnObj = { hello: () => 'world' }) => {n // Object.fromEntries() ponyfill (in 6 lines) - supported only in Node v12+, modern browsers are finen // https://github.com/feross/fromentriesn function fromEntries(iterable) {n return [...iterable].reduce((obj, [key, val]) => {n obj[key] = valn return objn }, {})n }n return (Object.fromEntries || fromEntries)(n Object.entries(fnObj)n .filter(([key, value]) => typeof value === 'function')n .map(([key, value]) => [key, value.toString()]) // eslint-disable-line no-evaln )n}",
materializeFns: "(fnStrObj = { hello: "() => 'world'" }) => {n return Object.fromEntries(n Object.entries(fnStrObj).map(([key, value]) => {n if (value.startsWith('function')) {n // some trickery is needed to make oldschool functions work :-)n return [key, eval(`() => ${value}`)()] // eslint-disable-line no-evaln } else {n // arrow functions just workn return [key, eval(value)] // eslint-disable-line no-evaln }n })n )n}",
makeHandler: "() => ({n // Used by simple `navigator` getter evasionsn getterValue: value => ({n apply(target, ctx, args) {n // Let's fetch the value first, to trigger and escalate potential errorsn // Illegal invocations like `navigator.__proto__.vendor` will throw heren utils.cache.Reflect.apply(...arguments)n return valuen }n })n})" },
_mainFunction: "(utils, opts) => {n const isSecure = document.location.protocol.startsWith('https')nn // In headful on secure origins the permission should be "default", not "denied"n if (isSecure) {n utils.replaceGetterWithProxy(Notification, 'permission', {n apply() {n return 'default'n }n })n }nn // Another weird behavior:n // On insecure origins in headful the state is "denied",n // whereas in headless it's "prompt"n if (!isSecure) {n const handler = {n apply(target, ctx, args) {n const param = (args || [])[0]nn const isNotifications =n param && param.name && param.name === 'notifications'n if (!isNotifications) {n return utils.cache.Reflect.apply(...arguments)n }nn return Promise.resolve(n Object.setPrototypeOf(n {n state: 'denied',n onchange: nulln },n PermissionStatus.prototypen )n )n }n }n // Note: Don't use `Object.getPrototypeOf` heren utils.replaceWithProxy(Permissions.prototype, 'query', handler)n }n }",
_args: [{}]
}), (({_utilsFns: _utilsFns, _mainFunction: _mainFunction, _args: _args}) => {
const utils = Object.fromEntries(Object.entries(_utilsFns).map((([key, value]) => [key, eval(value)])));
utils.init(), eval(_mainFunction)(utils, ..._args)
})({
_utilsFns: {
init: "() => {n utils.preloadCache()n}",
stripProxyFromErrors: "(handler = {}) => {n const newHandler = {n setPrototypeOf: function (target, proto) {n if (proto === null)n throw new TypeError('Cannot convert object to primitive value')n if (Object.getPrototypeOf(target) === Object.getPrototypeOf(proto)) {n throw new TypeError('Cyclic __proto__ value')n }n return Reflect.setPrototypeOf(target, proto)n }n }n // We wrap each trap in the handler in a try/catch and modify the error stack if they thrown const traps = Object.getOwnPropertyNames(handler)n traps.forEach(trap => {n newHandler[trap] = function () {n try {n // Forward the call to the defined proxy handlern return handler[trap].apply(this, arguments || [])n } catch (err) {n // Stack traces differ per browser, we only support chromium based ones currentlyn if (!err || !err.stack || !err.stack.includes(`at `)) {n throw errn }nn // When something throws within one of our traps the Proxy will show up in error stacksn // An earlier implementation of this code would simply strip lines with a blacklist,n // but it makes sense to be more surgical here and only remove lines related to our Proxy.n // We try to use a known "anchor" line for that and strip it with everything above it.n // If the anchor line cannot be found for some reason we fall back to our blacklist approach.nn const stripWithBlacklist = (stack, stripFirstLine = true) => {n const blacklist = [n `at Reflect.${trap} `, // e.g. Reflect.get or Reflect.applyn `at Object.${trap} `, // e.g. Object.get or Object.applyn `at Object.newHandler.<computed> [as ${trap}] ` // caused by this very wrapper :-)n ]n return (n err.stackn .split('\n')n // Always remove the first (file) line in the stack (guaranteed to be our proxy)n .filter((line, index) => !(index === 1 && stripFirstLine))n // Check if the line starts with one of our blacklisted stringsn .filter(line => !blacklist.some(bl => line.trim().startsWith(bl)))n .join('\n')n )n }nn const stripWithAnchor = (stack, anchor) => {n const stackArr = stack.split('\n')n anchor = anchor || `at Object.newHandler.<computed> [as ${trap}] ` // Known first Proxy line in chromiumn const anchorIndex = stackArr.findIndex(line =>n line.trim().startsWith(anchor)n )n if (anchorIndex === -1) {n return false // 404, anchor not foundn }n // Strip everything from the top until we reach the anchor linen // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)n stackArr.splice(1, anchorIndex)n return stackArr.join('\n')n }nn // Special cases due to our nested toString proxiesn err.stack = err.stack.replace(n 'at Object.toString (',n 'at Function.toString ('n )n if ((err.stack || '').includes('at Function.toString (')) {n err.stack = stripWithBlacklist(err.stack, false)n throw errn }nn // Try using the anchor method, fallback to blacklist if necessaryn err.stack = stripWithAnchor(err.stack) || stripWithBlacklist(err.stack)nn throw err // Re-throw our now sanitized errorn }n }n })n return newHandlern}",
stripErrorWithAnchor: "(err, anchor) => {n const stackArr = err.stack.split('\n')n const anchorIndex = stackArr.findIndex(line => line.trim().startsWith(anchor))n if (anchorIndex === -1) {n return err // 404, anchor not foundn }n // Strip everything from the top until we reach the anchor line (remove anchor line as well)n // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)n stackArr.splice(1, anchorIndex)n err.stack = stackArr.join('\n')n return errn}",
replaceProperty: "(obj, propName, descriptorOverrides = {}) => {n return Object.defineProperty(obj, propName, {n // Copy over the existing descriptors (writable, enumerable, configurable, etc)n ...(Object.getOwnPropertyDescriptor(obj, propName) || {}),n // Add our overrides (e.g. value, get())n ...descriptorOverridesn })n}",
preloadCache: "() => {n if (utils.cache) {n returnn }n utils.cache = {n // Used in our proxiesn Reflect: {n get: Reflect.get.bind(Reflect),n apply: Reflect.apply.bind(Reflect)n },n // Used in `makeNativeString`n nativeToStringStr: Function.toString + '' // => `function toString() { [native code] }`n }n}",
makeNativeString: "(name = '') => {n return utils.cache.nativeToStringStr.replace('toString', name || '')n}",
patchToString: "(obj, str = '') => {n const handler = {n apply: function (target, ctx) {n // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + ""`n if (ctx === Function.prototype.toString) {n return utils.makeNativeString('toString')n }n // `toString` targeted at our proxied Object detectedn if (ctx === obj) {n // We either return the optional string verbatim or derive the most desired result automaticallyn return str || utils.makeNativeString(obj.name)n }n // Check if the toString protype of the context is the same as the global prototype,n // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test casen const hasSameProto = Object.getPrototypeOf(n Function.prototype.toStringn ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtinsn if (!hasSameProto) {n // Pass the call on to the local Function.prototype.toString insteadn return ctx.toString()n }n return target.call(ctx)n }n }nn const toStringProxy = new Proxy(n Function.prototype.toString,n utils.stripProxyFromErrors(handler)n )n utils.replaceProperty(Function.prototype, 'toString', {n value: toStringProxyn })n}",
patchToStringNested: "(obj = {}) => {n return utils.execRecursively(obj, ['function'], utils.patchToString)n}",
redirectToString: "(proxyObj, originalObj) => {n const handler = {n apply: function (target, ctx) {n // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + ""`n if (ctx === Function.prototype.toString) {n return utils.makeNativeString('toString')n }nn // `toString` targeted at our proxied Object detectedn if (ctx === proxyObj) {n const fallback = () =>n originalObj && originalObj.namen ? utils.makeNativeString(originalObj.name)n : utils.makeNativeString(proxyObj.name)nn // Return the toString representation of our original object if possiblen return originalObj + '' || fallback()n }nn if (typeof ctx === 'undefined' || ctx === null) {n return target.call(ctx)n }nn // Check if the toString protype of the context is the same as the global prototype,n // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test casen const hasSameProto = Object.getPrototypeOf(n Function.prototype.toStringn ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtinsn if (!hasSameProto) {n // Pass the call on to the local Function.prototype.toString insteadn return ctx.toString()n }nn return target.call(ctx)n }n }nn const toStringProxy = new Proxy(n Function.prototype.toString,n utils.stripProxyFromErrors(handler)n )n utils.replaceProperty(Function.prototype, 'toString', {n value: toStringProxyn })n}",
replaceWithProxy: "(obj, propName, handler) => {n const originalObj = obj[propName]n const proxyObj = new Proxy(obj[propName], utils.stripProxyFromErrors(handler))nn utils.replaceProperty(obj, propName, { value: proxyObj })n utils.redirectToString(proxyObj, originalObj)nn return truen}",
replaceGetterWithProxy: "(obj, propName, handler) => {n const fn = Object.getOwnPropertyDescriptor(obj, propName).getn const fnStr = fn.toString() // special getter function stringn const proxyObj = new Proxy(fn, utils.stripProxyFromErrors(handler))nn utils.replaceProperty(obj, propName, { get: proxyObj })n utils.patchToString(proxyObj, fnStr)nn return truen}",
mockWithProxy: "(obj, propName, pseudoTarget, handler) => {n const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))nn utils.replaceProperty(obj, propName, { value: proxyObj })n utils.patchToString(proxyObj)nn return truen}",
createProxy: "(pseudoTarget, handler) => {n const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))n utils.patchToString(proxyObj)nn return proxyObjn}",
splitObjPath: "objPath => ({n // Remove last dot entry (property) ==> `HTMLMediaElement.prototype`n objName: objPath.split('.').slice(0, -1).join('.'),n // Extract last dot entry ==> `canPlayType`n propName: objPath.split('.').slice(-1)[0]n})",
replaceObjPathWithProxy: "(objPath, handler) => {n const { objName, propName } = utils.splitObjPath(objPath)n const obj = eval(objName) // eslint-disable-line no-evaln return utils.replaceWithProxy(obj, propName, handler)n}",
execRecursively: "(obj = {}, typeFilter = [], fn) => {n function recurse(obj) {n for (const key in obj) {n if (obj[key] === undefined) {n continuen }n if (obj[key] && typeof obj[key] === 'object') {n recurse(obj[key])n } else {n if (obj[key] && typeFilter.includes(typeof obj[key])) {n fn.call(this, obj[key])n }n }n }n }n recurse(obj)n return objn}",
stringifyFns: "(fnObj = { hello: () => 'world' }) => {n // Object.fromEntries() ponyfill (in 6 lines) - supported only in Node v12+, modern browsers are finen // https://github.com/feross/fromentriesn function fromEntries(iterable) {n return [...iterable].reduce((obj, [key, val]) => {n obj[key] = valn return objn }, {})n }n return (Object.fromEntries || fromEntries)(n Object.entries(fnObj)n .filter(([key, value]) => typeof value === 'function')n .map(([key, value]) => [key, value.toString()]) // eslint-disable-line no-evaln )n}",
materializeFns: "(fnStrObj = { hello: "() => 'world'" }) => {n return Object.fromEntries(n Object.entries(fnStrObj).map(([key, value]) => {n if (value.startsWith('function')) {n // some trickery is needed to make oldschool functions work :-)n return [key, eval(`() => ${value}`)()] // eslint-disable-line no-evaln } else {n // arrow functions just workn return [key, eval(value)] // eslint-disable-line no-evaln }n })n )n}",
makeHandler: "() => ({n // Used by simple `navigator` getter evasionsn getterValue: value => ({n apply(target, ctx, args) {n // Let's fetch the value first, to trigger and escalate potential errorsn // Illegal invocations like `navigator.__proto__.vendor` will throw heren utils.cache.Reflect.apply(...arguments)n return valuen }n })n})" },
_mainFunction: "(utils, { fns, data }) => {n fns = utils.materializeFns(fns)nn // That means we're running headfuln const hasPlugins = 'plugins' in navigator && navigator.plugins.lengthn if (hasPlugins) {n return // nothing to do heren }nn const mimeTypes = fns.generateMimeTypeArray(utils, fns)(data.mimeTypes)n const plugins = fns.generatePluginArray(utils, fns)(data.plugins)nn // Plugin and MimeType cross-reference each other, let's do that nown // Note: We're looping through `data.plugins` here, not the generated `plugins`n for (const pluginData of data.plugins) {n pluginData.__mimeTypes.forEach((type, index) => {n plugins[pluginData.name][index] = mimeTypes[type]nn Object.defineProperty(plugins[pluginData.name], type, {n value: mimeTypes[type],n writable: false,n enumerable: false, // Not enumerablen configurable: truen })n Object.defineProperty(mimeTypes[type], 'enabledPlugin', {n value:n type === 'application/x-pnacl'n ? mimeTypes['application/x-nacl'].enabledPlugin // these reference the same plugin, so we need to re-use the Proxy in order to avoid leaksn : new Proxy(plugins[pluginData.name], {}), // Prevent circular referencesn writable: false,n enumerable: false, // Important: `JSON.stringify(navigator.plugins)`n configurable: truen })n })n }nn const patchNavigator = (name, value) =>n utils.replaceProperty(Object.getPrototypeOf(navigator), name, {n get() {n return valuen }n })nn patchNavigator('mimeTypes', mimeTypes)n patchNavigator('plugins', plugins)nn // All donen }",
_args: [{
fns: {
generateMimeTypeArray: "(utils, fns) => mimeTypesData => {n return fns.generateMagicArray(utils, fns)(n mimeTypesData,n MimeTypeArray.prototype,n MimeType.prototype,n 'type'n )n}",
generatePluginArray: "(utils, fns) => pluginsData => {n return fns.generateMagicArray(utils, fns)(n pluginsData,n PluginArray.prototype,n Plugin.prototype,n 'name'n )n}",
generateMagicArray: "(utils, fns) =>n function(n dataArray = [],n proto = MimeTypeArray.prototype,n itemProto = MimeType.prototype,n itemMainProp = 'type'n ) {n // Quick helper to set props with the same descriptors vanilla is usingn const defineProp = (obj, prop, value) =>n Object.defineProperty(obj, prop, {n value,n writable: false,n enumerable: false, // Important for mimeTypes & plugins: `JSON.stringify(navigator.mimeTypes)`n configurable: truen })nn // Loop over our fake data and construct itemsn const makeItem = data => {n const item = {}n for (const prop of Object.keys(data)) {n if (prop.startsWith('__')) {n continuen }n defineProp(item, prop, data[prop])n }n return patchItem(item, data)n }nn const patchItem = (item, data) => {n let descriptor = Object.getOwnPropertyDescriptors(item)nn // Special case: Plugins have a magic length property which is not enumerablen // e.g. `navigator.plugins[i].length` should always be the length of the assigned mimeTypesn if (itemProto === Plugin.prototype) {n descriptor = {n ...descriptor,n length: {n value: data.__mimeTypes.length,n writable: false,n enumerable: false,n configurable: true // Important to be able to use the ownKeys trap in a Proxy to strip `length`n }n }n }nn // We need to spoof a specific `MimeType` or `Plugin` objectn const obj = Object.create(itemProto, descriptor)nn // Virtually all property keys are not enumerable in vanillan const blacklist = [...Object.keys(data), 'length', 'enabledPlugin']n return new Proxy(obj, {n ownKeys(target) {n return Reflect.ownKeys(target).filter(k => !blacklist.includes(k))n },n getOwnPropertyDescriptor(target, prop) {n if (blacklist.includes(prop)) {n return undefinedn }n return Reflect.getOwnPropertyDescriptor(target, prop)n }n })n }nn const magicArray = []nn // Loop through our fake data and use that to create convincing entitiesn dataArray.forEach(data => {n magicArray.push(makeItem(data))n })nn // Add direct property access based on types (e.g. `obj['application/pdf']`) afterwardsn magicArray.forEach(entry => {n defineProp(magicArray, entry[itemMainProp], entry)n })nn // This is the best way to fake the type to make sure this is false: `Array.isArray(navigator.mimeTypes)`n const magicArrayObj = Object.create(proto, {n ...Object.getOwnPropertyDescriptors(magicArray),nn // There's one ugly quirk we unfortunately need to take care of:n // The `MimeTypeArray` prototype has an enumerable `length` property,n // but headful Chrome will still skip it when running `Object.getOwnPropertyNames(navigator.mimeTypes)`.n // To strip it we need to make it first `configurable` and can then overlay a Proxy with an `ownKeys` trap.n length: {n value: magicArray.length,n writable: false,n enumerable: false,n configurable: true // Important to be able to use the ownKeys trap in a Proxy to strip `length`n }n })nn // Generate our functional function mocks :-)n const functionMocks = fns.generateFunctionMocks(utils)(n proto,n itemMainProp,n magicArrayn )nn // We need to overlay our custom object with a JS Proxyn const magicArrayObjProxy = new Proxy(magicArrayObj, {n get(target, key = '') {n // Redirect function calls to our custom proxied versions mocking the vanilla behaviorn if (key === 'item') {n return functionMocks.itemn }n if (key === 'namedItem') {n return functionMocks.namedItemn }n if (proto === PluginArray.prototype && key === 'refresh') {n return functionMocks.refreshn }n // Everything else can pass through as normaln return utils.cache.Reflect.get(...arguments)n },n ownKeys(target) {n // There are a couple of quirks where the original property demonstrates "magical" behavior that makes no sensen // This can be witnessed when calling `Object.getOwnPropertyNames(navigator.mimeTypes)` and the absense of `length`n // My guess is that it has to do with the recent change of not allowing data enumeration and this being implemented weirdlyn // For that reason we just completely fake the available property names based on our data to match what regular Chrome is doingn // Specific issues when not patching this: `length` property is available, direct `types` props (e.g. `obj['application/pdf']`) are missingn const keys = []n const typeProps = magicArray.map(mt => mt[itemMainProp])n typeProps.forEach((_, i) => keys.push(`${i}`))n typeProps.forEach(propName => keys.push(propName))n return keysn },n getOwnPropertyDescriptor(target, prop) {n if (prop === 'length') {n return undefinedn }n return Reflect.getOwnPropertyDescriptor(target, prop)n }n })nn return magicArrayObjProxyn }",
generateFunctionMocks: "utils => (n proto,n itemMainProp,n dataArrayn) => ({n /** Returns the MimeType object with the specified index. */n item: utils.createProxy(proto.item, {n apply(target, ctx, args) {n if (!args.length) {n throw new TypeError(n `Failed to execute 'item' on '${n proto[Symbol.toStringTag]n }': 1 argument required, but only 0 present.`n )n }n // Special behavior alert:n // - Vanilla tries to cast strings to Numbers (only integers!) and use them as property index lookupn // - If anything else than an integer (including as string) is provided it will return the first entryn const isInteger = args[0] && Number.isInteger(Number(args[0])) // Cast potential string to number first, then check for integern // Note: Vanilla never returns `undefined`n return (isInteger ? dataArray[Number(args[0])] : dataArray[0]) || nulln }n }),n /** Returns the MimeType object with the specified name. */n namedItem: utils.createProxy(proto.namedItem, {n apply(target, ctx, args) {n if (!args.length) {n throw new TypeError(n `Failed to execute 'namedItem' on '${n proto[Symbol.toStringTag]n }': 1 argument required, but only 0 present.`n )n }n return dataArray.find(mt => mt[itemMainProp] === args[0]) || null // Not `undefined`!n }n }),n /** Does nothing and shall return nothing */n refresh: proto.refreshn ? utils.createProxy(proto.refresh, {n apply(target, ctx, args) {n return undefinedn }n })n : undefinedn})" },
data: {
mimeTypes: [{
type: "application/pdf",
suffixes: "pdf",
description: "",
__pluginName: "Chrome PDF Viewer" }, {
type: "application/x-google-chrome-pdf",
suffixes: "pdf",
description: "Portable Document Format",
__pluginName: "Chrome PDF Plugin" }, {
type: "application/x-nacl",
suffixes: "",
description: "Native Client Executable",
__pluginName: "Native Client" }, {
type: "application/x-pnacl",
suffixes: "",
description: "Portable Native Client Executable",
__pluginName: "Native Client" }],
plugins: [{
name: "Chrome PDF Plugin",
filename: "internal-pdf-viewer",
description: "Portable Document Format",
__mimeTypes: ["application/x-google-chrome-pdf"]
}, {
name: "Chrome PDF Viewer",
filename: "mhjfbmdgcfjbbpaeojofohoefgiehjai",
description: "",
__mimeTypes: ["application/pdf"]
}, {
name: "Native Client",
filename: "internal-nacl-plugin",
description: "",
__mimeTypes: ["application/x-nacl", "application/x-pnacl"]
}]
}
}]
}), !1 === navigator.webdriver || void 0 === navigator.webdriver || delete Object.getPrototypeOf(navigator).webdriver, (({_utilsFns: _utilsFns, _mainFunction: _mainFunction, _args: _args}) => {
const utils = Object.fromEntries(Object.entries(_utilsFns).map((([key, value]) => [key, eval(value)])));
utils.init(), eval(_mainFunction)(utils, ..._args)
})({
_utilsFns: {
init: "() => {n utils.preloadCache()n}",
stripProxyFromErrors: "(handler = {}) => {n const newHandler = {n setPrototypeOf: function (target, proto) {n if (proto === null)n throw new TypeError('Cannot convert object to primitive value')n if (Object.getPrototypeOf(target) === Object.getPrototypeOf(proto)) {n throw new TypeError('Cyclic __proto__ value')n }n return Reflect.setPrototypeOf(target, proto)n }n }n // We wrap each trap in the handler in a try/catch and modify the error stack if they thrown const traps = Object.getOwnPropertyNames(handler)n traps.forEach(trap => {n newHandler[trap] = function () {n try {n // Forward the call to the defined proxy handlern return handler[trap].apply(this, arguments || [])n } catch (err) {n // Stack traces differ per browser, we only support chromium based ones currentlyn if (!err || !err.stack || !err.stack.includes(`at `)) {n throw errn }nn // When something throws within one of our traps the Proxy will show up in error stacksn // An earlier implementation of this code would simply strip lines with a blacklist,n // but it makes sense to be more surgical here and only remove lines related to our Proxy.n // We try to use a known "anchor" line for that and strip it with everything above it.n // If the anchor line cannot be found for some reason we fall back to our blacklist approach.nn const stripWithBlacklist = (stack, stripFirstLine = true) => {n const blacklist = [n `at Reflect.${trap} `, // e.g. Reflect.get or Reflect.applyn `at Object.${trap} `, // e.g. Object.get or Object.applyn `at Object.newHandler.<computed> [as ${trap}] ` // caused by this very wrapper :-)n ]n return (n err.stackn .split('\n')n // Always remove the first (file) line in the stack (guaranteed to be our proxy)n .filter((line, index) => !(index === 1 && stripFirstLine))n // Check if the line starts with one of our blacklisted stringsn .filter(line => !blacklist.some(bl => line.trim().startsWith(bl)))n .join('\n')n )n }nn const stripWithAnchor = (stack, anchor) => {n const stackArr = stack.split('\n')n anchor = anchor || `at Object.newHandler.<computed> [as ${trap}] ` // Known first Proxy line in chromiumn const anchorIndex = stackArr.findIndex(line =>n line.trim().startsWith(anchor)n )n if (anchorIndex === -1) {n return false // 404, anchor not foundn }n // Strip everything from the top until we reach the anchor linen // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)n stackArr.splice(1, anchorIndex)n return stackArr.join('\n')n }nn // Special cases due to our nested toString proxiesn err.stack = err.stack.replace(n 'at Object.toString (',n 'at Function.toString ('n )n if ((err.stack || '').includes('at Function.toString (')) {n err.stack = stripWithBlacklist(err.stack, false)n throw errn }nn // Try using the anchor method, fallback to blacklist if necessaryn err.stack = stripWithAnchor(err.stack) || stripWithBlacklist(err.stack)nn throw err // Re-throw our now sanitized errorn }n }n })n return newHandlern}",
stripErrorWithAnchor: "(err, anchor) => {n const stackArr = err.stack.split('\n')n const anchorIndex = stackArr.findIndex(line => line.trim().startsWith(anchor))n if (anchorIndex === -1) {n return err // 404, anchor not foundn }n // Strip everything from the top until we reach the anchor line (remove anchor line as well)n // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)n stackArr.splice(1, anchorIndex)n err.stack = stackArr.join('\n')n return errn}",
replaceProperty: "(obj, propName, descriptorOverrides = {}) => {n return Object.defineProperty(obj, propName, {n // Copy over the existing descriptors (writable, enumerable, configurable, etc)n ...(Object.getOwnPropertyDescriptor(obj, propName) || {}),n // Add our overrides (e.g. value, get())n ...descriptorOverridesn })n}",
preloadCache: "() => {n if (utils.cache) {n returnn }n utils.cache = {n // Used in our proxiesn Reflect: {n get: Reflect.get.bind(Reflect),n apply: Reflect.apply.bind(Reflect)n },n // Used in `makeNativeString`n nativeToStringStr: Function.toString + '' // => `function toString() { [native code] }`n }n}",
makeNativeString: "(name = '') => {n return utils.cache.nativeToStringStr.replace('toString', name || '')n}",
patchToString: "(obj, str = '') => {n const handler = {n apply: function (target, ctx) {n // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + ""`n if (ctx === Function.prototype.toString) {n return utils.makeNativeString('toString')n }n // `toString` targeted at our proxied Object detectedn if (ctx === obj) {n // We either return the optional string verbatim or derive the most desired result automaticallyn return str || utils.makeNativeString(obj.name)n }n // Check if the toString protype of the context is the same as the global prototype,n // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test casen const hasSameProto = Object.getPrototypeOf(n Function.prototype.toStringn ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtinsn if (!hasSameProto) {n // Pass the call on to the local Function.prototype.toString insteadn return ctx.toString()n }n return target.call(ctx)n }n }nn const toStringProxy = new Proxy(n Function.prototype.toString,n utils.stripProxyFromErrors(handler)n )n utils.replaceProperty(Function.prototype, 'toString', {n value: toStringProxyn })n}",
patchToStringNested: "(obj = {}) => {n return utils.execRecursively(obj, ['function'], utils.patchToString)n}",
redirectToString: "(proxyObj, originalObj) => {n const handler = {n apply: function (target, ctx) {n // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + ""`n if (ctx === Function.prototype.toString) {n return utils.makeNativeString('toString')n }nn // `toString` targeted at our proxied Object detectedn if (ctx === proxyObj) {n const fallback = () =>n originalObj && originalObj.namen ? utils.makeNativeString(originalObj.name)n : utils.makeNativeString(proxyObj.name)nn // Return the toString representation of our original object if possiblen return originalObj + '' || fallback()n }nn if (typeof ctx === 'undefined' || ctx === null) {n return target.call(ctx)n }nn // Check if the toString protype of the context is the same as the global prototype,n // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test casen const hasSameProto = Object.getPrototypeOf(n Function.prototype.toStringn ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtinsn if (!hasSameProto) {n // Pass the call on to the local Function.prototype.toString insteadn return ctx.toString()n }nn return target.call(ctx)n }n }nn const toStringProxy = new Proxy(n Function.prototype.toString,n utils.stripProxyFromErrors(handler)n )n utils.replaceProperty(Function.prototype, 'toString', {n value: toStringProxyn })n}",
replaceWithProxy: "(obj, propName, handler) => {n const originalObj = obj[propName]n const proxyObj = new Proxy(obj[propName], utils.stripProxyFromErrors(handler))nn utils.replaceProperty(obj, propName, { value: proxyObj })n utils.redirectToString(proxyObj, originalObj)nn return truen}",
replaceGetterWithProxy: "(obj, propName, handler) => {n const fn = Object.getOwnPropertyDescriptor(obj, propName).getn const fnStr = fn.toString() // special getter function stringn const proxyObj = new Proxy(fn, utils.stripProxyFromErrors(handler))nn utils.replaceProperty(obj, propName, { get: proxyObj })n utils.patchToString(proxyObj, fnStr)nn return truen}",
mockWithProxy: "(obj, propName, pseudoTarget, handler) => {n const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))nn utils.replaceProperty(obj, propName, { value: proxyObj })n utils.patchToString(proxyObj)nn return truen}",
createProxy: "(pseudoTarget, handler) => {n const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))n utils.patchToString(proxyObj)nn return proxyObjn}",
splitObjPath: "objPath => ({n // Remove last dot entry (property) ==> `HTMLMediaElement.prototype`n objName: objPath.split('.').slice(0, -1).join('.'),n // Extract last dot entry ==> `canPlayType`n propName: objPath.split('.').slice(-1)[0]n})",
replaceObjPathWithProxy: "(objPath, handler) => {n const { objName, propName } = utils.splitObjPath(objPath)n const obj = eval(objName) // eslint-disable-line no-evaln return utils.replaceWithProxy(obj, propName, handler)n}",
execRecursively: "(obj = {}, typeFilter = [], fn) => {n function recurse(obj) {n for (const key in obj) {n if (obj[key] === undefined) {n continuen }n if (obj[key] && typeof obj[key] === 'object') {n recurse(obj[key])n } else {n if (obj[key] && typeFilter.includes(typeof obj[key])) {n fn.call(this, obj[key])n }n }n }n }n recurse(obj)n return objn}",
stringifyFns: "(fnObj = { hello: () => 'world' }) => {n // Object.fromEntries() ponyfill (in 6 lines) - supported only in Node v12+, modern browsers are finen // https://github.com/feross/fromentriesn function fromEntries(iterable) {n return [...iterable].reduce((obj, [key, val]) => {n obj[key] = valn return objn }, {})n }n return (Object.fromEntries || fromEntries)(n Object.entries(fnObj)n .filter(([key, value]) => typeof value === 'function')n .map(([key, value]) => [key, value.toString()]) // eslint-disable-line no-evaln )n}",
materializeFns: "(fnStrObj = { hello: "() => 'world'" }) => {n return Object.fromEntries(n Object.entries(fnStrObj).map(([key, value]) => {n if (value.startsWith('function')) {n // some trickery is needed to make oldschool functions work :-)n return [key, eval(`() => ${value}`)()] // eslint-disable-line no-evaln } else {n // arrow functions just workn return [key, eval(value)] // eslint-disable-line no-evaln }n })n )n}",
makeHandler: "() => ({n // Used by simple `navigator` getter evasionsn getterValue: value => ({n apply(target, ctx, args) {n // Let's fetch the value first, to trigger and escalate potential errorsn // Illegal invocations like `navigator.__proto__.vendor` will throw heren utils.cache.Reflect.apply(...arguments)n return valuen }n })n})" },
_mainFunction: "(utils, opts) => {n const getParameterProxyHandler = {n apply: function(target, ctx, args) {n const param = (args || [])[0]n const result = utils.cache.Reflect.apply(target, ctx, args)n // UNMASKED_VENDOR_WEBGLn if (param === 37445) {n return opts.vendor || 'Intel Inc.' // default in headless: Google Inc.n }n // UNMASKED_RENDERER_WEBGLn if (param === 37446) {n return opts.renderer || 'Intel Iris OpenGL Engine' // default in headless: Google SwiftShadern }n return resultn }n }nn // There's more than one WebGL rendering contextn // https://developer.mozilla.org/en-US/docs/Web/API/WebGL2RenderingContext#Browser_compatibilityn // To find out the original values here: Object.getOwnPropertyDescriptors(WebGLRenderingContext.prototype.getParameter)n const addProxy = (obj, propName) => {n utils.replaceWithProxy(obj, propName, getParameterProxyHandler)n }n // For whatever weird reason loops don't play nice with Object.defineProperty, here's the next best thing:n addProxy(WebGLRenderingContext.prototype, 'getParameter')n addProxy(WebGL2RenderingContext.prototype, 'getParameter')n }",
_args: [{}]
}), (() => {
try {
if (window.outerWidth && window.outerHeight) return;
const n = 85;
window.outerWidth = window.innerWidth, window.outerHeight = window.innerHeight + n
} catch (n) {
}
})(), (({_utilsFns: _utilsFns, _mainFunction: _mainFunction, _args: _args}) => {
const utils = Object.fromEntries(Object.entries(_utilsFns).map((([key, value]) => [key, eval(value)])));
utils.init(), eval(_mainFunction)(utils, ..._args)
})({
_utilsFns: {
init: "() => {n utils.preloadCache()n}",
stripProxyFromErrors: "(handler = {}) => {n const newHandler = {n setPrototypeOf: function (target, proto) {n if (proto === null)n throw new TypeError('Cannot convert object to primitive value')n if (Object.getPrototypeOf(target) === Object.getPrototypeOf(proto)) {n throw new TypeError('Cyclic __proto__ value')n }n return Reflect.setPrototypeOf(target, proto)n }n }n // We wrap each trap in the handler in a try/catch and modify the error stack if they thrown const traps = Object.getOwnPropertyNames(handler)n traps.forEach(trap => {n newHandler[trap] = function () {n try {n // Forward the call to the defined proxy handlern return handler[trap].apply(this, arguments || [])n } catch (err) {n // Stack traces differ per browser, we only support chromium based ones currentlyn if (!err || !err.stack || !err.stack.includes(`at `)) {n throw errn }nn // When something throws within one of our traps the Proxy will show up in error stacksn // An earlier implementation of this code would simply strip lines with a blacklist,n // but it makes sense to be more surgical here and only remove lines related to our Proxy.n // We try to use a known "anchor" line for that and strip it with everything above it.n // If the anchor line cannot be found for some reason we fall back to our blacklist approach.nn const stripWithBlacklist = (stack, stripFirstLine = true) => {n const blacklist = [n `at Reflect.${trap} `, // e.g. Reflect.get or Reflect.applyn `at Object.${trap} `, // e.g. Object.get or Object.applyn `at Object.newHandler.<computed> [as ${trap}] ` // caused by this very wrapper :-)n ]n return (n err.stackn .split('\n')n // Always remove the first (file) line in the stack (guaranteed to be our proxy)n .filter((line, index) => !(index === 1 && stripFirstLine))n // Check if the line starts with one of our blacklisted stringsn .filter(line => !blacklist.some(bl => line.trim().startsWith(bl)))n .join('\n')n )n }nn const stripWithAnchor = (stack, anchor) => {n const stackArr = stack.split('\n')n anchor = anchor || `at Object.newHandler.<computed> [as ${trap}] ` // Known first Proxy line in chromiumn const anchorIndex = stackArr.findIndex(line =>n line.trim().startsWith(anchor)n )n if (anchorIndex === -1) {n return false // 404, anchor not foundn }n // Strip everything from the top until we reach the anchor linen // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)n stackArr.splice(1, anchorIndex)n return stackArr.join('\n')n }nn // Special cases due to our nested toString proxiesn err.stack = err.stack.replace(n 'at Object.toString (',n 'at Function.toString ('n )n if ((err.stack || '').includes('at Function.toString (')) {n err.stack = stripWithBlacklist(err.stack, false)n throw errn }nn // Try using the anchor method, fallback to blacklist if necessaryn err.stack = stripWithAnchor(err.stack) || stripWithBlacklist(err.stack)nn throw err // Re-throw our now sanitized errorn }n }n })n return newHandlern}",
stripErrorWithAnchor: "(err, anchor) => {n const stackArr = err.stack.split('\n')n const anchorIndex = stackArr.findIndex(line => line.trim().startsWith(anchor))n if (anchorIndex === -1) {n return err // 404, anchor not foundn }n // Strip everything from the top until we reach the anchor line (remove anchor line as well)n // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)n stackArr.splice(1, anchorIndex)n err.stack = stackArr.join('\n')n return errn}",
replaceProperty: "(obj, propName, descriptorOverrides = {}) => {n return Object.defineProperty(obj, propName, {n // Copy over the existing descriptors (writable, enumerable, configurable, etc)n ...(Object.getOwnPropertyDescriptor(obj, propName) || {}),n // Add our overrides (e.g. value, get())n ...descriptorOverridesn })n}",
preloadCache: "() => {n if (utils.cache) {n returnn }n utils.cache = {n // Used in our proxiesn Reflect: {n get: Reflect.get.bind(Reflect),n apply: Reflect.apply.bind(Reflect)n },n // Used in `makeNativeString`n nativeToStringStr: Function.toString + '' // => `function toString() { [native code] }`n }n}",
makeNativeString: "(name = '') => {n return utils.cache.nativeToStringStr.replace('toString', name || '')n}",
patchToString: "(obj, str = '') => {n const handler = {n apply: function (target, ctx) {n // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + ""`n if (ctx === Function.prototype.toString) {n return utils.makeNativeString('toString')n }n // `toString` targeted at our proxied Object detectedn if (ctx === obj) {n // We either return the optional string verbatim or derive the most desired result automaticallyn return str || utils.makeNativeString(obj.name)n }n // Check if the toString protype of the context is the same as the global prototype,n // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test casen const hasSameProto = Object.getPrototypeOf(n Function.prototype.toStringn ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtinsn if (!hasSameProto) {n // Pass the call on to the local Function.prototype.toString insteadn return ctx.toString()n }n return target.call(ctx)n }n }nn const toStringProxy = new Proxy(n Function.prototype.toString,n utils.stripProxyFromErrors(handler)n )n utils.replaceProperty(Function.prototype, 'toString', {n value: toStringProxyn })n}",
patchToStringNested: "(obj = {}) => {n return utils.execRecursively(obj, ['function'], utils.patchToString)n}",
redirectToString: "(proxyObj, originalObj) => {n const handler = {n apply: function (target, ctx) {n // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + ""`n if (ctx === Function.prototype.toString) {n return utils.makeNativeString('toString')n }nn // `toString` targeted at our proxied Object detectedn if (ctx === proxyObj) {n const fallback = () =>n originalObj && originalObj.namen ? utils.makeNativeString(originalObj.name)n : utils.makeNativeString(proxyObj.name)nn // Return the toString representation of our original object if possiblen return originalObj + '' || fallback()n }nn if (typeof ctx === 'undefined' || ctx === null) {n return target.call(ctx)n }nn // Check if the toString protype of the context is the same as the global prototype,n // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test casen const hasSameProto = Object.getPrototypeOf(n Function.prototype.toStringn ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtinsn if (!hasSameProto) {n // Pass the call on to the local Function.prototype.toString insteadn return ctx.toString()n }nn return target.call(ctx)n }n }nn const toStringProxy = new Proxy(n Function.prototype.toString,n utils.stripProxyFromErrors(handler)n )n utils.replaceProperty(Function.prototype, 'toString', {n value: toStringProxyn })n}",
replaceWithProxy: "(obj, propName, handler) => {n const originalObj = obj[propName]n const proxyObj = new Proxy(obj[propName], utils.stripProxyFromErrors(handler))nn utils.replaceProperty(obj, propName, { value: proxyObj })n utils.redirectToString(proxyObj, originalObj)nn return truen}",
replaceGetterWithProxy: "(obj, propName, handler) => {n const fn = Object.getOwnPropertyDescriptor(obj, propName).getn const fnStr = fn.toString() // special getter function stringn const proxyObj = new Proxy(fn, utils.stripProxyFromErrors(handler))nn utils.replaceProperty(obj, propName, { get: proxyObj })n utils.patchToString(proxyObj, fnStr)nn return truen}",
mockWithProxy: "(obj, propName, pseudoTarget, handler) => {n const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))nn utils.replaceProperty(obj, propName, { value: proxyObj })n utils.patchToString(proxyObj)nn return truen}",
createProxy: "(pseudoTarget, handler) => {n const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))n utils.patchToString(proxyObj)nn return proxyObjn}",
splitObjPath: "objPath => ({n // Remove last dot entry (property) ==> `HTMLMediaElement.prototype`n objName: objPath.split('.').slice(0, -1).join('.'),n // Extract last dot entry ==> `canPlayType`n propName: objPath.split('.').slice(-1)[0]n})",
replaceObjPathWithProxy: "(objPath, handler) => {n const { objName, propName } = utils.splitObjPath(objPath)n const obj = eval(objName) // eslint-disable-line no-evaln return utils.replaceWithProxy(obj, propName, handler)n}",
execRecursively: "(obj = {}, typeFilter = [], fn) => {n function recurse(obj) {n for (const key in obj) {n if (obj[key] === undefined) {n continuen }n if (obj[key] && typeof obj[key] === 'object') {n recurse(obj[key])n } else {n if (obj[key] && typeFilter.includes(typeof obj[key])) {n fn.call(this, obj[key])n }n }n }n }n recurse(obj)n return objn}",
stringifyFns: "(fnObj = { hello: () => 'world' }) => {n // Object.fromEntries() ponyfill (in 6 lines) - supported only in Node v12+, modern browsers are finen // https://github.com/feross/fromentriesn function fromEntries(iterable) {n return [...iterable].reduce((obj, [key, val]) => {n obj[key] = valn return objn }, {})n }n return (Object.fromEntries || fromEntries)(n Object.entries(fnObj)n .filter(([key, value]) => typeof value === 'function')n .map(([key, value]) => [key, value.toString()]) // eslint-disable-line no-evaln )n}",
materializeFns: "(fnStrObj = { hello: "() => 'world'" }) => {n return Object.fromEntries(n Object.entries(fnStrObj).map(([key, value]) => {n if (value.startsWith('function')) {n // some trickery is needed to make oldschool functions work :-)n return [key, eval(`() => ${value}`)()] // eslint-disable-line no-evaln } else {n // arrow functions just workn return [key, eval(value)] // eslint-disable-line no-evaln }n })n )n}",
makeHandler: "() => ({n // Used by simple `navigator` getter evasionsn getterValue: value => ({n apply(target, ctx, args) {n // Let's fetch the value first, to trigger and escalate potential errorsn // Illegal invocations like `navigator.__proto__.vendor` will throw heren utils.cache.Reflect.apply(...arguments)n return valuen }n })n})" },
_mainFunction: "(utils, opts) => {n try {n // Adds a contentWindow proxy to the provided iframe elementn const addContentWindowProxy = iframe => {n const contentWindowProxy = {n get(target, key) {n // Now to the interesting part:n // We actually make this thing behave like a regular iframe window,n // by intercepting calls to e.g. `.self` and redirect it to the correct thing. :)n // That makes it possible for these assertions to be correct:n // iframe.contentWindow.self === window.top // must be falsen if (key === 'self') {n return thisn }n // iframe.contentWindow.frameElement === iframe // must be truen if (key === 'frameElement') {n return iframen }n // Intercept iframe.contentWindow[0] to hide the property 0 added by the proxy.n if (key === '0') {n return undefinedn }n return Reflect.get(target, key)n }n }nn if (!iframe.contentWindow) {n const proxy = new Proxy(window, contentWindowProxy)n Object.defineProperty(iframe, 'contentWindow', {n get() {n return proxyn },n set(newValue) {n return newValue // contentWindow is immutablen },n enumerable: true,n configurable: falsen })n }n }nn // Handles iframe element creation, augments `srcdoc` property so we can intercept furthern const handleIframeCreation = (target, thisArg, args) => {n const iframe = target.apply(thisArg, args)nn // We need to keep the originals aroundn const _iframe = iframen const _srcdoc = _iframe.srcdocnn // Add hook for the srcdoc propertyn // We need to be very surgical here to not break other iframes by accidentn Object.defineProperty(iframe, 'srcdoc', {n configurable: true, // Important, so we can reset this latern get: function() {n return _srcdocn },n set: function(newValue) {n addContentWindowProxy(this)n // Reset property, the hook is only needed oncen Object.defineProperty(iframe, 'srcdoc', {n configurable: false,n writable: false,n value: _srcdocn })n _iframe.srcdoc = newValuen }n })n return iframen }nn // Adds a hook to intercept iframe creation eventsn const addIframeCreationSniffer = () => {n /* global document */n const createElementHandler = {n // Make toString() nativen get(target, key) {n return Reflect.get(target, key)n },n apply: function(target, thisArg, args) {n const isIframe =n args && args.length && `${args[0]}`.toLowerCase() === 'iframe'n if (!isIframe) {n // Everything as usualn return target.apply(thisArg, args)n } else {n return handleIframeCreation(target, thisArg, args)n }n }n }n // All this just due to iframes with srcdoc bugn utils.replaceWithProxy(n document,n 'createElement',n createElementHandlern )n }nn // Let's gon addIframeCreationSniffer()n } catch (err) {n // console.warn(err)n }n }",
_args: []
});
但是你要明白,我们这里要教的是chromium浏览器源码层面,以一种更底层的方式来修改。
第二步,定位要修改的源码。如果是使用vs2019打开chromium项目,将会被数以万计的解决方案所淹没。面对这一庞大的工程,小白肯定是目瞪口呆的。因为我们采取在线浏览定位的方式。
chromium在线源码浏览:源码浏览。在这个网站上可以轻松快捷地查看最新版的chromium的源代码,而且有强大的搜索跳转功能,已经能够满足我们修改chromium源码的需求了,因为我们仅仅是要抹除一些特征用于自动化,而非进行浏览器的二次开发。
接着,进入chromium/src当中,切换到third_party/blink目录之下,搜索<span class="ne-text">Navigator::webdriver</span>
,之所以在blink目录之下,是因为chromium使用的是blink内核,在blink之下可以缩小范围,减少搜索量。
许多人可能想问,我为什么知道是搜<span class="ne-text">Navigator::webdriver</span>
?事实上,我也不知道要搜的是这个,我是先搜索了<span class="ne-text">webdriver</span>
,之后又把navigator相关的文件查看完毕,才知道是这个,这里只是为了让各位更快定位。
chromium中的原始代码如下所示:
复制代码 隐藏代码bool Navigator::webdriver() const {
if (RuntimeEnabledFeatures::AutomationControlledEnabled())
return true;
bool automation_enabled = false;
probe::ApplyAutomationOverride(GetExecutionContext(), automation_enabled);
return automation_enabled;
}
我们稍作修改,可以让它永远返回false,或者按照你的需求让它返回其他东西。
复制代码 隐藏代码bool Navigator::webdriver() const {
return false;
}
修改完毕之后,我们返回src目录之下,在cmd中输入<span class="ne-text">autoninja -C outbuild mini_installer</span>
这次的更新会非常迅速,回编译成功之后,我们就可以看到webdriver已经被修改了。
如果要修改<span class="ne-text">isTrust</span>
也是同理的,直接搜索修改就好了:
复制代码 隐藏代码bool isTrusted() const { return is_trusted_; }
4.如何随机浏览器指纹
💡 Tips:随机指纹使用的是一样的方法。
随机指纹使用的方法和上边一般无二。
但是有一个很重要的点需要各位看官明白,浏览器指纹并非是有一个固定的参数或者方法,修改之后就可以过一切网站的指纹检测,不是这样的。
永远要根据你要攻克的网站来动态地确定,不排除大多数网站采取一样的指纹检测策略,比如知名的FP指纹检测:fingerprint。
说白了,和JS逆向一样,你要随机哪个网站的指纹,先要查看一下该网站的JS代码,看看它计算指纹的时候使用的是哪个方法。举个非常简单的例子,拿canvas指纹来说,创建canvas画布会有非常多的API接口可用于指纹计算:
复制代码 隐藏代码function makeTextImage(canvas: HTMLCanvasElement, context: CanvasRenderingContext2D) {
canvas.width = 240 canvas.height = 60 context.textBaseline = 'alphabetic' context.fillStyle = '#f60' context.fillRect(100, 1, 62, 20)
context.fillStyle = '#069' const printedText = `Cwm fjordbank gly ${String.fromCharCode(55357, 56835) /* 😃 */}` context.fillText(printedText, 2, 15)
context.fillStyle = 'rgba(102, 204, 0, 0.2)' context.font = '18pt Arial' context.fillText(printedText, 4, 45)
return save(canvas)
}function save(canvas: HTMLCanvasElement) {
return canvas.toDataURL()
}
canvas画布的创建有<span class="ne-text">width</span>
和<span class="ne-text">height</span>
,可能该网站在计算指纹的时候,会指定宽高,那我们可以直接把底层的宽高随机掉,或者更改为你需要的数值。
或者说这个网站进行指纹检测会使用颜色,那么干脆颜色也随机好了:
那如果我使用的是<span class="ne-text">toDataURL</span>
呢?一样的,随便改改类型或者result就可以:
推及到其他的浏览器指纹上,webgl,webaudio,webrtc,font,css font,navigator,SSL/TLS指纹都是同理的,只要有耐心,一个一个去找对应的代码实现,可以把所有的指纹都随机掉。
5.浏览器固定指纹
💡 Tips:上边讲的方法是定位,或者说可以做到随机,但是遇到了新的需求.....
在随机浏览器指纹的过程中,慢慢遇到了一种新的需求,那就是固定指纹。也就是我事先本地写好一套指纹,然后浏览器开启后就用我这一套指纹,什么时候我想更改了,再替换本地文件就可以了。
这种需求常用于跨境电商,指纹不能纯随机,而且如果每次都写死,每次再回编译,再运行,这样也太麻烦了,浪费了许多时间,那么这种需求怎么解决呢?
非常简单,通过命令行传参的形式来实现,命令行指定使用我们的文件,然后在chromium源码中进行读取即可。对于这种方式,因为利益相关,就不在这里展开了,毕竟我自己有时候也靠这个东西吃饭呢。
本次的分享就到此为止,谢谢诸位捧场。
该内容转载自网络,更多内容请点击“阅读原文”
原文始发于微信公众号(web安全工具库):爬虫自动化之定制浏览器随机指纹
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论