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] }" }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!