本帖最后由 firetree 于 2023-12-2 15:04 编辑
本帖最后由 firetree 于 2023-12-2 15:02 编辑
众所周知,面对webpack等工具,将业务代码放在层层嵌套的作用域里,我们往往束手无策。这个时候,很多开发者就会Hook底层API,比如Object.defineProperty等等。但是,如果我们要处理的代码没有用到这类API,好像还没有很好的办法。
这个时候,有人提出了在webpack把被打包的模块push入一个数组的时候,趁机更改模块内容,通过JS的元编程功能,用被替换的字符串生成一个新的函数替代原本的函数。(这个帖子)
毫无疑问这种方法的局限性是不小的,一旦源码稍有修改,webpack重新编译,你要的那个函数在模块里的位置可能就变了,参数的数量也可能会变,通过替换字符串来修改代码也不优雅。这个帖子,也让我感觉现在我要介绍的这种方法似乎还没有被大规模地提出来。
那就是在Object.prototype上面定义getter和setter。只要这个网站从对象里读写了属性,即使获取不到这个对象本身,也会触发原型链上的getter和setter,因此你也能篡改网站脚本获得的值。这么做,可以说是把所有对象的同名属性都hook了,所以可以说是针对属性名的hook。
并且,很多时候webpack不会破坏属性的名字,比如f = e.speed
,这里面的speed并没有也变成单字母的名字,这可能是因为webpack考虑到代码的别处还有可能存在需要用到这个属性的名字的地方,比如let name = 'speed'; doSomethingWith(someObject[name])
。这就意味着你直接hook speed这个属性名就可以了。
比如说我现在用的:
function hookPropertyName(prop, getter, setter) {
let raw_prop = prop + '$raw'
let has_getter = typeof getter === 'function'
let has_setter = typeof setter === 'function'
Object.defineProperty(Object.prototype, prop, {
get() {
return has_getter ? (getter(this) ?? this[raw_prop]) : this[raw_prop]
},
set(val) {
this[raw_prop] = has_setter ? ((setter(this, val)) ?? val) : val
}
})
}
这样一来,setter和getter可以获取到当前的对象,setter还可以获取到要设置的val,并且它们还可以篡改操作的结果。
当然这种方法也不是没有缺点的。
如果一个对象用字面量创建的时候,就具有了某种属性,而不是通过赋值添加属性,那就不会触发原型链上的getter和setter,而是直接操作这个对象本身的这个属性,也就hook不上了,比如t = {speed: 1}
,那这个speed
属性就hook不上了。不过,如果t
将要被设为另外某个对象的属性(比如g.player = t
),那你也可以hook player
属性名,趁机修改t
。
据我观察,webpack生成的代码里面从t = {}
这样开始,然后再赋予一些别的属性的也不少,这样就可以hook,可以说使用场景还是很广泛的。虽然这种方法不能完全替代以上的方法,但是也能和Hook API的方法形成互补。
如果属性名会重名,你可能需要额外判断一下。比如:
let a = {}, b = {}
a.speed = 'normal'
b.speed = 1.0
//...
if (a.speed === 'fast') //...
//...
if (b.speed > 2) //...
这个时候可能就要用我上面代码里的let raw_prop = prop + '$raw'
这个机制,获取到obj.speed$raw
判断一下是a还是b。
此外,这么做也可能会导致一些问题。如果网站脚本检测了对象的属性是不是它自身的,那这么在原型链上定义的getter和setter就会出问题。不过,webpack并不会自动地这么做,所以大部分网站应该不会出问题。
我之所以没有用__defineGetter__
、__defineSetter__
,是因为它确实在我测试的时候出了问题。这么定义的getter和setter,会被for ... in ...
循环检测到,那也就是说任何一个普通的对象遇到这样的循环的时候,都会多迭代一个凭空多出来的属性。在我测试的时候遇到了一个这样循环然后在每个属性上面设置属性的(比如{key1: {id: 1}, key2: {id: 2}}
,这里面每个值都有一个id
属性),迭代到speed
的时候得到了999
(我测试的时候是把speed
改为999
),没法设置id
属性,就给我报错了。
目前,我还没有充分实战,但是通过在一个模仿MC的网页游戏里开挂我已经确认这是一定程度上可行的。
(玩家们请不要责怪我,我从未为非作歹,只在小房间里试验,我不是你们所遇到的那些作弊者,我只是一个研究JS的人。实际上反作弊系统更新以后,我所研发出来的几乎所有东西,飞行,加速,无限二段跳,都不能与服务器同步了。)