在前端逆向、调试、性能监控等场景中,Hook(钩子)技术是不可或缺的核心手段。它能让我们在不破坏原有代码结构的前提下,“截胡”函数执行、监控数据流转,甚至修改程序行为——比如捕获加密参数、定位解密逻辑、拦截请求等。但要真正用好Hook,首先得搞懂它的底层支撑:JavaScript对象属性机制。今天就从原理到实战,手把手教你掌握JS Hook技术,轻松实现函数截胡。

一、先搞懂:什么是JS Hook技术?

Hook,中文译为“钩子”,本质是一种动态拦截技术。简单来说,就是在程序运行过程中,拦截目标函数(或对象属性)的执行/访问,在其执行前、执行后插入我们自己的逻辑,从而实现对原始行为的监控、扩展或修改。

核心亮点:不侵入原有代码。无需修改目标函数的源代码,就能实现对其行为的控制,这也是Hook在前端逆向、调试中如此实用的关键——毕竟我们无法直接修改别人网站的源码,但可以通过Hook“借力打力”。

举个通俗的例子:就像在快递运输的中途装了一个监控,快递(函数执行、数据流转)正常送达,但我们能实时看到快递的状态(监控逻辑),甚至可以在中途拦截、修改快递内容(修改逻辑)。

二、Hook的底层支撑:JS对象属性机制

很多人用Hook只知其然,不知其所以然,其实Hook的核心原理,完全依赖于JavaScript的对象属性机制。想要灵活运用Hook,必须先搞懂对象属性的“底层规则”。

1. 对象属性的3个核心特性

JavaScript中,每个对象的属性(比如obj.name),除了“名字(name)”和“值(value)”,还有3个隐藏的核心特性,它们决定了我们能对这个属性做什么操作:

  • writable(可写性):控制属性的值是否能被修改。比如设置为false,就无法给这个属性重新赋值。

  • enumerable(可枚举性):控制属性是否能在for...in循环中被遍历到。比如设置为false,遍历对象时会“隐藏”这个属性。

  • configurable(可配置性):控制属性是否能被删除,或重新定义其特性(比如把writabletrue改成false)。

默认情况下,我们手动创建的普通对象(比如const obj = { name: '张三' }),这3个特性的值都是true——也就是说,属性可修改、可枚举、可配置。

2. 关键概念:属性描述符(Property Descriptor)

属性描述符,就是用来“描述”对象属性这3个核心特性的结构化对象。它分为两种类型,也是实现Hook的核心工具,务必分清:

(1)数据描述符:描述“有具体值”的属性

适用于直接存储值的属性,包含4个配置项(3个核心特性+1个值):

  • value:属性的实际值(比如name: '张三'中,value就是'张三')。

  • writable:是否可写(true/false)。

(2)存取描述符:用函数拦截属性的“读/写”

不直接存储值,而是通过两个函数来拦截属性的“读取”和“赋值”操作,这也是Hook最常用的方式。包含4个配置项(3个核心特性+2个函数):

  • get:读取属性时自动调用的函数,返回值就是属性的“实际值”。

  • set:给属性赋值时自动调用的函数,接收赋值的新值,可在函数内处理赋值逻辑。

⚠️⚠️⚠️ 重要提醒:数据描述符和存取描述符是互斥的。如果定义了getset,就不能再设置valuewritable,否则会报错

3. 操作属性描述符的2个核心方法

想要查看、修改属性的特性,实现Hook,就必须掌握这两个方法:

(1)Object.getOwnPropertyDescriptor():查看属性特性

用于获取对象某个属性的完整描述符,语法:Object.getOwnPropertyDescriptor(对象, 属性名)

示例:查看普通对象的属性描述符

const person = { name: '张三', age: 25 };
// 查看name属性的描述符
const nameDescriptor = Object.getOwnPropertyDescriptor(person, 'name');
console.log(nameDescriptor);

输出结果(默认特性全为true):

{
    "value": "张三",
    "writable": true,
    "enumerable": true,
    "configurable": true
}

(2)Object.defineProperty():定义/修改属性特性

用于给对象添加新属性,或修改已有属性的特性,语法:Object.defineProperty(对象, 属性名, 描述符对象)

示例:定义一个“只读、不可枚举、不可删除”的属性

const config = {};
// 给config添加appkey属性,设置为只读、不可枚举、不可删除
Object.defineProperty(config, 'appkey', {
    value: '123456',
    writable: false, // 不可写
    enumerable: false, // 不可枚举
    configurable: false // 不可配置(不能删除、不能修改特性)
});

// 尝试修改appkey的值
config.appkey = 'abcdefg';
console.log(config.appkey); // 输出:123456(修改失败)

注意:现代浏览器默认是非严格模式,修改只读属性不会报错,但修改无效;如果加上'use strict'(严格模式),会直接抛出“不能对只读属性重新赋值”的错误。

4. 存取描述符实战:拦截属性的读/写

利用getset,我们可以轻松实现对属性的拦截——这就是Hook的最基础形态。

const data = {};

// 给data添加message属性,用get/set拦截读/写
Object.defineProperty(data, 'message', {
    get: function() {
        console.log('🔍有人正在读取message属性');
        return '默认值'; // 读取时返回的值
    },
    set: function(newValue) {
        console.log('✏️有人正在修改message属性:', newValue);
        // 可以在这里添加自定义逻辑,比如校验新值
        if (typeof newValue === 'string') {
            console.log('✅赋值有效');
        } else {
            console.log('❌赋值无效(必须是字符串)');
        }
    },
    enumerable: true,
    configurable: true
});

// 测试拦截效果
console.log(data.message); // 触发get,输出提示+默认值
data.message = '12345'; // 触发set,输出提示+赋值有效
data.message = 123; // 触发set,输出提示+赋值无效

运行后会发现,只要读取或修改data.message,我们的拦截逻辑就会触发——这就是Hook的核心思想:拦截操作,插入自定义逻辑

三、实战进阶:JS Hook如何“强行截胡”函数?

了解了对象属性机制,我们就可以正式进入Hook实战了。在JavaScript中,函数也是一种对象,所以我们可以通过修改函数对象的属性(比如重写函数),实现对函数的截胡。

1. Hook的基本结构

无论是什么场景的Hook,核心结构都离不开这3步,记牢就能应对80%的需求:

function hook() {
    // 1. 保存原始函数(关键!避免覆盖后无法调用原始逻辑)
    let originalFunc = 被Hook的函数;
    
    // 2. 重写函数,插入自定义逻辑(截胡核心)
    被Hook的函数 = function(参数) {
        // 执行前插入逻辑(比如监控参数、打印日志)
        console.log('🔍函数被调用,参数:', 参数);
        
        // 调用原始函数,获取返回值(保证原有逻辑正常执行)
        let result = originalFunc.apply(this, arguments);
        
        // 执行后插入逻辑(比如监控返回值、修改返回值)
        console.log('✅函数执行完成,返回值:', result);
        
        // 返回原始结果(或修改后的结果)
        return result;
    };
}

核心原则:先保存原始函数,再重写函数。如果直接重写,会导致原始函数无法调用,破坏原有程序逻辑。

2. 3个高频实战案例(直接复制可用)

下面结合实际逆向场景,分享3个最常用的Hook案例,覆盖“请求头、请求参数、响应解析”,都是前端逆向中经常遇到的场景。

案例1:请求头Hook——捕获加密参数(观鸟网实战)

场景:观鸟网的请求头中,有一个加密参数sign,我们需要捕获这个参数的值,定位其加密逻辑。

思路:重写XMLHttpRequest.prototype.setRequestHeader(设置请求头的方法),拦截sign参数的设置过程。

// 封装成自执行函数,避免污染全局变量
(function () {
    // 1. 保存原始方法
    var originalSetHeader = XMLHttpRequest.prototype.setRequestHeader;
    
    // 2. 重写setRequestHeader方法
    XMLHttpRequest.prototype.setRequestHeader = function (key, value) {
        // 自定义逻辑:拦截sign参数,触发断点
        if (key === 'sign') {
            debugger; // 断点触发,此时value就是加密后的sign值
            console.log('📌捕获到sign参数:', value);
        }
        // 3. 调用原始方法,保证请求头正常设置
        return originalSetHeader.apply(this, arguments);
    }
})();

使用方法:

  1. 打开观鸟网,按F12打开开发者工具,切换到「Sources > 片段(Snippets)」;

  2. 新建片段,粘贴上述代码,保存并执行;

  3. 刷新页面,当请求头中设置sign时,会自动触发断点,此时就能看到加密后的sign值,再通过调用栈定位加密函数。

案例2:请求参数Hook——捕获URL中的加密参数(闲鱼实战)

场景:闲鱼请求参数中,加密参数sign会拼接到URL中,我们需要捕获这个参数,分析其生成逻辑。

思路:重写XMLHttpRequest.prototype.open(创建请求的方法),判断URL中是否包含sign,触发断点。

(function () {
    // 1. 保存原始方法
    var originalOpen = window.XMLHttpRequest.prototype.open;
    
    // 2. 重写open方法
    window.XMLHttpRequest.prototype.open = function (method, url, async) {
        // 自定义逻辑:判断URL中是否包含sign参数
        if (url.indexOf("sign") !== -1) {
            debugger; // 断点触发,此时url就是包含sign的请求地址
            console.log('📌捕获到含sign的URL:', url);
        }
        // 3. 调用原始方法,保证请求正常创建
        return originalOpen.apply(this, arguments);
    };
})();

使用方法:

  1. 打开闲鱼首页,打开开发者工具,创建片段并执行上述代码;

  2. 点击页面翻页、搜索等操作,触发请求;

  3. 当URL中包含sign时,会触发断点,通过调用栈就能找到sign的生成位置。

案例3:响应数据解析Hook——定位解密逻辑(数位观察网实战)

场景:数位观察网的响应数据是加密密文,解密后会调用JSON.parse解析成JSON,我们需要定位解密函数的位置。

思路:重写JSON.parse方法,在解析数据时触发断点,通过调用栈回溯到解密函数。

(function () {
    // 1. 保存原始方法
    var originalParse = JSON.parse;
    
    // 2. 重写JSON.parse方法
    JSON.parse = function (value) {
        // 自定义逻辑:触发断点,此时value就是解密后的字符串
        debugger;
        console.log('📌捕获到JSON解析数据:', value);
        // 3. 调用原始方法,保证数据正常解析
        return originalParse.apply(this, arguments);
    }
})();

使用方法:

  1. 打开目标网站,在开发者工具的控制台中粘贴上述代码并执行;

  2. 刷新页面,当网站调用JSON.parse解析解密后的数据时,会触发断点;

  3. 查看调用栈(Call Stack),往上回溯,就能找到解密函数的位置(解密函数的返回值,就是JSON.parse的参数value)。

四、Hook实战注意事项

  • 执行时机要早:Hook代码必须在目标函数被调用前注入(比如页面刷新前执行),否则目标函数已经执行完毕,Hook无法生效(这也是很多人Hook失败的核心原因)。

  • 保存原始函数:务必先保存原始函数,再重写,否则会破坏原有程序逻辑,导致页面异常。

  • 注意作用域:如果网站用了Webpack打包、闭包封装,全局Hook可能无效,需要在对应作用域内注入Hook代码。

  • 避免过度Hook:不要Hook所有函数,只针对目标函数进行拦截,否则会影响页面性能,甚至导致调试混乱。

五、总结

JS Hook技术的核心,是利用JavaScript对象属性的存取描述符,通过“保存原始函数→重写函数→插入自定义逻辑”的流程,实现对函数的截胡。它的优势在于“不侵入原有代码”,这让它在前端逆向、调试、性能监控等场景中发挥着不可替代的作用。

从底层原理来看,理解对象属性的3个核心特性、两种描述符,是用好Hook的基础;从实战来看,掌握“请求头、请求参数、响应解析”这3个高频场景的Hook方法,就能应对大部分前端逆向需求。

当然,Hook技术也在不断升级,比如网站会通过重写原生函数、代码混淆等方式反Hook,但万变不离其宗——只要掌握了对象属性机制和Hook的核心逻辑,就能找到对应的破解方法。