[油猴脚本开发指南]script标签初始化对象劫持
# 开篇在我们写脚本的时候,经常碰到一种情况,就是网页调用了外部的库,而我们想要去修改内容或者使用一些功能,这时候通常会束手无策,所以我在这里简单的提供一些我自己的想法,可能存在一些问题,可以评论下写出。
通常分为两种情况,一种情况是网页的功能数据暴露在了windows的内部,这时候我们可以考虑直接对其进行劫持。另外一种是在封闭作用域内,这种情况我们可以考虑对其内部的一些函数进行劫持。
这里我们以https://www.acwing.com/problem/content/1/为例,我想修改这个编辑器的内容

通过这里的domclass类可以知道,这个是一个ace编辑器,手册在https://ace.c9.io/#nav=embedding
顺便一提,目前常见的有两种编辑器,一种是ace编辑器,一种是codemirror编辑器,目前两种并无优劣,ace个人认为开发更舒服一点
并且查看windows下挂载了ace,我们可以直接使用
ace.edit("code_editor").setValue('8888')
(备注,初始化编辑器通常使用ace.edit函数进行初始化)
来进行设置内容,因为ace编辑器允许反复初始化,并返回相同的内容,这里可以用相等来进行判断

但是这么简单就没有挑战了!所以我们搞的可以更复杂一点。
我们现在document-start的时候对脚本注入,然后debugger

可以看到这时候body里的script函数很少的
这时候我们查看一下ace对象是否有初始化

证明我们注入的时候ace还没初始化。我们再看一下加载完毕的body内的内容,可以看到差距非常大

那这时候我们就可以考虑一个想法,在监控body内的dom变化的时候,检测是否有ace,如果存在就开始劫持ace.edit,直接把对象挂载到window上,这样就把编辑器的对象直接暴露到了外部出去,这里我直接提供一下代码
```javascript
window.oldace=null
document.querySelector('head').addEventListener("DOMNodeInserted", function(event) {
if(window.ace!==undefined&&window.oldace===null&&window.ace.edit!==undefined){
window.oldace=window.ace.edit
window.ace.edit=function(...args){
let ret=window.oldace.call(this,...args)
window.setace=ret
return ret
}
}
});
```
if判断window.ace不为undefine的时候检测存储编辑器对象是否为空,如果为空则检测ace.edit是否存在,因为根据我的观察ace的各种函数是逐步初始化的,所以必须进行检测。
如果检测到了edit函数,则对其包装一层,并将返回的内容暴露到window下。
这时候我们直接控制window下的oldace就可以操作编辑器了!
# 附加
我们也可以使用数据响应来对其进行劫持完成目的,这时候可以对edit再进行一下处理
```javascript
let _ace;
Object.defineProperty(unsafeWindow, 'ace', {
configurable: true,
get: () => _ace,
set: value => {
console.log('ace被赋值了');
_ace = value;
}
});
```
# 结语
撒花~
李恒道 发表于 2021-9-12 18:28
@cxxjackie 大佬,监控某个js文件彻底加载完毕有什么好一点的办法么....这样感觉其实还是不太对劲 ...
那种完全封闭的应该监控不了,除非他自己对外发了消息,监听页面节点插入是比较常见的做法,如果是这种暴露全局对象的,用Object.defineProperty劫持set更好,像这样:let _ace;
Object.defineProperty(unsafeWindow, 'ace', {
configurable: true,
get: () => _ace,
set: value => {
console.log('ace被赋值了');
_ace = value;
}
});
监听ace.edit的话,再嵌套一层Object.defineProperty就好了(可以封装成Promise写起来好看点)。 李恒道 发表于 2021-9-13 19:49
我怀疑可能存在更核心的api用来注入,如果能找到比较优良的注入点就舒服了
然而菜的抠脚,理论可以,实际 ...
试着写了个Promise来实现,可能不太完善:
```javascript
function waitForProperty(obj, ...props) {
let _obj = obj;
let prop;
const realDP = Object.defineProperty;
const waitForValue = () => {
return new Promise(resolve => {
Object.defineProperty = function() {
if (arguments === _obj && arguments === prop) {
const value = 'value' in arguments ? arguments.value : arguments.get.call(_obj);
Object.defineProperty = realDP;
resolve(value);
}
return realDP.apply(Object, arguments);
};
realDP.call(Object, _obj, prop, {
configurable: true,
enumerable: false,
get: () => undefined,
set: value => {
realDP.call(Object, _obj, prop, {
configurable: true,
enumerable: true,
writable: true,
value: value
});
Object.defineProperty = realDP;
resolve(value);
}
});
});
};
return new Promise(async (resolve, reject) => {
while (props.length > 0) {
prop = props.shift();
if (prop in _obj) {
_obj = _obj;
} else {
_obj = await waitForValue();
}
if (props.length > 0 && typeof _obj !== 'function' && typeof _obj !== 'object') {
return reject(`The property '${prop}' is not a function or object.`);
}
}
resolve(_obj);
});
}
```
调用方式就像这样:`await waitForProperty(unsafeWindow, 'a', 'b', 'c')`
前后端的话当然是前端啦,具体的就不细说了,个人隐私个人隐私~
@cxxjackie 大佬,监控某个js文件彻底加载完毕有什么好一点的办法么....这样感觉其实还是不太对劲 学到了!! cxxjackie 发表于 2021-9-12 19:39
那种完全封闭的应该监控不了,除非他自己对外发了消息,监听页面节点插入是比较常见的做法,如果是这种暴 ...
加上去了,完全封闭的可以使用Object.defineProperty劫持之类的在基础api做手脚拿到数据,我之前阿里云盘的时候尝试过 李恒道 发表于 2021-9-13 14:52
加上去了,完全封闭的可以使用Object.defineProperty劫持之类的在基础api做手脚拿到数据,我之前阿里云盘 ...
劫持基础API应该可以,这我确实没想那么多。不过Object.defineProperty只能监听到直接赋值的操作,对于同样用Object.defineProperty修改get的就没什么办法了,还得加上对defineProperty这个函数本身的劫持,这种情况可能就得祭出Proxy了,我记得Proxy是可以同时处理get、set和defineProperty的。 cxxjackie 发表于 2021-9-13 19:14
劫持基础API应该可以,这我确实没想那么多。不过Object.defineProperty只能监听到直接赋值的操作,对于同 ...
我怀疑可能存在更核心的api用来注入,如果能找到比较优良的注入点就舒服了
然而菜的抠脚,理论可以,实际屁都不会 cxxjackie 发表于 2021-9-13 19:14
劫持基础API应该可以,这我确实没想那么多。不过Object.defineProperty只能监听到直接赋值的操作,对于同 ...
话说哥哥你是前端还是后端啊 cxxjackie 发表于 2021-9-13 21:11
试着写了个Promise来实现,可能不太完善:
```javascript
硬到我了!
页:
[1]
2