Hook 代码段

Hook 的核心原理是利用 JavaScript 弱类型语言特性,用自定义函数替换原生函数,在调用时插入 debugger 或日志,从而定位加密逻辑。 两种核心实现方式: 监控某个对象上指定属性的读取、写入、删除操作,精确定位属性被谁写入。 代理整个对象,监控其上所有属性的读写操作,适合不知道属性名时的全量监控。 Hook 内置对象的原型方法,监控 String、Array、Object 等原生方法的调用。 优先用 XHR/fetch 断点定位,再向上溯源:不要一开始就 Hook 加密函数,先在 DevTools Network 面板开启"XHR/fet

分享

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

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

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

两种核心实现方式:

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

注入方式

方式 说明
控制台直接粘贴 刷新页面前在 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');

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;
        var _onreadystatechange = this.onreadystatechange;
        this.addEventListener('readystatechange', function () {
            if (this.readyState === 4) {
                console.log('[XHR response] url =', this._url);
                console.log('[XHR response] status =', this.status);
                // this.responseText 即为响应体文本
                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);
            };

            // 拦截 onopen
            ws.onopen = function (event) {
                console.log('[WebSocket OPEN]');
                if (_onmessage && ws._onopen) return ws._onopen.call(this, event);
            };

            // 保存到全局方便调试
            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 });
}

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);
            // Hook 各加密算法的 encrypt/decrypt
            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);

场景速查表

目标 推荐 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)
防止控制台被清空 console.clear 阻止(15.3)

最佳实践

优先用 XHR/fetch 断点定位,再向上溯源:不要一开始就 Hook 加密函数,先在 DevTools Network 面板开启"XHR/fetch breakpoints"匹配目标 URL,断到请求发出时,再通过调用栈往上找参数生成的位置,能快速缩小范围。

Hook 里用条件 debugger 而非无条件 debugger:加密函数往往被高频调用,无条件 debugger 会每次都暂停。先 console.log 观察参数规律,再加条件限制只在目标参数时暂停。

const orig = CryptoJS.AES.encrypt;
CryptoJS.AES.encrypt = function(data, key) {
    // 只在数据包含 'token' 字段时断点
    if (JSON.stringify(data).includes('token')) debugger;
    return orig.apply(this, arguments);
};

Proxy Hook 优于 Object.defineProperty HookObject.defineProperty 只能拦截已知属性,Proxy 可以拦截动态属性和方法调用。对不确定属性名的对象用 Proxy,对已知属性用 defineProperty(开销更低)。

Cookie Hook 优先用 Object.defineProperty(document, 'cookie', ...):这是最可靠的 Cookie 读写监控方式,比 Hook XMLHttpRequest 更早拦截,且能同时捕获读和写。

保存原函数引用确保可恢复:始终用 const orig = target.method 保存原函数,调试完后 target.method = orig 恢复,避免影响后续正常功能的测试。


常见陷阱

陷阱:Hook fetch 后响应体只能读一次

现象: Hook 里 response.json() 打印了数据,但调用方再次调用 .json() 报错 body stream already read

原因: Response 的 body 是流,只能消费一次。Hook 里读取后,原调用方就读不到了。

解决:response.clone() 克隆响应,Hook 读克隆体,把原始响应传给调用方。

const origFetch = window.fetch;
window.fetch = async function(...args) {
    const res = await origFetch.apply(this, args);
    const clone = res.clone(); // 克隆,Hook 读克隆体
    clone.json().then(d => console.log('fetch response:', d)).catch(() => {});
    return res; // 返回原始响应给调用方
};

陷阱:Hook XMLHttpRequest.prototype.send 拿不到请求头

现象: Hook send 能打印 body,但加密参数在请求头里,看不到。

原因: 请求头通过 setRequestHeader 方法添加,send 触发时已经设置完成,但 Hook 的 send 拿不到已设置的头部集合(XHR 内部维护)。

解决: 同时 Hook XMLHttpRequest.prototype.setRequestHeader,在里面记录所有头部。

const headers = {};
const origSetHeader = XMLHttpRequest.prototype.setRequestHeader;
XMLHttpRequest.prototype.setRequestHeader = function(name, value) {
    headers[name] = value;
    if (name.toLowerCase().includes('sign')) debugger;
    return origSetHeader.apply(this, arguments);
};

陷阱:无限 debugger 绕过后 Hook 失效

现象: 成功绕过无限 debugger,但同时注入的 Hook 也不触发了。

原因: 无限 debugger 往往通过 evalFunction 构造动态执行,绕过方式通常是 Hook 掉 eval/Function,导致所有动态代码都不执行,目标加密函数也在其中。

解决: 区分无限 debugger 代码和加密代码:只在 eval 内容包含 debugger 关键字时拦截,其他正常执行。

const origEval = eval;
window.eval = function(code) {
    if (code.includes('debugger')) return; // 只拦截含 debugger 的动态代码
    return origEval.call(this, code);
};

参见

阅读更多

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