测试网站https://www.bilibili.com/
首先可以找到存在__vue_app__
属性
查看源码可以知道在 mount 函数中的
mount(rootContainer, isHydrate, namespace) {
if (!isMounted) {
//省略
rootContainer.__vue_app__ = app;
return getComponentPublicInstance(vnode.component);
}
}
还有一个_vnode 属性来自于mountElement
函数的
def(el, "__vnode", vnode, true);
但是我们想在想进行全局劫持,势必要混入自己的代码,Vue3调用是createApp(rootComponent).mount(dom)
而rootContainer.__vue_app__ = app;
是在挂接之后才能出现的
我们要劫持势必要在初始化时劫持,于是可以顺着createApp
找找思路
function createApp(rootComponent, rootProps = null) {
if (!isFunction(rootComponent)) {
rootComponent = extend({}, rootComponent);
}
//省略
return app;
};
其中可以看到初始化的时候有判断isFunction
编译之后的会通过sfc将vue模板文件编译成对象,所以大概率会走到下面的extend
函数,那我们看看extend是怎么实现的const extend = Object.assign;
可以发现这部分有利用的机会!
const assign = Object.assign
let isRun = false
Object.assign = function (...args) {
if (args.length==2&&args[1]?.render !== undefined && !isRun) {
let b=args[1]
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模板上级调用如下
render.call(
thisProxy,
proxyToUse,
renderCache,
true ? shallowReadonly(props) : props,
setupState,
data,
ctx
)
可以看到最后一个就是ctx!
那我们就可以通过render最后一个参数找到appContext,根据appContext再对全局混入数据!
理论建立完毕了!
// ==UserScript==
// @name Vue3 Mixin Inject
// @namespace https://bbs.tampermonkey.net.cn/
// @version 0.1.0
// @description try 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[1]?.render !== undefined && !isRun) {
let b=args[1]
const originRender = b.render
let isInject=false
b.render = function (...args) {
if (!isInject) {
args[5]['_'].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)
}
然后测试一下