补环境基础(四) Hook插件
1.Hook函数插件
Hook函数:
可以在不修改原函数代码的情况下,在函数执行前后插入自定义逻辑,同时还能修改函数的
toString()
行为和name
属性,让钩子函数看起来更像原生函数。
主要功能
1.自定义toString
模拟原生函数的输出(如function xxx() { [native code] }
)。
!function(){const $toString = Function.prototype.toString;const symbol =Symbol();const myToString = function(){return typeof this ==='function'&&this[symbol] ||$toString.call(this);}function set_native(func,key,value){Object.defineProperty(func,key,{enumerable:false,configurable:true,writable:true,value:value});}delete Function.prototype.toString;set_native(Function.prototype,"toString",myToString)set_native(Function.prototype.toString,symbol,"function toString() { [native code] }");xu.setnative= function (func,funcname){set_native(func,symbol,`function ${funcname || func.name ||''}() { [native code] }`);}}();
2.函数重命名
用于修改函数的name
属性
xu.rename = function(func, name){Object.defineProperty(func, "name", {writable: false, // 不可写enumerable: false, // 不可枚举configurable: true, // 可配置(未来可修改)value: name // 新的name值})
}
3.自定义hook逻辑
在函数执行前后插入自定义逻辑(如日志、参数修改、返回值处理等)。
hook = function (func, funcInfo, isDebug, onEnter, onLeave, isExc) {
/*1.func 原函数,需要hook的函数2.funcInfo 是一个对象,传入 objName,funName 方便查看一些日志信息3.isDebug 是否进行调试 boolean类型 用于关键点定位4.onEnter 函数,原函数执行前的操作,改原函数的入参5.onLeave 原函数执行后的操作 可以操作原函数的返回值6.isExc 是否执行原函数
*/if (typeof func !== "function") return func; // 如果不是函数,直接返回// 初始化funcInfo(默认全局对象下的函数)if (funcInfo === undefined) {funcInfo = { objName: "globalThis", funcName: func.name || "" };}// 默认参数处理if (isDebug === undefined) isDebug = false;if (!onEnter) { // 默认的进入回调(打印参数)onEnter = function(obj){console.log(`{hook|${funcInfo.objName}[${funcInfo.funcName}]正在调用,参数是${JSON.stringify(obj.args)}}`);};}if (!onLeave) { // 默认的离开回调(打印返回值)onLeave = function (obj){console.log(`{hook|${funcInfo.objName}[${funcInfo.funcName}]正在调用,返回值是[${obj.result}}]`);};}if (isExc === undefined) isExc = true;// 定义钩子包装函数const hookFunc = function () {if (isDebug) debugger; // 调试模式下触发断点// 收集参数const obj = { args: [] };for (let i = 0; i < arguments.length; i++) {obj.args[i] = arguments[i];}// 执行进入回调(原函数执行前)onEnter.call(this, obj);// 执行原函数(根据isExc决定是否执行)let result;if (isExc) {result = func.apply(this, obj.args); // 用apply保证this指向正确}obj.result = result;// 执行离开回调(原函数执行后)onLeave.call(this, obj);return obj.result; // 返回结果}// 让钩子函数看起来像原函数:设置toString和namexu.setnative(hookFunc, funcInfo.funcName);xu.rename(hookFunc, funcInfo.funcName);return hookFunc; // 返回包装后的钩子函数
}
完整代码及测试
xu={};
!function(){const $toString = Function.prototype.toString;const symbol =Symbol();const myToString = function(){return typeof this ==='function'&&this[symbol] ||$toString.call(this);}function set_native(func,key,value){Object.defineProperty(func,key,{enumerable:false,configurable:true,writable:true,value:value});}delete Function.prototype.toString;set_native(Function.prototype,"toString",myToString)set_native(Function.prototype.toString,symbol,"function toString() { [native code] }");xu.setnative= function (func,funcname){set_native(func,symbol,`function ${funcname || func.name ||''}() { [native code] }`);}}();
xu.rename = function(func,name){Object.defineProperty(func,"name",{writable:false,enumerable:false,configurable:true,value:name})
}
hook = function (func,funcInfo,isDebug,onEnter,onLeave,isExc){if(typeof func !== "function"){return func;}if(funcInfo === undefined ){funcInfo = {};funcInfo.objName = "globalThis";funcInfo.funcName = func.name || "";}if(isDebug === undefined){isDebug = false;}if (!onEnter){onEnter = function(obj){console.log(`{hook|${funcInfo.objName}[${funcInfo.funcName}]正在调用,参数是${JSON.stringify(obj.args)}}`);};}if (!onLeave){onLeave = function (obj){console.log(`{hook|${funcInfo.objName}[${funcInfo.funcName}]正在调用,返回值是[${obj.result}}]`);};}if(isExc === undefined){isExc = true;}hookFunc = function (){if(isDebug){debugger;};let obj = {};obj.args = [];for(let i =0;i<arguments.length;i++) {obj.args[i] = arguments[i];};//原函数执行前onEnter.call(this,obj)//原函数正在执行let result;func.apply(this,obj.args);if(isExc) {result = func.apply(this, obj.args)}obj.result = result//原函数执行后onLeave.call(this,obj)return obj.result}xu.setnative(hookFunc,funcInfo.funcName)xu.rename(hookFunc,funcInfo.funcName)return hookFunc
}function add(a,b){return a+b;};xm = {"objName":"obj","funcName":"add"
};
onEnter = function (obj){console.log(`[${xm.funcName}]调用前的参数${JSON.stringify(obj.args)}`)
}
onleave = function(obj){console.log(`[${xm.funcName}]调用后返回值[${obj.result}]`);
}add = hook(add,xm,true,onEnter,onleave,true);
console.log(add(1, 5));
console.log(add.toString())
console.log(add.name)
console.log(Function.prototype.toString.call(add))
2.Hook对象及原型插件
Hook对象插件
Hook对象插件:
用于hook对象的属性,如果该属性是函数(或访问器方法 get/set),则用之前定义的
hook
函数插件对其进行包装,从而在该属性(函数)执行前后插入自定义逻辑(如日志、调试等),同时保持原属性的其他特性(可配置性、可枚举性等)。
常用于需要监控或增强对象属性(尤其是方法)的场景,例如:监控window.alert
的调用、拦截对象属性的读写等。
xu.hookObj = function hookObj(obj,objName,proName,isDebugger){/*1.obj:需要被 hook 的目标对象(如window、自定义对象等)。2.objName:目标对象的名称(字符串,用于日志或标识,如"window"、"myObj")。3.proName:需要 hook 的对象属性名(字符串,如"add"、"name")。4.isDebugger:是否开启调试模式(布尔值,开启后会触发debugger断点)。*/let oldDescripor = Object.getOwnPropertyDescriptor(obj,proName);let newDescripor = {};//是否可配置if(!oldDescripor.configurable){return}newDescripor.configurable = oldDescripor.configurable;//是否枚举newDescripor.enumerable = oldDescripor.enumerable;//是否可写oldDescripor.writableif(oldDescripor.hasOwnProperty("writable")){newDescripor.writable = oldDescripor.writable};//配置属性描述符Valueif(oldDescripor.hasOwnProperty("value")){let value = oldDescripor.value;if(typeof value != "function"){return;};let funcInfo = {"objName": objName,"funcName": proName}newDescripor.value = xu.hook(value,funcInfo,isDebugger)};//配置属性描述符getif(oldDescripor.hasOwnProperty("get")){let get = oldDescripor.get;let funcInfo = {"objName": objName,"funcName": `get ${proName}`}newDescripor.get = xu.hook(get,funcInfo,isDebugger)};//配置属性描述符setif(oldDescripor.hasOwnProperty("set")){let set = oldDescripor.get;let funcInfo = {"objName": objName,"funcName": `set ${proName}`}newDescripor.set = xu.hook(set,funcInfo,isDebugger)};Object.defineProperty(obj,proName,newDescripor);
}
Hook原型对象插件
Hook原型对象插件:
用于批量 hook 某个构造函数原型对象上的所有自身属性(包括方法、getter、setter 等),本质是对
hookObj
的进一步扩展,实现 “一劳永逸” 地监控某个类型所有实例的方法调用。
xu.hookProto = function hookProto (proto,isDebug){let protoObj = proto.prototype;let name = proto.name;for(const prop in Object.getOwnPropertyDescriptors(protoObj)){xu.hookObj(protoObj,`${name}.prototype`,prop,isDebug);}console.log(`hook ${name}.prototype`);
};