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

输出结果如下:

image

然后,我们看一下经典的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.parsethis 通常不重要,但这是标准写法),并将参数完整透传给原生函数。

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' }

输出结果如下:

image 

上述示例代码中,没有使用...rest,因为 JSON.parse 的标准用法通常只接受一个参数(即 JSON 字符串)。

// 标准调用
JSON.parse('{"a": 1}'); 
虽然标准用法只有一个参数,但在 JavaScript 中,函数调用非常灵活。加上 ...rest 是为了兼容所有可能的调用方式,确保 Hook 的代码是完美透传的。
JSON.parse 其实支持第二个参数(reviver 函数):
// 带有第二个参数的调用
JSON.parse('{"a": 1}', function(key, value) { return value; });

使用 ...rest 可以确保无论调用者传了几个参数,我们都能原封不动地传给原生函数。 

 

posted @ 2026-04-20 20:30  chenlight  阅读(4)  评论(0)    收藏  举报