JS 逆向 Hook 与代码片段大全

Hook 的核心原理是利用 JavaScript 弱类型语言特性,用自定义函数替换原生函数,在调用时插入 debugger 或日志,从而定位加密逻辑。 两种核心实现方式: Hook 注入方式 浏览器 DevTools 场景 Node.js 补环境场景 参考 监控某个对象上指定属性的读取、写入、删除操作,精确定位属性被谁写入。 代理整个对象,监控其上所有属性的读写操作,适合不知道属性名时的全量监控。 对整个对象做 Proxy 代理,每次访问都打印 对象名.属性名 的完整路径,适合追踪链式访问。 Hook 内置对象的原型方法,监控 String、Array、

分享

官方文档:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript

Hook 的核心原理是利用 JavaScript 弱类型语言特性,用自定义函数替换原生函数,在调用时插入 debugger 或日志,从而定位加密逻辑。

被 Hook 的函数必须和 debugger 在同一个作用域内,否则 Hook 无效。Hook 代码必须在目标代码执行之前注入,推荐通过 Tampermonkey 的 @run-at document-start 保证优先级。

两种核心实现方式:

方式 适用场景 备注
Object.defineProperty 监控对象属性读写 对已有属性可精确控制 getter/setter
Proxy 代理整个对象的所有操作 更全面,但需要通过代理对象访问才能触发

目录

Hook 注入方式

浏览器 DevTools 场景

Node.js 补环境场景

参考


注入方式

方式 说明
控制台直接粘贴 刷新页面前在 Console 面板粘贴执行,适合快速测试
Tampermonkey 脚本 写成用户脚本,@run-at document-start,持久化注入
Chrome Local Override 下载目标 JS 文件,添加 Hook 代码,启用本地覆盖
Fiddler/mitmproxy 注入 通过代理在响应体中动态插入 Hook 代码

1. 对象监控

1.1 Object.defineProperty 监控属性读写

监控某个对象上指定属性的读取、写入、删除操作,精确定位属性被谁写入。

// 使用场景:追踪 window 上某个变量被谁赋值
(function () {
    var _value;
    Object.defineProperty(window, 'targetProp', {
        configurable: true,
        get: function () {
            console.log('[GET] targetProp =', _value);
            console.log(new Error().stack);
            return _value;
        },
        set: function (val) {
            console.log('[SET] targetProp =', val);
            console.log(new Error().stack);
            _value = val;
        }
    });
})();

1.2 Proxy 代理整个对象

代理整个对象,监控其上所有属性的读写操作,适合不知道属性名时的全量监控。

// 使用场景:监控 window 对象上所有属性的 get/set,找出未知的加密全局变量
const proxyWindow = new Proxy(window, {
    get: function (target, prop, receiver) {
        console.log('[GET]', prop, '=', target[prop]);
        return Reflect.get(target, prop, receiver);
    },
    set: function (target, prop, val) {
        console.log('[SET]', prop, '=', val);
        console.log(new Error().stack);
        return Reflect.set(target, prop, val);
    },
    deleteProperty: function (target, prop) {
        console.log('[DELETE]', prop);
        return Reflect.deleteProperty(target, prop);
    }
});

// 注意:Proxy 是一个壳,必须通过 proxyWindow 访问才会触发拦截
// window = proxyWindow; // 谨慎操作,某些场景会破坏页面

1.3 监控对象上特定属性(通用模板)

// 使用场景:监控任意对象 obj 上名为 propName 的属性
function watchProperty(obj, propName) {
    var _val = obj[propName];
    Object.defineProperty(obj, propName, {
        configurable: true,
        enumerable: true,
        get: function () {
            console.log('[GET]', propName, '=', _val);
            return _val;
        },
        set: function (newVal) {
            console.log('[SET]', propName, ':', _val, '->', newVal);
            console.log(new Error().stack);
            _val = newVal;
        }
    });
}

// 调用示例
watchProperty(window, 'sign');
watchProperty(window, '_token');

1.4 watch() — 带路径追踪的 Proxy 监控

对整个对象做 Proxy 代理,每次访问都打印 对象名.属性名 的完整路径,适合追踪链式访问。

function watch(obj, name) {
    const handler = {
        get: function (target, prop) {
            console.log('get:', `${name}.${prop}`, '===>', Reflect.get(...arguments));
            return Reflect.get(...arguments);
        },
        set: function (target, prop, value, receiver) {
            console.log('set:', `${name}.${prop}`, '===>', value);
            return Reflect.set(target, prop, value, receiver);
        }
    };
    return new Proxy(obj, handler);
}

// 用法:把目标对象替换为被监控的代理对象
window.navigator = watch(window.navigator, 'navigator');

// 监控自定义对象
const config = watch({ token: '', uid: 0 }, 'config');
config.token = 'abc';  // 控制台输出: set: config.token ===> abc

2. 函数 Hook

2.1 简单函数替换(记录参数和返回值)

// 使用场景:Hook 任意全局函数,记录每次调用的入参和出参
(function () {
    var _original = window.targetFunction;
    window.targetFunction = function () {
        console.log('[CALL] targetFunction args:', arguments);
        console.log(new Error().stack);
        var result = _original.apply(this, arguments);
        console.log('[RETURN] targetFunction result:', result);
        return result;
    };
    // 伪装成原生函数,防止被检测
    window.targetFunction.toString = function () {
        return _original.toString();
    };
})();

2.2 Hook alert(常用于快速测试)

// 使用场景:阻止页面弹出 alert 干扰调试,同时记录 alert 内容
var _alert = window.alert;
window.alert = function () {
    console.log('[ALERT]', arguments[0]);
    console.log(new Error().stack);
    // _alert.apply(this, arguments); // 注释掉则阻止弹窗
};
window.alert.toString = function () {
    return 'function alert() { [native code] }';
};

2.3 通用函数 Hook 模板(带 debugger)

// 使用场景:在加密函数调用时自动断下,配合调用栈分析
(function () {
    var _fn = window.encryptFunc; // 替换为目标函数名
    window.encryptFunc = function () {
        debugger; // 断下后查看 Call Stack
        return _fn.apply(this, arguments);
    };
    window.encryptFunc.toString = _fn.toString.bind(_fn);
})();

3. 原型链 Hook

Hook 内置对象的原型方法,监控 String、Array、Object 等原生方法的调用。

3.1 String.prototype.split Hook

// 使用场景:某些加密函数通过 split 分割字符串,hook 后可找到分割时机
String.prototype._split = String.prototype.split;
String.prototype.split = function (val) {
    var str = this.toString();
    if (str.length > 10) { // 过滤短字符串,减少噪音
        console.log('[split] this =', str, ', separator =', val);
        console.log(new Error().stack);
    }
    return str._split(val);
};
String.prototype.split.toString = function () {
    return 'function split() { [native code] }';
};

3.2 RegExp.prototype.test Hook

// 使用场景:绕过正则检测(如反调试检测、UA 检测),或定位正则使用位置
RegExp.prototype._test = RegExp.prototype.test;
RegExp.prototype.test = function (val) {
    console.log('[RegExp.test] pattern =', this.toString(), ', input =', val);
    return this._test(val);
    // 如需强制返回 true:return true;
};
RegExp.prototype.test.toString = function () {
    return 'function test() { [native code] }';
};

3.3 Array.prototype.push Hook

// 使用场景:监控数组被 push 元素,定位加密结果被存入数组的位置
Array.prototype._push = Array.prototype.push;
Array.prototype.push = function () {
    console.log('[Array.push] item =', arguments[0], ', array length =', this.length);
    console.log(new Error().stack);
    return this._push.apply(this, arguments);
};
Array.prototype.push.toString = function () {
    return 'function push() { [native code] }';
};

3.4 Object.prototype 通用 Hook(慎用)

// 使用场景:监控某个属性名在所有对象上的读写(很重,调试时用,上线禁用)
(function () {
    var _defineProperty = Object.defineProperty;
    Object.defineProperty = function (obj, prop, descriptor) {
        if (prop === 'sign') { // 替换为目标属性名
            console.log('[defineProperty] prop =', prop, ', obj =', obj);
            console.log(new Error().stack);
        }
        return _defineProperty.call(this, obj, prop, descriptor);
    };
})();

4. XMLHttpRequest Hook

4.1 完整 XHR Hook(记录请求方法、URL、请求头、请求体)

// 使用场景:全量监控 XHR 请求,记录所有请求信息和调用栈
(function () {
    var hook_open = XMLHttpRequest.prototype.open;
    var hook_setRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
    var hook_send = XMLHttpRequest.prototype.send;

    XMLHttpRequest.prototype.open = function () {
        this._method = arguments[0];
        this._url = arguments[1];
        return hook_open.call(this, ...arguments);
    };

    XMLHttpRequest.prototype.setRequestHeader = function () {
        console.log(
            '[XHR setRequestHeader] url =', this._url,
            '\n  header:', arguments[0], '=', arguments[1]
        );
        console.log(new Error().stack);
        return hook_setRequestHeader.call(this, ...arguments);
    };

    XMLHttpRequest.prototype.send = function () {
        this._data = arguments[0];
        if (this._data != null) {
            console.log(
                '[XHR send]\n  method:', this._method,
                '\n  url:', this._url,
                '\n  body:', this._data
            );
        } else {
            console.log('[XHR send]\n  method:', this._method, '\n  url:', this._url);
        }
        console.log(new Error().stack);
        return hook_send.call(this, ...arguments);
    };
})();

4.2 XHR URL 断点 Hook(定位请求发起位置)

// 使用场景:URL 包含目标关键字时断下,定位请求发起的代码位置
(function () {
    var _open = window.XMLHttpRequest.prototype.open;
    window.XMLHttpRequest.prototype.open = function (method, url, async) {
        if (url.indexOf('login') !== -1) {  // 替换为目标 URL 关键字
            debugger;
        }
        return _open.apply(this, arguments);
    };
})();

4.3 XHR 请求头断点 Hook(定位自定义签名 header)

// 使用场景:定位 X-Sign、Authorization 等自定义请求头的生成位置
(function () {
    var _setRequestHeader = window.XMLHttpRequest.prototype.setRequestHeader;
    window.XMLHttpRequest.prototype.setRequestHeader = function (key, value) {
        if (key === 'X-Sign') {  // 替换为目标 header key
            debugger;
        }
        return _setRequestHeader.apply(this, arguments);
    };
})();

4.4 XHR 响应拦截 Hook

// 使用场景:监控并可修改 XHR 的响应数据
(function () {
    var _open = XMLHttpRequest.prototype.open;
    XMLHttpRequest.prototype.open = function (method, url) {
        this._url = url;
        this.addEventListener('readystatechange', function () {
            if (this.readyState === 4) {
                console.log('[XHR response] url =', this._url);
                console.log('[XHR response] status =', this.status);
                console.log('[XHR response] body =', this.responseText.substring(0, 200));
            }
        });
        return _open.apply(this, arguments);
    };
})();

4.5 Proxy 方式 Hook XHR(拦截构造过程)

// 使用场景:在创建 XHR 实例时即介入,适合需要修改响应的场景
XMLHttpRequest = new Proxy(XMLHttpRequest, {
    construct: function (target, args) {
        var xhr = new target(...args);
        xhr.onreadystatechange = function () {
            if (xhr.readyState === 1) {
                // 请求发送前,可在此注入 header
                xhr.setRequestHeader('X-Custom', 'injected');
            }
            if (xhr.readyState === 4) {
                if (xhr.status === 401) {
                    console.log('[XHR] 401 Unauthorized');
                }
            }
        };
        return xhr;
    }
});

5. fetch Hook

5.1 基础 fetch Hook(记录所有参数和调用栈)

// 使用场景:全量监控 fetch 请求,记录 URL、配置项和调用栈
(function () {
    var _fetch = window.fetch;
    window.fetch = function () {
        console.log('[fetch] args:', ...arguments);
        console.log(new Error().stack);
        return _fetch.apply(this, arguments);
    };
})();

5.2 fetch 请求拦截(可修改请求参数)

// 使用场景:拦截特定 URL 的 fetch 请求,可修改请求参数或断下
const { fetch: _fetch } = window;
window.fetch = async function (...args) {
    let [resource, config] = args;

    // 请求拦截:打印请求信息
    console.log('[fetch request] url =', resource);
    console.log('[fetch request] config =', config);

    // 可在此修改 resource 或 config
    // resource = 'https://new-url.example.com/api';

    const response = await _fetch(resource, config);
    return response;
};

5.3 fetch 响应拦截(可读取/修改响应体)

// 使用场景:读取 fetch 响应内容,定位响应数据解密位置
const { fetch: _fetch } = window;
window.fetch = async function (...args) {
    let [resource, config] = args;
    let response = await _fetch(resource, config);

    // 克隆响应(原始响应只能被消费一次)
    const cloned = response.clone();
    cloned.text().then(function (body) {
        console.log('[fetch response] url =', resource);
        console.log('[fetch response] status =', response.status);
        console.log('[fetch response] body =', body.substring(0, 300));
    });

    return response;
};

5.4 Proxy 方式 Hook fetch

// 使用场景:使用 Proxy apply 陷阱拦截 fetch,等价于上面的写法
window.fetch = new Proxy(window.fetch, {
    apply: function (target, that, args) {
        console.log('[fetch] url =', args[0]);
        console.log(new Error().stack);
        var result = target.apply(that, args);
        result.then(function (res) {
            if (res.status === 401) {
                console.log('[fetch] 401 Unauthorized');
            }
        });
        return result;
    }
});

// 使用场景:定位特定 Cookie 的生成位置,当 cookie 包含目标 key 时断下
(function () {
    var cookieTemp = '';
    Object.defineProperty(document, 'cookie', {
        set: function (val) {
            if (val.indexOf('your_key') !== -1) {  // 替换为目标 cookie key
                debugger;
            }
            console.log('[Cookie SET]', val);
            console.log(new Error().stack);
            cookieTemp = val;
            return val;
        },
        get: function () {
            return cookieTemp;
        }
    });
})();
// 使用场景:更稳定地 Hook cookie,通过获取 Document.prototype 上的原生访问器实现
(function () {
    var cookie_accessor = Object.getOwnPropertyDescriptor(Document.prototype, 'cookie');
    var get_cookie = cookie_accessor.get;
    var set_cookie = cookie_accessor.set;

    Object.defineProperty(document, 'cookie', {
        get: function () {
            return get_cookie.call(document);
        },
        set: function (cookie) {
            console.log('[Cookie SET]', cookie);
            console.log(new Error().stack);
            set_cookie.call(document, cookie);
        }
    });
})();
// 使用场景:完整接管 cookie 的读写,维护 key-value 映射,记录每次变更
(function () {
    'use strict';

    document.temp_cookie = document.cookie;

    Object.defineProperty(document, 'cookie', {
        get: function () {
            return document.temp_cookie;
        },
        set: function (cookie) {
            console.log('[Cookie SET]', cookie);
            console.log(new Error().stack);

            var key_arr = [];
            var val_arr = [];

            // 处理带过期时间等属性的 cookie(格式:key=val; expires=...)
            if (cookie.split('=').length > 2) {
                var temp_k = cookie.split('=')[0];
                var temp_val = cookie.substring(cookie.indexOf('=') + 1, cookie.indexOf(';'));
                key_arr.push(temp_k);
                val_arr.push(temp_val);
            }

            // 合并已有 cookie
            if (document.temp_cookie !== '') {
                document.temp_cookie.split('; ').forEach(function (item) {
                    var k = item.split('=')[0];
                    var v = item.split('=')[1];
                    if (typeof temp_k !== 'undefined' && temp_k === k) {
                        // skip, 已处理
                    } else {
                        key_arr.push(k);
                        val_arr.push(v);
                    }
                });
                document.temp_cookie = '';
            }

            // 处理简单 cookie(格式:key=val)
            if (!(cookie.split('=').length > 2)) {
                var newKey = cookie.split('=')[0];
                var newVal = cookie.split('=')[1];
                var found = false;
                for (var i = 0; i < key_arr.length; i++) {
                    if (key_arr[i] === newKey) {
                        val_arr[i] = newVal;
                        found = true;
                        break;
                    }
                }
                if (!found) {
                    key_arr.push(newKey);
                    val_arr.push(newVal);
                }
            }

            // 重新组合 cookie 字符串
            document.temp_cookie = key_arr.map(function (k, i) {
                return k + '=' + val_arr[i];
            }).join('; ');
        }
    });
})();

7. localStorage / sessionStorage Hook

7.1 localStorage 全方法监控

// 使用场景:监控所有 localStorage 操作(setItem/getItem/removeItem/clear),打印调用栈
(function () {
    'use strict';

    var _setItem = localStorage.setItem;
    localStorage.setItem = function () {
        console.log('[localStorage.setItem] key =', arguments[0], ', value =', arguments[1]);
        console.log(new Error().stack);
        return _setItem.call(this, ...arguments);
    };

    var _getItem = localStorage.getItem;
    localStorage.getItem = function () {
        var val = _getItem.call(this, ...arguments);
        console.log('[localStorage.getItem] key =', arguments[0], ', value =', val);
        console.log(new Error().stack);
        return val;
    };

    var _removeItem = localStorage.removeItem;
    localStorage.removeItem = function () {
        console.log('[localStorage.removeItem] key =', arguments[0]);
        console.log(new Error().stack);
        return _removeItem.call(this, ...arguments);
    };

    var _clear = localStorage.clear;
    localStorage.clear = function () {
        console.log('[localStorage.clear] all items removed');
        console.log(new Error().stack);
        return _clear.call(this, ...arguments);
    };
})();

7.2 sessionStorage 全方法监控

// 使用场景:监控所有 sessionStorage 操作
(function () {
    'use strict';

    var _setItem = sessionStorage.setItem;
    sessionStorage.setItem = function () {
        console.log('[sessionStorage.setItem] key =', arguments[0], ', value =', arguments[1]);
        console.log(new Error().stack);
        return _setItem.call(this, ...arguments);
    };

    var _getItem = sessionStorage.getItem;
    sessionStorage.getItem = function () {
        var val = _getItem.call(this, ...arguments);
        console.log('[sessionStorage.getItem] key =', arguments[0], ', value =', val);
        console.log(new Error().stack);
        return val;
    };

    var _removeItem = sessionStorage.removeItem;
    sessionStorage.removeItem = function () {
        console.log('[sessionStorage.removeItem] key =', arguments[0]);
        console.log(new Error().stack);
        return _removeItem.call(this, ...arguments);
    };

    var _clear = sessionStorage.clear;
    sessionStorage.clear = function () {
        console.log('[sessionStorage.clear] all items removed');
        console.log(new Error().stack);
        return _clear.call(this, ...arguments);
    };
})();

7.3 带条件断点的 localStorage Hook

// 使用场景:只在操作特定 key 时断下,过滤无关的读写操作
(function () {
    var TARGET_KEY = 'token'; // 替换为目标 key
    var _setItem = localStorage.setItem;
    localStorage.setItem = function (key, value) {
        if (key === TARGET_KEY) {
            console.log('[localStorage.setItem TARGET]', key, '=', value);
            debugger;
        }
        return _setItem.call(this, key, value);
    };
})();

8. debugger 反调试绕过

8.1 置空定时器(绕过 setInterval 无限 debugger)

// 使用场景:绕过通过 setInterval 循环触发 debugger 的反调试
// 方式一:直接置空 setInterval,会影响页面所有定时器(副作用大)
setInterval = function () {};

// 方式二:Hook setInterval,过滤含 debugger 的函数(推荐)
var _setInterval = setInterval;
setInterval = function (func, timer) {
    if (func.toString().indexOf('debugger') !== -1) {
        console.log('[反调试] 检测到 setInterval 无限 debugger,已拦截');
        return null;
    }
    return _setInterval(func, timer);
};
setInterval.toString = function () {
    return 'function setInterval() { [native code] }';
};

8.2 Hook Function 构造函数(绕过 new Function('debugger'))

// 使用场景:绕过通过 Function('debugger') 实现的无限 debugger(OB 混淆常见)
(function () {
    var _constructor = window.Function.prototype.constructor;
    window.Function.prototype.constructor = function () {
        var fnContent = arguments[arguments.length - 1];
        if (typeof fnContent === 'string' && fnContent.indexOf('debugger') !== -1) {
            console.log('[反调试] 拦截 Function("debugger")');
            return function () {};
        }
        return _constructor.apply(this, arguments);
    };
})();

8.3 绕过 eval('debugger')

// 使用场景:绕过通过 eval('debugger') 实现的反调试
var _eval = window.eval;
window.eval = function (src) {
    if (typeof src === 'string' && src.indexOf('debugger') !== -1) {
        console.log('[反调试] 拦截 eval("debugger")');
        return undefined;
    }
    return _eval.call(this, src);
};
window.eval.toString = function () {
    return 'function eval() { [native code] }';
};

8.4 清除所有已有定时器

// 使用场景:页面已有无限 debugger 定时器在运行,批量清除
for (var i = 1; i < 99999; i++) {
    window.clearInterval(i);
    window.clearTimeout(i);
}

8.5 组合反调试绕过(推荐一起使用)

// 使用场景:同时覆盖多种无限 debugger 实现方式
(function () {
    // 绕过 Function('debugger') 和 constructor('debugger')
    var _FunctionConstructor = Function.prototype.constructor;
    Function.prototype.constructor = function (arg) {
        if (typeof arg === 'string' && arg.indexOf('debugger') !== -1) {
            return function () {};
        }
        return _FunctionConstructor.apply(this, arguments);
    };

    // 绕过 setInterval(fn, t) 中含 debugger 的函数
    var _setInterval = window.setInterval;
    window.setInterval = function (func, timer) {
        if (typeof func === 'function' && func.toString().indexOf('debugger') !== -1) {
            return null;
        }
        return _setInterval.call(this, func, timer);
    };

    // 绕过 setTimeout(fn, t) 中含 debugger 的函数
    var _setTimeout = window.setTimeout;
    window.setTimeout = function (func, timer) {
        if (typeof func === 'function' && func.toString().indexOf('debugger') !== -1) {
            return null;
        }
        return _setTimeout.call(this, func, timer);
    };
})();

9. JSON.stringify / JSON.parse Hook

9.1 JSON.stringify 和 JSON.parse 完整 Hook

// 使用场景:监控请求体序列化(stringify)和响应体反序列化(parse),定位加密参数拼接位置
(function () {
    var _parse = JSON.parse;
    JSON.parse = function (str) {
        console.log('[JSON.parse] input =', str);
        console.log(new Error().stack);
        console.log('------------------------------------------------------------');
        return _parse(str);
    };

    var _stringify = JSON.stringify;
    JSON.stringify = function (obj) {
        console.log('[JSON.stringify] input =', obj);
        console.log(new Error().stack);
        console.log('------------------------------------------------------------');
        return _stringify(obj);
    };
})();

9.2 JSON Hook 带条件断点

// 使用场景:只在序列化/反序列化结果包含特定字段时断下
(function () {
    var TARGET = 'sign'; // 替换为目标字段名
    var _stringify = JSON.stringify;
    JSON.stringify = function (obj) {
        var result = _stringify(obj);
        if (result && result.indexOf(TARGET) !== -1) {
            console.log('[JSON.stringify 命中]', result);
            debugger;
        }
        return result;
    };

    var _parse = JSON.parse;
    JSON.parse = function (str) {
        if (typeof str === 'string' && str.indexOf(TARGET) !== -1) {
            console.log('[JSON.parse 命中]', str);
            debugger;
        }
        return _parse(str);
    };
})();

10. WebSocket Hook

10.1 Proxy 方式 Hook WebSocket(推荐)

// 使用场景:监控 WebSocket 连接的所有发送消息、接收消息、连接和关闭事件
(function () {
    var WebSocketProxy = new Proxy(window.WebSocket, {
        construct: function (target, args) {
            console.log('[WebSocket] 连接 URL:', args[0]);
            var ws = new target(...args);

            // 拦截 send(出站消息)
            var _send = ws.send.bind(ws);
            ws.send = function (data) {
                console.log('[WebSocket SEND]', data);
                console.log(new Error().stack);
                return _send(data);
            };

            // 拦截 onmessage(入站消息)
            var _onmessage = ws.onmessage;
            ws.onmessage = function (event) {
                console.log('[WebSocket RECV]', event.data);
                if (_onmessage) return _onmessage.call(this, event);
            };

            // 拦截 close
            var _close = ws.close.bind(ws);
            ws.close = function () {
                console.log('[WebSocket CLOSE]');
                return _close();
            };

            // 拦截 addEventListener(有些页面不用 onmessage,用事件监听)
            var _addEventListener = ws.addEventListener.bind(ws);
            ws.addEventListener = function (eventName, listener, options) {
                if (eventName === 'message') {
                    var _listener = listener;
                    listener = function (event) {
                        console.log('[WebSocket addEventListener message]', event.data);
                        return _listener.call(this, event);
                    };
                }
                return _addEventListener(eventName, listener, options);
            };

            // 保存到全局方便调试
            window._websockets = window._websockets || [];
            window._websockets.push(ws);

            return ws;
        }
    });

    window.WebSocket = WebSocketProxy;
})();

10.2 wsHook 风格(轻量 Hook,适合快速使用)

// 使用场景:简单场景下拦截 WebSocket 消息,可修改发送内容或阻止接收
var wsHook = {};
wsHook.before = null; // function(data, url, ws) { ... return data; }
wsHook.after = null;  // function(event, url, ws) { ... return event; }

(function () {
    var _WebSocket = window.WebSocket;
    window.WebSocket = function (url, protocols) {
        var ws = protocols ? new _WebSocket(url, protocols) : new _WebSocket(url);
        var _send = ws.send.bind(ws);

        ws.send = function (data) {
            if (wsHook.before) {
                data = wsHook.before(data, url, ws);
                if (data === null) return; // 返回 null 则阻止发送
            }
            return _send(data);
        };

        ws.addEventListener('message', function (event) {
            if (wsHook.after) {
                var result = wsHook.after(event, url, ws);
                if (result === null) return; // 返回 null 则阻止触发 onmessage
            }
        });

        return ws;
    };
    window.WebSocket.prototype = _WebSocket.prototype;
})();

// 使用示例
wsHook.before = function (data, url, ws) {
    console.log('[WS SEND]', url, data);
    return data;
};
wsHook.after = function (event, url, ws) {
    console.log('[WS RECV]', url, event.data);
    return event;
};

11. eval / Function Hook

11.1 eval Hook(监控动态代码执行)

// 使用场景:监控通过 eval 执行的动态代码,分析混淆代码中的动态生成逻辑
(function () {
    window.__cr_eval = window.eval;
    var myeval = function (src) {
        console.log('[eval]', src);
        console.log('======= eval end =======');
        debugger;
        return window.__cr_eval(src);
    };
    var _myeval = myeval.bind(null);
    _myeval.toString = window.__cr_eval.toString;
    Object.defineProperty(window, 'eval', { value: _myeval });
})();

11.2 eval 替换为 console.log(只打印不执行)

// 使用场景:直接输出 eval 内容而不执行,安全查看动态代码
var _eval_bak = window.eval;
window.eval = console.log;
// 恢复:window.eval = _eval_bak;

11.3 Function 构造函数 Hook(监控 new Function)

// 使用场景:监控通过 new Function(...) 动态创建的函数,常见于 OB 混淆代码
(function () {
    window.__cr_fun = window.Function;
    var myfun = function () {
        var args = Array.prototype.slice.call(arguments, 0, -1).join(',');
        var src = arguments[arguments.length - 1];
        console.log('[Function] args =', args);
        console.log('[Function] body =', src);
        console.log('======= Function end =======');
        debugger;
        return window.__cr_fun.apply(this, arguments);
    };
    myfun.toString = function () {
        return window.__cr_fun + '';
    };
    Object.defineProperty(window, 'Function', { value: myfun });
})();

12. Canvas 指纹 Hook

12.1 Hook toDataURL(拦截 Canvas 导出图像)

// 使用场景:拦截 canvas.toDataURL 调用,定位指纹采集代码位置,或返回固定值欺骗检测
(function () {
    var _toDataURL = HTMLCanvasElement.prototype.toDataURL;
    HTMLCanvasElement.prototype.toDataURL = function (type) {
        console.log('[canvas.toDataURL] type =', type);
        console.log('[canvas.toDataURL] canvas size:', this.width, 'x', this.height);
        console.log(new Error().stack);
        debugger; // 定位调用位置后可去掉
        return _toDataURL.apply(this, arguments);
        // 如需返回固定值(欺骗指纹):
        // return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==';
    };
})();

12.2 Hook getImageData(拦截像素数据读取)

// 使用场景:拦截 canvas 像素数据读取,阻止指纹采集
(function () {
    var _getImageData = CanvasRenderingContext2D.prototype.getImageData;
    CanvasRenderingContext2D.prototype.getImageData = function (sx, sy, sw, sh) {
        console.log('[canvas.getImageData] rect:', sx, sy, sw, sh);
        console.log(new Error().stack);
        debugger;
        return _getImageData.apply(this, arguments);
    };
})();

12.3 Canvas 指纹添加噪声(修改像素返回干扰值)

// 使用场景:为每次 canvas 采集添加随机噪声,使指纹不固定
(function () {
    var _getImageData = CanvasRenderingContext2D.prototype.getImageData;
    CanvasRenderingContext2D.prototype.getImageData = function (sx, sy, sw, sh) {
        var imageData = _getImageData.apply(this, arguments);
        // 在像素数据中添加微小随机噪声
        for (var i = 0; i < imageData.data.length; i += 4) {
            imageData.data[i] += Math.floor(Math.random() * 3) - 1;     // R
            imageData.data[i + 1] += Math.floor(Math.random() * 3) - 1; // G
            imageData.data[i + 2] += Math.floor(Math.random() * 3) - 1; // B
            // imageData.data[i + 3] 不修改 Alpha 通道
        }
        return imageData;
    };
})();

12.4 Hook toBlob

// 使用场景:拦截通过 toBlob 方式获取 canvas 数据的指纹采集
(function () {
    var _toBlob = HTMLCanvasElement.prototype.toBlob;
    HTMLCanvasElement.prototype.toBlob = function (callback, type, quality) {
        console.log('[canvas.toBlob] type =', type);
        console.log(new Error().stack);
        return _toBlob.apply(this, arguments);
    };
})();

13. 定时器 Hook

13.1 Hook setInterval(监控/过滤定时任务)

// 使用场景:查看页面注册了哪些定时任务,过滤含 debugger 的定时器
(function () {
    var _setInterval = window.setInterval;
    window.setInterval = function (func, delay) {
        var funcStr = typeof func === 'function' ? func.toString() : String(func);
        console.log('[setInterval] delay =', delay, '\n  func =', funcStr.substring(0, 100));
        console.log(new Error().stack);
        // 过滤 debugger
        if (funcStr.indexOf('debugger') !== -1) {
            console.log('[setInterval] 拦截含 debugger 的定时器');
            return null;
        }
        return _setInterval.apply(this, arguments);
    };
    window.setInterval.toString = function () {
        return 'function setInterval() { [native code] }';
    };
})();

13.2 Hook setTimeout

// 使用场景:监控延迟执行的任务,追踪延迟触发的加密逻辑
(function () {
    var _setTimeout = window.setTimeout;
    window.setTimeout = function (func, delay) {
        var funcStr = typeof func === 'function' ? func.toString() : String(func);
        if (delay > 0) { // 过滤 delay=0 的短任务,减少噪音
            console.log('[setTimeout] delay =', delay, '\n  func =', funcStr.substring(0, 100));
        }
        if (funcStr.indexOf('debugger') !== -1) {
            console.log('[setTimeout] 拦截含 debugger 的延迟任务');
            return null;
        }
        return _setTimeout.apply(this, arguments);
    };
    window.setTimeout.toString = function () {
        return 'function setTimeout() { [native code] }';
    };
})();

13.3 Hook requestAnimationFrame

// 使用场景:部分反调试使用 requestAnimationFrame 循环,hook 可定位或阻断
(function () {
    var _raf = window.requestAnimationFrame;
    window.requestAnimationFrame = function (callback) {
        var _callback = callback;
        callback = function (timestamp) {
            // console.log('[rAF] callback called');
            return _callback(timestamp);
        };
        return _raf(callback);
    };
})();

14. DOM 监控

14.1 MutationObserver 监控 DOM 变化

// 使用场景:监控页面 DOM 的动态变化,检测动态插入的 script 标签或 DOM 注入
(function () {
    var observer = new MutationObserver(function (mutations) {
        mutations.forEach(function (mutation) {
            // 监控新增节点
            mutation.addedNodes.forEach(function (node) {
                if (node.nodeName === 'SCRIPT') {
                    console.log('[MutationObserver] 新增 script 标签:', node.src || node.textContent.substring(0, 100));
                    console.log(new Error().stack);
                }
                if (node.nodeType === 1) { // ELEMENT_NODE
                    console.log('[MutationObserver] 新增元素:', node.outerHTML ? node.outerHTML.substring(0, 150) : node);
                }
            });
            // 监控属性变化
            if (mutation.type === 'attributes') {
                console.log('[MutationObserver] 属性变化:', mutation.target.tagName,
                    mutation.attributeName, '=', mutation.target.getAttribute(mutation.attributeName));
            }
        });
    });

    observer.observe(document.documentElement, {
        childList: true,    // 监控直接子节点增删
        subtree: true,      // 监控所有后代节点
        attributes: true,   // 监控属性变化
        characterData: true // 监控文本节点变化
    });
})();

14.2 Hook innerHTML / outerHTML

// 使用场景:监控动态 HTML 注入(可用于 XSS 分析或追踪动态渲染)
(function () {
    var prop = Object.getOwnPropertyDescriptor(Element.prototype, 'innerHTML');
    var _get = prop.get;
    var _set = prop.set;

    Object.defineProperty(Element.prototype, 'innerHTML', {
        get: function () {
            return _get.call(this);
        },
        set: function (val) {
            console.log('[innerHTML SET]', this.tagName, val.substring(0, 150));
            console.log(new Error().stack);
            return _set.call(this, val);
        }
    });
})();

14.3 监控特定元素属性变化

// 使用场景:监控某个 input 的 value 属性被动态修改
var targetEl = document.querySelector('#captcha-input'); // 替换为目标选择器
if (targetEl) {
    var observer = new MutationObserver(function (mutations) {
        mutations.forEach(function (m) {
            console.log('[DOM 属性变化]', m.attributeName, '=', targetEl.getAttribute(m.attributeName));
        });
    });
    observer.observe(targetEl, { attributes: true });
}

14.4 HTMLScriptElement.src Hook — 监控动态脚本注入

当页面通过 JS 动态创建 <script> 标签加载外部脚本时触发断点。

方式一:defineProperty(推荐,精确监控 src)

// 拦截所有 script 元素的 src 赋值
Object.defineProperty(HTMLScriptElement.prototype, 'src', {
    set: function (value) {
        console.log('[script.src]', value);
        debugger;  // 触发断点,查看调用栈定位加载来源
    }
});

方式二:Proxy 代理整个原型(监控所有属性)

// 拦截原型上的所有属性赋值,不仅限于 src
HTMLScriptElement.prototype = new Proxy(HTMLScriptElement.prototype, {
    set: function (target, prop, value) {
        console.log('[HTMLScriptElement]', prop, '=', value);
        debugger;
        return Reflect.set(target, prop, value);
    }
});

两种方式不要同时使用,会产生冲突。推荐用方式一定向监控 src,用方式二时监控所有属性。适用场景:网站动态插入第三方脚本(风控 JS、指纹采集 SDK),定位其加载时机和来源。


15. console 保护与重写

15.1 冻结 console(防止被网站覆盖)

// 使用场景:部分网站会覆盖 console.log 来阻止调试输出,冻结后无法修改
(function () {
    Object.freeze(console);
})();

15.2 防止 console.log 被重写(检测并恢复)

// 使用场景:在页面加载完成后检查 console.log 是否被覆盖,如被覆盖则恢复
(function () {
    var _log = console.log;
    var waiting_time = 3;
    var restored = false;

    function restoreLog() {
        if (console.log.toString() !== 'function log() { [native code] }') {
            console.log = _log;
            console.warn('[保护] console.log 被重写,已恢复原生实现');
        }
        restored = true;
    }

    window.addEventListener('load', restoreLog);
    setTimeout(function () {
        if (!restored) restoreLog();
    }, waiting_time * 1000);
})();

15.3 阻止 console.clear

// 使用场景:防止网站调用 console.clear() 清空调试输出
(function () {
    console.clear = function () {
        console.log('[console.clear 已被禁用]');
    };
})();

15.4 console.log Hook(记录所有输出)

// 使用场景:监控页面内所有 console.log 输出,追踪关键日志
(function () {
    var _log = console.log;
    console.log = function () {
        // 可在此过滤或修改日志
        _log.apply(console, arguments);
        // 可同时上报到自己的服务器:
        // fetch('/log', { method: 'POST', body: JSON.stringify([...arguments]) });
    };
})();

16. 加密函数定位

16.1 通过调用栈定位(通用方法)

// 使用场景:在任意位置打印当前调用栈,顺着栈向上找到加密函数入口
console.log(new Error().stack);
// 或者
console.trace('当前调用栈');

16.2 搜索关键词定位加密函数

// 在 Sources 面板 Ctrl+Shift+F 全局搜索以下特征词:
// MD5 特征:1732584193 / 271733879 / md5
// SHA1 特征:1009589776 / sha1
// SHA256 特征:sha256 / 0x6a09e667
// Base64 特征:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmno
// AES 特征:AES / aes / CryptoJS
// RSA 特征:RSAKey / BigInteger / PublicKey

16.3 Hook CryptoJS 加密方法(定位 AES/DES/RC4 等)

// 使用场景:在引入了 CryptoJS 的页面,Hook 所有对称加密方法
(function () {
    function hookCryptoMethod(obj, methodName) {
        if (!obj || !obj[methodName]) return;
        var _original = obj[methodName];
        obj[methodName] = function () {
            console.log('[CryptoJS.' + methodName + '] args:', ...arguments);
            console.log(new Error().stack);
            var result = _original.apply(this, arguments);
            console.log('[CryptoJS.' + methodName + '] result:', result.toString ? result.toString() : result);
            return result;
        };
    }

    // 等待 CryptoJS 加载后执行
    var timer = setInterval(function () {
        if (typeof CryptoJS !== 'undefined') {
            clearInterval(timer);
            if (CryptoJS.AES) {
                hookCryptoMethod(CryptoJS.AES, 'encrypt');
                hookCryptoMethod(CryptoJS.AES, 'decrypt');
            }
            if (CryptoJS.DES) {
                hookCryptoMethod(CryptoJS.DES, 'encrypt');
                hookCryptoMethod(CryptoJS.DES, 'decrypt');
            }
            if (CryptoJS.TripleDES) {
                hookCryptoMethod(CryptoJS.TripleDES, 'encrypt');
                hookCryptoMethod(CryptoJS.TripleDES, 'decrypt');
            }
            if (CryptoJS.RC4) {
                hookCryptoMethod(CryptoJS.RC4, 'encrypt');
                hookCryptoMethod(CryptoJS.RC4, 'decrypt');
            }
            if (CryptoJS.MD5) hookCryptoMethod(CryptoJS, 'MD5');
            if (CryptoJS.HmacSHA256) {
                hookCryptoMethod(CryptoJS, 'HmacSHA256');
                hookCryptoMethod(CryptoJS, 'HmacSHA1');
            }
            console.log('[CryptoJS Hook] 已注入所有加密方法');
        }
    }, 100);
})();

16.4 Hook JSEncrypt RSA 加密

// 使用场景:页面使用 JSEncrypt 进行 RSA 加密时,拦截 encrypt 方法获取明文和密文
(function () {
    var timer = setInterval(function () {
        if (typeof JSEncrypt !== 'undefined') {
            clearInterval(timer);
            var _encrypt = JSEncrypt.prototype.encrypt;
            JSEncrypt.prototype.encrypt = function (data) {
                console.log('[JSEncrypt.encrypt] 明文:', data);
                var result = _encrypt.call(this, data);
                console.log('[JSEncrypt.encrypt] 密文:', result);
                console.log(new Error().stack);
                return result;
            };
            console.log('[JSEncrypt Hook] 已注入 RSA encrypt');
        }
    }, 100);
})();

16.5 Hook window.btoa / atob(Base64)

// 使用场景:监控 Base64 编解码,定位数据编码位置
(function () {
    var _btoa = window.btoa;
    window.btoa = function (str) {
        console.log('[btoa] input =', str);
        console.log(new Error().stack);
        return _btoa.call(this, str);
    };

    var _atob = window.atob;
    window.atob = function (str) {
        var result = _atob.call(this, str);
        console.log('[atob] input =', str, ', output =', result);
        console.log(new Error().stack);
        return result;
    };
})();

16.6 XHR 断点触发后顺着调用栈追溯(流程说明)

// 逆向定位流程:
// 1. 用 XHR URL Hook 或 XHR Header Hook 让请求断下
// 2. 打开 DevTools -> Sources -> Call Stack 面板
// 3. 从栈底往上逐帧查看,跳过 jQuery/axios/webpack 等库的帧
// 4. 找到业务代码帧,双击定位到对应源码行
// 5. 在那一行附近搜索参数名(如 sign、token),找到生成逻辑
// 6. 在生成处打断点,重放请求,单步执行追踪

17. 其他实用 Hook

17.1 阻止 window.close 和 history 跳转

// 使用场景:阻止目标网站动态关闭标签页或强制跳转历史
(function () {
    window.close = function () {};
    window.history.go = function () {};
    window.history.back = function () {};
})();

17.2 固定 window 尺寸(绕过尺寸检测)

// 使用场景:某些网站通过检测窗口尺寸判断是否打开了 DevTools,固定尺寸可绕过
(function () {
    var FIXED_HEIGHT = 660;
    var FIXED_WIDTH = 1366;

    var hProp = Object.getOwnPropertyDescriptor(window, 'innerHeight');
    Object.defineProperty(window, 'innerHeight', {
        get: function () { return FIXED_HEIGHT; },
        set: function () { if (hProp && hProp.set) hProp.set.call(window, FIXED_HEIGHT); }
    });

    var wProp = Object.getOwnPropertyDescriptor(window, 'innerWidth');
    Object.defineProperty(window, 'innerWidth', {
        get: function () { return FIXED_WIDTH; },
        set: function () { if (wProp && wProp.set) wProp.set.call(window, FIXED_WIDTH); }
    });
})();

17.3 Hook Image.src(监控埋点和追踪像素)

// 使用场景:某些埋点通过 new Image().src = url 发请求,hook 可截获
(function () {
    var prop = Object.getOwnPropertyDescriptor(Image.prototype, 'src');
    var _nativeSet = prop.set;
    Object.defineProperty(Image.prototype, 'src', {
        get: prop.get,
        set: function (url) {
            console.log('[Image.src]', url);
            console.log(new Error().stack);
            return _nativeSet.call(this, url);
        }
    });
})();

17.4 Hook window.open

// 使用场景:阻止页面自动弹出新窗口,或监控弹窗 URL
(function () {
    var _open = window.open;
    window.open = function (url) {
        console.log('[window.open]', url);
        console.log(new Error().stack);
        // return _open.call(this, url); // 注释掉则阻止弹窗
    };
})();

17.5 格式化检测绕过(防检测 toString)

// 使用场景:部分网站检测函数是否被格式化(代码是否被美化),通过伪装 toString 绕过
// 原理:让被 hook 的函数的 toString 返回原生代码字符串,欺骗检测
function makeNative(fn, nativeFn) {
    fn.toString = function () {
        return nativeFn.toString();
    };
    return fn;
}

// 示例:Hook eval 并伪装
var _eval = window.eval;
window.eval = makeNative(function (src) {
    console.log('[eval]', src);
    return _eval(src);
}, _eval);

18. 环境 Hook 模块(Node.js 补环境)

在 Node.js 本地运行浏览器加密 JS 时使用,补全浏览器环境并对全局对象挂载深度 Proxy。

功能列表:

功能 说明
深度 Proxy 对任意对象/函数做递归代理,追踪 get/set/has/call
浏览器环境模拟 补全 window/navigator/document/localStorage
定时器拦截 记录 setTimeout/setInterval 调用
全局对象代理 对 window/document/navigator 等批量挂载 Proxy
防重复安装 通过 __envHookInstalled 标志防止重复执行

18.1 配置项

const CONFIG = {
    enableStack: true,   // 是否在日志中输出调用栈
    deepProxy: true,     // 是否对 get 返回值递归代理(深度追踪,会产生大量日志)
    logGet: true,        // 记录属性读取
    logSet: true,        // 记录属性写入
    logHas: false,       // 记录 in 操作符检测(通常不需要)
    logCall: true        // 记录函数调用
};

18.2 核心代理 createProxy()

对任意对象/函数递归包裹 Proxy,追踪所有操作并记录访问路径。WeakMap 缓存避免对同一对象重复代理。

function createProxy(target, path = "") {
    if (!isObject(target) && typeof target !== "function") return target;
    if (proxyCache.has(target)) return proxyCache.get(target);

    const handler = {
        get(target, property, receiver) {
            const value = Reflect.get(target, property, receiver);
            if (CONFIG.logGet) {
                console.log({ type: "get", path, property, valueType: typeof value, stack: safeStack() });
            }
            // deepProxy: 对返回值也代理,可以追踪链式调用
            if (CONFIG.deepProxy) return createProxy(value, `${path}.${String(property)}`);
            return value;
        },
        set(target, property, value, receiver) {
            if (CONFIG.logSet) console.log({ type: "set", path, property, value });
            return Reflect.set(target, property, value, receiver);
        },
        has(target, property) {
            if (CONFIG.logHas) console.log({ type: "has", path, property });
            return Reflect.has(target, property);
        },
        apply(target, thisArg, args) {
            if (CONFIG.logCall) console.log({ type: "call", path, args, stack: safeStack() });
            return Reflect.apply(target, thisArg, args);
        }
    };

    const proxy = new Proxy(target, handler);
    proxyCache.set(target, proxy);
    return proxy;
}

18.3 浏览器环境模拟 patchBrowserEnv()

function patchBrowserEnv() {
    if (!global.window) global.window = global;

    if (!global.navigator) {
        global.navigator = {
            userAgent: "Mozilla/5.0",
            platform: "Win32",
            languages: ["zh-CN", "zh"],
            webdriver: false       // 关键:不能为 true,否则触发反爬检测
        };
    }

    if (!global.document) {
        global.document = {
            cookie: "",
            createElement: () => ({})
        };
    }

    if (!global.localStorage) {
        const store = {};
        global.localStorage = {
            getItem:    k       => store[k],
            setItem:    (k, v)  => store[k] = v,
            removeItem: k       => delete store[k]
        };
    }
}

18.4 完整代码

将此代码放在目标 JS 文件最顶部,在 requireeval 之前执行。

(function () {
  if (global.__envHookInstalled) return;
  global.__envHookInstalled = true;

  const CONFIG = {
    enableStack: true,
    deepProxy: true,
    logGet: true,
    logSet: true,
    logHas: false,
    logCall: true
  };

  const proxyCache = new WeakMap();

  function safeStack() {
    if (!CONFIG.enableStack) return null;
    return new Error().stack;
  }

  function isObject(v) {
    return typeof v === "object" && v !== null;
  }

  function createProxy(target, path = "") {
    if (!isObject(target) && typeof target !== "function") return target;
    if (proxyCache.has(target)) return proxyCache.get(target);

    const handler = {
      get(target, property, receiver) {
        const value = Reflect.get(target, property, receiver);
        if (CONFIG.logGet) {
          console.log({ type: "get", path, property, valueType: typeof value, stack: safeStack() });
        }
        if (CONFIG.deepProxy) return createProxy(value, `${path}.${String(property)}`);
        return value;
      },
      set(target, property, value, receiver) {
        if (CONFIG.logSet) console.log({ type: "set", path, property, value });
        return Reflect.set(target, property, value, receiver);
      },
      has(target, property) {
        if (CONFIG.logHas) console.log({ type: "has", path, property });
        return Reflect.has(target, property);
      },
      apply(target, thisArg, args) {
        if (CONFIG.logCall) console.log({ type: "call", path, args, stack: safeStack() });
        return Reflect.apply(target, thisArg, args);
      }
    };

    const proxy = new Proxy(target, handler);
    proxyCache.set(target, proxy);
    return proxy;
  }

  function patchBrowserEnv() {
    if (!global.window) global.window = global;
    if (!global.navigator) {
      global.navigator = {
        userAgent: "Mozilla/5.0", platform: "Win32",
        languages: ["zh-CN", "zh"], webdriver: false
      };
    }
    if (!global.document) {
      global.document = { cookie: "", createElement: () => ({}) };
    }
    if (!global.localStorage) {
      const store = {};
      global.localStorage = {
        getItem: k => store[k],
        setItem: (k, v) => store[k] = v,
        removeItem: k => delete store[k]
      };
    }
  }

  function patchTimers() {
    const _setTimeout  = global.setTimeout;
    const _setInterval = global.setInterval;
    global.setTimeout  = (fn, delay, ...args) => { console.log("setTimeout", delay);  return _setTimeout(fn, delay, ...args);  };
    global.setInterval = (fn, delay, ...args) => { console.log("setInterval", delay); return _setInterval(fn, delay, ...args); };
  }

  function hookGlobals(list) {
    for (const name of list) {
      if (!(name in global)) continue;
      const original = global[name];
      Object.defineProperty(global, name, {
        configurable: true, enumerable: true,
        get() { return createProxy(original, name); }
      });
    }
  }

  patchBrowserEnv();
  patchTimers();
  hookGlobals(["window", "document", "navigator", "location", "history", "screen", "localStorage", "sessionStorage"]);

  console.log("Environment hook installed");
})();

deepProxy: true 时,链式属性访问会产生大量日志。建议先设置 CONFIG.deepProxy = false; CONFIG.logGet = false; 只看 set 和 call,找到目标后再开启深度追踪。


场景速查表

目标 推荐 Hook
定位 Cookie 生成 Cookie Hook(6.1 / 6.2)
定位 XHR 请求加密参数 XHR send Hook(4.1) + JSON.stringify Hook(9.1)
定位请求头(X-Sign 等)生成 XHR Header Hook(4.3)
定位 fetch 请求参数 fetch 请求拦截(5.2)
定位 localStorage token localStorage 条件断点 Hook(7.3)
监控 WebSocket 消息 WebSocket Proxy Hook(10.1)
绕过无限 debugger 组合反调试绕过(8.5)
分析动态混淆代码 eval Hook(11.1) + Function Hook(11.3)
阻止 Canvas 指纹采集 toDataURL Hook(12.1) + getImageData Hook(12.2)
定位 AES/DES 加密调用 CryptoJS Hook(16.3)
定位 RSA 加密调用 JSEncrypt Hook(16.4)
追踪 Base64 编码 btoa/atob Hook(16.5)
监控 DOM 动态注入 MutationObserver(14.1)
监控动态脚本加载 HTMLScriptElement.src Hook(14.4)
防止控制台被清空 console.clear 阻止(15.3)
Node.js 本地补环境 环境 Hook 模块(18.4)
监控对象全部属性访问路径 watch()(1.4)

最佳实践

Hook 必须在目标代码执行前注入:通过 Tampermonkey 设 @run-at document-start,或在 DevTools Sources → Snippets 中在页面加载前手动执行,否则原生函数已被调用,Hook 无法捕获历史调用。

按需选择 Hook 粒度:属性描述符 vs Proxy:对单个属性或已知键名用 Object.defineProperty,对整个对象的所有属性访问用 Proxy。Proxy 开销更高但覆盖面广,不要对高频调用对象无差别使用 Proxy。

// 仅 Hook 一个属性(开销低)
Object.defineProperty(document, 'cookie', {
    get() { console.trace('cookie read'); return cookieValue; },
    set(v) { console.trace('cookie write', v); cookieValue = v; }
});

// Hook 整个对象所有访问(覆盖广但开销高)
window = new Proxy(window, { get(t, k) { console.log('window.' + k); return t[k]; } });

条件 Hook 减少干扰:只在参数满足特定条件时插入 debugger,避免正常调用淹没真正的加密调用。

const orig = JSON.stringify;
JSON.stringify = function(val) {
    if (JSON.stringify(val).includes('sign')) debugger; // 只在有 sign 字段时断点
    return orig.apply(this, arguments);
};

恢复原函数要保存引用:Hook 前先把原函数存到变量,调试完后能恢复,避免影响页面正常功能。

const _origFetch = window.fetch;
window.fetch = function(...args) {
    console.log('fetch:', args[0]);
    return _origFetch.apply(this, args);
};
// 调试完后:window.fetch = _origFetch;

用快速索引表(文末)定位目标 Hook:本文末尾有按场景分类的快速查找表,遇到未知加密时先查表确定 Hook 类型,再找对应章节。


常见陷阱

陷阱:Hook 后目标函数返回 undefined 导致页面报错

现象: 注入 Hook 后,页面报错 Cannot read properties of undefined,原功能失效。

原因: Hook 函数忘记 return orig.apply(this, arguments),导致原函数不被调用,返回值丢失。

解决: 任何替换原函数的 Hook,最后一行必须 return 原函数调用结果。

// 错误:没有 return
window.btoa = function(s) {
    console.log('btoa:', s);
    orig_btoa(s); // 没有 return,调用方拿到 undefined
};

// 正确:return 原函数结果
window.btoa = function(s) {
    console.log('btoa:', s);
    return orig_btoa.call(this, s);
};

陷阱:Hook 不生效——目标代码用的是局部变量副本

现象: 已替换 window.btoa,但加密函数调用的 btoa 没有触发 Hook。

原因: 目标代码在模块作用域顶部执行了 const btoa = window.btoa,后续使用局部变量,Hook 替换 window.btoa 对它无效。

解决: 必须在目标代码执行之前注入 Hook;或改用 Object.defineProperty(globalThis, 'btoa', ...) 并设 configurable: false 阻止被覆盖。

陷阱:debugger 在 Hook 里无效

现象: Hook 函数里写了 debugger,但 DevTools 不暂停。

原因: DevTools 设置了"Pause on caught exceptions"且 debugger 被异常捕获;或者 DevTools 未打开,debugger 语句静默跳过;或者该脚本是 Worker 线程,需要在 Workers 面板调试。

解决: 确保 DevTools 已打开再刷新;检查是否在 Worker 上下文(URL 搜索 worker.js);尝试换成 console.trace() 先验证 Hook 是否被触发。


参见

阅读更多

Web 安全基础

1. HTML 转义(服务端渲染必须): 2. CSP(Content Security Policy): 3. HttpOnly Cookie:防止 JS 读取会话 Cookie: 4. 前端框架防护: 攻击者在第三方网站构造一个表单,诱导已登录用户提交,浏览器会自动携带目标站的 Cookie。 触发条件: 1. 用户已登录目标网站(Cookie 有效) 2. 目标 API 仅凭 Cookie 识别用户身份 3. 请求来源未验证 1. CSRF Token(推荐): 2. SameSite Cookie: 3. 验证 Origin/Referer 头:

By yellowdog

HTTP 协议深度指南

HTTP(HyperText Transfer Protocol)是 Web 的基础传输协议,基于 TCP/IP,采用请求/响应模型。 相关文档:Web安全基础(/web-an-quan-ji-chu/) FastAPI完全指南(/fastapi-wan-quan-zhi-nan/) Nginx完全指南(/nginx-wan-quan-zhi-nan/) 幂等性:多次执行相同请求,服务器状态结果相同。PUT /users/1 多次执行结果一致;POST /users 每次创建新资源,非幂等。 浏览器直接从本地缓存读取,不向服务器发送请求。 缓存命中时,状

By yellowdog

系统设计基础

SLA 对照表: 选择建议:无状态服务(Web 层、API 层)优先水平扩展;数据库初期垂直扩展,达到瓶颈后考虑分库分表或读写分离。 缓存穿透(查询不存在的 key,每次都打到 DB): 缓存击穿(热点 key 过期,瞬间大量请求打到 DB): 缓存雪崩(大量 key 同时过期,或缓存服务宕机): 令牌桶 Python 实现: Redis 实现分布式限流(滑动窗口): URL 命名规则: Cursor 分页响应格式: 雪花算法结构(64 bit): 定义:分布式系统不能同时满足以下三个特性: 在分布式环境中 P 是必须保证的,所以实际是 CP vs AP

By yellowdog

算法思路与模板

二分查找要求序列有序,每次将搜索范围缩减一半,时间复杂度 O(log n)。 两个指针从两端向中间收缩,常用于有序数组。 滑动窗口维护一个满足条件的区间 left, right,right 不断向右扩张,条件不满足时收缩 left。 滑动窗口通用框架: 1. 确定"子问题":原问题可以分解为哪些规模更小的同类问题 2. 定义 dpi 或 dpij 的含义,要足够清晰 3. 推导状态转移方程 4. 确定初始状态(边界条件) 5. 确定计算顺序(确保依赖的子问题先计算) 每件物品最多选一次。dpj = 容量为 j 时的最大价值,逆序遍历容量防止重复选取。 每

By yellowdog