Chrome 绕过< head>(受广泛保护的网站的)中的对象冻结

yduiuuwa  于 2022-12-16  发布在  Go
关注(0)|答案(1)|浏览(131)

背景

我曾经遇到过一个有横幅广告的网站,当一个adblock或者类似的程序删除了横幅广告元素,这个网站福尔斯向alerts发送垃圾邮件,说它无法加载广告,这使得浏览体验比广告更糟糕。
我喜欢修修补补,试图打破像这些网站上的“保护”,然而,在这个时候我已经没有主意了。
问题中的网站似乎采用了广泛的保护措施,防止篡改一般,所以,虽然这个问题是“简单地”做一些与对象冻结在头部,有多个限制,以如何做到这一点。

问题是

首先,我不想通过对横幅做一些特殊的处理来解决这个问题,比如让它们可见而不是删除它们,我想攻击alert
问题是,当网站加载它的index.html时,它会冻结<head>部分中的window.alert对象,如下所示:

<script>
    Object.defineProperty(window, 'alert', {
        configurable: false,
        writable: false
    })
</script>

之后就不可能了,例如:
window.alert = null;
考虑到我们无法触及警报本身,您可能会想到尝试查找并删除对警报的调用...
...不幸的是,这似乎是不可能的,因为该网站将无法工作没有JavaScript和网站加载其脚本的其余部分与CSRF-token.所有加载的脚本也使用CSRF-token在每个请求.
网站最初在头部加载一个“加载器脚本”,它受源策略保护,因此无法从其他任何地方提供此脚本。此加载器脚本本身使用CSRF令牌通过XMLHttpRequest加载其余脚本。
还有content-security-policy-头文件。
这基本上意味着不可能,例如,让一个网络服务器充当代理,它将加载网页,剥离头部的冻结脚本,并传递修改后的网页。至少就我所知,这正是CSRF令牌所防止的。
□ □ □我在哪里
我正在使用一个名为Resource Override的chrome扩展,例如,它可以让我:

  • 为网站设置基于正则表达式的规则和操作
  • 进一步设置基于正则表达式的URL规则和操作,当网站请求一些特定的URL
  • ·“响应”网站做出的任何请求,并以例如本地服务的文件进行响应,其限制是,该本地服务的文件将明显地以需要CSRF令牌的一切失败
  • 在网站上的任意位置注入JavaScript,只要网站已加载
  • 修改请求和响应标头

在头部注射js

对我来说,显而易见的第一步是简单地将一个window.alert = null注入到网站的<head>中,但这并不起作用,因为当页面和页面的html加载时,对象冻结已经存在于头部。
此外,这种方法永远不起作用,因为只有当存在要注入脚本的页面时,才能将脚本注入页面,但是在这种情况下,当这样的页面存在时,对象冻结代码也已经存在。

解冻窗口.alert对象

有很多关于在JavaScript中解冻/解冻冻结对象的Stackoverflow问题和答案。简而言之,这是不可能的。至少在这里没有帮助。

原型污染

老实说,我并不太熟悉原型污染攻击的应用,但我确实研究了一下,因为我觉得摆弄__proto__可以让我在某种程度上使window.alert可配置或可写。
我用我所掌握的知识尝试过的一件事是简单地扩展Object.prototype,它是所有对象的“根原型”,带有一个名为alert的属性,因此:

Object.prototype.alert = 999;

它的作用是当你做这样的事情时:

const arr = [1, 2, 3];
const str = "abc";

for(prop in arr) { 
    console.log(prop) // will log alert at some point
}

console.log(str.alert) // will log 999
  • 有趣的是,正确获取对象的所有可枚举属性名(包括原型链中的属性名)的唯一方法(警报),似乎是使用一个for...in-循环,如图所示。所有其他方法,如方便命名的Object.getOwnPropertyNames,将只直接返回对象上的属性,但不是它的原型链。可以在here中找到相关文档。*

不管怎样,这个想法是:

**A)**幸运的话,也可以覆盖window.alert,因为window也是一个对象,因此应该通过Object.prototype.x进行扩展
**B)**运气好一点,希望网站以某种荒谬的方式调用window.alert,这样在所有内容上都使用alert-property就会把事情搞砸

不出所料,这两件事都没有发生。

代理对象

我还尝试了一下Proxies,最初的理解是它可以让我“挂钩”到函数调用中,这样我就可以拦截对alert-function本身的调用,但这不是代理的工作方式,所以吸取了教训。

不过这确实提出了一个问题,是否有可能以其他方式拦截/侦听JavaScript中的函数调用。显然,我可以用自己的函数 Package 任何函数,但这样一来,所有函数都需要调用 Package 函数,而不是原来的函数,这在这里没有帮助。

反映API

还有一个JavaScript equivalent of a "Reflection API",但从外观和我做的一些测试来看,它也没有提供任何关于挂钩/侦听/拦截/重定向函数调用的功能,尽管文档somewhy指出:
Reflect是一个内置对象,为可拦截的JavaScript操作提供方法。

使用Chrome进行本地覆盖

由Chrome本地覆盖修改的脚本无法通过某种完整性检查,因此将被阻止。

其他想法

像往常一样,这些事情,我摆弄着各种不同的杂项想法,如:

  • 试图替换整个窗口对象,希望“解锁”窗口。alert
  • 天真地尝试将window.alert设置为可配置和可写
  • 尝试了Object.freeze = null,但在可以将其设置为null或进行修改之前,它可能已经被调用

现在怎么办
在我看来,任何事情都是可能的,尽管在这种情况下,解决方案可能会很复杂。我想我已经用尽了大多数快速和简单的方法,但我希望我错过了一些东西。
对我来说,一个仍在进行中的想法是首先尝试拦截和剥离内容安全策略头,以更好地允许拦截和修改获取脚本的请求。我也不确定这最终是否有效,因为似乎还有某种完整性验证正在进行。
有趣的是,似乎没有针对内联脚本的政策,所以内联脚本确实有效。正因为如此,我对基于JavaScript的解决方案最感兴趣,但我会接受任何东西。
我也会非常感兴趣想知道是否有一种方法可以运行JavaScript或者在加载网站之前设置一个“修改过的JavaScript上下文”?我想人们可以修补V8并删除其核心的整个警报功能,但我正在寻找比这少一点的东西。

总结

  • 网站正在发送垃圾邮件window.alert
  • 网站冻结警报对象,使其不可修改
  • 网站需要JavaScript才能运行
  • 网站广泛使用CSRF令牌,甚至在加载脚本时也是如此,因此简单地交换文件是不可能的
  • Website使用XMLHttpRequest加载所有重要脚本,并从受CSRF令牌保护的脚本中执行此操作,该脚本本身受源策略保护
  • 网站使用内容安全策略标头
  • 脚本文件具有适当的完整性检查,因此无法使用例如chrome本地覆盖修改
    *目标是“简单地”禁止此类网站发出警报

最后的话

我不会感到惊讶,因为它的广泛性,只是得到一个核弹,但至少我有乐趣写它。我发现这真的很有趣,如何看似良好的网站,以及它的具体功能,如警报功能,可以防止篡改这些天-调查这一直是一个大开眼界给我。
即便如此,这是相当有点可笑多少不同的措施需要使用;来源检查,完整性检查,散列内联脚本,内容安全策略,CSRF令牌,脚本加载在非常特定的方式和非常特定的地方,...所有这些都是为了防止我设置一个小警报函数为空在某种意义上!
如果有一个外卖从所有这一切,这将是 * 巨大的问题 *,为什么在永恒的f-是不可能简单地阻止网站发送警报?有权限控制的各种功能,那么为什么警报和提示不是其中之一?

编辑

正如我在回答中所说的,事实证明该网站的防篡改措施比我预期的还要广泛,因此我在这里包括了我在事后挖掘出来的内容。即使有人设法使alert对象可写,它仍然不能被轻易替换,因为这将触发额外的显式防篡改检查。
在我的回答中,我将跳过所有这些检查,但下面是我们要处理的问题:

tamperCheck()
{
    for (const check of [
        {ob: Function.prototype, fun: 'toString'},
        {ob: window, fun: 'alert'},
        {ob: window, fun: 'confirm'},
        {ob: Object, fun: 'freeze'},
        {ob: Object, fun: 'seal'},
        {ob: Object, fun: 'getPrototypeOf'},
        {ob: Object, fun: 'getOwnPropertyDescriptors'},
    ]) {
        const regex = new RegExp('^function ' + check.fun + '\\(\\)\\s*{\\s*\\[native code]\\s*}$');
        let d;

        try {
            d = delete check.ob[check.fun]['toString'];
        } catch {
            d = false;
        }

        if (
            !d
            || typeof Object.getPrototypeOf(check.ob[check.fun]) !== 'function'
            || typeof check.ob[check.fun] !== 'function'
            || check.ob[check.fun].name !== check.fun
            || check.ob[check.fun].toString().replaceAll('\n', '').match(regex) === null
        ) {
            return true;
        }

        Object.defineProperty(check.ob, check.fun, {value: check.ob[check.fun], configurable: false, writable: false});
    }

    return false;
}
r7knjye2

r7knjye21#

溶液
嗯,我无法入睡,直到我打败了这个东西,所以我做到了。这个解决方案被证明是相当复杂的,希望有更容易和更简单的解决方案在那里,但这是我的。

概述

这个解决方案需要创建一个chrome扩展。Manifest V2 extension是具体的。不幸的是,Manifest V2 extension将在2023年变得不受支持,正如我的扩展中的错误所示:
清单版本2已弃用,并且将在2023年移除支持。有关详细信息,请参阅https://developer.chrome.com/blog/mv2-transition/
但是现在这个方法是可行的,我对chrome扩展不是很熟悉,所以有可能用Manifest V3扩展也能做到。
该扩展可以做很多事情:

  • 它修改main_frame和sub_frame请求响应标头的content-security-policy标头,以便我们可以注入内联脚本
  • 它利用run_at: "document_start"内容脚本选项来尽早执行脚本的某些部分(感谢@evolutionxbox)
  • 它使用MutationObserver在执行静态<script>节点之前拦截这些节点
  • 它将一个脚本注入到访问和修补window.alert的页面
  • 它使用Proxy对象来隐藏已修补的window.alert,使其无法进行防篡改检查

绕过内容安全策略

当我切换到Manifest V2时,这个非常简单。我无法让它与Manifest V3扩展一起工作。我们使用declarativeNetRequest API让扩展修改content-security-policy头。

manifest.json需要以下属性(在常用属性之上):

{
  "permissions": [
    "declarativeNetRequest",
    "declarativeNetRequestWithHostAccess",
    "declarativeNetRequestFeedback",
    "*://<website.domain>/*"
  ],

  "declarative_net_request" : {
    "rule_resources" : [
      {
        "id": "ruleset_1",
        "enabled": true,
        "path": "rules.json"
      }
    ]
  }
}

rules.json看起来像这样:

[
  {
    "id" : 1,
    "priority": 1,

    "action" : { 
      "type" : "modifyHeaders",

      "responseHeaders": [
        { "header": "content-security-policy", "operation": "set", "value": "default-src * data: blob: filesystem: about: ws: wss: 'unsafe-inline' 'unsafe-eval'; script-src * data: blob: 'unsafe-inline' 'unsafe-eval'; " }
      ]
    },

    "condition" : {
      "urlFilter": "*://*/*",
      "resourceTypes": ["main_frame", "sub_frame", "script", "other"]
    }
  }
]

这样的manifest.json与rules.json结合在一起,使得扩展能够修改content-security-policy头,从而可以将内联脚本注入到页面中。The documentation here解释了为什么我们显然必须使用V2 manifest版本。

运行时间:“文档开始”

要在document_start运行扩展,manifest.json需要包含以下选项块:

"content_scripts": [
  {
    "matches": ["<website>"],
    "js": ["inject.js"],
    "run_at": "document_start"
  }
]

这将使扩展在document_start处执行inject.js中的任何内容,即使文档还没有加载。

拦截静态<script>节点创建

既然我们的脚本可以足够早地执行,那么在文档还没有加载之前,我们就可以设置一个MutationObserver来拦截添加到Web页面的静态服务<script>节点。

这是inject.js的开头:

class ScriptInterceptor {
    #observer
    #config = { childList: true, subtree: true };
    #handlerFn

    constructor(handlerFn) {
        this.#handlerFn = handlerFn;
        this.#observer = new MutationObserver(this.#callback.bind(this));
        this.#observer.observe(document, this.#config);
    }

    #isScriptMutation = (node) => { 
        return node.tagName === "SCRIPT";
    }

    #callback(mutationList, observer) {
        for(const mutation of mutationList) {
            for(const node of mutation.addedNodes) {
                if(this.#isScriptMutation(node)) {
                    this.#handlerFn(node);
                }
            }
        }
    }
}

const scriptTrace = "Object.defineProperty(window,'alert',{configurable:false,writable:false})";

const interceptor = new ScriptInterceptor((script) => {
    if(script.textContent.includes(scriptTrace)) {
        script.textContent = "";
        script.remove();
    }
});

我创建了一个ScriptInterceptor类,它在内部创建了一个MutationObserver示例,以监视整个文档中元素子列表或子树的任何变化。
当MutationObserver注意到一个或一组DOM突变时,我们会检查每个突变和受影响的DOM节点,同时过滤掉所有不是<script>节点的内容。
当我们遇到一个<script>节点时,我们调用一个handler函数,并将找到的节点传递给它。在本例中,我们的handler函数如下所示:

(script) => {
  if(script.textContent.includes(scriptTrace)) {
    script.textContent = "";
    script.remove();
  }
}

其中scriptTrace是冻结window.alert对象的特定<script>元素的代码内容:

const scriptTrace = "Object.defineProperty(window,'alert',{configurable:false,writable:false})";

这样就消除了对象的冻结,但是我们仍然没有对window.alert本身做任何事情来禁用它。

关于MutationObserver和静态提供的<script>块的旁注

所以我在这里使用了很多术语static,这是有原因的。MutationObserver方法不适用于动态添加到页面的脚本,例如这样的脚本:

const dynamicScript = document.createElement('script');
dynamicScript.text = `(() => { console.log("Hello!") })()`;
document.documentElement.appendChild(dynamicScript);

虽然MutationObserver会捕捉到它们被添加到页面,但它只会在脚本已经执行之后才这么做。
我的情况并不需要像这样处理拦截脚本,但我确实出于兴趣找到了一个解决方案,如果有人会发现自己在这种情况下:

Element.prototype.appendChild = () => { };
Element.prototype.append = () => { };
Element.prototype.prepend = () => { };
Element.prototype.after = () => { };
Element.prototype.before = () => { };
Element.prototype.insertAdjacentElement = () => { };
Element.prototype.insertAdjacentHTML = () => { };
Element.prototype.insertAdjacentText = () => { };
Element.prototype.replaceChildren = () => { };
Element.prototype.replaceWith = () => { };
Element.prototype.setHTML = () => { };

Object.defineProperty(Element.prototype, "appendChild", { configurable:false, writable:false });
Object.defineProperty(Element.prototype, "append", { configurable:false, writable:false });
Object.defineProperty(Element.prototype, "prepend", { configurable:false, writable:false });
Object.defineProperty(Element.prototype, "after", { configurable:false, writable:false });
Object.defineProperty(Element.prototype, "before", { configurable:false, writable:false });
Object.defineProperty(Element.prototype, "insertAdjacentElement", { configurable:false, writable:false });
Object.defineProperty(Element.prototype, "insertAdjacentHTML", { configurable:false, writable:false });
Object.defineProperty(Element.prototype, "insertAdjacentText", { configurable:false, writable:false });
Object.defineProperty(Element.prototype, "replaceChildren", { configurable:false, writable:false });
Object.defineProperty(Element.prototype, "replaceWith", { configurable:false, writable:false });
Object.defineProperty(Element.prototype, "setHTML", { configurable:false, writable:false });

这只是一个样板文件,但这里的想法是,我们实际上可以覆盖所有可能用于将<script>标记插入到页面中的方法,并由它们自己锁定它们。关键是不要让这些方法为空,而是在那里实现某种逻辑,以捕获并取消感兴趣的动态脚本的插入,并使其他一切正常工作。不过知道了也不错。

修补window.alert并使其不可检测

js脚本的其余部分如下所示:

const inject = () => {
    const fn = () => {

        // Alert actor
        const alert = () => { }

        alert.toString = () => { return "function alert() { [native code] }" }

        const antiTamperCheckFaker = {
            deleteProperty() {
                return true;
            },

            getPrototypeOf() {
                return () => { };
            }
        }

        const alertProxy = new Proxy(alert, antiTamperCheckFaker);
        window.alert = alertProxy;
        Object.defineProperty(window, 'alert', { configurable: false, writable: false });
    }

    const e = document.createElement('script');
    e.text = `(${fn.toString()})()`;
    document.documentElement.appendChild(e);
}

inject();

我们在这里做的事情本质上很简单--我们将window.alert转换为一个什么都不做的函数() => { }
然而,当我说到这里时,网站开始变砖了,因为我的window.alert补丁被网页中的一些反篡改检查检测到了。深入挖掘后,我发现了下面的篡改检查函数(我也将把它编辑到原始问题中):

tamperCheck()
{
    for (const check of [
        {ob: Function.prototype, fun: 'toString'},
        {ob: window, fun: 'alert'},
        {ob: window, fun: 'confirm'},
        {ob: Object, fun: 'freeze'},
        {ob: Object, fun: 'seal'},
        {ob: Object, fun: 'getPrototypeOf'},
        {ob: Object, fun: 'getOwnPropertyDescriptors'},
    ]) {
        const regex = new RegExp('^function ' + check.fun + '\\(\\)\\s*{\\s*\\[native code]\\s*}$');
        let d;

        try {
            d = delete check.ob[check.fun]['toString'];
        } catch {
            d = false;
        }

        if (
            !d
            || typeof Object.getPrototypeOf(check.ob[check.fun]) !== 'function'
            || typeof check.ob[check.fun] !== 'function'
            || check.ob[check.fun].name !== check.fun
            || check.ob[check.fun].toString().replaceAll('\n', '').match(regex) === null
        ) {
            return true;
        }

        Object.defineProperty(check.ob, check.fun, {value: check.ob[check.fun], configurable: false, writable: false});
    }

    return false;
}

它看起来有点混乱,但我们可以让它更简单,如果我们说:
check.ob = windowcheck.fun = alert
首先,我们可以将RegExp-line中的check.fun替换为“alert”:

const regex = new RegExp('^function alert \\(\\)\\s*{\\s*\\[native code]\\s*}$');

这个正则表达式在这里还没有用到,但是我会回到它上面。接下来我们有try... catch块:

let d;

try {
    d = delete check.ob[check.fun]['toString'];
} catch {
    d = false;
}

这实际上是检查是否可以成功执行delete window["alert"]["toString"]。如果我们看一下我的代码:

Object.defineProperty(window, 'alert', { configurable: false, writable: false });


我自己正在冻结alert对象,因此此delete操作将失败,因为您无法从冻结的对象中删除toString之类的属性或方法。
这是代理对象第一次发挥作用。正如你从我的代码中所读到的,我们实际上并没有设置window.alert = alert,而是做了window.alert = alertProxy。这个alertProxy基本上是我们前面定义的const alert = () => { },但它有一个额外的好处,我们可以为它指定特定的处理程序:

const antiTamperCheckFaker = {
    deleteProperty() {
        return true;
    },

    getPrototypeOf() {
        return () => { };
    }
}

为了绕过这个删除检查,我们“钩住”了delete window["alert"]["toString"]执行时调用的删除函数。我们覆盖了默认行为,不删除任何东西,而是简单地说“好的,它被删除了!"我们不能删除那个toString,因为我们已经覆盖了它,我们以后需要它。
接下来是typeof Object.getPrototypeOf(check.ob[check.fun]) !== 'function'检查,我们可以写为:

typeof Object.getPrototypeOf(window["alert"]) !== 'function'

这将检查window.alert的原型是否为函数().这不一定是个问题,因为我们的“警报参与者”应该是一个函数,但我记得根据经验,在某些情况下,使用代理可能会触发这样的检查。如果我们想要或必须使用对象而不是函数作为警报参与者,这也是这个检查失败的时候。如果需要的话,这可以用getPrototypeOf处理程序绕过,我们在antiTamperCheckFaker中有这个处理程序。
)虽然它清楚地表明了!== 'function',但如果任何一个检查为真,则if子句作为一个整体返回true,因此,如果window.alert不是函数,则检查的该部分将计算为true,从而使检查返回true,这是错误的。
然后是check.ob[check.fun] !== 'function'检查,它检查window.alert本身是否是一个函数,同样,我们用来替换window.alert的警告只需要是一个函数。

  • 如果我们可以用一个对象替换window.alert,我们可以更容易地绕过这些检查,但是这个检查本质上是一个要求,即无论我们用什么替换window.alert,它都必须是某种函数。*

然后我们进行check.ob[check.fun].name !== check.fun检查。这一次检查window中的当前alert函数的名称是否实际上不是alert。如果我们有以下情况,这将失败:
const myPatchedAlert = () => { }
而不是:
const alert = () => { }
在这种情况下,check.ob[check.fun].name将评估为“myPatchedAlert”。
然后我们有一张更大的支票

check.ob[check.fun].toString().replaceAll('\n', '').match(regex) === null

现在这个很有趣,你可以看到如果你在你的开发控制台输入window.alert.toString()会发生什么,但是我会破坏它,告诉你它会输出'function alert() { [native code] }',我们之前看到的难看的正则表达式,所以这个:

const regex = new RegExp('^function alert \\(\\)\\s*{\\s*\\[native code]\\s*}$');

基本上就是做一个比较,比如:
window.alert.toString() === 'function alert() { [native code] }'
当我们的警报const alert = () => { }.toString()调用时,它将输出() => { },这将使检查失败。幸运的是,我们覆盖了警报的toString()方法,以返回类似的[native code]:

alert.toString = () => { return "function alert() { [native code] }" }

虽然创建我们自己的toString()方法并让它返回我们想要的任何东西是很容易的,但请记住,在运行此检查之前,篡改检查实际上试图从警报中删除toString()方法。
我们通过冻结自己的警报来防止toString()方法被删除,但是我们还必须拦截删除操作,以便检查认为它成功地删除了不可删除的toString()
顺便说一句,我们不能使用Function.prototype.toString = ...,因为Function.prototype.toString = ...会保护它不被删除,因为它会是一个原型函数而不是alert的成员,因为检查也会检查Function.prototypetoString是否被篡改。
事实证明并非如此,Bergi给出了一个聪明的基于Function.prototype的解决方案:

Function.prototype.toString = function toString() { 
    return `function ${this.name}() { [native code] }`; 
};

无论如何
我得道歉。这整件事有点失控。所以这里可能不是我经历过的自我实现冒险的最佳地点。
我的辩护是,我确实提出了很多不错的解决方案,我想,这些方案涉及到扩展、脚本注入、防篡改方法和浏览器安全性等不寻常的问题。希望有人会发现自己在这里寻找答案。

完整文件

清单. json

{
  "name": "document_start script exector",
  "description": "document_start script exector",
  "version": "1.0",
  "manifest_version": 2,
  "permissions": [
    "declarativeNetRequest",
    "declarativeNetRequestWithHostAccess",
    "declarativeNetRequestFeedback",
    "*://<website>/*"
  ],

  "content_scripts": [
    {
      "matches": ["<website>"],
      "js": ["inject.js"],
      "run_at": "document_start"
    }
  ],

  "declarative_net_request" : {
    "rule_resources" : [
      {
        "id": "ruleset_1",
        "enabled": true,
        "path": "rules.json"
      }
    ]
  }
}

规则. json

[
  {
    "id" : 1,
    "priority": 1,

    "action" : { 
      "type" : "modifyHeaders",

      "responseHeaders": [
        { "header": "content-security-policy", "operation": "set", "value": "default-src * data: blob: filesystem: about: ws: wss: 'unsafe-inline' 'unsafe-eval'; script-src * data: blob: 'unsafe-inline' 'unsafe-eval'; " }
      ]
    },

    "condition" : {
      "urlFilter": "*://*/*",
      "resourceTypes": ["main_frame", "sub_frame", "script", "other"]
    }
  }
]

进样. js

class ScriptInterceptor {
    #observer
    #config = { childList: true, subtree: true };
    #handlerFn

    constructor(handlerFn) {
        this.#handlerFn = handlerFn;
        this.#observer = new MutationObserver(this.#callback.bind(this));
        this.#observer.observe(document, this.#config);
    }

    #isScriptMutation = (node) => { 
        return node.tagName === "SCRIPT";
    }

    #callback(mutationList, observer) {
        for(const mutation of mutationList) {
            for(const node of mutation.addedNodes) {
                if(this.#isScriptMutation(node)) {
                    this.#handlerFn(node);
                }
            }
        }
    }
}

const scriptTrace = "Object.defineProperty(window,'alert',{configurable:false,writable:false})";

const interceptor = new ScriptInterceptor((script) => {
    if(script.textContent.includes(scriptTrace)) {
        script.textContent = "";
        script.remove();
    }
});

const inject = () => {
    const fn = () => {

        // Alert actor
        const alert = () => { }

        alert.toString = () => { return "function alert() { [native code] }" }

        const antiTamperCheckFaker = {
            deleteProperty() {
                return true;
            },

            getPrototypeOf() {
                return () => { };
            }
        }

        const alertProxy = new Proxy(alert, antiTamperCheckFaker);
        window.alert = alertProxy;
        Object.defineProperty(window, 'alert', { configurable: false, writable: false });
    }

    const e = document.createElement('script');
    e.text = `(${fn.toString()})()`;
    document.documentElement.appendChild(e);
}

inject();

相关问题