李恒道 发表于 2024-10-16 17:33:17

【油猴开发指南】关于Vue3的劫持处理初步方案

测试网站https://www.bilibili.com/

首先可以找到存在`__vue_app__`属性
查看源码可以知道在 mount 函数中的

```js
mount(rootContainer, isHydrate, namespace) {
    if (!isMounted) {
    //省略
    rootContainer.__vue_app__ = app;
    return getComponentPublicInstance(vnode.component);
    }
}
```

还有一个_vnode 属性来自于`mountElement`函数的

```js
def(el, "__vnode", vnode, true);
```

但是我们想在想进行全局劫持,势必要混入自己的代码,Vue3调用是`createApp(rootComponent).mount(dom)`
而`rootContainer.__vue_app__ = app;`是在挂接之后才能出现的
我们要劫持势必要在初始化时劫持,于是可以顺着`createApp`找找思路
```js
function createApp(rootComponent, rootProps = null) {
      if (!isFunction(rootComponent)) {
          rootComponent = extend({}, rootComponent);
      }
         //省略
      return app;
};
```
其中可以看到初始化的时候有判断`isFunction`编译之后的会通过sfc将vue模板文件编译成对象,所以大概率会走到下面的`extend`函数,那我们看看extend是怎么实现的`const extend = Object.assign;`
可以发现这部分有利用的机会!
```js
const assign = Object.assign
let isRun = false
Object.assign = function (...args) {
    if (args.length==2&&args?.render !== undefined && !isRun) {
      let b=args
      const originRender = b.render
      b.render = function (...args) {
            console.log("被执行了", args)
            return originRender.call(this, ...args)
      }
      isRun = true
    }
    return assign.call(this, ...args)
}
```
这里我利用了劫持assign可以得到根组件的render函数,控制render是因为渲染模板相对其他的属性来说可能对数据的暴露拥有更多的访问机会

但是我们该怎么混入实例呢?根据逆向最后找到了`renderComponentRoot`函数的`vnode.shapeFlag & 4`分支,render模板上级调用如下
```js
            render.call(
            thisProxy,
            proxyToUse,
            renderCache,
            true ? shallowReadonly(props) : props,
            setupState,
            data,
            ctx
            )
```
可以看到最后一个就是ctx!
那我们就可以通过render最后一个参数找到appContext,根据appContext再对全局混入数据!
理论建立完毕了!
```js
// ==UserScript==
// @name         Vue3 Mixin Inject
// @namespace    https://bbs.tampermonkey.net.cn/
// @version      0.1.0
// @descriptiontry to take over the world!
// @author       You
// @match      https://www.bilibili.com/*
// @run-at       document-start
// @grant unsafeWindow
// ==/UserScript==

const assign = Object.assign
let isRun = false
Object.assign = function (...args) {
    if (args.length==2&&args?.render !== undefined && !isRun) {
      let b=args
      const originRender = b.render
      let isInject=false
      b.render = function (...args) {
            if (!isInject) {
                args['_'].appContext.mixins.push({
                  mounted() {
                        console.log("被创建了,实例数据",this.$props)
                  }
                })
                isInject = true
            }
            console.log("被执行了", args)
            return originRender.call(this, ...args)
      }
      isRun = true
    }
    return assign.call(this, ...args)
}
```
然后测试一下!(data/attachment/forum/202410/16/173251kj65zah74vub406h.jpeg)


tao233 发表于 2024-10-16 17:46:50

插眼,有空研究{:4_94:},

pxoxq 发表于 2024-10-17 10:55:07

gg牛哇,学习了学习了

krystal 发表于 2024-11-3 16:32:11

新版本的vue 打包编译后根组件上不存在render方法

krystal 发表于 2024-11-3 16:36:44

道哥有没有更好的方法{:4_94:}

李恒道 发表于 2024-11-4 09:20:08

krystal 发表于 2024-11-3 16:32
新版本的vue 打包编译后根组件上不存在render方法

哥哥可以来个测试地址看一眼
还有一种use劫持方法

krystal 发表于 2024-11-4 10:49:37

李恒道 发表于 2024-11-4 09:20
哥哥可以来个测试地址看一眼
还有一种use劫持方法

http://n1.tanyu.fun:7869/#/两只方式都不行,遇到这种没有use的也不行

李恒道 发表于 2024-11-4 12:34:28

krystal 发表于 2024-11-4 10:49
http://n1.tanyu.fun:7869/#/两只方式都不行,遇到这种没有use的也不行

这个页面也是同理的
只是render属于setup返回
还是会走assign,因为最后是以返回的render模板作为读取项
可以对setup做劫持返回劫持render
再mixin

李恒道 发表于 2024-11-4 12:36:39

krystal 发表于 2024-11-4 10:49
http://n1.tanyu.fun:7869/#/两只方式都不行,遇到这种没有use的也不行

!(data/attachment/forum/202411/04/123611c55qy6s3wac3was3.png)
调试可以以weakSet作为基础API的特征
可以直接追到rootComponent的assign的位置

krystal 发表于 2024-11-4 13:15:04

李恒道 发表于 2024-11-4 12:34
这个页面也是同理的
只是render属于setup返回
还是会走assign,因为最后是以返回的render模板作为读取项


学到了{:4_94:}
页: [1]
查看完整版本: 【油猴开发指南】关于Vue3的劫持处理初步方案