JS逆向:Hook 技术原理以及在 JS 逆向中的相关应用

1. Hook 技术原理

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

Hook 技术之所以能够实现有两个条件:
  • 客户端拥有 JS 的最高解释权,可以决定在任何时候注入 JS,而服务器无法阻止或干预。服务端只能通过检测和混淆的手段,另 Hook 难度加大,但是无法直接阻止。
  • 除了上面的必要条件之外,还有一个条件。就是 JS 是一种弱类型语言,同一个变量可以多次定义、根据需要进行不同的赋值,而这种情况如果在其他强类型语言中则可能会报错,导致代码无法执行。js 的这种特性,为我们 Hook 代码提供了便利。
JS 作用域问题 1:自执行函数的 Hook 问题

JS 变量是有作用域的,只有当被 hook 函数和 debugger 断点在同一个作用域的时候,才能 hook 成功
对于下面的自执行函数,在执行完之后我们实际上是无法 hook test 函数的。因为 test 是在自执行函数的作用域,而不是在全局作用域。而此时,自执行函数已经执行完了,test 函数已经被内存清空无法 hook。

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

争取的写法应该是下面这样,这样当程序被断下来以后才能 hook test 函数。

!(function(){
    var arg = 1;
    var test = function(){
        console.log(arg);
    }
debugger;
})()
JS 作用域问题 2:局部变量污染全局变量

在 Hook 的时候要注意一个 JS 代码的特性,就是 JS 在函数赋值的时候,会遵循一个原则:

当前作用域有变量则赋值该变量,当前作用域没有该变量则赋值在全局作用域定义该变量并赋值

比如:

var arg1 = null; 
function test1(){
  arg1 = 1;   // 注意这里没有 var
}; 
function test2(){
  console.log(arg1)
}; 
test1(); 
test2();

上述代码的执行结果 test2(); 打印出 1,而不是 null。只有当 test1 函数中得赋值语句是 var arg1 = 1; 的时候,才会 返回 null 而不是 1

JS 作用域问题 3:this 的指向问题

在不同的作用域中,同样的变量指向是不一样的。每个函数在定义被解析器解析时,都会创建两个特殊变量: this 和 arguments 。 每个函数都有属于自己的 this 对象, 这个 this 对象是在运行时基于函数的执行环境绑定的。

在 Hook 的时候,this 的指向遵循下面的原则:

全局作用域中,this = window
方法作用域中,this = 调用者
在浅滩函数中,this = 调用外部函数或内部函数的执行环境对象
在类方法里面,this = 类自己

总结来说就是:谁调用这个函数对象,this就指向谁(this指向的是调用执行函数的那个对象)
在 JS 中,如果需要改变 this 的指向,可以通过使用 call()apply() 改变函数执行环境的情况,以改变this 指向。
具体可以参考文章《 call/apply以及this指向的理解》,问占中有详细的解释。

2. Hook 的实现

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

两种方式的区别

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

从日常的实际应用层面来说,上面的问题并不需要过度关注。需要注意的是,第一种方式简单、但是太粗暴,容易影响原有代码的正常执行,也容易被检测到,而第二种方式会更优雅一些,具体需要结合具体需求选择合适的 Hook 方式。

  • 方法一:直接替换原有函数。
old_func = 被 hook 函数
被 hook 函数 = function(arguments){
  if 判断条件:
    my_task;
  return old_func.apply(arguments)
}
func.prototype.xxx = xxxx
  • 方法二: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 自定义内容
  }
})
  • 方法三:JS Proxy
    TODO
  • 方法四:浏览器插件油猴
    油猴,TODO

3. Hook 的应用

Hook http请求

http请求包括 ajax、src、href、表单等。

// 代码来源:https://www.cnblogs.com/amiezhang/p/9984690.html

function hookAJAX() {
    XMLHttpRequest.prototype.nativeOpen = XMLHttpRequest.prototype.open;
    var customizeOpen = function (method, url, async, user, password) {
      // do something
      this.nativeOpen(method, url, async, user, password);
    };

    XMLHttpRequest.prototype.open = customizeOpen;
  }

  /**
   *全局拦截Image的图片请求添加token
   *
   */
  function hookImg() {
    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)
      }
    })
  }
// 代码来源:https://www.cnblogs.com/amiezhang/p/9984690.html
修改返回的 response,干扰 JS 原有代码的运行。

在处理无限反 debugger 的时候,常用的一种方法就是 Fiddler + AutoResponse,然后删除 js 代码中的 debugger,或者直接修改成本地代码,都是在浏览器接收服务器返回的资源的时候,基于 Hook 思想完成的。
详细参见:《JS逆向:Fiddler + AutoResponder 篡改 js 破解企查查无限 debugger 问题》。

修改常用变量、函数,方便进行加密函数定位。

比如,如果推测加密过程中用到了 md5 加密,那么通过 Hook md5 函数,在其中打 debugger 断点的方式,就可以很快定位加密函数的位置。

导出加密函数的参数、结果值。

通过定义全局变量、window属性等方式,导出加密函数中的参数或者结果值。

cookie钩子:查找 cookie 生成入口

script 断点,在 js 刚运行时就把网页断住。
在 console 中输入下面的代码。

document.cookie_bak = document.cookie
Object.defineProperty(document, 'doockie',{
  get: function(){
    debugger;
    return document.cookie_bak;
  },
  set: function(val){
    debugger;
    return;
  }
})
cookie钩子:用于定位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;
    },
  });
})();
请求钩子

用于定位请求中关键参数生成位置。

(function () {
  'use strict';
  var cookieTemp = '';
  Object.defineProperty(document, 'cookie', {
    set: function (val) {
      if (val.indexOf('关键参数') != -1) {
        debugger;
      }
      console.log('Hook捕获到关键参数设置->', val);
      cookieTemp = val;
      return val;
    },
    get: function () {
      return cookieTemp;
    },
  });
})();
header钩子:用于定位header中关键参数生成位置
TODO
自己写插件

插件 js 文件,inject.js

// hook 代码
// 略

插件的配置文件:manifest.json

{
   "name": "Injection",
    "version": "2.0",
    "description": "RequestHeader钩子",
    "manifest_version": 2,
    "content_scripts": [
        {
            "matches": [
                "<all_urls>"
            ],
            "js": [
                "inject.js"
            ],
            "all_frames": true,
            "permissions": [
                "tabs"
            ],
            "run_at": "document_start"
        }
    ]
}
将无限 debugger 方法直接置空
debugger函数 = function(){};
Hook eval 绕过无限 debugger
eval_bak = eval
eval = function(val){
  debugger;
  return eval_bak(val);
}
eval.toString = function(){
  return "function eval() { [native code] }"
}
干掉定时器类触发的无限 debugger
for (var i = 1; i < 99999; i++)window.clearInterval(i);
利用 Hook 将 eval 函数替换成 console.log

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

eval_bak = eval;
eval = console.log;

4. 风控如何检测 Hook

4.1. toString 检测识别 Hook

toString 检测,指的是风控通过检测被 Hook 的函数 toString() 结果是否变化,来判断该函数是否被 Hook 的一种检测方法。当风控监测到 Hook 以后,可以返回假数据误导逆向工程师,也可以配合内存爆破进行反 debugger。
比如我们 Hook 了 eval 函数,这时风控就可以通过检测 eval.toString() 的返回值是否是 "function eval() { [native code] }" 来识别该函数是否被 Hook 了。

如何解决 toString 检测

如何解决 toString 检测呢?其实方法很简单,仍然需要使用 Hook 技术。我们只需要修改目标函数的 toString 方法。

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

5.2. 检测原型链

TODO

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,670评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,928评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,926评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,238评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,112评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,138评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,545评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,232评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,496评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,596评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,369评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,226评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,600评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,906评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,185评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,516评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,721评论 2 335

推荐阅读更多精彩内容