一键hook脚本原理

发布时间 2023-07-27 17:56:17作者: 守护式等待

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);

applydocument.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()