1. 概念

通过 hook 技术,可以改变函数的行为、捕获函数的调用或修改对象的属性。一般使用Object.defineProperty()来进行hook。

1
Object.defineProperty(obj, prop, descriptor)
  • obj:对象;
  • prop:对象的属性名;
  • descriptor:属性描述符;

Object.defineProperty() 允许精确地添加或修改对象上的属性。

对象中存在的属性描述符有两种主要类型:数据描述符和访问器描述符。数据描述符是一个具有可写或不可写值的属性。访问器描述符是由 getter/setter 函数对描述的属性。描述符只能是这两种类型之一,不能同时为两者。

数据描述符具有以下可选键值:

  • value
  • writable

访问器描述符具有以下可选键值:

  • get
  • set

我们一般hook使用的是get和set方法,下边简单演示一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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);

image-20230912171138146

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

2. 实现方法

1. fiddler 插件

编程猫https://download.csdn.net/download/weixin_39190382/88123061

image-20230912172159061

2. TamperMonkey

img

3. 常见hook代码

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(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

hook header 中的Authorization,下断

1
2
3
4
5
6
7
8
9
(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 关键字时,则插入断点:

1
2
3
4
5
6
7
8
9
(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() 时,则插入断点:

1
2
3
4
5
6
7
8
(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() 时,则插入断点:

1
2
3
4
5
6
7
8
(function() {
var parse = JSON.parse;
JSON.parse = function(params) {
console.log("Hook JSON.parse ——> ", params);
debugger;
return parse(params);
}
})();

6. hook eval

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(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
});
})();

7. hook Function

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
(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. 请求

1. 请求分类

在 JavaScript 中,可以使用多种方式发送 HTTP 请求,如下所示:

  1. XMLHttpRequest(XHR)对象:XHR 是原生 JavaScript 提供的一种发送 HTTP 请求的方式,它允许您异步获取来自服务器的数据,并更新页面内容。通过创建 XHR 对象并调用其方法(如 open()send()),您可以轻松地发送 GET、POST、PUT、DELETE 等类型的 HTTP 请求。

  2. Fetch API:Fetch API 是一种新的 Web API,它提供了一种更简洁且易于使用的方式来发送 HTTP 请求和处理响应。与 XHR 不同的是,Fetch API 返回一个 Promise 对象,从而使异步请求更加容易管理和处理。

  3. jQuery AJAX:jQuery AJAX 是一种使用 jQuery 库发送 HTTP 请求的方式。它通过向 $ajax() 函数传递参数(如 URL、数据、请求类型等)来发送请求,并使用回调函数来处理响应。

  4. Axios: Axios 是一个第三方库,它提供了一种基于 Promise 的方式来发送 HTTP 请求。Axios 提供了易于使用的 API,使得发送 GET、POST、PUT 等类型的请求变得非常简单。

  5. 使用浏览器内置的 fetch() 方法:与 Fetch API 类似,所有主流浏览器都提供了一个内置的 fetch() 方法,它也返回一个 Promise 对象,允许您轻松地发送 HTTP 请求和处理响应。

2. hook 请求

说明: 参照3.3进行修改

  1. XHR(XMLHttpRequest)钩子:
1
2
3
4
5
6
7
8
9
const originalOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function(method, url) {
// 在发送请求前的逻辑
console.log('拦截到请求:', method, url);
// 修改请求头
this.setRequestHeader('Authorization', 'Bearer TOKEN');

originalOpen.apply(this, arguments);
}
  1. Fetch API 钩子:
1
2
3
4
5
6
7
8
9
const originalFetch = window.fetch;
window.fetch = function(url, options) {
// 在发送请求前的逻辑
console.log('拦截到请求:', url, options);
// 修改请求参数
options.headers['Authorization'] = 'Bearer TOKEN';

return originalFetch(url, options);
}
  1. jQuery AJAX 钩子:
1
2
3
4
5
6
7
8
$.ajaxSetup({
beforeSend: function(xhr, settings) {
// 在发送请求前的逻辑
console.log('拦截到请求:', settings.url, settings.type);
// 修改请求头
xhr.setRequestHeader('Authorization', 'Bearer TOKEN');
}
});
  1. Axios 钩子:
1
2
3
4
5
6
7
8
9
10
axios.interceptors.request.use(function(config) {
// 在发送请求前的逻辑
console.log('拦截到请求:', config.url, config.method);
// 修改请求头
config.headers['Authorization'] = 'Bearer TOKEN';

return config;
}, function(error) {
return Promise.reject(error);
});
  1. 浏览器内置的 fetch() 方法:
1
2
3
4
5
6
7
8
9
10
11
12
const originalFetch = window.fetch;
window.fetch = function(url, options) {
// 在发送请求前的逻辑
console.log('拦截到请求:', url, options);
// 修改请求参数
if (!options.headers) {
options.headers = {};
}
options.headers['Authorization'] = 'Bearer TOKEN';

return originalFetch(url, options);
}