Js-Hook

Js-Hook

参考文章:https://blog.csdn.net/Ks_meng/article/details/127336084

参考文章:https://blog.csdn.net/qq_40558166/article/details/123525365

介绍:Hook 是一种常用的钩子技术,在系统没有调用函数之前,钩子程序首先得到控制权,这时钩子函数既可以加工处理该函数的执行行为,也可以强制结束消息的传递;简单来说修改原来 js 代码的功能就是 hook;

js 是一种弱类型语言,同一个变量可以被多次定义、根据需要进行不同的赋值,而这种情况在其他强类型语言(例如: java)则可能会报错,导致代码无法执行。js 的这种特性为 Hook 提供了便利;

1.js的作用域

js 的变量是有作用域的,只有当被 hook 的函数和debuger断点在同一个作用域的时候才能hook成功;

!(function(){
    var arg = 1;
    var test = function(){
        console.log(arg);
    }
debugger;
})()

// 补充 !(function(){})() 表示立即执行当前的函数闭包;

1.1 js 中的闭包、

闭包是javascript语言的一个难点,也是他的特色。闭包的作用,通过一系列方法将函数内部的变量(局部变量)转换为全局变量;

function myFunction() {
    var a = 4;
    return a * a;
}

这个实例在终端是没有办法直接访问到变量a;

var a = 4;
function myFunction() {
    return a * a;
}
>>> 16
// 在函数的内部可以访问到全局变量信息

在终端中可以直接访问到变量a;

局部变量只能用于定义它函数内部。对于其他的函数或脚本代码是不可用的。

全局和局部变量即便名称相同,它们也是两个不同的变量。修改其中一个,不会影响另一个的值。

变量的生命周期:全局变量的作用域是全局性的,即在整个 JavaScript 程序中,全局变量处处都在。而在函数内部声明的变量,只在函数内部起作用。这些变量是局部变量,作用域是局部性的;函数的参数也是局部性的,只在函数内部起作用。

javascript 内嵌函数

在 javascript 中所有函数都能访问到他们上一层的作用域。javascript支持嵌套函数。嵌套函数可以访问上一层的函数变量。

// 计数器案例,
function add(){
    var counter = 0;
    function plus(){
        counter += 1;
    }
    plus()
    return counter;
}
// 内嵌函数 plus 可以访问到上一层的变量 counter;

上述的计数器函数每次执行的结果都是1,因此我们需要用到闭包;需要我们在外部访问到plus函数,并确保counter执行一次;

var add = (function (){
    var counter = 0;
    return function (){
        // js 的函数可以访问到上一层的变量信息;
        return counter += 1;
    }
})();

变量 add 指定了函数的自我调用返回值;外层的函数执行一次,设置 coiunter 为 0,返回函数表达式;

add变量可以作为一个函数使用。非常棒的部分是它可以访问函数上一层作用域的计数器。这个叫作 JavaScript 闭包。它使得函数拥有私有变量变成可能。

计数器受匿名函数的作用域保护,只能通过 add 方法修改。

2.Hook的实现

两种实现方式:

  • 一种是,直接替换函数
  • 另一种是 Object.defineProperty通过为对象的属性赋值的方式进行 Hook;

两种方式的区别

  • 函数 hook 一般不会失败,除非 proto 模拟的不好被检测到;

  • 属性 hook ,当网站的所有逻辑采用Object.defineProperty绑定时,属性hook就会失效.同时无法进行第二次的 hook;

  • 第一种方式简单、但是太粗暴,容易影响原来代码的执行,也容易被检测到;

  • 第二种方式会更优雅一些,具体需要结合具体需求选择合适的 Hook 方式;

2.1 替换函数

补充:每个函数在定会以被解析器解析时,都会创建两个特殊变量:this 和 arguments;

apply()能够编写用于不同对象的方法;

var person = {
    fullName: function() {
        return this.firstName + " " + this.lastName;
    }
}
var person1 = {
    firstName: "Bill",
    lastName: "Gates",
}
person.fullName.apply(person1);  // 将返回 "Bill Gates"

通过apply调用对象的函数,并且传入参数;

// 语法, 伪代码;
old_func = 被 hook 函数
被 hook 函数 = function(arguments){
    if(判断条件){
    	my_task;
    }
    // 继续执行原来的函数信息;
    return old_func.apply(arguments)
}

示例:

// 无限 debuger 的hook示例;
// 原来函数的备份
Function.prototype.constructor_old = Function.prototype.constructor;
// 替换掉原来的函数
Function.prototype.constructor = function(){
    // 判断条件,查看参数是否是 debuger
    if(argument==='debugger'){
        // 满足的时候跳过原来的停止逻辑;
    }else{
        // 继续调用原来的旧的函数;
		return Function.prototype.constructor_old.apply(this,arguments)
	}
}

2.2 属性赋值

伪代码,

// 通过 Object.defineProperty
old_attr = obj.attr
Object.defineProperty(obj,"attr",{
    get: function() {
        debuger; // 设置程序断点
        if (判断条件){
            mytask;
        }
        return old_attr;
    },
    set: function(val){
        debuger;
        if(判断条件){
            my_task;
        }
        return "自定义的内容"
    }
})
// 这种 hook 的方式有一点类似于 面向对象的反射;

示例:

// hook-cookie
// 备份原来的 cookie 属性
document.cookie_bak = document.cookie
// 设置属性的信息
Object.defineProperty(document, 'coockie',{
    // 获取 cookie 的信息
    get: function(){
    	debugger;// 断点,可以定位到 cookie 发生改变的 js 代码的位置
    	return document.cookie_bak;// 继续返回原来的代码继续执行
  	},
    set: function(val){
        debugger; // 设置断点, 定位到 cookie 改变的位置;
    	return;
  	}
})

3.爬虫中常见的 hook代码

待补充:hook 的时机;

3.1 无限 debuger

// 注释说明版
// 原来函数的备份
Function.prototype.constructor_old = Function.prototype.constructor;
// 替换掉原来的函数
Function.prototype.constructor = function(){
    // 判断条件,查看参数是否是 debuger
    if(argument==='debugger'){
        // 满足的时候跳过原来的停止逻辑;
    }else{
        // 继续调用原来的旧的函数;
		return Function.prototype.constructor_old.apply(this,arguments)
	}
}

// 暴力破击 debuger
window.open = function(){}
window.setInteval = function(){}

非注释版

// 无注释版,可以直接使用;
Function.prototype.constructor_old = Function.prototype.constructor;
Function.prototype.constructor = function(){
    if(argument==='debugger'){
		
    }else{
		return Function.prototype.constructor_old.apply(this,arguments)
	}
}

cookie 的 hook 有着不同的用法,所有的与单一的;

// hook-cookie, 定位到cookie 中 JS 代码的位置信息;
// 备份原来的 cookie 属性
document.cookie_bak = document.cookie
// 设置属性的信息
Object.defineProperty(document, 'coockie',{
    // 获取 cookie 的信息
    get: function(){
    	debugger;// 断点,可以定位到 cookie 发生改变的 js 代码的位置
    	return document.cookie_bak;// 继续返回原来的代码继续执行
  	},
    set: function(val){
        debugger; // 设置断点, 定位到 cookie 改变的位置;
    	return;
  	}
})


document.cookie_bak = document.cookie
Object.defineProperty(document, 'coockie',{
    get: function(){
    	debugger;
    	return document.cookie_bak;
  	},
    set: function(val){
        debugger;
    	return;
  	}
})

目标cookie生成定位;

// 使用闭包
(function () {
  'use strict'; // 使用严格模式,禁用一些 js 的语法
  var cookieTemp = '';// 定义空字符串
  Object.defineProperty(document, 'cookie', {
   	
    set: function (val) {
      // 设置 cookie的信息,检测到 cookie的值的信息
      if (val.indexOf('目标cookie字符串') != -1) {
        debugger; // 设置断点
      }
      console.log('Hook捕获到cookie设置->', val);
      cookieTemp = val; //获取到cookie的信息;
      return val;
    },
    get: function () {
      return cookieTemp;// 返回 cookie的信息
    },
  });
})();



(function () {
  'use strict'; 
  var cookieTemp = '';
  Object.defineProperty(document, 'cookie', {
    set: function (val) {
      if (val.indexOf('目标cookie字符串') != -1) {
        debugger;
      }
      console.log('Hook捕获到cookie设置->', val);
      cookieTemp = val;
      return val;
    },
    get: function () {
      return cookieTemp;
    },
  });
})();

闭包形式的hook

// 声名函数
var code = function(){
    // 获取属性中的 cookie 信息
    var org = document.cookie.__lookupSetter__('cookie');
    document.__defineSetter__("cookie",function(cookie){
        if(cookie.indexOf('abcdefghijk')>-1){
            // 检测到 cookie 的的信息,设置断点,定位到JS代码的位置;
            debugger;
        }
        org = cookie;
    });
    document.__defineGetter__("cookie",function(){return org;});
}
// 创建 script 标签
var script = document.createElement('script');
// 将函数添加到闭包中;
script.textContent = '(' + code + ')()';
(document.head||document.documentElement).appendChild(script);
script.parentNode.removeChild(script);


var code = function(){
    var org = document.cookie.__lookupSetter__('cookie');
    document.__defineSetter__("cookie",function(cookie){
        if(cookie.indexOf('abcdefghijk')>-1){
            debugger;
        }
        org = cookie;
    });
    document.__defineGetter__("cookie",function(){return org;});
}
var script = document.createElement('script');
script.textContent = '(' + code + ')()';
(document.head||document.documentElement).appendChild(script);
script.parentNode.removeChild(script);

3.3 Hook-header

header 钩子用于定位 header中关键参数生成位置,

// hook-header
// 定义 code 函数
var code = function(){
    // XMLHttpRequest 仅限制于 ajax 请求头的设置;
	var org = window.XMLHttpRequest.prototype.setRequestHeader;
	window.XMLHttpRequest.prototype.setRequestHeader = function(key,value){
    	// 请求头中键名的设置检测
        if(key=='hook的键名称'){
        	debugger;// 检测到之后设置断点
    	}
        // 继续执行原来的函数
    	return org.apply(this,arguments);
	}
}
// 创建 script 标签;
var script = document.createElement('script');
// 设置标签中的内容,将 hook 代码写入到闭包中
script.textContent = '(' + code + ')()';
// 添加script标签到 <head></head>标签中
(document.head||document.documentElement).appendChild(script);
// 移除
script.parentNode.removeChild(script);



var code = function(){
	var org = window.XMLHttpRequest.prototype.setRequestHeader;
	window.XMLHttpRequest.prototype.setRequestHeader = function(key,value){
        if(key=='hook的键名称'){
        	debugger;
    	}
    	return org.apply(this,arguments);
	}
}
var script = document.createElement('script');
script.textContent = '(' + code + ')()';
(document.head||document.documentElement).appendChild(script);
script.parentNode.removeChild(script);

3.4 hook-url

url 的hook ,请求的hook信息;

(function () {
    // 限制 ajax请求
    var open = window.XMLHttpRequest.prototype.open;
    window.XMLHttpRequest.prototype.open = function (method, url, async) {
        // 请求路由中包含的路径,设置断点;
        if (url.indexOf("login") != -1) {
            debugger;
        }
        return open.apply(this, arguments);
    };
})();


(function () {
    var open = window.XMLHttpRequest.prototype.open;
    window.XMLHttpRequest.prototype.open = function (method, url, async) {
        if (url.indexOf("login") != -1) {
            debugger;
        }
        return open.apply(this, arguments);
    };
})();

// 也可以将这种方式修改成为上述的添加标签的方式

var code = function(){
var open = window.XMLHttpRequest.prototype.open;
window.XMLHttpRequest.prototype.open = function (method, url, async){
    if (url.indexOf("AbCdE")>-1){
        debugger;
    }
    return open.apply(this, arguments);
};
}
var script = document.createElement('script');
script.textContent = '(' + code + ')()';
(document.head||document.documentElement).appendChild(script);
script.parentNode.removeChild(script);

3.5 Hook-json-stringify

Json.stringify()方法用于将javascript值替换为json字符串,在某些站点的加密过程中可能会遇到,示例

(function() {
    var stringify = JSON.stringify;
    JSON.stringify = function(params) {
        console.log("Hook JSON.stringify ——> ", params);
        debugger;
        return stringify(params);
    }
})();


// 声名闭包
(function() {
    // 声名对象;
    var stringify = JSON.stringify;
    JSON.stringify = function(params) {
        console.log("Hook JSON.stringify ——> ", params);
        debugger;// 设置断点
        return stringify(params);
    }
})();

3.6 hook-json-parse

JSON.parse() 方法用于将一个 JSON 字符串转换为对象,在某些站点的加密过程中可能会遇到,

(function() {
    var parse = JSON.parse;
    JSON.parse = function(params) {
        console.log("Hook JSON.parse ——> ", params);
        debugger;
        return parse(params);
    }
})();


(function() {
    // 声名对象
    var parse = JSON.parse;
    JSON.parse = function(params) {
        // 替换原来的函数;
        console.log("Hook JSON.parse ——> ", params);
        debugger;// 设置断点;
        return parse(params);
    }
})();

3.7 Hook-eval

利用hookeval函数替换成console.log,当js代码中有eval函数执行了某种操作,逆向过程中需要分析操作的具体内容时,可以通过 Hook eval 替换成 console.log 的方式,将 eval 执行的代码打印出来。

// 备份eval
eval_bak = eval;
// 替换函数
eval = console.log;

JavaScript eval() 函数的作用是计算 JavaScript 字符串,并把它作为脚本代码来执行。如果参数是一个表达式,eval() 函数将执行表达式。如果参数是 Javascript 语句,eval() 将执行 Javascript 语句,经常被用来动态执行 JS。以下代码执行后,之后所有的 eval() 操作都会在控制台打印输出将要执行的 JS 源码:

(function() {
    // 保存原始方法
    window.__cr_eval = window.eval;
    // 重写 eval
    var myeval = function(src) {
        console.log(src);
        console.log("=============== eval end ===============");
        debugger;// 设置断点
        return window.__cr_eval(src);
    }
    // 屏蔽 JS 中对原生函数 native 属性的检测
    var _myeval = myeval.bind(null);
    _myeval.toString = window.__cr_eval.toString;
    Object.defineProperty(window, 'eval', {
        value: _myeval
    });
})();



(function() {
    // 保存原始方法
    window.__cr_eval = window.eval;
    // 重写 eval
    var myeval = function(src) {
        console.log(src);
        console.log("=============== eval end ===============");
        debugger;
        return window.__cr_eval(src);
    }
    // 屏蔽 JS 中对原生函数 native 属性的检测
    var _myeval = myeval.bind(null);
    _myeval.toString = window.__cr_eval.toString;
    Object.defineProperty(window, 'eval', {
        value: _myeval
    });
})();

3.8 Hook-Function

以下代码执行后,所有的函数操作都会在控制台打印输出将要执行的 JS 源码:

(function() {
    window.__cr_fun = window.Function;
    var myfun = function() {
        var args = Array.prototype.slice.call(arguments, 0, -1).join(","),
            src = arguments[arguments.length - 1];
        console.log(src);
        console.log("=============== Function end ===============");
        debugger;
        return window.__cr_fun.apply(this, arguments);
    }
    myfun.toString = function() {
        return window.__cr_fun + ""
    }
    Object.defineProperty(window, 'Function', {
        value: myfun
    });
})();


(function() {
    // 保存原始方法
    window.__cr_fun = window.Function;
    // 重写 function
    var myfun = function() {
        var args = Array.prototype.slice.call(arguments, 0, -1).join(","),
            src = arguments[arguments.length - 1];
        console.log(src);
        console.log("=============== Function end ===============");
        debugger;// 设置断点
        return window.__cr_fun.apply(this, arguments);
    }
    // 屏蔽js中对原生函数native属性的检测
    myfun.toString = function() {
        return window.__cr_fun + ""
    }
    Object.defineProperty(window, 'Function', {
        value: myfun
    });
})();

4.风控检测Hook

toString检测识别Hook

  • toString检测,指的是风控通过检测被 Hook的函数的toSting结果是否变化,来判断该函数是否被hook

  • 当风控检测到 hook 以后,可以返回假的数据误导逆向工程师,也可以配合内存爆破进行反debuger

  • 比如hook eval函数,这是风控可以通过检测eval.toString()返回值是否是'function eval() { [native code] }'来识别该函数是否被hook了;

  • 解决toString检测,只需要修改目标中的toString方法;

    // 直接将 toString方法hook成为指定的函数信息;
    eval.toString = function(){
    	return "function eval() { [native code] }"
    }
    
posted @ 2023-02-07 20:30  紫青宝剑  阅读(905)  评论(0编辑  收藏  举报