【web安全】js逆向之Hook篇
JavaScript 中的 Hook 是一种常见的编程技术,用于在代码执行某些特定操作时执行其他代码。具体来说,Hook 方法是一些预定义的函数,它们会在特定的事件发生时被调用。
在程序中将其理解为“劫持”可能会更好理解,我们可以通过 Hook 技术来劫持某个对象,把某个对象的程序拉出来替换成我们自己改写的代码片段,修改参数或替换返回值,从而控制它与其他对象的交互。
Hook方式
直接覆盖
在 JavaScript 逆向中,替换原函数的过程都可以被称为 Hook,如将window.alert函数替换:
window.alert = function(s){
console.log('Alert: ' + s);
}
这种直接替换的方式只能覆盖具体变量的成员函数,通常只在临时调试的时候用
使用Object.defineProperty()
语法:Object.defineProperty(obj, prop, descriptor)
,它的作用是在一个对象上定义一个新的属性,或者修改一个对象上已有的属性,并返回该对象,接收三个参数含义如下:
- obj:需要定义属性的对象
- prop:字符串或
Symbol
对象,指定要修改或定义的属性名 - descriptor:要修改或定义的属性描述符,可以取以下值:
属性名 | 默认值 | 含义 |
---|---|---|
get | undefined | 存取描述符,目标属性获取值的方法 |
set | undefined | 存取描述符,目标属性设置值的方法 |
value | undefined | 数据描述符,设置属性的值 |
writable | false | 数据描述符,目标属性的值是否可以被重写 |
enumerable | false | 目标属性是否可以被枚举 |
configurable | false | 目标属性是否可以被删除或是否可以再次修改特性 |
使用 Object.defineProperty()
方法定义对象属性(可以在细粒度下控制对象属性):
const object1 = {};
Object.defineProperty(object1, 'property1', {
value: 42,
writable: false, // 不可写
configurable: true, // 可以再次修改
enumerable: true, // 不可以被枚举
});
object1.property1 = 77;
// Throws an error in strict mode
console.log(object1.property1);
// Expected output: 42
Hook中最常用的是存取描述符,即get
、set
属性
get
:属性的 getter 函数,默认为undefined,当访问该属性时,会调用此函数,调用时不传入参数,但会传入this对象(由于继承关系,这个对象不一定是定义该属性的对象),返回值会作为属性的值。
set
:属性的setter函数,默认undefined,当属性的值被修改时,会调用此函数,传入一个参数,并将其值赋给属性值,同样会传入this对象。
一个例子演示:
function Archiver() {
let temperature = null;
const archive = [];
// hook temperature属性
Object.defineProperty(this, "temperature", {
get() {
// 添加了一行输出代码,其它不变
console.log("get!");
return temperature;
},
set(value) {
temperature = value;
archive.push({ val: temperature });
},
});
this.getArchive = () => archive;
}
const arc = new Archiver();
arc.temperature; // 'get!'
arc.temperature = 11;
arc.temperature = 13;
arc.getArchive(); // [{ val: 11 }, { val: 13 }]
通过以上的思路,可以想到对某些关键的元素进行hook,在属性被访问/修改/生成时执行自定义的一些操作(如debugger),以快速定位到关键代码。
使用Proxy
Proxy具有拦截函数执行功能,可用于运算符重载、对象模拟等功能,这种方法比较适合需要Hook某个对象中大部分属性、函数的场景。
拦截Array.prototype.push
方法的例子
const handler = {
get: function(target, prop) {
if (prop === 'push') {
return function(...args) {
console.log(`调用了Array.prototype.push方法,参数为:${args}`);
return target[prop].apply(target, args);
};
} else {
return target[prop];
}
}
};
const arr = [1, 2, 3];
const arrProxy = new Proxy(arr, handler);
arrProxy.push(4); \\"调用了Array.prototype.push方法,参数为:4"
Hook实践
以某花顺财经登录接口请求头的hexin-v
参数为例,演示如何Hook
登录接口发现每次请求头包含一个hexin-v
参数,值随机,全局搜索无结果,尝试定位到该值生成的代码
Fidder插件注入
Fiddler是一种流行的免费的跨平台Web调试代理工具(抓包软件),配合插件注入js代码(注意:Hook代码在网站代码加载之前执行)
Hook一下XMLHttpRequest
的setRequestHeader
方法,设置当请求头为hyxin-v
值时断下
(function () {
var org = window.XMLHttpRequest.prototype.setRequestHeader;
window.XMLHttpRequest.prototype.setRequestHeader = function (key, value) {
if (key == 'hexin-v') {
debugger;
}
return org.apply(this, arguments);
};
})();
开启Hook
刷新网站,发现直接被断下
跟堆栈,可以看到值是由W()函数生成
接着再进入W函数接着分析即可
TamperMonkey 注入
TamperMonkey 俗称油猴插件,网上搜索会有很多教程,此处略
浏览器插件注入
浏览器插件/扩展实质是在加载网页时执行插件的代码,所以也是可以用来注入Hook代码的,如何编写一个插件也是有相当多教程的,此处略。
部分hook模板
js语法不熟悉时,可以快速套用以下模板,当然必须根据具体场景,进行修改。
Hook Header
header 钩子用于定位 header 中关键参数生成位置,以下代码演示了当 header 中包含 Authorization
时,则插入断点
Chrome扩展为例:
manifest.json
{
"name": "Injection",
"version": "1.0",
"description": "RequestHeader钩子",
"manifest_version": 1,
"content_scripts": [
{
"matches": [
"<all_urls>"
],
"js": [
"inject.js"
],
"all_frames": true,
"permissions": [
"tabs"
],
"run_at": "document_start"
}
]
}
inject.js
(function () {
var org = window.XMLHttpRequest.prototype.setRequestHeader;
window.XMLHttpRequest.prototype.setRequestHeader = function (key, value) {
if (key == 'Authorization') {
debugger;
}
return org.apply(this, arguments);
};
})();
Hook cookie
cookie 钩子用于定位 cookie 中关键参数生成位置,以下代码演示了当 cookie 中匹配到了 __dfp
关键字, 则插入断点:
// 第一种方式
(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;
},
});
})();
// 第二种方式
(function () {
'use strict';
var org = document.cookie.__lookupSetter__('cookie');
document.__defineSetter__('cookie', function (cookie) {
if (cookie.indexOf('__dfp') != -1) {
debugger;
}
org = cookie;
});
document.__defineGetter__('cookie', function () {
return org;
});
})();
Hook url
请求钩子用于定位请求中关键参数生成位置,以下代码演示了当请求的 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);
Hook JSON.stringify
JSON.stringify()
方法用于将 JavaScript 值转换为 JSON 字符串,在某些站点的加密过程中可能会遇到,以下代码演示了遇到 JSON.stringify()
时,则插入断点:
(function() {
var stringify = JSON.stringify;
JSON.stringify = function(params) {
console.log("Hook JSON.stringify ——> ", params);
debugger;
return stringify(params);
}
})();
Hook JSON.parse
JSON.parse()
方法用于将一个 JSON 字符串转换为对象,在某些站点的加密过程中可能会遇到,以下代码演示了遇到 JSON.parse()
时,则插入断点:
(function() {
var parse = JSON.parse;
JSON.parse = function(params) {
console.log("Hook JSON.parse ——> ", params);
debugger;
return parse(params);
}
})();
Hook eval
eval经常被用来动态执行 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
});
})();
Hook Function
以下代码执行后,所有的函数操作都会在控制台打印输出将要执行的 JS 源码:
(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
});
})();
参考:
https://mp.weixin.qq.com/s/IYFyjVrVkHtUdCzn9arkJQ
https://cloud.tencent.com/developer/article/1639578