[ js.hook.js ]

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

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name  [ js.hook.js ]
// @description javascript钩子; 劫持方法/伪造参数/篡改结果/还原劫持
// @namespace js.hook.js
// @version 0.0.4
// @author  vc1
// ==/UserScript==
/*
 *
 *  [ js.hook.js ]
 *
 *  javascript钩子
 *
 *  * 劫持方法
 *  * 伪造参数
 *  * 篡改结果
 *  * 还原劫持
 *
 *  * 2016-10-31
 *  * vc1
 *
 */
(function(name, factory) {
    if (typeof define === "function" && define.amd) {
        define(name, factory);
    } else if (typeof module === "object" && module.exports) {
        module.exports = factory();
    } else {
        this[name] = factory();
    }
})('hook', function() {
    /*
     * 入口方法
     *
     * hook(alert) // 默认全局方法
     * hook(window, 'alert') // 指定方法所在对象
     * hook('window.tool.calc') // [推荐]字符串形式访问路径
     */
    function hook() {
        'use stric';
        if (this instanceof hook) return this;
        // hook('window.tool.calc')
        var fn_real, // 原始方法正身 - function calc() { ... }
            fn_name, // 被劫持的方法名 - 'calc'
            fn_object, // 被劫持的方法所在对象 - window.tool
            fn_object_name; // 所在对象名 - 'window.tool'

        var args = Array.prototype.slice.call(arguments),
            arg = args.pop();
        fn_object = args.pop() || root;
        fn_name = arg.name;
        if (typeof arg === 'string') {
            arg = arg.split('.');
            fn_name = arg.pop();
            fn_object_name = arg.join('.');
            fn_object = eval(fn_object_name || fn_object);
        }
        fn_real = fn_object[fn_name];

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

        // 存储钩子信息
        var storage;
        if (fn_object_name) {
            storage = hook.prototype.storage[fn_object_name] =
                hook.prototype
                .storage[fn_object_name] || {};
        } else {
            fn_object.__hook__ || Object.defineProperties && Object.defineProperties(fn_object, {
                '__hook__': {
                    value: {},
                    enumerable: false,
                    configurable: true
                }
            });
            storage = fn_object.__hook__;
        }

        // 已经对此方法劫持过了,返回已有的钩子
        if (storage[fn_name]) {
            return storage[fn_name].exports;
        }

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

        // 保存当前钩子
        storage[fn_name] = h;

        // 可以链式调用
        return h.exports;
    }

    hook.prototype.storage = {};
    var root = window || global,
        eval = root.eval;
    // 模拟Function.bind
    var bind = function(fn, scope) {
        return function() {
            return fn.apply(scope, arguments)
        }
    }

    /*
     * 替换原始方法
     *
     * 作用等于 temp=alert; alert=function(){// your function}
     *
     * fakeFn(arguments, data)
     * 接收到的参数列表, 原始方法信息, 对象实例或原对象, 执行时的作用域
     * flag为false,等于x=fn
     */
    hook.prototype.fake = function(fakeFn, flag) {
        var data = this.exports.data;
        var puppet = eval("(function " + this.fn_real.name +
            "() {" +
            "data.scope = this;" +
            (flag === false ?
                "return fakeFn.apply(this, arguments)" :
                "return fakeFn.call(this, arguments, data)"
            ) +
            "})");
        for (var prop in this.fn_real) {
            if (obj.hasOwnProperty(k)) {
                puppet[prop] = this.fn_real[prop];
            }
        }
        puppet.toLocaleString = puppet.toString = function() {
            return 'function () { [native code] }';
        };

        this.fn_puppet = this.exports.fn_puppet = puppet;
        this.fn_object[this.fn_name] = puppet;

        return this.exports;
    };

    /*
     * 在原方法前,劫持传入的参数
     *
     * fakeArg('直接替换为要传入的参数', '2', 3...)
     *
     */
    hook.prototype.fakeArg = function() {
        'use stric';
        this.__fakeArgRst__();
        this.fake_arg_fn = this.exports.data.fakeArgFn =
            __getFun__(
                arguments);
        return this.exports;
    };
    /*
     * fakeArgFn(function(原参数1, 2, 3...){
     *     return [修改后的参数1,2,3]
     *     // 无返回(undefinded)则使用原始参数
     *     // 清空传入参数可以返回一个空数组 return []
     * })
     *
     */
    hook.prototype.fakeArgFn = function(fn) {
        'use stric';
        this.__fakeArgRst__();
        this.fake_arg_fn = this.exports.data.fakeArgFn = fn;
        return this.exports;
    };

    /*
     * 在原方法后,劫持返回的数据
     *
     * fakeRst('直接替换为要返回的结果')
     *
     */
    hook.prototype.fakeRst = function(arg) {
        'use stric';
        this.__fakeArgRst__();
        this.fake_rst_fn = this.exports.data.fakeRstFn =
            __getFun__(
                arg);
        return this.exports;
    };
    /*
     * fakeRstFn(function(原返回值){
     *     return 修改后的返回值
     *     // 无返回(undefinded)则使用原始参数
     * })
     *
     */
    hook.prototype.fakeRstFn = function(fn) {
        'use stric';
        this.__fakeArgRst__();
        this.fake_rst_fn = this.exports.data.fakeRstFn = fn;
        return this.exports;
    };


    /*
     * 开启劫持arg/rst
     */
    hook.prototype.__fakeArgRst__ = function() {
        'use stric';
        if (typeof this.fn_puppet === 'function') return;
        var t = this;
        var fakeArgRstFn = function(args, data) {
            var faked_arg = data.fakeArgFn ? data.fakeArgFn
                .apply(this, args) || args : args;
            faked_arg = faked_arg === undefined ? args : faked_arg;
            typeof faked_arg !== 'string' && Array.prototype
                .slice.call(faked_arg).length === 0 && (faked_arg = [faked_arg]);
            var real_rst = data.fn_real.apply(this,
                faked_arg);
            var faked_rst = data.fakeRstFn ? data.fakeRstFn
                .call(this, real_rst) : real_rst;
            faked_rst = faked_rst === undefined ? real_rst : faked_rst;
            return faked_rst;
        };
        this.fake(fakeArgRstFn, true);
    };

    /*
     * 关闭劫持
     *
     * 传入参数为空:关闭前后所有劫持   hook(alert).off()
     * 传入字符串 "arg" 或 "rst":关闭对应劫持   hook(alert).off('arg')
     * 传入方法:关闭对应劫持
     *
     * 前后劫持全部关闭后,还原被 hook 的方法
     */
    hook.prototype.off = function(filter) {
        'use stric';;
        (!filter || filter === 'arg') && (this.fake_arg_fn = this.exports
            .data.fakeArgFn =
            null);
        (!filter || filter === 'rst') && (this.fake_rst_fn = this.exports
            .data.fakeRstFn =
            null);

        if (!this.fake_arg_fn && !this.fake_rst_fn) {
            this.fn_object[this.fn_name] = this.fn_real;
            this.fn_puppet = undefined;
            //delete this.storage[this.fn_object_name][this.fn_name];
        }

        return this.exports;
    };

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

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


    /*
     * 直接修改参数或返回结果
     */
    var __getFun__ = function(args) {
        'use stric';
        return /*typeof args[0] == 'function' ? args[0] :*/ function() {
            return args;
        };
    };

    return hook;
});




// 效果测试


/*



window.tool = {
    calc: function(msg, n) {
        console.warn('calc收到参数:' + msg + ', ' + n);
        var r = n * n;
        console.warn('calc结果:' + r);
        return r;
    }
}



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

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


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


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



*/



/*



function print(msg){
    document.write((msg||'<br>') + '<br>');
}

window.tool = {
    calc: function(msg, n) {
        print('calc收到参数:' + msg + ', ' + n);
        var r = n * n;
        print('calc结果:' + r);
        return r;
    }
}


print('一个计算器:');

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


print("劫持后:\nhook('window.tool.calc').fakeArg('这个计算器坏了', -1).fakeRst(function(right){\n" +
"    print('fakeRst:计算器结果返回:' + right);\n " +
"    return '<(ˉ^ˉ)> 告诉你坏了'\n" +
"}");
hook('window.tool.calc').fakeArg('这个计算器坏了', -1).fakeRstFn(function(right){
    print('fakeRst:计算器返回的结果:' + right);
    return '<(ˉ^ˉ)> 告诉你坏了'
});
print(tool.calc);
print('设置参数:' + '专注于计算平方的计算器' + ', ' + 42);
print('接收到的结果:' + tool.calc('专注于计算平方的计算器', 42));
print();
print('\n');


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



*/