一键hook脚本原理
1.第一个知识点: 原型链
先拿平时大家最经常hook document.cookie来举个例子。
Object.defineProperty(document,”cookie”,{
get:function(){},
set:function(){}
});
然后我们需要自己去实现里面的代码逻辑
而这个代码又有一个问题。事实上cookie是不在document对象下的
Object.getOwnPropertyDescriptors(document);
用这个api我们可以查看document下的所有对象属性。

就很怪,没有cookie对象,那document是怎么拿到cookie的值的?
这就涉及到原型链的知识点了。document获取cookie的时候,发现自身没有这个对象时,
会从document.__proto__ 去寻找有没有cookie这个对象。(document.__proto__ == HTMLDocument.prototype),还是没有,然后就继续这个操作寻找,直到原型链的最后一层。
然后的话,在Document.prototype 下发现了这个cookie对象,并获取它的值。
但是不等于Document.prototype.cookie 来获取值,这样调用会报错的,因为它不知道调用者是谁。
Object.getOwnPropertyDescriptors(Document.prototype);
打印看看

那么事实上hook代码应该是
Object.defineProperty(Document.prototype,”cookie”,{
get:function(){},
set:function(){}
});
这样的话,只要是对象继承Document.prototype,并获取cookie这个值的时候都会被我们hook住,而不只是仅仅hook document来调用的时候。
所以我们只需要hook所有原型链上的方法,或者原型链上所有对象的get set方法,就能实现像v神插件那样一键hook的功能。
2.第二个知识点:apply的妙用
但是我们hook 那么多的方法,总不能一个个去手动实现方法内容吧?能不能一键给所有方法添加一个拦截器,调用的时候先走到拦截器,先执行我们的逻辑,再走到原生方法里
这就要用到apply这个实用的api了。
举个例子
我们hook document.createElement
document.createElement_ = document.createElement;
document.createElement =
function createElement (参数1){
//写hook逻辑;return document.createElement_(参数1);
}
这还要自己手动传参,那么多方法,我怎么知道他到底有几个参数,要如何传参给原来的方法?
用apply的话就很简单了
document.createElement_ = document.createElement;
document.createElement = function createElement()
{//写hook逻辑;
return document.createElement_.apply(undefined,arguments);
}
arguments是一个数组,存放的就是当前方法里传进来的参数
我们原先调用方法是 document.createElement(“a”);
用apply,document.createElement.apply(undefined, [“a”,]);
它会帮我们去分配参数,和方法体定义的传参一一对应上
再来说说apply方法传进去的第一个参数
document.createElement.apply(undefined, [“a”,]) == document.createElement.apply(document, [“a”,])
这样是等同的。 如果你传入undefined,他就默认调用者是document.你可以通过第一个参数来修改调用者。
document.createElement.apply(window, [“a”,])

这样就报错了。以上就是hook的核心原理。
3.核心api就三个
1.apply
2.Object.getOwnPropertyDescriptors
- Tips:可以用Object.getOwnPropertyDescriptors(Document.prototype).cookie.get
- 来得到get/set cookie的原生方法。等同于
- Object.getOwnPropertyDescriptor(Document.prototype,‘cookie’).get
3. Object.defineProperty
- 对方法/对象的get set重新定义,来为我们添加一层拦截器。
4.hook.js
globalMy = {};
globalMy.console_log = console.log;
globalMy.is_log = true;
globalMy.is_debug = false;
globalMy.not_log = [];
globalMy.want_debug = [];
globalMy.set_func_prop = function(func_descriptors){
let prop_track = [];
for (let name in func_descriptors){
if(name == "caller" || name == "arguments" || name == "prototype"){
continue;
}
let descriptor = func_descriptors[name];
let val = descriptor["value"];
let attr = {
configurable : descriptor["configurable"],
enumerable : descriptor["enumerable"],
}
if(descriptor["writable"]){
attr["writable"] = descriptor["writable"];
}
if(val == undefined){
debugger;
}
else{
attr["value"] = val;
}
prop_track.push({name:name,attr:attr});
}
return prop_track;
};
//保护函数,保护toString
(() => {
'use strict';
const $toString = Function.toString;
const myFunction_toString_symbol = Symbol('('.concat('', ')_', (Math.random() + '').toString(36)));
//key
const myToString = function () {
return typeof this == 'function' && this[myFunction_toString_symbol] || $toString.call(this);
};
function set_native(func, key, value) {
try {
Object.defineProperty(func, key, {
"enumerable": false,
"configurable": true,
"writable": true,
"value": value
})
} catch (e) {
globalMy.console_log("保护函数出错 => ", e)
debugger
}
}
set_native(Function.prototype, "toString", myToString);
//自己定义一个getter方法
set_native(Function.prototype.toString, myFunction_toString_symbol, "function toString() { [native code] }");
globalMy.functionprotect = (func, func_name, type) => {
set_native(func, myFunction_toString_symbol, `function ${
func_name || ''}() { [native code] }`);
}
;
}
).call(globalMy);
globalMy.check_debug = function check_debug(key) {
if (globalMy.is_debug || globalMy.want_debug.indexOf(key + '') !== -1) {
debugger;
}
}
globalMy.check_log = function check_log(obj, param_func_name, args, result) {
if (globalMy.is_log && globalMy.not_log.indexOf(param_func_name) === -1) {
let arg;
if (args.length === 1) {
arg = args[0]
} else {
arg = []
for (let i = 0; i < args.length; i++) {
arg.push(args[i]);
}
}
let property;
try {
property = JSON.stringify(obj)
} catch (e) {
property = obj
}
globalMy.console_log("[*] ", " 调用者 =>", obj, "属性值 => ", property, " 函数名 => ", param_func_name, " 传参 => ", arg, " 结果 => ", result)
}
}
// hook HTMLXxxxElement 下的所有属性的get set方法,以及方法的hook
globalMy.hook_HTML = function fuck_HTML(html) {
let html_name = html.name;
globalMy[html_name] = {};
let descriptors = Object.getOwnPropertyDescriptors(html.prototype);
for (let key in descriptors) {
if (key !== 'constructor') {
let param_value = descriptors[key].value;
if (typeof param_value === 'function') {
let attr = {
configurable: descriptors[key]['configurable'],
enumerable: descriptors[key]['enumerable'],
}
if(descriptors[key]["writable"]){
attr["writable"] = descriptors[key]["writable"];
}
let param_func_name = param_value.name;
globalMy[html_name][param_func_name] = function () {
let result = param_value.apply(this, arguments);
globalMy.check_debug(key);
globalMy.check_log(this, param_func_name, arguments, result);
return result;
}
globalMy.functionprotect(globalMy[html_name][param_func_name], param_func_name)
attr['value'] = globalMy[html_name][param_func_name];
Object.defineProperty(html.prototype, key, attr);
} else if (typeof param_value === 'undefined') {
let attr = {
configurable: descriptors[key]['configurable'],
enumerable: descriptors[key]['enumerable'],
}
if (descriptors[key]['writable']) {
attr['writable'] = descriptors[key]['writable']
}
// globalMy.console_log(descriptors[key])
if (descriptors[key]['get']) {
let param_func_name = descriptors[key]['get'].name;
globalMy[html_name][key + '_get'] = function () {
let result = descriptors[key]['get'].apply(this, arguments);
globalMy.check_debug(key);
globalMy.check_log(this, param_func_name, arguments, result);
return result;
}
let func_attr_track = globalMy.set_func_prop(Object.getOwnPropertyDescriptors(descriptors[key]['get']));
var i = 0;
while (i < func_attr_track.length){
Object.defineProperty(globalMy[html_name][key + '_get'],func_attr_track[i].name,func_attr_track[i].attr);
i++;
}
globalMy.functionprotect(globalMy[html_name][key + '_get'], param_func_name, 'get');
attr['get'] = globalMy[html_name][key + '_get'];
} else {
attr['get'] = undefined;
}
if (descriptors[key]['set']) {
let param_func_name = descriptors[key]['set'].name;
globalMy[html_name][key + '_set'] = function () {
let result = descriptors[key]['set'].apply(this, arguments);
globalMy.check_debug(key);
globalMy.check_log(this, param_func_name, arguments, result);
return result;
}
let func_attr_track = globalMy.set_func_prop(Object.getOwnPropertyDescriptors(descriptors[key]['set']));
var i = 0;
while (i < func_attr_track.length){
Object.defineProperty(globalMy[html_name][key + '_set'],func_attr_track[i].name,func_attr_track[i].attr);
i++;
}
globalMy.functionprotect(globalMy[html_name][key + '_set'], param_func_name, 'set')
attr['set'] = globalMy[html_name][key + '_set'];
} else {
attr['set'] = undefined;
}
try {
Object.defineProperty(html.prototype, key, attr);
} catch (e) {
debugger
}
}
}
}
}
// hook Object, hook 像window location 这样的属性,不得行
globalMy.hook_obj = function hook_obj(html) {
let html_name = html.name;
globalMy[html_name] = {};
let descriptors = Object.getOwnPropertyDescriptors(html);
for (let key in descriptors) {
if (key !== 'constructor') {
let param_value = descriptors[key].value;
if (typeof param_value === 'function') {
let attr = {
configurable: descriptors[key]['configurable'],
enumerable: descriptors[key]['enumerable'],
}
if(descriptors[key]["writable"]){
attr["writable"] = descriptors[key]["writable"];
}
let param_func_name = param_value.name;
globalMy[html_name][param_func_name] = function () {
let result = param_value.apply(this, arguments);
globalMy.check_debug(key);
globalMy.check_log(this, param_func_name, arguments, result);
return result;
}
globalMy.functionprotect(globalMy[html_name][param_func_name], param_func_name)
attr['value'] = globalMy[html_name][param_func_name];
Object.defineProperty(html, key, attr);
} else if (typeof param_value === 'undefined') {
let attr = {
configurable: descriptors[key]['configurable'],
enumerable: descriptors[key]['enumerable'],
}
if (descriptors[key]['writable']) {
attr['writable'] = descriptors[key]['writable']
}
// globalMy.console_log(descriptors[key])
if (descriptors[key]['get']) {
let param_func_name = descriptors[key]['get'].name;
globalMy[html_name][key + '_get'] = function () {
let result = descriptors[key]['get'].apply(this, arguments);
globalMy.check_debug(key);
globalMy.check_log(this, param_func_name, arguments, result);
return result;
}
let func_attr_track = globalMy.set_func_prop(Object.getOwnPropertyDescriptors(descriptors[key]['get']));
var i = 0;
while (i < func_attr_track.length){
Object.defineProperty(globalMy[html_name][key + '_get'],func_attr_track[i].name,func_attr_track[i].attr);
i++;
}
globalMy.functionprotect(globalMy[html_name][key + '_get'], param_func_name, 'get')
attr['get'] = globalMy[html_name][key + '_get']
} else {
attr['get'] = undefined;
}
if (descriptors[key]['set']) {
let param_func_name = descriptors[key]['set'].name;
globalMy[html_name][key + '_set'] = function () {
let result = descriptors[key]['set'].apply(this, arguments);
globalMy.check_debug(key);
globalMy.check_log(this, param_func_name, arguments, result);
return result;
}
let func_attr_track = globalMy.set_func_prop(Object.getOwnPropertyDescriptors(descriptors[key]['set']));
var i = 0;
while (i < func_attr_track.length){
Object.defineProperty(globalMy[html_name][key + '_set'],func_attr_track[i].name,func_attr_track[i].attr);
i++;
}
globalMy.functionprotect(globalMy[html_name][key + '_set'], param_func_name, 'set')
attr['set'] = globalMy[html_name][key + '_set']
} else {
attr['set'] = undefined;
}
try {
Object.defineProperty(html, key, attr);
} catch (e) {
debugger
}
}
}
}
}
globalMy.init = function() {
console.clear = function () {
};
globalMy.functionprotect(console.clear, "clear");
globalMy.dom = [MimeTypeArray, PluginArray, MimeType, Plugin, Request, Path2D, NodeList, MediaEncryptedEvent, MediaQueryList, InputDeviceCapabilities, IDBRequest, IDBOpenDBRequest, IDBFactory, CSSStyleDeclaration, Image, WebSocket, XMLHttpRequestEventTarget, XMLHttpRequest, EventTarget, Node, Element, HTMLElement, WebGLRenderingContext, CanvasRenderingContext2D, HTMLAnchorElement, HTMLImageElement, HTMLFontElement, HTMLOutputElement, HTMLAreaElement, HTMLInputElement, HTMLFormElement, HTMLParagraphElement, HTMLAudioElement, HTMLLabelElement, HTMLFrameElement, HTMLParamElement, HTMLBaseElement, HTMLLegendElement, HTMLFrameSetElement, HTMLPictureElement, HTMLBodyElement, HTMLLIElement, HTMLHeadingElement, HTMLPreElement, HTMLBRElement, HTMLLinkElement, HTMLHeadElement, HTMLProgressElement, HTMLButtonElement, HTMLMapElement, HTMLHRElement, HTMLQuoteElement, HTMLCanvasElement, HTMLMarqueeElement, HTMLHtmlElement, HTMLScriptElement, HTMLDataElement, HTMLMediaElement, HTMLIFrameElement, HTMLTimeElement, HTMLDataListElement, HTMLMenuElement, HTMLSelectElement, HTMLTitleElement, HTMLDetailsElement, HTMLMetaElement, HTMLSlotElement, HTMLTableRowElement, HTMLDialogElement, HTMLMeterElement, HTMLSourceElement, HTMLTableSectionElement, HTMLDirectoryElement, HTMLModElement, HTMLSpanElement, HTMLTemplateElement, HTMLDivElement, HTMLObjectElement, HTMLStyleElement, HTMLTextAreaElement, HTMLDListElement, HTMLOListElement, HTMLTableCaptionElement, HTMLTrackElement, HTMLEmbedElement, HTMLOptGroupElement, HTMLTableCellElement, HTMLUListElement, HTMLFieldSetElement, HTMLOptionElement, HTMLTableColElement, HTMLUnknownElement, HTMLTableElement, HTMLVideoElement]
for (var i = 0; i < globalMy.dom.length - 1; i++) {
globalMy.hook_HTML(globalMy.dom[i])
}
//不想全局debug,可以关闭全局degbug,单独将你想要debug的方法名或者属性名传入进去
globalMy.want_debug = ['cookie']
// globalMy.want_debug = ['appendChild', 'createElement', 'target']
globalMy.not_log = ['get hidden', 'get visibilityState']
// 打印 鼠标事件
globalMy.hook_HTML(MouseEvent)
globalMy.hook_HTML(Document)
//hook Object.prototype 会把toString也hook了,会打印很多不必要的数据。可以hook,但是不要hook toString
//globalMy.hook_HTML(Object)
// hook Object 可以打印对方是否使用了Object里的函数做检测
globalMy.hook_obj(Object)
}
globalMy.init()