skygongque / tts

微软 tts 文本转语音 音频下载
860 stars 200 forks source link

tampermonkey 脚本的注入点思考 #1

Closed puteulanus closed 2 years ago

puteulanus commented 2 years ago

需要的数据在 synthesizer 的 synthesisCompleted,而 synthesizer 是由 new SpeechSDK.SpeechSynthesizer 产生的实例

首先想到通过劫持 SpeechSynthesizer 的构造函数来替换 new 出来的对象

Object.defineProperty(SpeechSDK, 'SpeechSynthesizer', { get: function () {} })

失败,SpeechSDK.SpeechSynthesizer 本身是 defineProperty 设置的 unconfigurable 的 getter,所以没法用 defineProperty 替换掉(悲)

尝试再朝上一层,在 SpeechSDK 加载之前劫持 defineProperty,以便在设置 SpeechSDK.SpeechSynthesizer 的时候拿到这个属性的操纵权

Object.defineProperty = (() => {
        const defineProperty = Object.defineProperty.bind(Object)
        return (...args) => {
            if (args[1] === 'SpeechSynthesizer') {
              console.log(args)
            }
            return defineProperty(...args)
        }
})()

成功,拿到 SpeechSynthesizer 的 getter,getter 执行返回 SpeechSynthesizer 类,外面是 new 这个类来获得 synthesizer 实例 我们给它在里面直接实例化好,并且用 defineProperty 把 synthesisCompleted 预先设置成 setter,以便在外面对实例设置这个回调的时候,拿到它的参数

    Object.defineProperty = (() => {
        const defineProperty = Object.defineProperty.bind(Object)
        return (...args) => {
            if (args[1] === 'SpeechSynthesizer') args[2].get = (() => {
                // 获得 SpeechSynthesizer 类
                const SpeechSynthesizer = args[2].get()
                return () => {
                    // 构造虚假的构造函数,返回修改后的实例
                    return function (...args) {
                        // 实例化
                        const synthesizer = new SpeechSynthesizer(...args)
                        let synthesisCompleted;
                        // 在实例的 synthesisCompleted 被设置的时候注入
                        defineProperty(synthesizer, 'synthesisCompleted', {
                            configurable: true,
                            get: function () {
                                return synthesisCompleted;
                            },
                            set: function (func) {
                                const proxy = (...args) => {
                                    // 注入点,拿到 privAudioData
                                    console.log(args[1].privResult.privAudioData)
                                    return func(...args)
                                }
                                synthesisCompleted = proxy
                            }
                        })
                        return synthesizer
                    }
                }
            })()
            return defineProperty(...args)
        }
    })()

这样就能直接实现直接注入进 synthesisCompleted 函数了

A50530A0-4080-4EF0-B1A2-E02951B687A8

完整 tampermonkey 脚本:

// ==UserScript==
// @name         New Userscript
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  try to take over the world!
// @author       You
// @match        https://azure.microsoft.com/zh-cn/services/cognitive-services/text-to-speech/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=microsoft.com
// @grant        none
// @run-at       document-start
// ==/UserScript==

(function() {
    'use strict';

    // Your code here...
    Object.defineProperty = (() => {
        const defineProperty = Object.defineProperty.bind(Object)
        return (...args) => {
            if (args[1] === 'SpeechSynthesizer') args[2].get = (() => {
                const SpeechSynthesizer = args[2].get()
                return () => {
                    return function (...args) {
                        const synthesizer = new SpeechSynthesizer(...args)
                        let synthesisCompleted;
                        defineProperty(synthesizer, 'synthesisCompleted', {
                            configurable: true,
                            get: function () {
                                return synthesisCompleted;
                            },
                            set: function (func) {
                                const proxy = (...args) => {
                                    console.log(args[1].privResult.privAudioData)
                                    return func(...args)
                                }
                                synthesisCompleted = proxy
                            }
                        })
                        return synthesizer
                    }
                }
            })()
            return defineProperty(...args)
        }
    })()
})();
skygongque commented 2 years ago

牛的牛的,这样代码量大大减少了。 Object.defineProperty 在hook的时候确实用的挺多的,一般是你提到的第一种用法,直接劫持Object.defineProperty好思路(直接动了别人的object了),用处应该不止于此。