一、Hook 技术原理
Hook是一种钩子技术,在系统没有调用函数之前,钩子程序就先得到控制权,这时候钩子函数既可以加工处理该函数的执行行为,也可以强制结束消息的传递,简单来说就是修改原有的js代码就是hook。
JS 是一种弱类型语言,同一个变量可以多次定义、根据需要进行不同的赋值,而这种情况如果在其他强类型语言中则可能会报错,导致代码无法执行。js 的这种特性,为我们 Hook 代码提供了便利。
二、js作用域问题
1.自执行函数的hook问题
js变量是有作用域的,只有当被hook函数和debugger断点在同一个作用域的时候,才能hook成功。
!(function(){
var arg = 1;
var test = function(){
console.log(arg);
}
debugger;
})()
2.局部变量污染全局变量
在Hook的时候要注意js代码的特性,js在函数赋值的时候会遵循一个原则:
当前作用域有变量则赋值该变量,当前作用域没有该变量则赋值在全局作用域定义该变量并赋值。
var arg1 = null;
function test1(){
arg1 = 1; // 注意这里没有 var
};
function test2(){
console.log(arg1)
};
test1();
test2();
3.this的指向问题
- 不同的作用域中,相同变量的指向不一样。
- 每个函数在定义被解析器解析时,都会创建两个特殊变量:this和arguments。
- 每个函数都有属于自己的this对象,这个this对象时在运行时基于函数的执行环境绑定的。
全局作用域中,this = window;
方法作用域中,this = 调用者;
在浅滩函数中,this = 调用外部函数或内部函数的执行环境对象;
在类方法里面,this = 类自己;
谁调用这个函数对象,this就指向谁(this指向的是调用执行函数的那个对象)。
在 JS 中,如果需要改变 this 的指向,可以通过使用 call() 和 apply() 改变函数执行环境的情况,以改变this 指向。
三、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 自定义内容
}
})
四、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] }"
}
3.干掉定时器类触发的无限 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.无限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
1.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] }" }