李恒道 发表于 2021-9-12 18:28:15

[油猴脚本开发指南]script标签初始化对象劫持

# 开篇

在我们写脚本的时候,经常碰到一种情况,就是网页调用了外部的库,而我们想要去修改内容或者使用一些功能,这时候通常会束手无策,所以我在这里简单的提供一些我自己的想法,可能存在一些问题,可以评论下写出。

通常分为两种情况,一种情况是网页的功能数据暴露在了windows的内部,这时候我们可以考虑直接对其进行劫持。另外一种是在封闭作用域内,这种情况我们可以考虑对其内部的一些函数进行劫持。

这里我们以https://www.acwing.com/problem/content/1/为例,我想修改这个编辑器的内容

![图片.png](data/attachment/forum/202109/12/182057skdavzv98rv88958.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "图片.png")

通过这里的domclass类可以知道,这个是一个ace编辑器,手册在https://ace.c9.io/#nav=embedding

顺便一提,目前常见的有两种编辑器,一种是ace编辑器,一种是codemirror编辑器,目前两种并无优劣,ace个人认为开发更舒服一点

并且查看windows下挂载了ace,我们可以直接使用

ace.edit("code_editor").setValue('8888')

(备注,初始化编辑器通常使用ace.edit函数进行初始化)

来进行设置内容,因为ace编辑器允许反复初始化,并返回相同的内容,这里可以用相等来进行判断

![图片.png](data/attachment/forum/202109/12/182255yjzricujc29ubict.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "图片.png")

但是这么简单就没有挑战了!所以我们搞的可以更复杂一点。

我们现在document-start的时候对脚本注入,然后debugger

![图片.png](data/attachment/forum/202109/12/182420y1usdtibist9vkw1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "图片.png")

可以看到这时候body里的script函数很少的

这时候我们查看一下ace对象是否有初始化

![图片.png](data/attachment/forum/202109/12/182457e0ytyra0wbar33wr.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "图片.png")

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

![图片.png](data/attachment/forum/202109/12/182525bc2yeras557eyaza.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "图片.png")

那这时候我们就可以考虑一个想法,在监控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;
}
});

```

# 结语

撒花~

cxxjackie 发表于 2021-9-12 19:39:45

李恒道 发表于 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写起来好看点)。

cxxjackie 发表于 2021-9-13 21:11:12

李恒道 发表于 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')`

前后端的话当然是前端啦,具体的就不细说了,个人隐私个人隐私~

李恒道 发表于 2021-9-12 18:28:53

@cxxjackie 大佬,监控某个js文件彻底加载完毕有什么好一点的办法么....这样感觉其实还是不太对劲

无了 发表于 2021-9-12 18:41:01

学到了!!

李恒道 发表于 2021-9-13 14:52:56

cxxjackie 发表于 2021-9-12 19:39
那种完全封闭的应该监控不了,除非他自己对外发了消息,监听页面节点插入是比较常见的做法,如果是这种暴 ...

加上去了,完全封闭的可以使用Object.defineProperty劫持之类的在基础api做手脚拿到数据,我之前阿里云盘的时候尝试过

cxxjackie 发表于 2021-9-13 19:14:48

李恒道 发表于 2021-9-13 14:52
加上去了,完全封闭的可以使用Object.defineProperty劫持之类的在基础api做手脚拿到数据,我之前阿里云盘 ...

劫持基础API应该可以,这我确实没想那么多。不过Object.defineProperty只能监听到直接赋值的操作,对于同样用Object.defineProperty修改get的就没什么办法了,还得加上对defineProperty这个函数本身的劫持,这种情况可能就得祭出Proxy了,我记得Proxy是可以同时处理get、set和defineProperty的。

李恒道 发表于 2021-9-13 19:49:34

cxxjackie 发表于 2021-9-13 19:14
劫持基础API应该可以,这我确实没想那么多。不过Object.defineProperty只能监听到直接赋值的操作,对于同 ...

我怀疑可能存在更核心的api用来注入,如果能找到比较优良的注入点就舒服了
然而菜的抠脚,理论可以,实际屁都不会

李恒道 发表于 2021-9-13 19:49:47

cxxjackie 发表于 2021-9-13 19:14
劫持基础API应该可以,这我确实没想那么多。不过Object.defineProperty只能监听到直接赋值的操作,对于同 ...

话说哥哥你是前端还是后端啊

李恒道 发表于 2021-9-13 23:07:25

cxxjackie 发表于 2021-9-13 21:11
试着写了个Promise来实现,可能不太完善:

```javascript


硬到我了!
页: [1] 2
查看完整版本: [油猴脚本开发指南]script标签初始化对象劫持