上一主题 下一主题
ScriptCat,新一代的脚本管理器脚本站,与全世界分享你的用户脚本油猴脚本开发指南教程目录
返回列表 发新帖

【油猴开发指南】实战破解Vue百度文库复制

[复制链接]
  • TA的每日心情
    开心
    2023-2-28 23:59
  • 签到天数: 191 天

    [LV.7]常住居民III

    611

    主题

    4982

    回帖

    5855

    积分

    管理员

    非物质文化遗产社会摇传承人

    积分
    5855

    荣誉开发者管理员油中2周年生态建设者喜迎中秋

    发表于 2023-7-9 03:10:04 | 显示全部楼层 | 阅读模式

    百度文库新版页面使用Vue2开发
    本文意在领大家分析如何对Vue框架开发的页面进行分析
    熟悉常用的框架分发代码等等

    正文

    这里我们以一篇百度文库的文章为例
    https://wenku.baidu.com/view/5d102fd05322aaea998fcc22bcd126fff7055dfd.html?fr=hp_Database&_wkts_=1688832801754
    随便选中一段文字,然后点击复制
    图片.png
    发现跳转到了百度文库会员购买
    图片.png
    那么我们开始分析这个按钮
    查看事件监听器,只有一个click事件,跳转进去
    到了

                    e = o._wrapper = function(t) {
                        if (t.target === t.currentTarget || t.timeStamp >= i || t.timeStamp <= 0 || t.target.ownerDocument !== document)
                            return o.apply(this, arguments)
                    }

    这里基本没什么分支,我们直接在o.apply(this, arguments)打断点
    点击复制按钮后断下,往里走,到了

                function n() {
                    var t = arguments
                      , r = n.fns;
                    if (!Array.isArray(r))
                        return Jt(r, null, arguments, e, "v-on handler");
                    for (var i = r.slice(), o = 0; o < i.length; o++)
                        Jt(i[o], null, t, e, "v-on handler")
                }

    这里我们就是Vue的分发代码了,如果是单个的就只调用一次,如果是多个同样的事件就循环触发事件的回调函数代码
    我们这里直接打印n.fns,看到是一个函数,直接跳转进去
    发现是一个明显的render模板
    图片.png
    我们在t.clickCopy()部分打一个断点继续往里追
    找到了

                    clickCopy: function() {
                        var t = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : "reader";
                        if (a.log.xsend(102222, {
                            position: t
                        }),
                        a.log.xsend(dt, {
                            behavior: ut.CLICK,
                            module: [vt.ReaderPlugin, "copy", t].join("_")
                        }),
                        "remind" === t && !this.canCopy)
                            return this.buyVip("remind");
                        if (this.setVisible(!1),
                        this.setReminderVisible(!1),
                        this.setIsCopyActivated(!0),
                        this.isTaskUser && 1 === this.taskStatus) {
                            var e = this.taskStatus
                              , i = ++e;
                            (0,
                            at.Q)(1, i)
                        }
                    },

    根据调试我们并走到了

                       if (this.setVisible(!1),
                        this.setReminderVisible(!1),
                        this.setIsCopyActivated(!0),
                        this.isTaskUser && 1 === this.taskStatus) {
                            var e = this.taskStatus
                              , i = ++e;
                            (0,
                            at.Q)(1, i)
                        }

    所以这里一定有跳转到购买会员页面的代码,我们先从setVisible跟一下看看
    走进去到了

                    n[r] = function() {
                        for (var e = [], n = arguments.length; n--; )
                            e[n] = arguments[n];
                        var r = this.$store.commit;
                        if (t) {
                            var o = S(this.$store, "mapMutations", t);
                            if (!o)
                                return;
                            r = o.context.commit
                        }
                        return "function" == typeof i ? i.apply(this, [r].concat(e)) : r.apply(this.$store, [i].concat(e))
                    }

    有store字样,疑似是Vuex或者pinia,是vue的全局状态存储
    我们看代码可以直接在最后一行打断点
    走到了

    function(n, r, i) {
                            var o = _(n, r, i)
                              , a = o.payload
                              , s = o.options
                              , c = o.type;
                            s && s.root || (c = e + c),
                            t.commit(c, a, s)
                        }

    还是在最后一行跟

                this.commit = function(t, e, n) {
                    return s.call(o, t, e, n)
                }

    找到了

            p.prototype.commit = function(t, e, n) {
                var r = this
                  , i = _(t, e, n)
                  , o = i.type
                  , a = i.payload
                  , s = (i.options,
                {
                    type: o,
                    payload: a
                })
                  , c = this._mutations[o];
                c && (this._withCommit((function() {
                    c.forEach((function(t) {
                        t(a)
                    }
                    ))
                }
                )),
                this._subscribers.slice().forEach((function(t) {
                    return t(s, r.state)
                }
                )))
            }

    这里c = this._mutations[o];就是对应的store的分发函数了
    我们打印一下c跳转进去
    发现是

    (function(e) {
                            n.call(t, r.state, e)
                        }

    是一层包裹代码,我们直接打断点,然后进n里
    就找到了用户写的分发函数啦!

                        setVisible: function(e, t) {
                            e.visible = t
                        },

    用同样的方法可以找到全部设置状态的函数,这里我们分别找到了三个,发现没什么特殊的地方,那一定还有其他原因!
    我们把目光挪到this.isTaskUser && 1 === this.taskStatus
    因为代码是

           if (this.setVisible(!1),
                        this.setReminderVisible(!1),
                        this.setIsCopyActivated(!0),
                        this.isTaskUser && 1 === this.taskStatus) {

    ,表达式前面的都对if没有任何意义,那么缩减代码就是

     if (this.isTaskUser && 1 === this.taskStatus) {

    我们应该去看isTaskUser和taskStatus!
    因为这个是vue的sfc文件生成的代码
    我们直接在同文件搜索

    (0,s.mapState)("visitUserInfo", ["isTaskUser", "taskStatus"]))

    这是一个mapState函数,即将全局状态的数据映射到文件中,从而缩减代码的长度
    如正常我们获取状态数的数据需要store.visitUserInfo.isTaskUser,而使用mapState缩减了代码长度,只需要使用isTaskUser即可等价于store.visitUserInfo.isTaskUser

    那我们的目标就是触发this.isTaskUser && 1 === this.taskStatus的逻辑

    我们可以在(0,s.mapState)("visitUserInfo", ["isTaskUser", "taskStatus"]))上打一个断点,因为这个是组件初始化的时候执行,所以我们需要刷新页面

    可以发现正常断下了,然后往里走
    图片.png
    走到了

                return function(e, n) {
                    return "string" != typeof e ? (n = e,
                    e = "") : "/" !== e.charAt(e.length - 1) && (e += "/"),
                    t(e, n)
                }

    可以看到是兼容性判断,继续看t函数
    里面将e进行兼容性处理,然后遍历,挂载到对象n上,所以读取属性是在挂载的函数里
    图片.png

    我们在var e = this.$store.state打一个断点等待断下
    然后输入

    this.$store.state.visitUserInfo.isTaskUser=true
    this.$store.state.visitUserInfo.taskStatus=1

    解除这部分的断点,再次触发复制尝试一下
    发现流程进去了
    图片.png

    走进去后唯一比较有意义的代码就是

    (a.ZP.commit("visitUserInfo/setTaskStatus", e

    我们按之前的流程走就可以走到函数里,但是也有一个比较方便的办法,就是直接搜索setTaskStatus,找到了

                        setTaskStatus: function(e, t) {
                            e.taskStatus = t
                        },

    上面的代码可以看到修改了taskStatus,因为全局状态如果被修改是会在其他地方响应的,所以我们尝试搜索taskStatus,除了我们的代码只有另外一处

                      H.Z)({
                                url: "/user/interface/setuserbackground",
                                data: c,
                                method: "POST"
                            })).then((function() {
                                if (n.setThemeDefaultVal(n.viewTheme),
                                n.showBttomTheme = !1,
                                n.isTaskUser && 2 === n.taskStatus) {
                                    var t = n.taskStatus
                                      , e = ++t;
                                    (0,
                                    it.Q)(1, e)
                                }
                            }

    其代码是在api"/user/interface/setuserbackground后调用该属性判断,所以我们可以确定该属性几乎没有作用。

    那我们的目光只能放在
    this.setVisible(!1),
    this.setReminderVisible(!1),
    this.setIsCopyActivated(!0),
    这三个里了,根据之前的经验,我们再进行搜索

    严谨的说应该按刚才的方式找函数,如果你有经验了可以直接搜索

    他们分别修改了
    visible
    remindVisible
    isCopyActivated

    所以我们依次再分别搜索这三个遍历看看谁依赖了他们三个,这个时候优先排查搜索结果最少的,我们细致排查后找到了

    watch: {
         isCopyActivated: function(t) {
            t && !this.canCopy && (a.log.xsend(H, {
                behavior: U.SHOW,
                module: [G.ReaderPlugin, "Copy", "copybtn"].join("_"),
                content: this.selectedTextTrim
            }),
            this.fetchCopyTimes(this.selectedTextTrim, !0))
         },
    },

    这个函数专门监听了isCopyActivated变量,还检测了this.canCopy是否可以复制,如果不可以复制调用this.fetchCopyTimes(this.selectedTextTrim, !0)),我们进这个函数看看

    图片.png

    发现明显的clickBuyVipBtn字样和开通vip,很有可能就是这个函数跳转到百度Vip付费页面的!
    我们在

         isCopyActivated: function(t) {
            ...
            this.fetchCopyTimes(this.selectedTextTrim, !0))
         },

    这个函数中还没执行到 this.fetchCopyTimes之前下断点,设置 this.fetchCopyTimes=()=>{},发现没有跳转到付费购买vip页面,说明我们找对了!那问题就变成了canCopy设置为true了
    在该js文件搜索canCopy找到了

    s.mapGetters)("readerPlugin", ["canCopy", "selectedTextTrim", "formatedText"]))
        canCopy: function(e, t, n) {
            var r = n.vipInfo
              , o = n.docInfo;
            return !!(null != r && r.isVip || (null == o ? void 0 : o.docBizType) === c.RV.SECRET || (null == o ? void 0 : o.docBizCategory) === c.ll.FREE || null != o && o.hasGot)
        },

    获取的是n.vipInfo.isVip,我们在这里打印n,发现n就是vuex的状态树
    往上堆栈回溯一层可以看到

    t._wrappedGetters[e] = function(t) {
        return n(r.state, r.getters, t.state, t.getters)
    }

    根据对比发现r是局部状态树,t是根状态树,那我们的问题就变成了如何得到根状态树并且设置vipInfo.isVip

    说明该变量在vuex中,继续搜索canCopy
    查阅vuex源码可知会挂载到$store上,具体分析就不表明了
    所以可以写出代码

    document.querySelector('.header-wrapper').__vue__.$store.state.vipInfo.isVip=true

    根据测试可以正常复制
    我们成功破解了百度文库复制!

    结语

    撒花~

    已有3人评分好评 油猫币 理由
    朱焱伟 + 1 + 7 ggnb!
    wyn665817 + 1 + 7 ggnb!
    wwwwwllllk + 1 + 7 ggnb!

    查看全部评分 总评分:好评 +3  油猫币 +21 

    混的人。
    ------------------------------------------
    進撃!永遠の帝国の破壊虎---李恒道
    个人宣言:この世界で私に胜てる人とコードはまだ生まれていません。死ぬのが怖くなければ来てください。
  • TA的每日心情
    无聊
    2023-11-2 17:37
  • 签到天数: 275 天

    [LV.8]以坛为家I

    106

    主题

    437

    回帖

    939

    积分

    荣誉开发者

    积分
    939

    荣誉开发者油中2周年卓越贡献生态建设者

    发表于 2023-7-9 08:02:59 | 显示全部楼层
    道哥录视频也搞起来吧
    I frequently record, because want to leave something.
    回复

    使用道具 举报

  • TA的每日心情
    开心
    2023-2-28 23:59
  • 签到天数: 191 天

    [LV.7]常住居民III

    611

    主题

    4982

    回帖

    5855

    积分

    管理员

    非物质文化遗产社会摇传承人

    积分
    5855

    荣誉开发者管理员油中2周年生态建设者喜迎中秋

    发表于 2023-7-9 13:10:39 | 显示全部楼层
    wwwwwllllk 发表于 2023-7-9 08:02
    道哥录视频也搞起来吧

    主要事情太多了...视频还太烧时间,搞得我很难长期维护
    混的人。
    ------------------------------------------
    進撃!永遠の帝国の破壊虎---李恒道
    个人宣言:この世界で私に胜てる人とコードはまだ生まれていません。死ぬのが怖くなければ来てください。
    回复

    使用道具 举报

  • TA的每日心情
    慵懒
    2022-3-8 11:41
  • 签到天数: 2 天

    [LV.1]初来乍到

    22

    主题

    846

    回帖

    1347

    积分

    荣誉开发者

    积分
    1347

    荣誉开发者卓越贡献油中2周年生态建设者油中3周年挑战者 lv2

    发表于 2023-7-9 22:29:54 | 显示全部楼层
    百度文库我也研究过,除了isVip,还有以下几个参数值得改动:
    $store.state.vipInfo.global_vip_status
    这个改成1,在某些情况下可能有影响。
    $store.state.userInfo.isLogin
    这个改成true,可以实现免登录复制。
    $store._vm._computedWatchers['readerPlugin/formatedText'].getter
    这个用于给复制文本加上百度文库的小尾巴,将其劫持后返回$store._vm['readerPlugin/selectedTextTrim']即可。

    这些参数在不同页面的注入点可能不太一样,而且百度会改,维护挺麻烦的,还是灰色地带可能被百度搞,所以我就没发脚本
    回复

    使用道具 举报

  • TA的每日心情
    开心
    2023-2-28 23:59
  • 签到天数: 191 天

    [LV.7]常住居民III

    611

    主题

    4982

    回帖

    5855

    积分

    管理员

    非物质文化遗产社会摇传承人

    积分
    5855

    荣誉开发者管理员油中2周年生态建设者喜迎中秋

    发表于 2023-7-10 14:49:05 | 显示全部楼层
    cxxjackie 发表于 2023-7-9 22:29
    百度文库我也研究过,除了isVip,还有以下几个参数值得改动:
    $store.state.vipInfo.global_vip_status
    这 ...

    c大原来早就研究过了
    太细了!
    混的人。
    ------------------------------------------
    進撃!永遠の帝国の破壊虎---李恒道
    个人宣言:この世界で私に胜てる人とコードはまだ生まれていません。死ぬのが怖くなければ来てください。
    回复

    使用道具 举报

    发表回复

    本版积分规则

    快速回复 返回顶部 返回列表