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

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

利用hookeval函数替换成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] }"
    }
posted @   紫青宝剑  阅读(1347)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示