[ js.hook.js ]

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

当前为 2016-10-26 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name  [ js.hook.js ]
// @description javascript钩子; 劫持方法/伪造参数/篡改结果/还原劫持
// @namespace js.hook.js
// @version 0.0.4
// @author  vc1
// ==/UserScript==
/*
 *
 *  [ js.hook.js ]
 *
 *  javascript钩子
 *
 *  * 劫持方法
 *  * 伪造参数
 *  * 篡改结果
 *  * 还原劫持
 *
 *  * 2016-10-25
 *  * 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, 'alert')
     * hook('MyOjbect.User.info.edit')
     */

    function hook() {
        'use stric'
        if (this instanceof hook) return this;

        var fn_real, // 原始方法正身
            fn_name, // 被劫持的方法名
            fn_object_name,
            fn_object; // 被劫持的方法所在对象
        // 'window.alert'
        // 'alert'
        // alert
        // window, 'alert'
        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 {
            /*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: 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;
                }
            }
        };*/
        h.exports = {
            fake: bind(h.fake, h),
            fakeArg: bind(h.fakeArg, h),
            fakeRst: bind(h.fakeRst, 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
            }
        };

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

        return h.exports;
    }

    hook.prototype.storage = {};
    var root = window || global,
        eval = root.eval;
    var bind = function(fun, scope) {
        return function() {
            return fun.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 +
            "() {debugger;" +
            "data.scope = this;" +
            (flag === false ?
                "return fakeFn.apply(this, arguments)" :
                "return fakeFn.call(this, arguments, data)"
            ) +
            "})");
        for (var prop in Object.getOwnPropertyNames(this.fn_real)) {
            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('直接替换为要传入的参数', ...)
     * fakeArg(function(原参数,){
     *     //
     *     return [修改后的参数1,2,3]
     * })
     *
     * 无返回则采用原始参数
     */
    hook.prototype.fakeArg = function() {
        'use stric'
        this.__fakeArgRst__();
        this.fake_arg_fn = this.exports.data.fakeArgFn =
            __getFun__(
                arguments);
        return this.exports;
    };

    hook.prototype.fakeArgFn = function(fn) {
        'use stric'
        this.__fakeArgRst__();
        this.fake_arg_fn = this.exports.data.fakeArgFn = fn;
        return this.exports;
    };

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

    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;
            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;
            return faked_rst;
        };
        this.fake(fakeArgRstFn, true);
    };

    /*
     * 关闭劫持
     *
     * 传入参数为空:关闭前后所有劫持   hook(alert).off()
     * 传入字符串 "arg" 或 "rst":关闭对应劫持   hook(alert).off('arg')
     * 传入方法:关闭对应劫持
     *
     * 前后劫持全部关闭后,还原被 hook 的方法
     */
    hook.prototype.off = function(filter) {
        'use stric'
        if (!filter) {
            this.fake_arg_fn = this.exports.data.fakeArgFn =
                null;
            this.fake_rst_fn = this.exports.data.fakeRstFn =
                null;
        } else {
            filter === 'arg' && (this.fake_arg_fn = this.exports
                .data.fakeArgFn =
                null);
            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('<(ˉ^ˉ)> 告诉你坏了');");
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("还原后:\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('<(ˉ^ˉ)> 告诉你坏了');");
hook('window.tool.calc').fakeArg('这个计算器坏了', -1).fakeRst('<(ˉ^ˉ)> 告诉你坏了');
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();



*/