jsHook原理(一)
JS Hook 是运行时劫持 / 拦截 / 修改 JavaScript 函数 / 对象 / 属性行为的技术,不修改源码即可插入自定义逻辑。
一、核心思想
- 把原始函数 / 方法 / 属性保存起来
- 用代理 / 包装函数 / 劫持后的属性替换它
- 调用时:先跑你的代码 → 再跑原代码(或完全替换)
- 作用:监控、修改参数 / 返回值、打日志、加逻辑、绕过限制
二、Web 端常用实现方式(浏览器 / Node)
1. 函数劫持(最常用)
保存原函数 → 替换成包装函数
以JSON.parse为例子
JSON.parse() 是 JavaScript 中一个用于将 JSON 字符串转换为 JavaScript 对象或值的内置方法。💡 基本用法
JSON.parse() 可以将表示对象、数组、字符串、数字、布尔值和 null 的 JSON 字符串还原为对应的 JavaScript 值。// 解析 JSON 对象字符串
const jsonString = '{"name":"张三","age":30,"city":"北京"}';
const obj = JSON.parse(jsonString);
console.log(obj); // 输出: 张三
// 解析 JSON 数组字符串
const jsonArray = '[1, 2, 3]';
const arr = JSON.parse(jsonArray);
console.log(arr); // 输出: [1, 2, 3]
// 解析基本类型
JSON.parse('true'); // 输出: true
JSON.parse('123'); // 输出: 123
JSON.parse('"hello"'); // 输出: 'hello'
JSON.parse('null'); // 输出: null
输出结果如下:

然后,我们看一下经典的jsHook案例:
// 1. 保存原函数
const originalParse = JSON.parse;
// 2. 替换成自己的函数
JSON.parse = function (...args) {
console.log("被解析的 JSON 字符串:", args[0]);
// 3. 调用原生 parse
const result = originalParse.apply(this, args);
console.log("解析结果:", result);
return result;
};
JSON.parse('{"name":"test","age":"28"}');
这段代码是一个非常经典的 Hook(钩子) 技术实现。它的核心目的是拦截并监控 JavaScript 中
JSON.parse 的行为。在逆向工程、调试复杂应用或分析网络请求数据时,这段代码非常有用。它可以帮你定位数据是在哪里被解析的,以及解析前后的具体数据内容。
以下是这段代码的详细逻辑解析、应用场景以及需要注意的“坑”:
代码逻辑深度解析
这段代码采用了“保存 - 重写 - 调用”的标准 Hook 模式:
- 保存原函数 (
const originalParse = JSON.parse;)- 目的:在覆盖原生方法之前,必须先保存一个引用。
- 原因:如果不保存,你将无法再调用真正的 JSON 解析功能,导致程序逻辑中断或陷入死循环。
- 重写方法 (
JSON.parse = function (...args) { ... })- 目的:劫持对
JSON.parse的所有调用。 - 机制:利用 JavaScript 的动态特性,将全局对象
JSON上的parse属性替换为你自定义的函数。 - 参数处理:使用
...args(剩余参数) 来接收所有传入的参数(通常是 JSON 字符串,有时也可能包含 reviver 函数)。
- 目的:劫持对
- 注入逻辑 (
console.log(...))- 目的:这是 Hook 的核心价值所在。你在数据解析前打印了原始字符串,这通常用于查看加密前的密文或接口返回的原始数据。
- 调用原生 (
originalParse.apply(this, args))- 目的:保证程序正常运行。
- 细节:使用
.apply(this, args)是为了确保this指向正确(虽然在JSON.parse中this通常不重要,但这是标准写法),并将参数完整透传给原生函数。
2.进阶:修改参数 / 返回值
修改传入的 JSON 字符串
// 1. 保存原生方法
const originalParse = JSON.parse;
// 2. 重写 JSON.parse
JSON.parse = function (text, ...rest) {
console.log(`🕵️♂️ 拦截到原始数据: ${text}`);
// 篡改逻辑:将 "test" 替换为 "hooked"
const newText = text.replace("test", "hooked");
console.log(`🔧 篡改后数据: ${newText}`);
// 调用原生方法并返回结果
return originalParse.call(this, newText, ...rest);
};
// --- 测试场景 ---
// 场景 A:包含 "test" 的字符串
const jsonStrA = '{"name": "test", "value": 123}';
const objA = JSON.parse(jsonStrA);
console.log("✅ 解析结果 A:", objA);
// 预期输出: { name: 'hooked', value: 123 }
// 场景 B:不包含 "test" 的字符串
const jsonStrB = '{"name": "admin", "role": "user"}';
const objB = JSON.parse(jsonStrB);
console.log("✅ 解析结果 B:", objB);
// 预期输出: { name: 'admin', role: 'user' }
输出结果如下:
上述示例代码中,没有使用...rest,因为 JSON.parse 的标准用法通常只接受一个参数(即 JSON 字符串)。
// 标准调用
JSON.parse('{"a": 1}');
虽然标准用法只有一个参数,但在 JavaScript 中,函数调用非常灵活。加上
...rest 是为了兼容所有可能的调用方式,确保 Hook 的代码是完美透传的。JSON.parse 其实支持第二个参数(reviver 函数):// 带有第二个参数的调用
JSON.parse('{"a": 1}', function(key, value) { return value; });
使用 ...rest 可以确保无论调用者传了几个参数,我们都能原封不动地传给原生函数。

浙公网安备 33010602011771号