HOOK 构造器
1、asyncFunction 通过闭包钩子拦截和替换 JavaScript 中的 AsyncFunction
构造器
// --- 拦截 Async Function --- // 1. 获取异步函数的原型构造器(即 `AsyncFunction` 构造器) // `async function() {}` 是一个异步函数表达式,通过获取其原型的构造器属性, // 可以获得系统内置的 `AsyncFunction` 构造器。 var oldAsyncFunctionConstructor = Object.getPrototypeOf(async function() {}).constructor; // 2. 使用 `Closure` 函数包装原始的 `AsyncFunction` 构造器。 // 这样创建的新构造器在执行时,会首先替换掉代码中的 `debugger` 字符串。 var newAsyncFunctionConstructor = Closure(oldAsyncFunctionConstructor); // 3. 将原始的 `toString` 方法绑定到新的构造器。 // 这样可以确保调用 `newAsyncFunctionConstructor.toString()` 时, // 返回的是原始构造器的内容,而不是包装后的版本。 // 这个步骤是为了保证新构造器在输出时表现得与原始构造器一致。 newAsyncFunctionConstructor.toString = oldAsyncFunctionConstructor.toString.bind(oldAsyncFunctionConstructor); // 4. 将拦截后的新构造器赋值为异步函数的构造器。 // 使用 `Object.defineProperty` 可以直接设置 `AsyncFunction` 构造器, // 将新构造器绑定到原来的 `AsyncFunction` 原型上。 // 设置 `writable: false` 禁止重写,但 `configurable: true` 保留重新配置的可能性。 Object.defineProperty(oldAsyncFunctionConstructor.prototype, "constructor", { value: newAsyncFunctionConstructor, // 设置新的构造器为新包装后的构造器 writable: false, // 设置 `writable: false`,不允许外部修改构造器 configurable: true // 允许重新配置 });
代码解释
- 获取
AsyncFunction
构造器:AsyncFunction
是异步函数的构造器,但它在 JavaScript 中并没有暴露为直接调用的全局对象。因此,代码通过async function() {}
获取其构造器。 - 使用
Closure
函数包装:使用Closure
函数来包装原始构造器,使得每次使用AsyncFunction
创建异步函数时,debugger
关键字会被自动移除。 - 绑定
toString
方法:绑定原始toString
方法,以确保输出的代码内容一致,避免暴露包装后的构造器。 - 更新构造器定义:将
newAsyncFunctionConstructor
设置为异步函数的构造器,保证所有异步函数的创建行为都使用新的构造器。
2、这段代码分为两个部分:一部分是拦截 setAttribute
方法以移除 debugger
,另一部分是拦截 iframe
元素的 contentWindow
属性,以便在 iframe 中注入代码。以下是详细的注释和解释:
第一部分:拦截 setAttribute
方法
// --- 拦截 setAttribute 方法 --- // 1. 备份原始的 `setAttribute` 方法 // `setAttribute` 是 DOM 元素中用于设置属性的方法。 var oldSetAttribute = window.Element.prototype.setAttribute; // 2. 重写 `setAttribute` 方法 // 这里定义一个新函数覆盖 `setAttribute`,以便拦截属性设置的值。 window.Element.prototype.setAttribute = function(name, value) { // 3. 检查属性值是否为字符串,并移除其中的 `debugger` 关键字 // 如果 `value` 是字符串,则使用正则表达式去除 `debugger` 关键字。 if (typeof value == "string") value = value.replace(/debugger/g, ""); // 4. 调用原始的 `setAttribute` 方法,传递修改后的值 // 通过 `.call(this, name, value)` 保证 `setAttribute` 方法的上下文和参数一致。 oldSetAttribute.call(this, name, value); };
代码解释
- 备份原始方法:代码将
setAttribute
方法的原始版本保存为oldSetAttribute
。 - 拦截属性设置:当用户调用
setAttribute
设置 DOM 元素属性时,拦截输入值。 - 移除
debugger
:如果属性值是字符串类型,删除其中的debugger
关键字。 - 调用原始方法:调用原始
setAttribute
方法,将修改后的值设置到元素属性中。
第二部分:拦截 iframe
的 contentWindow
属性
// --- 拦截 iframe 的 contentWindow 属性 --- // 1. 获取 iframe 元素的 `contentWindow` 属性描述符中的 `get` 方法 // 这里通过 `Object.getOwnPropertyDescriptor` 方法获取 `contentWindow` 属性的 getter 方法。 var oldContentWindow = Object.getOwnPropertyDescriptor(HTMLIFrameElement.prototype, "contentWindow").get; // 2. 重写 `contentWindow` 的 getter // 使用 `Object.defineProperty` 重新定义 `contentWindow` 属性,添加自定义逻辑。 Object.defineProperty(window.HTMLIFrameElement.prototype, "contentWindow", { get() { // 3. 调用原始的 `contentWindow` getter 方法,获取 iframe 的 `contentWindow` 对象 var newV = oldContentWindow.call(this); // 4. 检查 `contentWindow` 对象上是否存在 `inject` 属性 // 如果 `inject` 属性不存在,则添加它并调用 `core` 函数进行代码注入。 if (!newV.inject) { newV.inject = true; // 设置 `inject` 属性为 `true` core.call(newV, globalConfig, newV); // 执行 `core` 函数,将配置注入到 iframe 的 `contentWindow` } // 5. 返回 `contentWindow` 对象 return newV; } });
代码解释
- 获取
contentWindow
的原始 getter:通过Object.getOwnPropertyDescriptor
获取 iframe 的contentWindow
属性的原始 getter 方法,以便后续调用。 - 拦截
contentWindow
的访问:重新定义contentWindow
的 getter 方法,添加代码注入逻辑。 - 注入代码:每次访问 iframe 的
contentWindow
时,检查是否已经设置inject
属性。如果未设置,则调用core
函数将globalConfig
和newV
注入到 iframe 的上下文中,以实现代码注入。 - 返回
contentWindow
:返回 iframe 的contentWindow
对象,确保代码逻辑不会影响对contentWindow
的正常访问。
总结
这段代码实现了两个主要功能:
- 拦截
setAttribute
方法:自动删除所有通过setAttribute
设置的字符串属性值中的debugger
关键字。 - 拦截 iframe 的
contentWindow
属性:每次访问 iframe 的contentWindow
时,在其上下文中注入指定的代码。这种方式常用于防止 iframe 内部的 JavaScript 执行带有debugger
的代码,并在加载 iframe 时注入特定的功能或配置。
3、通过重写constructor 来过debugger
// --- Function.prototype.constructor 构造器反调试 --- // 1. 保存原始的构造器函数,备份到 `_constructor` 变量中 var _constructor = Function.prototype.constructor; // 2. 重写 `Function.prototype.constructor`,用于拦截 `Function` 构造的代码 Function.prototype.constructor = function(s) { // 3. 检查传入的字符串 `s` 是否为 "debugger"。 // 如果 `s` 等于 "debugger",则输出到控制台,并返回 `null`,阻止执行。 if (s == "debugger") { console.log(s); return null; // 返回 `null`,防止构造 `debugger` 语句 } // 4. 否则,调用原始构造器 `_constructor` 来处理其他代码 return _constructor(s); };
4、通过重写eval来过debugger反调试
// --- eval 构造器的反调试 --- (function() { 'use strict'; // 1. 备份原始的 `eval` 函数,保存到 `eval_` 变量中 var eval_ = window.eval; // 2. 重写 `window.eval` 函数 window.eval = function(x) { // 3. 调用原始的 `eval`,在执行之前,将代码中的 "debugger;" 替换为空白字符 eval_(x.replace("debugger;", " ; ")); }; // 4. 将新的 `eval` 的 `toString` 方法指向原始的 `eval.toString`, // 以便在外部查看时,新 `eval` 的输出保持一致。 window.eval.toString = eval_.toString; })();