[ js.hook.js ]

javascript钩子; 劫持方法/伪造参数/篡改结果/还原劫持

2015-11-24 या दिनांकाला. सर्वात नवीन आवृत्ती पाहा.

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey, Greasemonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्क्रिप्ट व्यवस्थापक एक्स्टेंशन इंस्टॉल करावे लागेल.

(माझ्याकडे आधीच युझर स्क्रिप्ट व्यवस्थापक आहे, मला इंस्टॉल करू द्या!)

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

(माझ्याकडे आधीच युझर स्टाईल व्यवस्थापक आहे, मला इंस्टॉल करू द्या!)

// ==UserScript==
// @name  [ js.hook.js ]
// @description javascript钩子; 劫持方法/伪造参数/篡改结果/还原劫持
// @namespace js.hook.js
// @version 0.0.2
// @author  vc1
// ==/UserScript==



;
(function (name, ctx) {

    /*
     *
     *  [ js.hook.js ]
     *
     *  javascript钩子
     *
     *  * 劫持方法
     *  * 伪造参数
     *  * 篡改结果
     *  * 还原劫持
     *
     *  * 2015-11-24
     *  * vc1
     *
     */

    // 'use stric'

    var definition = (function () {

        /*
         * 入口方法
         *
         * hook(alert)
         * hook('window.alert')
         * hook('MyOjbect.User.info.edit')
         */
        function hook() {
            if (this instanceof hook) {
                return hook.prototype.__init__.apply(this, arguments);
            }

            var t = hook.prototype.__getTarget__.apply(null, arguments);

            // 已经劫持过了,返回已有的钩子
            if (hook.prototype.storage[t.fn_object_name] && hook.prototype.storage[t.fn_object_name][t.fn_name]) {
                return hook.prototype.storage[t.fn_object_name][t.fn_name].exports;
            }

            return new hook(t.fn_object, t.fn_object_name, t.fn_name, t.fn_real);
        }

        hook.prototype.storage = {};
        var eval = window.eval;

        hook.prototype.__init__ = function (fn_object, fn_object_name, fn_name, fn_real) {
            // 原始方法正身
            this.fn_real = fn_real;
            // 被劫持的方法名
            this.fn_name = fn_name;
            // 被劫持的方法所在对象,默认 window
            this.fn_object = fn_object;
            // 所在对象名称
            this.fn_object_name = fn_object_name;
            // 伪造传入参数
            this.fakeArgFn = null;
            // 伪造返回结果
            this.fakeRstFn = null;
            // 对外暴露的功能
            this.exports = {
                fake: this.fake.bind(this),
                fakeArg: this.fakeArg.bind(this),
                fakeRst: this.fakeRst.bind(this),
                off: this.off.bind(this),
                offArg: this.offArg.bind(this),
                offRst: this.offRst.bind(this),
            };

            var t = this;
            this.exports_var = Object.defineProperties({},
            {
                'fn_real': {
                    value: fn_real,
                    enumerable: true
                },
                'fn_name': {
                    value: fn_name,
                    enumerable: true
                },
                'fn_object_name': {
                    value: fn_object_name,
                    enumerable: true
                },
                'fn_object': {
                    value: fn_object,
                    enumerable: true
                },
                fakeArgFn: {
                    get: function () {
                        return t.fakeArgFn;
                    }
                },
                fakeRstFn: {
                    get: function () {
                        return t.fakeRstFn;
                    }
                }
            });
            // 保存当前钩子
            this.storage[fn_object_name] = this.storage[fn_object_name] || {};
            this.storage[fn_object_name][fn_name] = this;

            return this.exports;
        };

        // 支持多种输入形式
        hook.prototype.__getTarget__ = function () {
            var fn_real, // 原始方法正身
                fn_name = 'alert',
                // 被劫持的方法名
                fn_object_name = "Window",
                fn_object = window; // 被劫持的方法所在对象,默认 window
            if (arguments.length === 1) {
                var arg = arguments[0];
                // 传入字符串
                if (arg.__proto__ === String.prototype) {
                    var dotidx = arg.lastIndexOf('.');
                    if (~dotidx) { // 'window.alert'
                        fn_object_name = arg.slice(0, dotidx);
                        fn_object = eval(fn_object_name);
                        fn_name = arg.slice(dotidx + 1);
                        fn_real = fn_object[fn_name];
                    } else { // 'alert'
                        fn_name = arg;
                    }
                } else { // 传入一个方法,所在对象默认 window
                    fn_real = arg;
                    fn_name = fn_real.name;
                }
            } else if (arguments.length == 2) { // 不推荐
                fn_real = fn_object[fn_name];
                fn_name = arguments[1];
                fn_object = arguments[0];
                fn_object_name = fn_object.constructor.name;
            }

            if (!(fn_object && fn_name && fn_real)) {
                console.error(fn_object);
                console.error(fn_object_name);
                console.error(fn_name);
                console.error(fn_real);
                throw new Error('hook fail');
            }

            return {
                'fn_real': fn_real,
                'fn_name': fn_name,
                'fn_object': fn_object,
                'fn_object_name': fn_object_name,
            };
        };

        /*
         * 替换原始方法
         *
         * 作用等于 temp=alert; alert=function(){// your function}
         *
         * fakeFn(arguments, t.exports_var, scope, this)
         * 接收到的参数列表, 原始方法信息, 对象实例或原对象, 执行时的作用域
         *
         */
        hook.prototype.fake = function (fakeFn) {
            var t = this;
            var puppet = eval("(function " + this.fn_real.name + "() {" +
                "var scope = this instanceof t.fn_object.constructor ? this :" +
                "    t.fn_object;" +
                "return fakeFn.call(scope, arguments, t.exports_var.fn_real, scope, t.exports_var, this);" +
                "})");
            for (var prop in this.fn_real) {
                puppet[prop] = this.fn_real[prop];
            }
            puppet.toString = function () {
                return 'function ' + t.fn_real.name + '() { [native code] }';
            };
            this.fn_object[this.fn_name] = puppet;
            return this.exports;
        };

        /*
         * 在原方法前,劫持传入的参数
         *
         * fakeArg('直接替换为要传入的参数', ...)
         * fakeArg(function(原参数,){
         *     //
         *     return [修改后的参数]
         * })
         *
         * 无返回则采用原始参数
         */
        hook.prototype.fakeArg = function (arg) {
            this.__fakeArgRst__();
            this.fakeArgFn = this.__getFun__(arguments);
            return this.exports;
        };

        /*
         * 在原方法后,劫持返回的数据
         *
         * fakeRst('直接替换为要传入的参数')
         * fakeRst(function(原返回值){
         *     //
         *     return 修改后的返回值
         * })
         */
        hook.prototype.fakeRst = function (arg) {
            this.__fakeArgRst__();
            this.fakeRstFn = this.__getFun__(arg);
            return this.exports;
        };


        /*
         * 开启劫持arg/rst
         */
        hook.prototype.__fakeArgRst__ = function () {
            var t = this;
            var fakeArgRstFn = function (args, fn_real, scope, t, raw_this) {
                var faked_arg = t.fakeArgFn ? t.fakeArgFn.apply(scope, args) || args : args;
                faked_arg && !Array.isArray(faked_arg) &&
                    (!faked_arg.hasOwnProperty('callee') && !faked_arg.hasOwnProperty('length')) &&
                    (faked_arg = [faked_arg]);
                var real_rst = t.fn_real.apply(scope, faked_arg);
                var faked_rst = t.fakeRstFn ? t.fakeRstFn.call(scope, real_rst) : real_rst;
                return faked_rst;
            };
            this.fake(fakeArgRstFn);
        };

        /*
         * 关闭劫持
         *
         * 传入参数为空:关闭前后所有劫持   hook(alert).off()
         * 传入字符串 "arg" 或 "rst":关闭对应劫持   hook(alert).off('arg')
         * 传入方法:关闭对应劫持
         *
         * 前后劫持全部关闭后,还原被 hook 的方法
         */
        hook.prototype.off = function (filter) {
            if (!filter) {
                delete this.fakeArgFn;
                delete this.fakeRstFn;
            } else if (typeof filter === 'function' || filter.__proto__ === String.prototype) {
                (this.fakeArgFn === fn || filter === 'arg') && delete this.fakeArgFn;
                (this.fakeRstFn === fn || filter === 'rst') && delete this.fakeRstFn;
            }

            if (!this.fakeArgFn && !this.fakeRstFn) {
                this.fn_object[this.fn_name] = this.fn_real;
                //delete this.storage[this.fn_object_name][this.fn_name];
            }

            return this.exports;
        };

        /*
         * 关闭前面的参数劫持
         *
         */
        hook.prototype.offArg = function (filter) {
            filter = filter || 'arg';
            this.off(filter);
            return this.exports;
        };

        /*
         * 关闭后面的结果劫持
         *
         */
        hook.prototype.offRst = function (filter) {
            filter || 'rst';
            this.off(filter);
            return this.exports;
        };


        /*
         * 直接修改参数或返回结果
         */
        hook.prototype.__getcloser__ = function (args) {
            return function () {
                return args;
            };
        };
        hook.prototype.__getFun__ = function (arg) {
            return typeof arg[0] == 'function' ? arg[0] : this.__getcloser__(arg);
        };

        return hook;

    });

    //检测上下文环境是否为AMD或CMD
    var hasDefine = typeof define === 'function',
        // 检测上下文环境是否为Node
        hasExports = typeof module !== 'undefined' && typeof module !== 'function' && module.exports;
    if (!name) {
        return definition();
    } else if (ctx) {
        // 设置环境后则挂载到此对象
        /*
        void function(name, ctx){
            ...
        }('hook', window);
        */
        ctx[name] = definition();
    } else if (hasDefine) {
        //AMD环境或CMD环境
        define(name, definition);
    } else if (hasExports) {
        //定义为普通Node模块
        module.exports = definition();
    }
    return definition();



    // 效果演示

    /*



    window.tool = {
        say: '一个计算器开始工作了:',
        calc: function (msg, n) {
            console.log(this.say);
            console.warn('calc收到参数:' + msg + ', ' + n);
            var r = n * n;
            console.warn('calc结果:' + r);
            return r;
        }
    }

    var hook = window.js_hook_js;

    console.clear();
    console.info('一个计算器:');

    console.group('原始方法:\n\ntool.calc');
    console.log(tool.calc);
    console.info('设置参数:' + '专注于计算平方的计算器' + ', ' + 42);
    console.info('接收到的结果:' + tool.calc('专注于计算平方的计算器', 42));
    console.groupEnd();
    console.log('\n');

    console.group("劫持后:\n\nhook('window.tool.calc').fakeArg('这个计算器坏了', -1).fakeRst('<(ˉ^ˉ)> 告诉你坏了');");
    hook('window.tool.calc').fakeArg('这个计算器坏了', -1).fakeRst('<(ˉ^ˉ)> 告诉你坏了');
    console.log(tool.calc);
    console.info('设置参数:' + '专注于计算平方的计算器' + ', ' + 42);
    console.info('接收到的结果:' + tool.calc('专注于计算平方的计算器', 42));
    console.groupEnd();
    console.log('\n');

    console.group("还原后:\n\nhook('window.tool.calc').off();");
    hook('window.tool.calc').off();
    console.log(tool.calc);
    console.info('设置参数:' + '专注于计算平方的计算器' + ', ' + 42);
    console.info('接收到的结果:' + tool.calc('专注于计算平方的计算器', 42));
    console.groupEnd();
    console.log('\n');

    console.group("替换:\n\nhook('window.tool.calc').fake(function(){...})");
    hook('window.tool.calc').fake(function (args, fn_real, scope, h, raw_scope) {
        console.log('调用者:' + args.callee.caller);
        console.log('this:');
        console.log(this);
        console.log('args:');
        console.log(args);
        console.log('fn_real:');
        console.log(fn_real);
        console.log('scope:');
        console.log(scope);
        console.log('h:');
        console.log(h);
        console.log('raw_scope:');
        console.log(raw_scope);
        return fn_real.apply({
            say: '怎么还是那个计算器:'
        }, args);
    });
    console.log(tool.calc);
    console.info('设置参数:' + '专注于计算平方的计算器' + ', ' + 42);
    console.info('接收到的结果:' + (function calc_caller() {
        return tool.calc.call({
            say: '换个计算器吧'
        }, '专注于计算平方的计算器', 42);
    })());
    console.groupEnd();
    console.log('\n');



    */

})('js_hook_js', window);