0x00 前言
Hook是一种动态修改函数执行流程或返回结果的方法,在实际应用中非常广泛。Javascript作为一种动态语言,也可以进行Hook操作。随着Javascript语言的发展,Hook的方法也越来越多,本文介绍了常见的几种Hook方法。
0x01 最简单的Hook方法
Javascript中的函数可以直接被覆盖,因此,这也是最简单的一种Hook方法。
window.alert = function(s){
console.log('Alert: ' + s);
}
> alert('Hello!')
Alert: Hello!
这种方法简单粗暴,但是只能覆盖具体变量的成员函数。
0x02 Hook类方法
考虑以下这种情况:希望Hook所有元素的setAttribute
方法。
方案一
:遍历获取所有元素,并Hook每个元素的setAttribute
方法。
首先,页面中的元素数量非常多,而且不断有新元素动态创建出来,如果要监控新元素创建,还得Hook document.createElement
等函数,甚至还需要考虑通过其它方式动态创建出来的函数。因此,这种方案不是最佳方案。
方案二
:直接Hook类方法
Element.prototype.setAttribute = function(attr, value){
console.log('setAttribute ' + attr + ' ' + value);
}
> document.body.setAttribute('xx', 123)
setAttribute xx 123
这里利用了原型链来进行类方法的Hook。
0x03 利用Getter/Setter进行Hook
document.domain
是一个只读对象,正常是不允许乱修改的。
> document.domain = 'test.com'
Uncaught DOMException: Failed to set the 'domain' property on 'Document': 'test.com' is not a suffix of 'www.baidu.com'.
at <anonymous>:1:16
乱修改这个值的话,浏览器会直接报错。
但是下面这段代码却可以将document.domain
改成可修改的对象。
Object.defineProperty(document, 'domain', {
configurable: true,
enumerable: true,
get: function() {
return this._domain || location.host;
},
set: function(value) {
this._domain = value;
}
});
> document.domain = 'test.com'
"test.com"
> document.domain
"test.com"
如果将configurable
属性设为false
,document.domain
就可以避免被其他人使用Object.defineProperty
来修改了。
> Object.defineProperty(document, 'domain', {value: 'xxx'});
Uncaught TypeError: Cannot redefine property: domain
at Function.defineProperty (<anonymous>)
at <anonymous>:1:8
同样的方法还可以用来修改:navigator.userAgent
等属性。
Object.defineProperty(navigator, 'userAgent', {
configurable: true,
enumerable: true,
value: 'MyBrowser 1.0.0'
});
> navigator.userAgent
"MyBrowser 1.0.0"
不过这种方法只影响使用Javascript获取的数据,不会影响浏览器发送请求时带上的浏览器标识。
事实上,有多很多系统对象都是不允许使用Object.defineProperty
修改的,例如:window.location
等。
0x04 使用Proxy
Proxy
是Chrome 49开始支持的一项新特性,具有动态代理功能,可以用于运算符重载、对象模拟等场景。
window.screen = new Proxy(window.screen, {
get: function (target, key) {
console.log('get', key);
return target[key];
}});
> screen.availHeight
get availHeight
864
这里通过创建screen
对象的代理,然后赋值给window.screen
,可以实现对属性、函数访问的拦截。这种方法比较适合需要Hook某个对象中大部分属性、函数的场景。
这种方法同时也适用于对函数的Hook。
document.createElement= new Proxy(document.createElement, { // Our hook to keep the track
apply: function (target, thisArg, args){
console.log("document.createElement is called with args: " + args);
return target.apply(thisArg, args);
}
});
> document.createElement('div')
document.createElement is called with args: div
<div></div>
当然,有很多对象是不能被替换的,例如:window
、document
、navigator
、location
等。
0x05 总结
Javascript是一门非常灵活的语言,并且新的接口和规范不断出现,以后还会出现更多的可以用作Hook的方法。