HookManager.js

var FRIDA = null; 
const co = require("co");
const fs = require("fs");
const md5 = require("md5");
const Chalk = require("chalk");
const Path = require("path");

const CLASS = require("./CoreClass.js");
const CONST = require("./CoreConst.js");
const UT = require("./Utils.js");
const Logger = require("./Logger.js")();
const JSHelper = require("./JavaScriptHelper.js");
const FridaHelper = require("./FridaHelper");

var HOOK_TYPE = {
    AFTER: 0x1,
    BEFORE: 0x2,
    OVERLOAD: 0x3
};

const HM_SPAWN = 1;
const HM_ATTACH_GADGET = 2;
const HM_ATTACH_APP = 3;
const HM_ATTACH_PID = 4;

function getLetterFromType(typename){
    for(let i in CONST.WORDS){
        if(CONST.WORDS[i]==typename) return i;
    }
    return null;
}

class VariableArray
{
    constructor(data){
        this.data = data;
        return this;
    }

    getData(){
        return this.data;
    }

    write(){
        let str=` [
            `;
        for(let i=0; i<this.data.length; i++){
            if(typeof this.data[i] == 'string'){
                str += "'"+this.data[i]+"',";
            }else if(typeof this.data[i] == 'object'){
                str += JSON.stringify(this.data[i])+",";
            }else if(typeof this.data[i] == 'int'){
                str += this.data[i]+",";
            }else{
                Logger.error('Unsupported hook variable : type of nested data not supported.');
            }
        }
        
        return str.substr(0,str.length-1)+`
            ],`;
    }
}

/*
DO NOT USE
*/
class VariableObject
{
    constructor(data){
        this.data = data;
        return this;
    }

    write(){
        return JSON.stringify(this.data)+",";
    }
}

/**
 * Represente un hook (actif ou non)
 * @param {string} name The hook name
 * @param {string} src The hook script source code
 * @constructor 
 */
function Hook(context){
    this.id = null;

    // ! important
    // It is used in order to link in-hook method call with method declared outside of the hook
    this.parentID = null;
    this.customName = false;
    this.method = null;
    this.name = null; //name;
    this.description = null;
    this.script = null;//src;
    this.enabled = true;
    this.native = false;
    this.isIntercept = false;
    this.onMatch = null;

    this.context = context;
    this.edited = false;

    this.method = null;
    
    this.when = false;

    this.after = false;
    this.before = false;

    this.color = null;

    this.variables = null,
    this.code = {
        varID: null,
        before: null,
        after: null,
        replace: null,
        custom: null
    }
    return this;
}


/**
 * Set enable flag with the given boolean. 
 * @deprecated
 */
Hook.prototype.setEnable = function(bool){
    this.enabled = bool;
    this.context.trigger({
        type: (bool==true? "probe.enable":"probe.disable"),
        data: {            
            hook: this
        }
    });
}
Hook.prototype.enable = function(){
    this.enabled = true;
    this.context.trigger({
        type: "probe.enable",
        data: {            
            hook: this
        }
    });
}
Hook.prototype.disable = function(){
    this.enabled = false;
    this.context.trigger({
        type: "probe.disable",
        data: {            
            hook: this
        }
    });
}
Hook.prototype.isEnable = function(){
    return this.enabled;
}

Hook.prototype.modifyScript = function(script){

    this.script = script;
    this.edited = true;

    this.context.trigger({
        type: "probe.post_code_change",
        data: {            
            hook: this,
            method: this.method 
        }
    });
} 

Hook.prototype.hasVariables = function(){
    return (this.variables!=null);
}

Hook.prototype.setupVariables = function(){
    let code="\t\tvar "+this.code.varID+` = {
        `;
    for(let i in this.variables){
        code += "\t\t"+i+":";
        code += this.variables[i].write();
    }
    return code+`
        };`;
}

Hook.prototype.getVariable = function(name){
    return this.variables[name];
}

Hook.prototype.isModified = function(){
    return this.edited;
} 

Hook.prototype.isCustomHook = function(){
    return this.code.custom != null;
}

Hook.prototype.getCustomCode = function(){
    return this.code.custom;
}

Hook.prototype.setCustomCode = function(script){
    this.code.custom = script;
}


/**
 * To check if it is a native hook
 * @returns {boolean} Returns TRUE if it hooks a native function, else FALSE
 * @function
 */
Hook.prototype.isNative = function(){
    return this.isNative;
}

/**
 * To check if the hook is called before the hooked function
 * @returns {boolean} Returns TRUE if the hook is called before the function, else FALSE
 * @deprecated
 * @function
 */
Hook.prototype.isBefore = function(){
    return this.when <= 0;
}

/**
 * To check if the hook is called after the hooked function
 * @returns {boolean} Returns TRUE if the hook is called after the function, else FALSE
 * @deprecated
 * @function
 */
Hook.prototype.isAfter = function(){
    return this.when>0;
}

/**
 * To check if the hook perform an intercept (it modifiy value or execution path)
 * @returns {boolean} Returns TRUE if the hook is an intercept, else FALSE
 * @function
 */
Hook.prototype.isIntercept = function(){
    return this.isIntercept;
}

/**
 * To set the Unique ID of the hook
 * @param {string} id The Unique ID of the hook
 * @returns {Hook} The instance of this hook
 * @function 
 */
Hook.prototype.setID = function(id){
    this.id = id;
    return this;
}

/**
 * To get the Unique ID of the hook
 * @returns {string} id The Unique ID of this hook
 * @function
 */
Hook.prototype.getID = function(){
    return this.id;
}

/**
 * To set the parent ID if available, like an HookSet ID.
 * @param {string} id The parent ID 
 * @returns {Hook} The instance of this hook
 * @function
 */
Hook.prototype.setParentID = function(id){
    this.parentID = id;
    return this;
}

/**
 * To get the parent ID if available, like an HookSet ID.
 * @returns {String} The parent ID
 * @function
 */
Hook.prototype.getParentID = function(){
    return this.parentID;
}

/**
 * To set the name of the hook. 
 * By default, it's the signature of the hooked method
 * @param {string} id The parent ID 
 * @returns {Hook} The instance of this hook
 * @function
 */
Hook.prototype.setName = function(name){
    this.name = name;
    this.customName = true;
    return this;
}

/**
 * To set the built hook code to exec BEFORE the hooked function.
 * @param {string} code The built source code of the hook
 * @returns {Hook} The instance of this hook
 * @function
 */
Hook.prototype.setInterceptBefore = function(code){
    this.code.before = code;
    return this;
}

/**
 * To set the built hook code to exec AFTER the hooked function.
 * @param {string} code The built source code of the hook
 * @returns {Hook} The instance of this hook
 * @function
 */
Hook.prototype.setInterceptAfter = function(code){
    this.code.after = code;
    return this;
}

/**
 * To set the built hook code to exec in place of the hooked function.
 * @param {string} code The builnt source code of the hook
 * @returns {Hook} The instance of this hook
 * @function
 */
Hook.prototype.setInterceptReplace = function(code){
    this.code.replace = code;
    return this;
}

/**
 * To make an instance of Object which not contain circular reference
 * and which are ready to be serialized.
 * @returns {Object} - Returns an Object instance representing the type
 */
Hook.prototype.toJsonObject = function(){
    let o = new Object();
    o.id = this.id;
    o.parentID = this.parentID;
    o.color = this.color;
    o.customName = this.customName;
    o.name = this.name;
    o.enable = this.enabled;
    o.method = this.method.signature();
    o.script = UT.b64_encode(UT.encodeURI(this.script));
    o.edited = this.edited;
    o.isIntercept = this.isIntercept;
    if(this.variables != null){
        o.variables = {
            id: this.code.varID,
            data: {}
        };
        //console.log(this.variables);
        for(let i in this.variables){
            o.variables.data[i] = this.variables[i].write();
        }
    }
    o.code = {
        //variable: (this.code.variable!=null)? UT.b64_decode(this.code.dynamic) : null,
        before: (this.code.before!=null)? UT.b64_encode(this.code.before) : null,
        after: (this.code.after!=null)? UT.b64_encode(this.code.after) : null,
        replace: (this.code.replace!=null)? UT.b64_encode(this.code.replace) : null,
    };
    return o;
}

Hook.prototype.updateWith = function(object,method){
    this.id = object.id;
    this.parentID = object.parentID;
    this.customName = object.customName;
    this.name = object.name;
    this.enabled = object.enable;

    // resolve method
    this.method = method;

    this.script = UT.decodeURI(UT.b64_decode(object.script));
    this.edited = object.edited;
    this.isIntercept = object.isIntercept;
    this.code = {
        dynamic: (object.code.dynamic!=null)? UT.b64_decode(object.code.dynamic) : null,
        before: (object.code.before!=null)? UT.b64_decode(object.code.before) : null,
        after: (object.code.after!=null)? UT.b64_decode(object.code.after) : null,
        replace: (object.code.replace!=null)? UT.b64_decode(object.code.replace) : null,
    };
    return this;
}

Hook.prototype.dataObjAutoCast = function(argtype, argname){
    let val = null;

    switch(argtype){
        case "java.lang.String":
            val = argname;
            break;
        case "java.lang.CharSequence":
            val = argname; //".toString()";
            break;
    }

    return val;
};
Hook.prototype.dataPrimAutoCast = function(argtype,argname){
    let val = null;

    switch(argtype){
        case "int":
            val = argname;
            break;
    }

    return val;
};


/**
 * To build the code source corresponding an array of parameters 
 * 
 * It builds :
 *  - Argument part of the signature needed by Frida in order 
 *    to identtfy good function to overload 
 *  - Source code of the object send by the hook to the frida client
 *  
 * @param {ObjectType|BasicType} args_arr An array of Types
 * @function
 * 
 */
Hook.prototype.makeArgsHelper = function(args_arr){
    if(args_arr.length ==0) return null;
    let raw_name = null;
    let helper = {
        // Value to pass to the "overload()" method of Frida
        call_signature: "",
        // Value to set as parameters name in the hook and in the call
        // to the hooked function
        hook_args: "",
        // Format string for the logger
        logger: "",
        // TODO 
        data: "",
    };
    let v = 0, arg=null, dataval="";

    for(let i in args_arr){
        
        arg = "arg"+v;

        if(args_arr[i] instanceof CLASS.BasicType){

            raw_name = getLetterFromType(args_arr[i]._name);

            if(args_arr[i].arr){
                helper.call_signature += "'["+raw_name+"',";
            }else{
                helper.call_signature += "'"+args_arr[i]._name+"',";
            }

            dataval = this.dataPrimAutoCast(args_arr[i]._name,arg);
            if(dataval != null)
                helper.data += arg+":"+dataval+",";

            helper.hook_args +=  arg+",";
        }
        else if(args_arr[i] instanceof CLASS.ObjectType){

            if(args_arr[i].arr){
                // frida 11.0.2
                // helper.call_signature += "'[L"+args_arr[i]._name+"',";
                // frida 12.2.26
                helper.call_signature += "'[L"+args_arr[i]._name+";',";
            }else{
                //helper.call_signature += "'L"+args_arr[i]._name+";',";
                helper.call_signature += "'"+args_arr[i]._name+"',";
            }

            dataval = this.dataObjAutoCast(args_arr[i]._name,arg);
            if(dataval != null)
                helper.data += arg+":"+dataval+",";

            helper.hook_args +=  arg+",";
        }
        v++;
    }

    helper.call_signature = helper.call_signature.substr(0,helper.call_signature.length-1);
    helper.hook_args = helper.hook_args.substr(0,helper.hook_args.length-1);
    return helper;
};

Hook.prototype.makeRetHelper = function(ret){
    if(ret == null) return null;

    let helper = {
        // Format string for the logger
        logger: "",
        // TODO 
        data: "",
    };
    let dataval="";


    if(ret instanceof CLASS.BasicType){

        dataval = this.dataPrimAutoCast(ret._name,"ret");
        if(dataval != null)
            helper.data += "ret:"+dataval;
    }
    else if(ret instanceof CLASS.ObjectType){

        dataval = this.dataObjAutoCast(ret._name,"ret");
        if(dataval != null)
            helper.data += "ret:"+dataval;
    }
     
    return helper;
};


/**
 * To create the Frida hook script for a specific method.
 * Each token starting and ending by "@@" will be replaced by his value 
 * in the final script.
 * 
 * The available tokens are :
        "@@__CLSDEF__@@": md5(method.enclosingClass.name),
        "@@__FQCN__@@": method.enclosingClass.name,
        "@@__METHDEF__@@": md5(method.__signature__),
        "@@__METHNAME__@@": (method.name=='<init>')? '$init' : method.name,
        "@@__METHSIGN__@@": method.__signature__,
        "@@__ARGS__@@": "",
        "@@__HOOK_ARGS__@@": "",
        "@@__HOOK_ARGS2__@@": "",
        "@@__RET__@@": "",
        "@@__ARGS_VAL__@@": "",
        "@@__HOOK_ID__@@": UT.b64_encode(this.id),
        "@@__CTX__@@":"",
        "@@__ARGS_DATA__@@":"null",
        "@@__RET_DATA__@@":"",
 * 
 * The resulting script is stored into the 'script' field of 
 * the 'Hook' instance.
 *
 * @param {Method} method The method to hook
 * @function
 */
Hook.prototype.makeHookFor = function(method){
    /*if(method instanceof CLASS.MissingReference){
        console.log(Chalk.bold.yellow("TODO : implement MissingReference probing"));
        this.enable = false;
        return null;
    }*/

    let tags = {
        "@@__CLSDEF__@@": md5(method.enclosingClass.name),
        "@@__FQCN__@@": method.enclosingClass.name,
        "@@__METHDEF__@@": md5(method.__signature__),
        "@@__METHNAME__@@": (method.name=='<init>')? '$init' : method.name,
        "@@__METHSIGN__@@": method.__signature__,
        "@@__ARGS__@@": "",
        "@@__HOOK_ARGS__@@": "",
        "@@__HOOK_ARGS2__@@": "",
        "@@__RET__@@": "",
        "@@__ARGS_VAL__@@": "",
        "@@__HOOK_ID__@@": UT.b64_encode(this.id),
        "@@__CTX__@@":"",
        "@@__ARGS_DATA__@@":"null",
        "@@__RET_DATA__@@":"",
        "@@__VAR__@@":""
    }; 

    tags["@@__VAR__@@"] = md5(this.id)+"_VAR";
    this.code.varID = tags["@@__VAR__@@"];

    let retHelp = this.makeRetHelper(method.ret);
    tags["@@__RET_DATA__@@"] = "{"+retHelp.data+"}";

    if(this.parentID != null){
        tags["@@__CTX__@@"] = "ctx_"+md5(this.parentID);
    }
    if(method.args.length > 0){
        let argHelp = this.makeArgsHelper(method.args);
        tags["@@__ARGS__@@"] = argHelp.call_signature;
        tags["@@__ARGS_DATA__@@"] = "{"+argHelp.data+"}";
        tags["@@__HOOK_ARGS__@@"] = argHelp.hook_args;
        tags["@@__HOOK_ARGS2__@@"] = ", "+argHelp.hook_args;
    }
    
    /*
    if(method.ret != null){
        if(!(method.ret instanceof CLASS.BasicType) || !method.ret.isVoid()){
            tags["@@__RET__@@"] = ", ret:ret";
        }
    }
    */

    // TODO : dont redifine cls_$$ and meth_$$ when another hook has already defiened it.

    let script = `

        var cls_@@__CLSDEF__@@ = Java.use('@@__FQCN__@@');

        var meth_@@__METHDEF__@@ = cls_@@__CLSDEF__@@.@@__METHNAME__@@.overload(@@__ARGS__@@);

        meth_@@__METHDEF__@@.implementation = function(@@__HOOK_ARGS__@@) {
    `;
    
    if(this.code.replace != null){
        script += this.code.replace;
        script += `
        }

        `;

        // replace token
        for(let i in tags){
            do{
                script = script.replace(i,tags[i]);
            }while(script.indexOf(i)>-1);
        }

        this.method = method;
        this.name = method.__signature__;
        this.enable();
        this.script = script;

        return true;
    }

    // BEFORE insert
    if(this.isIntercept && this.code.before!=null){
        script += this.code.before;
    }else if(this.isIntercept == false){
        // __METHSIGN__
        script += `
            send({ id:"@@__HOOK_ID__@@", msg:"@@__METHSIGN__@@", data:@@__ARGS_DATA__@@, action:"None before", after:false @@__ARGS_VAL__@@ });
        `;
        /*script += `
            send({ id:"@@__HOOK_ID__@@", msg:"@@__FQCN__@@.@@__METHNAME__@@()", data:@@__ARGS_DATA__@@, action:"None before", after:false @@__ARGS_VAL__@@ });
        `;*/

    }

    script += `            var ret = meth_@@__METHDEF__@@.call(this @@__HOOK_ARGS2__@@);`;

    //  AFTER insert
    if(this.isIntercept && this.code.after!=null){
        script += this.code.after;
    }else if(this.isIntercept == false){
        script += `
            send({ id:"@@__HOOK_ID__@@", msg:"@@__METHSIGN__@@", data:@@__RET_DATA__@@, action:"None before", after:true @@__ARGS_VAL__@@ });
        `;
    }

    script += `
        return ret;
    }
    `;
    
    // replace token
    for(let i in tags){
        do{
            script = script.replace(i,tags[i]);
        }while(script.indexOf(i)>-1);
    }

    this.method = method;
    method.probing = true;
    this.name = method.__signature__;
    this.enable();
    this.script = script;
    return true;
    //console.log(script);
}



Hook.prototype.buildCustomScript = function(method){
    if(method instanceof CLASS.MissingReference){
        console.log(Chalk.bold.yellow("TODO : implement MissingReference probing"));
        this.enable = false;
        return null;
    }

    let builtScript = this.code.custom;
    let tags = {
        "@@__CLSDEF__@@": md5(method.enclosingClass.name),
        "@@__FQCN__@@": method.enclosingClass.name,
        "@@__METHDEF__@@": md5(method.__signature__),
        "@@__METHNAME__@@": (method.name=='<init>')? '$init' : method.name,
        "@@__METHSIGN__@@": method.__signature__,
        "@@__ARGS__@@": "",
        "@@__HOOK_ARGS__@@": "",
        "@@__HOOK_ARGS2__@@": "",
        "@@__RET__@@": "",
        "@@__ARGS_VAL__@@": "",
        "@@__HOOK_ID__@@": UT.b64_encode(this.id),
        "@@__CTX__@@":"",
        "@@__ARGS_DATA__@@":"null",
        "@@__RET_DATA__@@":""
    }; 

    tags["@@__VAR__@@"] = tags["@@__HOOK_ID__@@"]+"_VAR";
    this.code.varID = tags["@@__VAR__@@"];


    let retHelp = this.makeRetHelper(method.ret);
    tags["@@__RET_DATA__@@"] = "{"+retHelp.data+"}";

    if(this.parentID != null){
        tags["@@__CTX__@@"] = "ctx_"+md5(this.parentID);
    }
    if(method.args.length > 0){
        let argHelp = this.makeArgsHelper(method.args);
        tags["@@__ARGS__@@"] = argHelp.call_signature;
        tags["@@__ARGS_DATA__@@"] = "{"+argHelp.data+"}";
        tags["@@__HOOK_ARGS__@@"] = argHelp.hook_args;
        tags["@@__HOOK_ARGS2__@@"] = ", "+argHelp.hook_args;
    }
    
    for(let i in tags){
        while(builtScript.indexOf(i)>-1){
            builtScript = builtScript.replace(i,tags[i]);
        }
    }

    this.script = builtScript;
    this.method = method;
    method.probing = true;
    this.name = method.__signature__;
    this.enable();
    return true;
}   

Hook.prototype.generateDynamicCode = function(){
    //this.code.dynamic = 
}

Hook.prototype.setMethod = function(method){
    this.method = method;
}
Hook.prototype.getMethod = function(){
    return this.method;
}




/**
 * To represent a message sent by a hook from the device to the desktop
 * @constructor 
 */
function HookMessage(){
    this.data = null;
    this.msg = null;
    this.match = null;
    this.isIntercept = false;
    this.hook = null;
    this.when = null;
    this.action = "";
    this.tags = null;
    return this;
}

HookMessage.prototype.isBefore = function(){
    return this.when <= 0;
}

HookMessage.prototype.isAfter = function(){
    return this.when>0;
}

HookMessage.prototype.getHook = function(){
    return this.hook;
}

HookMessage.prototype.setTags = function(tags){
    this.tags = tags;
}

HookMessage.prototype.getTags = function(tags){
    return this.tags;
}

HookMessage.prototype.addTag = function(tag){
    if(this.tags == null) this.tags = [];
    this.tags.push(tag);
}
/**
 * To make an instance of Object which not contain circular reference
 * and which are ready to be serialized.
 * @returns {Object} Returns an Object instance representing the type
 */
HookMessage.prototype.toJsonObject = function(){
    let o = new Object();
    o.data = this.data;
    o.hook = this.hook;
    o.msg = this.msg;
    o.match = this.match;
    o.action = this.action;
    o.isIntercept = this.isIntercept;
    
    if(this.tags != null && this.tags.length > 0)
        o.tags = this.tags;

//    if(this.hook!=null)
//        o.hook = this.hook.toJsonObject();
    o.after = this.isAfter();
    o.before = this.isBefore();
    return o;
}

/**
 * To represent a hook primitive.
 * A hook primitive is like a hook template, it allows a developer or a user
 * to define hooks in different files and combine it in order to be injected 
 * by using a single script.   
 * @constructor
 */
function HookPrimitive(config){
    this.when = null;
    this.method_signature = null;
    this.isIntercept = false;
    this.isCustom = false;
    this.interceptBefore = null;
    this.interceptAfter = null;
    this.interceptReplace = null;
    this.onMatch = null;
    this.custom = false;
    this.variables = null;
    this.raw = null;

    for(let i in config){
        if(i!="multiple_method" && i!="method")
            this[i] = config[i];
    }
    if(config.method!=null) this.method_signature = config.method;
    return this;
}


/**
 * Create a object shared with others hook callback
 * @param {Object} config Shared object config 
 */
/*HookPrimitive.prototype.addVariable = function(config){
    this.variable = config;
    return this;
}*/

/**
 * Get the shared object from this hookset
 * @returns {Object} Shared object
 * @function
 */
HookPrimitive.prototype.getVariables = function(){
    return this.variables;
}



HookPrimitive.prototype.setMethod = function(method){
    this.method_signature = method;
}

HookPrimitive.prototype.buildRawMethod = function(raw){
    raw.__signature__ = raw.signature();
    return raw;
}

/**
 * To built a probing hook from the current primitive 
 * @param {DexcaliburProject} context The reference to the current Project instance
 * @param {HookSet} set The hookset where the hook primitive is defined
 * @returns {Hook} The hook ready to be injected
 * @function
 */
HookPrimitive.prototype.toProbe = function(context,set){
    let hook = new Hook(context), method=null;
    
    hook.variables = this.variables;

    if(this.raw == null)
        method = context.find.get.method(this.method_signature);
    else
        method = this.buildRawMethod(this.raw);

    if(method==undefined){
        Logger.error("[HOOK] Method not found by signature");
        console.log(this);
    }
        //hook.setID( context.hook.nextHookIdFor(method));
    hook.setID( md5(context.hook.nextHookIdFor(method)));
    
    hook.setParentID(set.id);//name);
    hook.makeHookFor(method);

    return hook;
}


/**
 * To built an intercepting hook from the current primitive 
 * @param {Project} context The reference to the current Project instance
 * @param {HookSet} set The hookset where the hook primitive is defined
 * @returns {Hook} The hook ready to be injected
 * @function
 */
HookPrimitive.prototype.toIntercept = function(context,set){

    let hook = new Hook(context);

    hook.variables = this.variables;

    if(this.raw == null)
        method = context.find.get.method(this.method_signature);
    else{
        method = this.buildRawMethod(this.raw);
        //console.log(method, context.hook.nextHookIdFor(method));
    }

    hook.setID( md5(context.hook.nextHookIdFor(method)));
    hook.setParentID(set.id);//name);
    hook.isIntercept = true;
    hook.onMatch = this.onMatch;

    //console.log(this);
    if(this.interceptBefore != null){
        hook.setInterceptBefore(this.interceptBefore);
    }
    if(this.interceptAfter != null){
        hook.setInterceptAfter(this.interceptAfter);
    }
    if(this.interceptReplace != null){
        hook.setInterceptReplace(this.interceptReplace);
    }
    if(this.customCode != null){
        hook.setCustomCode(this.customCode);
    }
    

    if(!hook.isCustomHook()){
        hook.makeHookFor(method);
    }else{
        hook.buildCustomScript(method);
    }

    hook.color = this.color;
    //console.log(hook);
    return hook;          
}

/**
 * To make an instance of Object which not contain circular reference
 * and which are ready to be serialized.
 * @returns {Object} Returns an Object instance representing the type
 */
HookPrimitive.prototype.toJsonObject = function(){
    let o = new Object();
    o.when = this.when;
    o.method = this.method_signature;
    o.interceptBefore = (this.interceptBefore!=null)?this.interceptBefore:null; 
    o.interceptAfter = (this.interceptAfter!=null)?this.interceptAfter:null;
    o.interceptReplace = (this.interceptReplace!=null)?this.interceptReplace:null;
    // o.onMatch
    return o;
}


/**
 * To configure and manage a static part of the hook code 
 * shared by all hooks and where class are searched.
 * Each hook set can define one custom prologue and several dependencies.
 * 
 * 
 * @param {*} config 
 */
function HookPrologue(config){
    this.parentID = null;
    this.script = null;
    this.builtScript = null;
    this.context = null;

    for(let i in config) this[i]=config[i];
    return false;
}

/**
 * To check if the prologue is enable of not. 
 * It is disabled when the parent is disabled 
 *  
 * @return {Boolean} Returns TRUE if enabled, else FALSE
 * @function
 */
HookPrologue.prototype.isEnable = function(){
    for(let i in this.context.hook.hooksets)
        console.log(i,this.parentID);

    return this.context.hook.getHookSet(this.parentID).isEnable();
}

/**
 * To build the prologue Frida script
 * 
 * In order to differentiate several prologues and avoid
 * conflicts, the @@__CTX__@@ token will be replaced by the hash 
 * of the parent HookSet.
 * 
 * @function
 */
HookPrologue.prototype.buildScript = function(){
    let script=this.script;
    let tags = {
        "@@__CTX__@@": "ctx_"+md5(this.parentID)
    };

    for(let i in tags){
        do{
            script = script.replace(i,tags[i]);
        }while(script.indexOf(i)>-1);
    }

    this.builtScript = script;
}

/**
 * To inject dependencies into HookPrologue
 * 
 * @param {Project} ctx The context of the project 
 * @function
 */
HookPrologue.prototype.injectContext = function(ctx){
    this.context = ctx;
    this.buildScript();
    return this;
}

/**
 * 
 */
HookPrologue.prototype.toJsonObject = function(){
    let o = new Object();
    o.parentID = this.parentID;
    o.script = this.script;
    return o;
}



/**
 * Represents a session of hooking.
 * 
 * A session comonly starts when the Frida final script is loaded and 
 * finish at the next start. 
 * 
 * (TODO : or when the device is disconnected) 
 *   
 * @param {*} manager 
 */
class HookSession
{
    constructor(manager) {
        /**
         * The stack containing the received message
         * @var 
         */
        this.message = [];

        /**
         * The associated HookManager
         * (TODO : 1 hookManager by device)
         * @var 
         */
        this.hookManager = manager;

        /**
         * Follow hookset matches
         */
        this.sets_matches = {};

        /**
         * The timestamp of the session
         * @var
         */
        this.time = UT.time();

        /**
         * To hold some references from frida-node 
         */
        this.frida = {
            session: null,
            device: null,
            script: null,
            pid: null
        };
    }

    set fridaSession( pSession){ this.frida.session = pSession; }

    get fridaSession(){  return this.frida.session; }


    set fridaDevice( pDevice){
        this.frida.device = pDevice;
    }

    get fridaDevice(){
        return this.frida.device;
    }


    set fridaScript( pScript){
        this.frida.script = pScript;
    }

    get fridaScript(){
        return this.frida.script;
    }


    set pid( pPID){
        this.frida.pid = pPID;
    }

    get pid(){
        return this.frida.pid;
    }

    /**
     * To push a new message from a hook into the session.
     * Each message are an instance of HookMessage
     * 
     * @method
     */
    push(msg){
        let hm = new HookMessage();

        if(msg.type == "error") return null;

        // TODO : mettre tout 'msg' dans 'hm' ou 'hm.data'

        // console.log(msg);
        if(msg.payload.id != undefined && msg.payload.id != null){
            //hook = this.hookManager.findHook(UT.b64_decode(msg.payload.id));
            hm.hook = msg.payload.id;
        }

        hm.match = (msg.payload.match!=null)? msg.payload.match : false; 
        hm.msg = msg.payload.msg;
        hm.data = msg.payload.data;
        hm.action = msg.payload.action;
        hm.when = (msg.after)? 1 : 0;


        if(msg.payload.tags != null) hm.setTags(msg.payload.tags);

        this.message.push(hm)
        
        if(hm.match)
        this.hookManager.trigger(hm);

        return hm;
    }

    /**
     * 
     * @param {*} hookset 
     * @param {*} name 
     * @param {*} value 
     * @method
     */
    addMatch(hookset,name,value=null){
        if(this.sets_matches[hookset]==null) this.sets_matches[hookset] = {};
        if(this.sets_matches[hookset][name]==null) this.sets_matches[hookset][name] = {};

        if(value!=null)
            for(let i in value)
                this.sets_matches[hookset][name][i] = value[i];
    }

    /**
     * @method
     */
    hasMessages( pOffset=0){
        return this.message.length > pOffset;
    }

    /**
     * @method
     */
    messages(){
        return this.message;
    }

    getMessages( pOffset, pSize ){
        let arr = [];
        for(let i=pOffset; i<pOffset+pSize; i++){
            // not null and not undefined
            if(this.message[i] != null){
                arr.push(this.message[i]);
            }
        }

        return arr;
    }
    /**
     * @method
     */
    toJsonObject( pOffset=0, pSize=-1){
        let o = new Object(), limit=pSize;
        o.message = [];

        if(limit==-1) 
            limit = this.message.length;

        limit += pOffset;
        for(let i=pOffset; i<limit; i++){
            if(this.message[i] != null)
                o.message.push(this.message[i].toJsonObject());
        }

        o.size = o.message.length;
        return o;
    }
}


/**
 * 
 * @param {DexcaliburProject} ctx The project instance
 * @param {Boolean} nofrida If equals to 1 then the Frida script will not be loaded and Frida library not include  
 */
class HookManager
{
    /**
     * 
     * @param {*} pProject 
     * @param {*} nofrida 
     * @constructor
     */
    constructor(pProject, pNofrida=0){
        this.context = pProject;
        this.logs = [];
        this.hooks = [];
        this.hooksets = {};
        this.prologues = [];
        this.sessions = [];
        this.requires = [];
        //this.requiresNode = [];
        this.listeners = {};
        this.scanners = {};
        this._sess = null;
        this.frida_disabled = pNofrida;

        if(this.frida_disabled==false){
            FRIDA = require("frida");
            //FRIDA_LOAD = require("frida-load");
        }

        return this;
    }



    /**
     * To get frida_disabled status.
     * 
     * @return {Boolean} Frida-feature status
     * @method
     */
    isFridaDisabled(){
        return (this.frida_disabled!=0);
    }

    /**
     * To print help into CLI
     * 
     * @method
     */
    help(){
        console.log(`Module :
    NativeObserver
    Reflect
    RootBypass`);
    }

    /**
     * @deprecated
     */
    refreshScanner(){

        let self = this;
        UT.forEachFileOf(
            Path.join(__dirname, "..", "scanner"),
            function(path,file){
                let s = file.substr(0,file.lastIndexOf("."));
                if(self.scanners[s]==null){
                    self.scanners[s] = require(path);
                    self.scanners[s].injectContext(self.context);
                    Logger.info("[HookManager:refreshScanner] New scanner added : "+s);
                }
            },false);
    }

    /**
     * 
     * @param {*} requires
     * @method 
     */
    addRequires(requires){
        for(let i=0; i<requires.length; i++){
            if(this.requires.indexOf(requires[i])==-1){
                this.requires.push(requires[i]);
            }
        }
    };

    /**
     * 
     * @param {*} requires
     * @method 
     */
    removeRequires(requires){
        let offset=-1;
        for(let i=0; i<requires.length; i++){
            offset = this.requires.indexOf(requires[i]);
            if(offset>-1) this.requires[offset] = null;
        }
    };

    /**
     * To insert required modules into the generated Frida script
     * 
     * DEXC_MODULE = {};
     * 
     * @method
     */
    prepareRequires(){
        let req = "", loaded = {};   
        for(let i=0; i<this.requires.length; i++){
            if(this.requires[i]!=null && loaded[this.requires[i]]==null){
                req += fs.readFileSync(Path.join(__dirname,"requires",this.requires[i]+".js"));
                loaded[this.requires[i]] = true;
            }
        }  

        return req;
    }


    /**
     * To build global hook scripts
     * 
     * @returns {String} Hook script
     * @method
     */
    prepareHookScript(){
        let script = `Java.perform(function() {
            var DEXC_MODULE = {};
        `;

        // include hookset requirements
        //if(this.requiresNode.length > 0)
        //   script = this.prepareRequiresNode()+"\n"+script;
        
        script += this.prepareRequires();
        
        for(let i in this.prologues){
            if(this.prologues[i].isEnable()){
                script += this.prologues[i].builtScript;
            }
        }


        for(let i in this.hooks){
            if(this.hooks[i].isEnable()){
                if(this.hooks[i].hasVariables()){
                    script += this.hooks[i].setupVariables();                
                }
                script += this.hooks[i].script;
            }
        }

        script += "});"
        return script;
    }

    /**
     * To create a new hook session
     * 
     * @returns {HookSession} Current - freshly created - hooking session
     * @method
     */
    newSession(){
        var sess =new HookSession(this)
        // TODO : add configuration flush/keep previous sessions 
        this.sessions.push(sess);
        return sess;
    }


    /**
     * To start hooking by spawning the target application
     *  
     * @param {String} pHookScript Hook script
     * @param {String} pAppName Application UID
     * @method 
     */
    startBySpawn(pAppName, pHookScript= null){
        this.start(pHookScript, HM_SPAWN, pAppName);
    }

    /**
     * 
     * @param {*} pAppName 
     * @param {*} pHookScript 
     */
    startByAttachToGadget(pAppName, pHookScript= null){
        this.start(pHookScript, HM_ATTACH_GADGET, pAppName);
    }

    /**
     * 
     * @param {*} pPID 
     * @param {*} pHookScript 
     */
    startByAttachTo(pPID=null, pHookScript= null){
        this.start(pHookScript, HM_ATTACH_PID, pPID);
    }

    /**
     * 
     * @param {*} pAppName 
     * @param {*} pHookScript 
     */
    startByAttachToApp(pAppName, pHookScript= null){
        this.start(pHookScript, HM_ATTACH_APP, pAppName);
    }



    /**
     * To start hooking
     * 
     * start -> script ? -> NO : prepareHookScipt()
     *                   -> YES: use given script
     *       -> 
     
      * @param {*} hook_script 
      * @param {*} pType 
      * @param {*} pExtra 
      * @param {*} pDevice 
      * 
      * @method
      */
     start(hook_script, pType=null, pExtra=null, pDevice=null){
        
        let target = null;
        let PROBE_SESSION = this.newSession();
        
        if(hook_script == null){
            hook_script = this.prepareHookScript();
        }

        if(this.frida_disabled){
            Logger.info("[HOOK MANAGER] Frida is disabled ! Hook and session prepared but not start() ignored");
            return null;
        } 

        // retrieve default  device from project
        if(pDevice == null){
            target = this.context.getDevice();
        }
        // else, it uses specified device
        else{
            target = pDevice;
        }

        // start Frida
        // do spawn + attach
        var hookRoutine = co.wrap(function *() {
            let session = null, pid=null, applications=null, bridge=null;
            
            let device = null;

            device = yield FridaHelper.getDevice(target);
/*
            bridge = target.getDefaultBridge();
            
            if(bridge.isNetworkTransport()){
                device = yield FRIDA.getDeviceManager().addRemoteDevice(bridge.ip+':'+bridge.port);
            }else{
                device = yield FRIDA.getDevice(bridge.deviceID);
            }
  */          
            
            PROBE_SESSION.fridaDevice = device;

            switch(pType){
                case HM_SPAWN:
                    pid = yield device.spawn([pExtra]);
                    PROBE_SESSION.pid = pid;
                    
                    session = yield device.attach(pid);
                    PROBE_SESSION.fridaSession = session;

                    Logger.info('spawned:', pid);
                    break;
                case HM_ATTACH_APP:
                    applications = yield device.enumerateApplications();
                    for(let i=0; i<applications.length; i++){
                        if(applications[i].identifier == pExtra)
                            pid = applications[i].pid;
                    }

                    if(pid > -1) {
                        PROBE_SESSION.pid = pid;
                        session = yield device.attach(pid);
                        PROBE_SESSION.fridaSession = session;

                        Logger.info('attached to '+pExtra+" (pid="+pid+")");
                    }else{
                        throw new Error('Failed to attach to application ('+pExtra+' not running).');
                    }
                    
                    break;
                case HM_ATTACH_GADGET:
                    applications = yield device.enumerateApplications();
                    if(applications.length == 1 && applications[0].name == "Gadget") {
                        PROBE_SESSION.pid = applications[0].pid;

                        session = yield device.attach(applications[0].pid);
                        PROBE_SESSION.fridaSession = session;

                        Logger.info('attached to Gadget:', pid);
                    }else
                        Logger.error('Failed to attach to Gadget.');

                    break;
                case HM_ATTACH_PID:
                    PROBE_SESSION.pid = pid;

                    session = yield device.attach(pid);
                    PROBE_SESSION.fridaSession = session;

                    Logger.info('spawned:', pid);
                    break;
                default:
                    Logger.error('Failed to attach/spawn');
                    return;
                    break;
            }

            const script = yield session.createScript(hook_script);

             // For frida-node > 11.0.2
             script.message.connect(message => {
                PROBE_SESSION.push(message);//{ msg:message, d:data });
                //console.log('[*] Message:', message);
            });    
            
        
            yield script.load();


            PROBE_SESSION.fridaScript = script;

            console.log('script loaded', script);
            yield device.resume(pid);
        });

        hookRoutine()
            .catch(error => {
            console.log(error);
            console.log('error:', error.message);
            });
    }


    addHookSet(set){
        if(this.hooksets[set.getID()]!=null){
            console.log("[Error] HookManager : An hook set already exists for this ID");
            return false;
        }
        this.hooksets[set.getID()] = set;
        return true;   
    }
    getHookSets(){
        return this.hooksets;   
    }
    getHookSet(id){
        return this.hooksets[id];   
    }
    hasListener(hookid){
        return (this.listeners[hookid] != null);
    }

    // add a listener to call when the HookSession receive a HookMessage with match=true
    addMatchListener(hookid,callback,weight=-1){
        if(this.listeners[hookid]==null)
            this.listeners[hookid] = [];

        this.listeners[hookid].push(callback);  
        return this;
    }
    trigger(event){

        // INFO : event.hook = HookMessage.hook = msg.id
        let hookid = UT.b64_decode(event.hook);
        if(!this.hasListener(hookid)) return false;

        for(let i=0; i<this.listeners[hookid].length; i++){
            this.listeners[hookid][i](this.context,event);
        }
    }
    isProbing(method){
        for(let i in this.hooks){
            if(this.hooks[i].name == method.__signature__ && this.hooks[i].enable){
                return true;
            }
        }
        return false;
    }

    /**
     * 
     */
    getProbe(method){
        for(let i in this.hooks){
            if(this.hooks[i].name == method.__signature__){
                return this.hooks[i];
            }
        }
        return null;
    }

    /**
     * To get all hooks
     * @returns {Hook[]} An array containing all hooks
     */
    getHooks(){
        return this.hooks;
    }

    /**
     * To get a hook by its ID.
     * 
     * @param {String} id The hook ID as provide by the hook trace
     * @return {Hook} The matching hook, then null. 
     * @function
     */
    getHookByID(id){
        for(let i in this.hooks){
            if(this.hooks[i].id == id){
                return this.hooks[i];
            }
        }
        return null;
    }
    removeHook(hook){
        let res=[], pop=null;
        for(let i in this.hooks){
            if(this.hooks[i].id != hook.getID()){
                res.push(this.hooks[i]);
            }else{
                pop = this.hooks[i];
            }
        }
        this.hooks = res;
        return pop;
    }
    findHook(hookId){
        for(let i in this.hooks){
            if(this.hooks[i].id == hookId){
                return this.hooks[i];
            }
        }
        return null;
    }
    findHookByMethod(method){
        let match = [];
        for(let i in this.hooks){
            if(this.hooks[i].name == method.__signature__){
                match.push(this.hooks[i]);
            }
        }
        return match;
    }
    nextHookIdFor(method){
    //    return method.__signature__+"@@"+this.findHookByMethod(method).length;
        return method.signature()+"@@"+this.findHookByMethod(method).length;

    }
    probe(method){
        let hook = null;
        if(method instanceof CLASS.Class){
            console.log("TODO");
        }else if(method instanceof CLASS.Method){
            hook = new Hook(this.context);

            //hook.setID( this.nextHookIdFor(method));
            hook.setID( md5(this.nextHookIdFor(method)));
            
            //hook.makeProbeFor(method);
            hook.makeHookFor(method);

            //hook.setMethod(method);
            // method.setProbing(true);
            method.probing = true;

            console.log("[PROBE] Add : ",hook.name)
            this.hooks.push(hook);
        }
        return hook;
    }

    addPrologue(prologue){
        return this.prologues.push( this.prologue.injectContext(this.context));
    }
    removePrologueOf(hookset){
        let npro = [];
        for(let i=0; i<this.prologues.length; i++){
            if(this.prologues[i].parentID != hookset.getID()){
                npro.push(this.prologues[i]);
            }   
        }
        this.prologues = npro;
    }


    removeHooksOf(hookset){
        let npro = [];
        for(let i=0; i<this.hooks.length; i++){
            if(this.hooks[i].parentID != hookset.getID()){
                npro.push(this.hooks[i]);
            }   
        }
        this.prologues = npro;
    }

    /**
     * To list hooks
     * 
     * @method
     */
    list(){
        return this.hooks;
    }

    /**
     * To get latest hook session
     * 
     * @returns {HookSession} Latest hook session
     * @method
     */
    lastSession(){
        if(this.sessions.length == null){
            return null;
        }
        return this.sessions[this.sessions.length-1];
    }
}

/**
 * Group of hook 
 * 
 * @param {*} config 
 */
function HookSet(config){
    this.id = null;
    this.name = null;
    this.description = null;
    this.prologue = null;
    this.intercepts = [];
    this.probes = [];
    this.context = null;
    this.enable = false;
    this.requires = [];
    this.color = null;
   // this.requiresNode = [];

    for(let i in config) this[i] = config[i];
    return this;
}
HookSet.prototype.isEnable = function(){
    return this.enable;
}
HookSet.prototype.getID = function(){
    return this.id;
}
HookSet.prototype.injectContext = function(context){
    this.context = context;

    // forward to the prologue
    if(this.prologue!=null)
        this.prologue.context = this.context;
    
    // register the hookset to the HookManager
    this.context.hook.addHookSet(this);

    return this; 
}
HookSet.prototype.addPrologue = function(code){
    //this.prologue = code;
    this.prologue = new HookPrologue({
        parentID: this.id,
        script: code
    });

    return this;
}
HookSet.prototype.require = function(module){
    this.requires.push(module);
}
/*
HookSet.prototype.requireNodeModule = function(module){
    this.requiresNode.push(module);
}*/
/**
 * Create a object shared with others hook callback
 * @param {Object} config Shared object config 
 */
HookSet.prototype.addHookShare = function(config){
    this.share = config;
    return this;
}



/**
 * Get the shared object from this hookset
 * @returns {Object} Shared object
 * @function
 */
HookSet.prototype.getHookShare = function(){
    return this.share;
}
HookSet.prototype.addProbe = function(probeConfig){
    if(probeConfig.method != null){
        if(typeof probeConfig.method != "string"){
            let probe = null;
            for(let i=0; i<probeConfig.method.length; i++){
                probe = new HookPrimitive(probeConfig);
                probe.setMethod( probeConfig.method[i]);
                this.probes.push( probe);            
            }
        }else{
            probe = new HookPrimitive(probeConfig);
            probe.setMethod( probeConfig.method);
            this.probes.push( probe); 
            //this.probes.push( new HookPrimitive(probeConfig));
        }    
    }else{
        probe = new HookPrimitive(probeConfig);
        this.probes.push( probe); 
    }
     return this;
}

/*
HookSet.prototype.addProbe = function(probeConfig){
    if(probeConfig.multiple_method != null){
        let probe = null;
        for(let i=0; i<probeConfig.multiple_method.length; i++){
            probe = new HookPrimitive(probeConfig);
            probe.setMethod( probeConfig.multiple_method[i]);
            this.probes.push( probe);            
        }
    }else
        this.probes.push( new HookPrimitive(probeConfig));
    
    return this;
}
*/
HookSet.prototype.addIntercept = function(interceptConfig){
    if(interceptConfig.method == null && interceptConfig.raw == null){
        Logger.error("[HOOK MANAGER] addIntercept(): The method to hook is not defined");
        return null;
    }

    let primitive;

    if(interceptConfig.method !=null){
        if(typeof interceptConfig.method != "string"){
            for(let i=0; i<interceptConfig.method.length; i++){
                primitive = new HookPrimitive(interceptConfig);
                primitive.setMethod( interceptConfig.method[i]);
                primitive.isIntercept = true;
                primitive.color = this.color;

                this.intercepts.push( primitive);            
            }
        }else{
            primitive = new HookPrimitive(interceptConfig);
            primitive.isIntercept = true;
            primitive.setMethod( interceptConfig.method);
            primitive.color = this.color;

            this.intercepts.push( primitive);
        }
    }
    else{
        primitive = new HookPrimitive(interceptConfig);
        primitive.color = this.color;
        this.intercepts.push( primitive); 
    }


    return this;
}

HookSet.prototype.addCustomHook = function(config){
    if(config.method == null && config.raw == null){
        Logger.error("[HOOK MANAGER] addCustomHook(): The method to hook is not defined");
        return null;
    }

    if(config.method !=null){
        if(typeof config.method != "string"){
            for(let i=0; i<config.method.length; i++){
                config.custom = true;
                primitive = new HookPrimitive(config);
                primitive.isIntercept = true;
                primitive.isCustom = true;
                primitive.setMethod( config.method);
                primitive.color = this.color;

                this.intercepts.push( primitive);           
            }
        }else{
            config.custom = true;
            primitive = new HookPrimitive(config);
            primitive.isIntercept = true;
            primitive.isCustom = true;
            primitive.setMethod( config.method);
            primitive.color = this.color;

            this.intercepts.push( primitive);
        }
        
    }
}

/*
HookSet.prototype.addIntercept = function(interceptConfig){
    let primitive = null;
    if(interceptConfig.multiple_method != null){
        for(let i=0; i<interceptConfig.multiple_method.length; i++){
            primitive = new HookPrimitive(interceptConfig);
            primitive.setMethod( interceptConfig.multiple_method[i]);
            primitive.isIntercept = true;
            this.intercepts.push( primitive);            
        }
    }else{
        primitive = new HookPrimitive(interceptConfig);
        primitive.isIntercept = true;
        this.intercepts.push( primitive);
    }

    return this;
}*/
/*
HookSet.prototype.addSyscallProbe = function(probeConfig){
    this.native_probes.push( new HookPrimitive(probeConfig));
    return this;
}
HookSet.prototype.addSyscallIntercept = function(probeConfig){
    this.native_intercepts.push( new HookPrimitive(probeConfig));
    return this;
}*/
HookSet.prototype.disable = function(){
    let hookManager = this.context.hook; //ctx.hook;
    let hook, method, hconfig;

    if(this.prologue != null)
        hookManager.removePrologueOf(this);

    hookManager.removeHooksOf(this);
    this.enable = false;
}

HookSet.prototype.deploy = function(){
    let hookManager = this.context.hook; //ctx.hook;
    let hook, method, hconfig;

    // if the hookset is already deployed only not deployed hooks are generated
    if(this.enable === false){
        hookManager.addRequires(this.requires);
        //hookManager.addRequiresNode(this.requiresNode);

        if(this.prologue != null)
            hookManager.prologues.push(
                this.prologue.injectContext(this.context)
            );
    }

    /*
    if(this.shares != null)
        hookManager.shares.push(
            this.prologue.injectContext(this.context)
        );
            */

    for(let i in this.probes){
        if(!(this.probes[i] instanceof Hook)){
            hook = this.probes[i].toProbe(this.context, this);

            console.log("[PROBE][HOOK SET] Add : ",hook.name)
            this.probes[i] = hook;
            hookManager.hooks.push(this.probes[i]);

            if(hook.onMatch != null)
                hookManager.addMatchListener(hook.getID(),hook.onMatch);
        }
    }
  
    for(let i in this.intercepts){
        if(!(this.intercepts[i] instanceof Hook)){
            hook = this.intercepts[i].toIntercept(this.context, this);

            if(hook.isCustomHook())
                Logger.info("[INTERCEPT][HOOK SET][CUSTOM] Add : ",hook.name)
            else
                Logger.info("[INTERCEPT][HOOK SET] Add : ",hook.name)
            
            this.intercepts[i] = hook;    
            hookManager.hooks.push(this.intercepts[i]);   
            
            if(hook.onMatch != null)
                hookManager.addMatchListener(hook.getID(),hook.onMatch);
        }
    }

    this.enable = true;
}
HookSet.prototype.toJsonObject = function(){
    let o = new Object();
    for(let i in this){
        switch(i){
            case "id":
            case "name":
            case "enable":
            case "description":
                o[i] = this[i];
                break;
            case "prologue":
                if(this[i]!=null)
                    o[i] = this[i].toJsonObject();
                else
                    o[i] = "";
                break;
            case "probes":
            case "intercepts":
                o[i] = [];
                for(let j=0;  j<this[i].length; j++)
                    o[i].push(this[i][j].toJsonObject());
                break;
            case "context":
                break;
        }
    }
    return o;
}

module.exports = {
    Manager: HookManager,
    Hook: Hook,
    HookPrimitive: HookPrimitive,
    HookMessage: HookMessage,
    HookSession: HookSession,
    HookSet: HookSet,
    VariableArray: VariableArray,
    VariableObject: VariableObject,
};