JS逆向学习笔记(二)-Hook技术

一.写在前面

        Hook的意思是钩子,所以又叫做钩子技术。在JS逆向中,Hook能够帮助我们找到某些关键方法的位置。那么怎么理解呢?Hook 实际上是 Windows 中提供的一种用以替换 DOS 下“中断”的系统机制,Hook 的概念在 Windows 桌面软件开发很常见,特别是各种事件触发的机制,在对特定的系统事件进行 Hook 后,一旦发生已 Hook 事件,对该事件进行 Hook 的程序就会收到系统的通知,这时程序就能在第一时间对该事件做出响应。在程序中将其理解为“劫持”可能会更好理解,我们可以通过 Hook 技术来劫持某个对象,把某个对象的程序拉出来替换成我们自己改写的代码片段,修改参数或替换返回值,从而控制它与其他对象的交互。下面的内容是我转载的知乎上的东西,不是我自己写,但是也有部分是我自己写的。JS 逆向之 Hook,吃着火锅唱着歌,突然就被麻匪劫了! - 知乎 (zhihu.com)

二.Hook的理解

        在 JavaScript 逆向中,替换原函数的过程都可以被称为 Hook,以下先用一段简单的代码理解 Hook 的过程:

function a() {
  console.log("I'm a.");
}

a = function b() {
  console.log("I'm b.");
};

a()  // I'm b.

直接覆盖原函数是最简单的做法,以上代码将 a 函数进行了重写,再次调用 a 函数将会输出 I'm b.,如果还想执行原来 a 函数的内容,可以使用中间变量进行储存:

function a() {
  console.log("I'm a.");
}

var c = a;

a = function b() {
  console.log("I'm b.");
};

a()  // I'm b.
c()  // I'm a.

此时,调用 a 函数会输出 I'm b.,调用 c 函数会输出 I'm a.

这种原函数直接覆盖的方法通常只用来进行临时调试,实用性不大,但是它能够帮助我们理解 Hook 的过程,在实际 JS 逆向过程中,我们会用到更加高级一点的方法,比如 Object.defineProperty()原文里面对这个方法解释的不太清楚,特别是我这种没学过JS的,所以我找了另外的资料来了解这个函数

Object.defineProperty() 是 JavaScript 中的一个方法,用于在对象上定义一个新属性或修改现有属性的特性(attribute)。这个方法提供了更精细的控制,允许你定义属性的可枚举性、可写性、可配置性等。

语法如下:

Object.defineProperty(obj, prop, descriptor)
  • obj:要定义属性的对象。
  • prop:要定义或修改的属性的名称。
  • descriptor:一个对象,用于描述或修改指定属性的特性。

descriptor 对象的属性包括:

  • value:属性的值,默认为 undefined
  • writable:属性值是否可写,默认为 false
  • enumerable:属性是否可枚举,默认为 false
  • configurable:属性是否可配置,默认为 false

使用 Object.defineProperty(),你可以更精确地控制属性的行为。例如,你可以将属性设置为不可枚举,这样在使用 for...in 循环时就不会遍历到这个属性;或者将属性设置为不可写,防止其值被修改。

示例:

let obj = {};

Object.defineProperty(obj, 'myProperty', {
    value: 42,
    writable: false, // 将属性设置为不可写
    enumerable: true, // 将属性设置为可枚举
    configurable: false // 将属性设置为不可配置
});

console.log(obj.myProperty); // 输出 42

// 以下操作会导致错误,因为属性是不可写的
obj.myProperty = 100;

// 以下操作会导致错误,因为属性是不可配置的
delete obj.myProperty;

这样的属性定义使得我们可以更加精确地控制对象的属性,确保其在运行时的行为符合我们的需求。

在 Hook 中,使用最多的是存取描述符,即 get 和 set。

get:属性的 getter 函数,如果没有 getter,则为 undefined,当访问该属性时,会调用此函数,执行时不传入任何参数,但是会传入 this 对象(由于继承关系,这里的 this 并不一定是定义该属性的对象),该函数的返回值会被用作属性的值。

set:属性的 setter 函数,如果没有 setter,则为 undefined,当属性值被修改时,会调用此函数,该方法接受一个参数,也就是被赋予的新值,会传入赋值时的 this 对象。

用一个例子来演示:

var people = {
  name: 'Bob',
};
var count = 18;

// 定义一个 age 获取值时返回定义好的变量 count
Object.defineProperty(people, 'age', {
  get: function () {
    console.log('获取值!');
    return count;
  },
  set: function (val) {
    console.log('设置值!');
    count = val + 1;
  },
});

console.log(people.age);
people.age = 20;
console.log(people.age);

输出:

获取值!
18
设置值!
获取值!
21

通过这样的方法,我们就可以在设置某个值的时候,添加一些代码,比如 debugger;,让其断下,然后利用调用栈进行调试,找到参数加密、或者参数生成的地方,需要注意的是,网站加载时首先要运行我们的 Hook 代码,再运行网站自己的代码,才能够成功断下,这个过程我们可以称之为 Hook 代码的注入。

三.Hook注入的方法

        TamperMonkey 俗称油猴插件,是一款免费的浏览器扩展和最为流行的用户脚本管理器,支持很多主流的浏览器, 包括 Chrome、Microsoft Edge、Safari、Opera、Firefox、UC 浏览器、360 浏览器、QQ 浏览器等等,基本上实现了脚本的一次编写,所有平台都能运行,可以说是基于浏览器的应用算是真正的跨平台了。用户可以在 GreasyFork、OpenUserJS 等平台直接获取别人发布的脚本,功能众多且强大,比如视频解析、去广告等。

        我们以某奇艺的 cookie 为例来演示如何编写 TamperMonkey 脚本,首先去应用商店安装 TamperMonkey,安装过程不再赘述,然后点击图标,添加新脚本,或者点击管理面板,再点击加号新建脚本,写入以下代码:

// ==UserScript==
// @name         Cookie Hook
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  Cookie Hook 脚本示例
// @author       K哥爬虫
// @match        *
// @icon         https://www.kuaidaili.com/img/favicon.ico
// @grant        none
// @run-at       document-start
// ==/UserScript==

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

下面我将对这段代码进行一个简单的解释:

// ==UserScript==
// @name         Cookie Hook
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  Cookie Hook 脚本示例
// @author       K哥爬虫
// @match        *
// @icon         https://www.kuaidaili.com/img/favicon.ico
// @grant        none
// @run-at       document-start
// ==/UserScript==

这是Tampermonkey脚本的元数据部分,包括脚本的名称、命名空间、版本、描述、作者、匹配的URL、图标等信息。

(function () {
  'use strict';
  var cookieTemp = '';

这里定义了一个立即执行的匿名函数,创建了一个局部变量cookieTemp,用于存储修改后的cookie。

 Object.defineProperty(document, 'cookie', {
    set: function (val) {
      if (val.indexOf('__dfp') != -1) {
        debugger;
      }
      console.log('Hook捕获到cookie设置->', val);
      cookieTemp = val;
      return val;
    },
    get: function () {
      return cookieTemp;
    },
  });

        这里使用Object.defineProperty重定义了document.cookie的行为。通过设置set方法,在每次设置cookie时触发,检查是否包含__dfp,如果是则会断住。然后,将原始的cookie值存储在cookieTemp中,并在控制台输出捕获到的cookie信息,再将val值返回。get方法用于获取cookie,返回之前存储的cookieTemp的值。

        在浏览器环境中,document是一个JavaScript对象,代表当前HTML文档。它是浏览器提供的一个接口,允许JavaScript代码与当前加载的网页进行交互和操作。document对象包含了当前文档的所有元素、属性和方法。通过document对象,你可以获取和修改页面上的内容、结构和样式,也可以处理用户的交互事件等。例如,通过document.getElementById你可以根据元素的ID获取相应的DOM元素,通过document.createElement可以创建新的元素。

        在上述代码中,Object.defineProperty(document, 'cookie', {...})这一行使用了document对象来重定义文档的cookie属性,实现了对document.cookie的拦截和修改。

        开启 TamperMonkey 插件,再次来到某奇艺首页,可以看到也成功被断下,同样的也可以跟进调用栈来进一步分析 __dfp 值的来源。

四.常见的Hook代码 

除了使用上述的 Object.defineProperty() 方法,还可以直接捕获相关接口,然后重写这个接口,以下列出了常见的 Hook 代码。注意:以下只是关键的 Hook 代码,具体注入的方式不同,要进行相关的修改。

1.Hook Cookie

Cookie Hook 用于定位 Cookie 中关键参数生成位置,以下代码演示了当 Cookie 中匹配到了 __dfp 关键字, 则插入断点:

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

2.Hook Header

Header Hook 用于定位 Header 中关键参数生成位置,以下代码演示了当 Header 中包含 Authorization 关键字时,则插入断点:

(function () {
    var org = window.XMLHttpRequest.prototype.setRequestHeader;
    window.XMLHttpRequest.prototype.setRequestHeader = function (key, value) {
        if (key == 'Authorization') {
            debugger;
        }
        return org.apply(this, arguments);
    };
})();

3.Hook URL

URL Hook 用于定位请求 URL 中关键参数生成位置,以下代码演示了当请求的 URL 里包含 login 关键字时,则插入断点:

(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);
    };
})();

4.Hook JSON.stringify

JSON.stringify() 方法用于将 JavaScript 值转换为 JSON 字符串,在某些站点的加密过程中可能会遇到,以下代码演示了遇到 JSON.stringify() 时,则插入断点:

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

5.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);
    }
})();

五.总结

        观察上面的hook代码,我么发现它的结构都很简单,就是保存函数,改写函数,调用原函数。按照这个思路,我们也可以写自己的hook代码。

        当我们在JS逆向找不到思路时,不妨尝试使用hook,这样,只要JS代码调用了我们所hook的代码就可以被断住,进而再调试跟栈分析。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值