Day 28 28.2 JS进阶之eval和hook函数

发布时间 2023-04-27 16:14:28作者: Chimengmeng

JS进阶之eval函数和hook函数

【一】eval

  • eval() 函数计算 JavaScript 字符串,并把它作为脚本代码来执行。

  • 如果参数是一个表达式,eval() 函数将执行表达式。

    • 如果参数是Javascript语句,eval()将执行 Javascript 语句。
eval(string)
// 
eval('[1,2,3,4,5].map(x=>x*x)')

http://tools.jb51.net/password/evalencode

【二】 Hook函数

【1】技术原理

  • Hook是一种钩子技术
    • 在系统没有调用函数之前,钩子程序就先得到控制权
    • 这时候钩子函数既可以加工处理该函数的执行行为,也可以强制结束消息的传递
    • 简单来说就是修改原有的js代码就是hook。
  • JS 是一种弱类型语言,同一个变量可以多次定义、根据需要进行不同的赋值
    • 而这种情况如果在其他强类型语言中则可能会报错,导致代码无法执行。
    • js 的这种特性,为我们 Hook 代码提供了便利。

【2】JS作用域问题

【2.1】自执行函数的hook问题

  • js变量是有作用域的
    • 只有当被hook函数和debugger断点在同一个作用域的时候,才能hook成功。
!(function(){
    var arg = 1;
    var test = function(){
        console.log(arg);
    }
debugger;
})()

【2.2】局部变量污染全局变量

  • 在Hook的时候要注意js代码的特性,js在函数赋值的时候会遵循一个原则:
    • 当前作用域有变量则赋值该变量,当前作用域没有该变量则赋值在全局作用域定义该变量并赋值。
var arg1 = null; 
function test1(){
  arg1 = 1;   // 注意这里没有 var
}; 
function test2(){
  console.log(arg1)
}; 
test1(); 
test2();

【2.3】this的指向问题

  • 不同的作用域中,相同变量的指向不一样。
  • 每个函数在定义被解析器解析时,都会创建两个特殊变量:this和arguments。
  • 每个函数都有属于自己的this对象,这个this对象时在运行时基于函数的执行环境绑定的。
全局作用域中,this = window;
方法作用域中,this = 调用者;
在浅滩函数中,this = 调用外部函数或内部函数的执行环境对象;
在类方法里面,this = 类自己;
  • 谁调用这个函数对象,this就指向谁(this指向的是调用执行函数的那个对象)。
  • 在 JS 中,如果需要改变 this 的指向
    • 可以通过使用 call() 和 apply() 改变函数执行环境的情况,以改变this 指向。

【三】HOOK实现

【1】Hook 实现有两种方式

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

【2】两种方式的区别

  • 函数hook
    • 一般不会hook失败;
    • 除非proto模拟的不好被检测到。
  • 属性hook
    • 当网站所有的逻辑都采用Object.defineProperty绑定时,属性hook就会失效;
    • 同时,Object.defineProperty无法进行二次hook。

【3】比较

  • 第一种方式简单、但是太粗暴,容易影响原有代码的正常执行,也容易被检测到
  • 第二种方式会更优雅一些,具体需要结合具体需求选择合适的 Hook 方式。

【4】HOOK方法

方法一:直接替换原有函数
old_func = 被 hook 函数
被 hook 函数 = function(arguments){
  if 判断条件:
    my_task;
  return old_func.apply(arguments)
}
func.prototype.xxx = xxxx
替换原函数
function foo() {
    console.log("foo功能...")
    return 123
}
foo()

var _foo = foo

foo = function () {
    console.log("截断开始...")
    debugger;
    _foo()
    console.log("截断结束...")
}
function foo(a, b, c, d, e, f) {
    console.log("foo功能...")
    console.log(a, b, c, d, e, f)
    return 123
}

var _foo = foo

foo = function () {
    console.log("截断开始...")
    // _foo(arguments)
    _foo.apply(this,arguments)
    console.log("截断结束...")
}

foo(1,2,3,4,5,6,7)
方法二:Object.defineProperty 为对象的属性赋值
odl_attr = obj.attr
Object.defineProperty(obj, 'attr', {
  get: function(){
    debugger;
    if 判断条件:
      my_task;
    return old_attr
  },
  set: function(val){
    debugger;
    if 判断条件:
      my_task;
    return 自定义内容
  }
})

【四】HOOK的应用

  • Hook http请求
    • http请求包括 ajax、src、href、表单等。
/**
 * 全局拦截ajax请求
 * **/
(function () {
    XMLHttpRequest.prototype.nativeOpen = XMLHttpRequest.prototype.open;
    var customizeOpen = function (method, url, async, user, password) {
        debugger;
        // do something
        this.nativeOpen(method, url, async, user, password);
    };

    XMLHttpRequest.prototype.open = customizeOpen;
})()


/**
 *全局拦截Image的图片请求添加token
 *
 */
(function () {
    const property = Object.getOwnPropertyDescriptor(Image.prototype, 'src');
    const nativeSet = property.set;

    function customiseSrcSet(url) {
        // do something
        nativeSet.call(this, url);
    }

    Object.defineProperty(Image.prototype, 'src', {
        set: customiseSrcSet,
    });
})()

/**
 * 拦截全局open的url添加token
 *
 */
function hookOpen() {
    const nativeOpen = window.open;
    window.open = function (url) {
        // do something
        nativeOpen.call(this, url);
    };
}

function hookFetch() {
    var fet = Object.getOwnPropertyDescriptor(window, 'fetch')
    Object.defineProperty(window, 'fetch', {
        value: function (a, b, c) {
            // do something
            return fet.value.apply(this, args)
        }
    })
}

【五】Cookie钩子实例

(1)查找cookie生成入口

  • 打script断点,在js刚运行时就把网页断住,并在console中输入下列代码:
document.cookie_bak = document.cookie
Object.defineProperty(document, 'coockie',{
  get: function(){
    debugger;
    return document.cookie_bak;
  },
  set: function(val){
    debugger;
    return;
  }
})

(2)定位cookie中关键参数生成位置

  • 当cookie中匹配到了 目标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;
    },
  });
})();

(3)header钩子

  • header 钩子用于定位 header 中关键参数生成位置,以下代码演示了当 header 中包含 Authorization 时,则插入断点
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);

(4)cookie 钩子

  • cookie 钩子用于定位 cookie 中关键参数生成位置,以下代码演示了当 cookie 中匹配到了 abcdefghijk, 则插入断点:
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);

(5)请求钩子

  • 请求钩子用于定位请求中关键参数生成位置,以下代码演示了当请求的 url 里包含 AbCdE 时,则插入断点:
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);

【六】处理debugger

(1)置空

debugger函数 = function(){};

(2)Hook eval函数绕过无限debugger

eval_bak = eval
eval = function(val){
  debugger;
  return eval_bak(val);
}
eval.toString = function(){
  return "function eval() { [native code] }"
}

eval函数案例

console.log("程序开始")
// eval("console.log('yuan')")
window["e"+"v"+"a"+"l"]("console.log('eval yuan')")
console.log("程序结束")
var _eval = eval

eval = function (src) {
    console.log("eval截断开始...")
    debugger;
    _eval.apply(this, src)
    console.log("eval截断结束...")
}

(3)干掉定时器类触发的无限 debugger

for (var i = 1; i < 99999; i++)window.clearInterval(i);
  • 利用Hookeval函数替换成console.log
  • 当 js 代码中有 eval 函数执行了某种操作,逆向过程中需要分析操作的具体内容时
    • 可以通过 Hook eval 替换成 console.log 的方式,将 eval 执行的代码打印出来。
eval_bak = eval;
eval = console.log;

(4)无限debugger

Function.prototype.constructor_old = Function.prototype.constructor
Function.prototype.constructor = function(){
    if(argument==='debugger'){}   
    else{
	return Function.prototype.constructor_old.apply(this,arguments)
}
}

(5)debugger暴力破解

window.open = function(){}
window.setInteval = function(){}

【七】风控检测Hook

  • toString 检测识别 Hook

    • toString 检测

      • 指的是风控通过检测被 Hook 的函数 toString() 结果是否变化,来判断该函数是否被 Hook 的一种检测方法;
    • 当风控监测到 Hook 以后,可以返回假数据误导逆向工程师,也可以配合内存爆破进行反 debugger。

    • 比如我们 Hook 了 eval 函数,这时风控就可以通过检测 eval.toString() 的返回值是否是 “function eval() { [native code] }” 来识别该函数是否被 Hook 了。

      • 解决 toString 检测,我们只需要修改目标函数的 toString 方法。

        eval.toString = function(){
        	return "function eval() { [native code] }"
        }
        

【八】案例

案例1: Hook eval

console.log("程序开始")
// eval("console.log('yuan')")
window["e"+"v"+"a"+"l"]("console.log('eval yuan')")
console.log("程序结束")
var _eval = eval

eval = function (src) {
    console.log("eval截断开始...")
    debugger;
    _eval.apply(this, src)
    console.log("eval截断结束...")
}

案例2: Hook JSON.stringify

  • JSON.stringify() 方法用于将 JavaScript 值转换为 JSON 字符串,
    • 在某些站点的加密过程中可能会遇到,
    • 以下代码演示了遇到 JSON.stringify() 时,则插入断点:
(function() {
    var stringify = JSON.stringify;
    JSON.stringify = function(params) {
        console.log("Hook JSON.stringif:::", params);
        debugger;
        return stringify(params);
    }
})();

案例3: Hook JSON.parse

  • JSON.parse() 方法用于将一个 JSON 字符串转换为对象,
    • 在某些站点的加密过程中可能会遇到,
    • 以下代码演示了遇到 JSON.parse() 时,则插入断点:
(function() {
    var parse = JSON.parse;
    JSON.parse = function(params) {
        console.log("Hook JSON.parse::: ", params);
        debugger;
        return parse(params);
    }
})();
  • 一般使用Object.defineProperty()来进行属性操作的hook。
    • 那么我们了解一下该方法的使用。
Object.defineProperty(obj, prop, descriptor)

// 参数
obj:对象;
prop:对象的属性名;
descriptor:属性描述符;
  • 一般hook使用的是get和set方法,
    • 下边简单演示一下
var people = {
    name: '张三',
};

Object.defineProperty(people, 'age', {
    get: function () {
        console.log('获取值!');
        return count;
    },
    set: function (val) {
        console.log('设置值!');
        count = val + 1;
    },
});

people.age = 18;
console.log(people.age);
  • 通过这样的方法,
    • 我们就可以在设置某个值的时候,添加一些代码,比如 debugger;
    • 让其断下,然后利用调用栈进行调试,找到参数加密、或者参数生成的地方,
    • 需要注意的是,网站加载时首先要运行我们的 Hook 代码,
    • 再运行网站自己的代码,才能够成功断下,
    • 这个过程我们可以称之为 Hook 代码的注入。
(function(){
   'use strict'
    var _cookie = "";
    Object.defineProperty(document, 'cookie', {
        set: function(val) {
            console.log(val);
            debugger
            _cookie = val;
            return val;
        },
        get: function() {
            return _cookie;
        },
});
})()