Js-Hook
Js-Hook
参考文章:https://blog.csdn.net/Ks_meng/article/details/127336084
参考文章:https://blog.csdn.net/qq_40558166/article/details/123525365
介绍:Hook 是一种常用的钩子技术,在系统没有调用函数之前,钩子程序首先得到控制权,这时钩子函数既可以加工处理该函数的执行行为,也可以强制结束消息的传递;简单来说修改原来 js 代码的功能就是 hook;
js 是一种弱类型语言,同一个变量可以被多次定义、根据需要进行不同的赋值,而这种情况在其他强类型语言(例如: java)则可能会报错,导致代码无法执行。js 的这种特性为 Hook 提供了便利;
1.js的作用域
js 的变量是有作用域的,只有当被 hook 的函数和debuger
断点在同一个作用域的时候才能hook
成功;
!(function(){
var arg = 1;
var test = function(){
console.log(arg);
}
debugger;
})()
// 补充 !(function(){})() 表示立即执行当前的函数闭包;
1.1 js 中的闭包、
闭包是javascript
语言的一个难点,也是他的特色。闭包的作用,通过一系列方法将函数内部的变量(局部变量)转换为全局变量;
function myFunction() {
var a = 4;
return a * a;
}
这个实例在终端是没有办法直接访问到变量a;
var a = 4;
function myFunction() {
return a * a;
}
>>> 16
// 在函数的内部可以访问到全局变量信息
在终端中可以直接访问到变量a;
局部变量只能用于定义它函数内部。对于其他的函数或脚本代码是不可用的。
全局和局部变量即便名称相同,它们也是两个不同的变量。修改其中一个,不会影响另一个的值。
变量的生命周期:全局变量的作用域是全局性的,即在整个 JavaScript 程序中,全局变量处处都在。而在函数内部声明的变量,只在函数内部起作用。这些变量是局部变量,作用域是局部性的;函数的参数也是局部性的,只在函数内部起作用。
javascript 内嵌函数
在 javascript 中所有函数都能访问到他们上一层的作用域。javascript
支持嵌套函数。嵌套函数可以访问上一层的函数变量。
// 计数器案例,
function add(){
var counter = 0;
function plus(){
counter += 1;
}
plus()
return counter;
}
// 内嵌函数 plus 可以访问到上一层的变量 counter;
上述的计数器函数每次执行的结果都是1,因此我们需要用到闭包;需要我们在外部访问到plus
函数,并确保counter
执行一次;
var add = (function (){
var counter = 0;
return function (){
// js 的函数可以访问到上一层的变量信息;
return counter += 1;
}
})();
变量 add 指定了函数的自我调用返回值;外层的函数执行一次,设置 coiunter 为 0,返回函数表达式;
add变量可以作为一个函数使用。非常棒的部分是它可以访问函数上一层作用域的计数器。这个叫作 JavaScript 闭包。它使得函数拥有私有变量变成可能。
计数器受匿名函数的作用域保护,只能通过 add 方法修改。
2.Hook的实现
两种实现方式:
- 一种是,直接替换函数
- 另一种是
Object.defineProperty
通过为对象的属性赋值的方式进行 Hook;
两种方式的区别
-
函数 hook 一般不会失败,除非 proto 模拟的不好被检测到;
-
属性 hook ,当网站的所有逻辑采用
Object.defineProperty
绑定时,属性hook
就会失效.同时无法进行第二次的 hook; -
第一种方式简单、但是太粗暴,容易影响原来代码的执行,也容易被检测到;
-
第二种方式会更优雅一些,具体需要结合具体需求选择合适的 Hook 方式;
2.1 替换函数
补充:每个函数在定会以被解析器解析时,都会创建两个特殊变量:this 和 arguments;
apply()
能够编写用于不同对象的方法;
var person = {
fullName: function() {
return this.firstName + " " + this.lastName;
}
}
var person1 = {
firstName: "Bill",
lastName: "Gates",
}
person.fullName.apply(person1); // 将返回 "Bill Gates"
通过apply
调用对象的函数,并且传入参数;
// 语法, 伪代码;
old_func = 被 hook 函数
被 hook 函数 = function(arguments){
if(判断条件){
my_task;
}
// 继续执行原来的函数信息;
return old_func.apply(arguments)
}
示例:
// 无限 debuger 的hook示例;
// 原来函数的备份
Function.prototype.constructor_old = Function.prototype.constructor;
// 替换掉原来的函数
Function.prototype.constructor = function(){
// 判断条件,查看参数是否是 debuger
if(argument==='debugger'){
// 满足的时候跳过原来的停止逻辑;
}else{
// 继续调用原来的旧的函数;
return Function.prototype.constructor_old.apply(this,arguments)
}
}
2.2 属性赋值
伪代码,
// 通过 Object.defineProperty
old_attr = obj.attr
Object.defineProperty(obj,"attr",{
get: function() {
debuger; // 设置程序断点
if (判断条件){
mytask;
}
return old_attr;
},
set: function(val){
debuger;
if(判断条件){
my_task;
}
return "自定义的内容"
}
})
// 这种 hook 的方式有一点类似于 面向对象的反射;
示例:
// hook-cookie
// 备份原来的 cookie 属性
document.cookie_bak = document.cookie
// 设置属性的信息
Object.defineProperty(document, 'coockie',{
// 获取 cookie 的信息
get: function(){
debugger;// 断点,可以定位到 cookie 发生改变的 js 代码的位置
return document.cookie_bak;// 继续返回原来的代码继续执行
},
set: function(val){
debugger; // 设置断点, 定位到 cookie 改变的位置;
return;
}
})
3.爬虫中常见的 hook代码
待补充:hook 的时机;
3.1 无限 debuger
// 注释说明版
// 原来函数的备份
Function.prototype.constructor_old = Function.prototype.constructor;
// 替换掉原来的函数
Function.prototype.constructor = function(){
// 判断条件,查看参数是否是 debuger
if(argument==='debugger'){
// 满足的时候跳过原来的停止逻辑;
}else{
// 继续调用原来的旧的函数;
return Function.prototype.constructor_old.apply(this,arguments)
}
}
// 暴力破击 debuger
window.open = function(){}
window.setInteval = function(){}
非注释版
// 无注释版,可以直接使用;
Function.prototype.constructor_old = Function.prototype.constructor;
Function.prototype.constructor = function(){
if(argument==='debugger'){
}else{
return Function.prototype.constructor_old.apply(this,arguments)
}
}
3.2 hook-cookie
cookie 的 hook 有着不同的用法,所有的与单一的;
// hook-cookie, 定位到cookie 中 JS 代码的位置信息;
// 备份原来的 cookie 属性
document.cookie_bak = document.cookie
// 设置属性的信息
Object.defineProperty(document, 'coockie',{
// 获取 cookie 的信息
get: function(){
debugger;// 断点,可以定位到 cookie 发生改变的 js 代码的位置
return document.cookie_bak;// 继续返回原来的代码继续执行
},
set: function(val){
debugger; // 设置断点, 定位到 cookie 改变的位置;
return;
}
})
document.cookie_bak = document.cookie
Object.defineProperty(document, 'coockie',{
get: function(){
debugger;
return document.cookie_bak;
},
set: function(val){
debugger;
return;
}
})
目标cookie生成定位;
// 使用闭包
(function () {
'use strict'; // 使用严格模式,禁用一些 js 的语法
var cookieTemp = '';// 定义空字符串
Object.defineProperty(document, 'cookie', {
set: function (val) {
// 设置 cookie的信息,检测到 cookie的值的信息
if (val.indexOf('目标cookie字符串') != -1) {
debugger; // 设置断点
}
console.log('Hook捕获到cookie设置->', val);
cookieTemp = val; //获取到cookie的信息;
return val;
},
get: function () {
return cookieTemp;// 返回 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;
},
});
})();
闭包形式的hook
// 声名函数
var code = function(){
// 获取属性中的 cookie 信息
var org = document.cookie.__lookupSetter__('cookie');
document.__defineSetter__("cookie",function(cookie){
if(cookie.indexOf('abcdefghijk')>-1){
// 检测到 cookie 的的信息,设置断点,定位到JS代码的位置;
debugger;
}
org = cookie;
});
document.__defineGetter__("cookie",function(){return org;});
}
// 创建 script 标签
var script = document.createElement('script');
// 将函数添加到闭包中;
script.textContent = '(' + code + ')()';
(document.head||document.documentElement).appendChild(script);
script.parentNode.removeChild(script);
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);
3.3 Hook-header
header 钩子用于定位 header中关键参数生成位置,
// hook-header
// 定义 code 函数
var code = function(){
// XMLHttpRequest 仅限制于 ajax 请求头的设置;
var org = window.XMLHttpRequest.prototype.setRequestHeader;
window.XMLHttpRequest.prototype.setRequestHeader = function(key,value){
// 请求头中键名的设置检测
if(key=='hook的键名称'){
debugger;// 检测到之后设置断点
}
// 继续执行原来的函数
return org.apply(this,arguments);
}
}
// 创建 script 标签;
var script = document.createElement('script');
// 设置标签中的内容,将 hook 代码写入到闭包中
script.textContent = '(' + code + ')()';
// 添加script标签到 <head></head>标签中
(document.head||document.documentElement).appendChild(script);
// 移除
script.parentNode.removeChild(script);
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);
3.4 hook-url
url 的hook ,请求的hook信息;
(function () {
// 限制 ajax请求
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);
};
})();
(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);
};
})();
// 也可以将这种方式修改成为上述的添加标签的方式
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);
3.5 Hook-json-stringify
Json.stringify()
方法用于将javascript
值替换为json
字符串,在某些站点的加密过程中可能会遇到,示例
(function() {
var stringify = JSON.stringify;
JSON.stringify = function(params) {
console.log("Hook JSON.stringify ——> ", params);
debugger;
return stringify(params);
}
})();
// 声名闭包
(function() {
// 声名对象;
var stringify = JSON.stringify;
JSON.stringify = function(params) {
console.log("Hook JSON.stringify ——> ", params);
debugger;// 设置断点
return stringify(params);
}
})();
3.6 hook-json-parse
JSON.parse()
方法用于将一个 JSON 字符串转换为对象,在某些站点的加密过程中可能会遇到,
(function() {
var parse = JSON.parse;
JSON.parse = function(params) {
console.log("Hook JSON.parse ——> ", params);
debugger;
return parse(params);
}
})();
(function() {
// 声名对象
var parse = JSON.parse;
JSON.parse = function(params) {
// 替换原来的函数;
console.log("Hook JSON.parse ——> ", params);
debugger;// 设置断点;
return parse(params);
}
})();
3.7 Hook-eval
利用hook
将eval
函数替换成console.log
,当js
代码中有eval
函数执行了某种操作,逆向过程中需要分析操作的具体内容时,可以通过 Hook eval 替换成 console.log 的方式,将 eval 执行的代码打印出来。
// 备份eval
eval_bak = eval;
// 替换函数
eval = console.log;
JavaScript eval() 函数的作用是计算 JavaScript 字符串,并把它作为脚本代码来执行。如果参数是一个表达式,eval() 函数将执行表达式。如果参数是 Javascript 语句,eval() 将执行 Javascript 语句,经常被用来动态执行 JS。以下代码执行后,之后所有的 eval() 操作都会在控制台打印输出将要执行的 JS 源码:
(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
});
})();
(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
});
})();
3.8 Hook-Function
以下代码执行后,所有的函数操作都会在控制台打印输出将要执行的 JS 源码:
(function() {
window.__cr_fun = window.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);
}
myfun.toString = function() {
return window.__cr_fun + ""
}
Object.defineProperty(window, 'Function', {
value: myfun
});
})();
(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.风控检测Hook
toString
检测识别Hook
-
toString
检测,指的是风控通过检测被 Hook的函数的toSting
结果是否变化,来判断该函数是否被hook
-
当风控检测到 hook 以后,可以返回假的数据误导逆向工程师,也可以配合内存爆破进行
反debuger
-
比如
hook eval
函数,这是风控可以通过检测eval.toString()
返回值是否是'function eval() { [native code] }'
来识别该函数是否被hook
了; -
解决
toString
检测,只需要修改目标中的toString
方法;// 直接将 toString方法hook成为指定的函数信息; eval.toString = function(){ return "function eval() { [native code] }" }