吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 17757|回复: 133
收起左侧

[Web逆向] 微软tts 下载音频按钮(tampermonkey脚本)的实现思路

    [复制链接]
天空宫阙 发表于 2022-3-19 10:29
本帖最后由 天空宫阙 于 2022-3-19 10:40 编辑

微软tts 下载音频按钮(tampermonkey脚本)的实现思路

目标网站

https://azure.microsoft.com/zh-cn/services/cognitive-services/text-to-speech/#features
  • 目的是解决微软官方的网页版demo,不能直接下载转换后的MP3文件

效果

image-20220319101032760.png

适合阅读的人群

  • 有一定的JS逆向基础的人群

    • 会使用chrome开发者工具进行抓包调试和断点调试
  • 如果只是想要实现微软tts 下载音频可以直接安装tampermonkey扩展后安装脚本即可,安装完成后会在声音合成后出现下载音频的按钮,脚本地址https://greasyfork.org/zh-CN/scripts/441531-%E5%BE%AE%E8%BD%AFtts-%E4%B8%8B%E8%BD%BD%E6%8C%89%E9%92%AE

理解本篇教程你将会对以下知识点有更深刻的理解

  1. tampermokey 脚本的注入时机,需要尽可能早的注入时使用@run-at document-start
  2. hook 的简单使用
  3. 函数或变量的导出,比如导出到window方便后续使用,JS扣代码常用
  4. 使用tampermokey 脚本 下载二进制文件的方法
  5. websocket的简单了解

0x01

首先在目标网站https://azure.microsoft.com/zh-cn/services/cognitive-services/text-to-speech/#features点击几次播放抓几次包发现以下的请求是用来传输合成的音频的

wss://eastus.tts.speech.microsoft.com/cognitiveservices/websocket/v1?Authorization=bearer%20eyJhbGciOiJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGRzaWctbW9yZSNobWFjLXNoYTI1NiIsInR5cCI6IkpXVCJ9.eyJyZWdpb24iOiJlYXN0dXMiLCJzdWJzY3JpcHRpb24taWQiOiI2MWIxODBlMmJkOGU0YWI2OGNiNmQxN2UxOWE5NjAwMiIsInByb2R1Y3QtaWQiOiJTcGVlY2hTZXJ2aWNlcy5TMCIsImNvZ25pdGl2ZS1zZXJ2aWNlcy1lbmRwb2ludCI6Imh0dHBzOi8vYXBpLmNvZ25pdGl2ZS5taWNyb3NvZnQuY29tL2ludGVybmFsL3YxLjAvIiwiYXp1cmUtcmVzb3VyY2UtaWQiOiIvc3Vic2NyaXB0aW9ucy9jMjU1ZGYzNi05NzRjLTQ2MGEtODMwYi0yNTE2NTEzYWNlYjIvcmVzb3VyY2VHcm91cHMvY3MtY29nbml0aXZlc2VydmljZXMtcHJvZC13dXMyL3Byb3ZpZGVycy9NaWNyb3NvZnQuQ29nbml0aXZlU2VydmljZXMvYWNjb3VudHMvYWNvbS1zcGVlY2gtcHJvZC1lYXN0dXMiLCJzY29wZSI6InNwZWVjaHNlcnZpY2VzIiwiYXVkIjoidXJuOm1zLnNwZWVjaHNlcnZpY2VzLmVhc3R1cyIsImV4cCI6MTY0NzU5NDk1MCwiaXNzIjoidXJuOm1zLmNvZ25pdGl2ZXNlcnZpY2VzIn0.PjYuGtrhh9RgkaH3iPYwPxBwtUDaWCjIBT_iQbPJf7g&X-ConnectionId=F4D719576F88485997E628643E905B76

有两个参数AuthorizationX-ConnectionId前者在网页的源码中,后者是uuid4,因为我们用tampermonkey脚本所以不需要搞定这两个参数的生成

image-20220318171347035.png

这个还是get请求,但该请求后改变传输协议为websockets,发送需要转换的文本后,服务器返回了一堆Binary Message(二进制消息),这些就是合成的声音。

image-20220318171615746.png

0x02

因为网页版会对合成的声音进行播放,所以前端肯定有对这些数据进行处理,我们只需要拿到前端处理完成的二进制文件就可以了。那问题就是处理后的二进制文件存哪里了?

可以进行简单的断点调试,没有思路就直接这个进去下一个断点,一步步往上找找看。

image-20220318172825273.png

在这里可找到websocket的onmessage

image-20220318204848429.png

这里l.provWebsocketClientWebsocket的一个实例

image-20220318205340359.png

继续往上追一层找到了每一次处理Binary消息的地方。

image-20220318204950954.png

0x03

但是这么找很难找到最后binary传输完成后的完整音频数据。

这里再换一个方法,可以看到websocket通讯的最后有一个含有Path:turn.end的包,尝试在上面同一个js文件中搜一下 turn.end然后下断点。

image-20220318215256773.png

可以发现定到了这里

image-20220318220501114.png

这里好多case 一开始以为是控制流平坦化,但并不是,而且变量名是有意义的,稍微仔细一点就可以看到这样一个synthesisCompleted

image-20220318220913937.png

而且有一个if的判断,synthesisCompleted翻译过来是合成完成,打一个断点,确实是在websockets传输完成后会断住,里面的g.privAudioData猜测就是合成的mp3。

image-20220318221113258.png

做一下实验,把这个二进制数据下载下载听一下,发现确实就是我们要的mp3

window.URL.createObjectURL(new Blob([g.privAudioData], { type: 'audio/mp3' }));

image-20220318221621847.png

一开始我想到用chrome的overrides,(fiddler的autoresponse也可以做到)。

image-20220318222128555.png

在把g.privAudioData导出到window这样就方便后面调用了

image-20220318222527046.png

但是后面在下载的时候遇到了浏览器限制,并且这种替换线上文件的方法,对用户的配置要求较高。

0x04

我的目标是做一个装上就可以用的tampermonkey脚本。

为了拿到g.privAudioData 我在当前的作用域中找找看有没有生命周期比较长的比如JSON等之类的可以hook,发现并没有找到,但是我发现g是传进来的,于是找上一层,不行再找上一层。

最终我找到document.addEventListener,<u>这个注入点可能不是最好的,如果大佬有更好的欢迎指正。</u>

先说上面一层是

image-20220318223715387.png

image-20220318223921100.png

发现这里定义了一个匿名函数赋给了全局变量window.initializeTTSDemo,此处也没有找到合适的hook点,但是arguments[1].privResult 里面有我们要的MP3,我们要的东西没丢。

'use strict';

(function ($) {
    // This demo is only supported in Edge, Firefox, Chrome and Opera. Source: http://caniuse.com/#search=getUserMedia

    window.initializeTTSDemo = function (localizedResources) {
        $(document).ready(function () {
                        // 省略好多代码            

            SpeechSDK = window.SpeechSDK;

            // 省略好多代码
            function SpeakOnce() {
                var config = SpeechSDK.SpeechTranslationConfig.fromAuthorizationToken(localizedResources.token, localizedResources.region),
                    synthesizer,
                    audioConfig;

                // due to a bug in Chromium (https://bugs.chromium.org/p/chromium/issues/detail?id=1028206)
                // mp3 playback has some beeps, using a higher bitrate here as a workaround.
                config.speechSynthesisOutputFormat = SpeechSDK.SpeechSynthesisOutputFormat.Audio24Khz160KBitRateMonoMp3;

                player = new SpeechSDK.SpeakerAudioDestination();
                player.onAudioEnd = function () {
                    stopli.hidden = true;
                    playli.hidden = false;
                };

                audioConfig = SpeechSDK.AudioConfig.fromSpeakerOutput(player);

                synthesizer = new SpeechSDK.SpeechSynthesizer(config, audioConfig);

                synthesizer.synthesisCompleted = function () {
                    //arguments[1].privResult 里面有我们要的MP3

                    synthesizer.close();
                    synthesizer = null;
                };

                synthesizer.SynthesisCanceled = function (s, e) {
                    var details;
                    stopli.hidden = true;
                    playli.hidden = false;
                    details = SpeechSDK.CancellationDetails.fromResult(e);
                    if (details.reason === SpeechSDK.CancellationReason.Error) {
                        status.innerText = localizedResources.srTryAgain;
                    }
                };

                synthesizer.speakSsmlAsync(ssml.value, function () { }, function (error) {
                    status.innerText = localizedResources.srTryAgain + ' ' + error;
                });
            }

           // 省略好多代码
})(jQuery);

我非常想重写initializeTTSDemo,但是失败了

在使用tampermonkey脚本 使用默认注入方式和改成了@run-at document-start都不能覆盖这个函数。

于是我继续找initializeTTSDemo执行的地方

image-20220318225231325.png

最终还是找到了index.html 狗头

hook document.addEventListener 可以改写initializeTTSDemo

image-20220318225605405.png
initializeTTSDemo中大部分都是抄的核心的修改如下:

synthesizer.synthesisCompleted = function () {
                            // 此时已经合成完毕了 arguments[1].privResult.privAudioData 为MP3文件
                            //debugger;
                            window.xtw.privAudioData = arguments[1].privResult.privAudioData;
                            window.xtw.playdlbut.innerHTML = "下载音频";
                            window.xtw.playdlbut.onclick = function () {
                                // https://stackoverflow.com/questions/19327749/javascript-blob-filename-without-link
                                function saveFile(name, type, data) {
                                    if (data !== null && navigator.msSaveBlob) {
                                        return navigator.msSaveBlob(new Blob([data], { type: type }), name);
                                    }
                                    var a = $("<a style='display: none;'/>");
                                    var url = window.URL.createObjectURL(new Blob([data], { type: type }));
                                    a.attr("href", url);
                                    a.attr("download", name);
                                    $("body").append(a);
                                    a[0].click();
                                    window.URL.revokeObjectURL(url);
                                    a.remove();
                                    window.xtw.playdlbut.innerHTML = "先点播放";
                                };
                                //debugger;
                                if (window.xtw.privAudioData) {
                                    saveFile(new Date().getTime(), "audio/mp3", window.xtw.privAudioData);
                                    window.xtw.privAudioData = null;
                                } else {
                                    alert('先点播放,等按钮变成下载音频再点击')
                                }
                            };
                            synthesizer.close();
                            synthesizer = null;
                        };

最后把按钮放在合适的位置,方便使用

if (!window.xtw.playdlbut) {
                        window.xtw.playdlbut = document.createElement("button");
                        window.xtw.playdlbut.innerHTML = "先点播放";
                        window.xtw.playdlbut.classList.add('button');
                        window.xtw.playdlbut.classList.add('button--primary01');
                        window.xtw.playdlbut.classList.add('svg-button');
                        window.xtw.li = document.createElement('li');
                        window.xtw.li.appendChild(window.xtw.playdlbut);
                        window.xtw.playliparent = document.getElementById('playli').parentElement
                        window.xtw.playliparent.appendChild(window.xtw.li);
                    }

完整的脚本代码

https://greasyfork.org/zh-CN/scripts/441531-%E5%BE%AE%E8%BD%AFtts-%E4%B8%8B%E8%BD%BD%E6%8C%89%E9%92%AE
https://github.com/skygongque/tts/tree/main/tampermonkeyScript

免费评分

参与人数 57威望 +2 吾爱币 +158 热心值 +51 收起 理由
x517302248 + 1 我很赞同!
fory + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
yuzhouxingzou + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
wo2007 + 1 + 1 我很赞同!
二师兄的夏天 + 1 用心讨论,共获提升!
i7-8700 + 1 终于盼到了
wxhwz + 1 + 1 用心讨论,共获提升!
厚厚 + 1 + 1 鼓励转贴优秀软件安全工具和文档!
nowthink + 1 + 1 谢谢@Thanks!
kingkongsheng + 1 + 1 谢谢@Thanks!
Cherry33 + 1 谢谢@Thanks!
aabbcc123123 + 1 + 1 用心讨论,共获提升!
sim163 + 1 + 1 我很赞同!
Courser + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
石碎大胸口 + 1 + 1 用心讨论,共获提升!
MatrixLau + 1 + 1 谢谢@Thanks!
君辰一梦 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
misaka10843 + 1 + 1 谢谢@Thanks!
奋斗0755 + 1 + 1 谢谢@Thanks!
雨打荷花 + 1 + 1 谢谢@Thanks!
flt + 1 + 1 谢谢@Thanks!
独行风云 + 2 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
上弦式神 + 1 + 1 我很赞同!
henrylong1989 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
wang777www + 1 + 1 谢谢@Thanks!
努力加载中 + 1 + 1 谢谢@Thanks!
纹路风 + 1 + 1 鼓励转贴优秀软件安全工具和文档!
涛之雨 + 2 + 100 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
zaomeng + 1 + 1 谢谢@Thanks!
聪本 + 1 + 1 实力无需多言,助人润物无声
277249 + 1 + 1 看不懂,但是感觉很牛B
chh13502 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
Warn + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
seekwe + 1 我很赞同!
狐狸爱葡萄 + 1 + 1 我很赞同!
randomone + 1 + 1 谢谢@Thanks!
wa_j + 1 + 1 热心回复!
yuxuechao + 1 我很赞同!
chinajxw + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
孤独伴酒 + 1 + 1 谢谢@Thanks!
a1067709136 + 1 + 1 谢谢@Thanks!
笙若 + 1 + 1 谢谢@Thanks!
googxy + 2 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
FcQ1688 + 1 用心讨论,共获提升!
52菜鸟 + 2 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
XINJIAN9 + 1 谢谢@Thanks!
loooooooong + 1 + 1 用心讨论,共获提升!
lucool + 1 + 1 我很赞同!
坐久落花多 + 1 + 1 这个脚本很赞!
oudaidai + 1 + 1 正好需要用到,非常感谢
qq63 + 1 + 1 热心回复!
wangwhc + 1 + 1 这个现在应该太广泛了,楼主辛苦,造福大家。
wshq + 1 + 1 用心讨论,共获提升!
H.Ra + 1 + 1 谢谢@Thanks!
我的睡公主 + 2 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
大兵马元帅 + 1 可以了,谢谢
愚无尽 + 2 + 1 支持原创,方便有效!

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

愚无尽 发表于 2022-3-19 10:48
长度10分钟限制 可惜了
WUAIBOCAI71 发表于 2022-3-20 11:32
膜拜大佬 天天看显示器 视力确实不行了。。。能听就尽量不看
 楼主| 天空宫阙 发表于 2022-3-19 10:31
大兵马元帅 发表于 2022-3-19 10:42
有没有成品,大佬
愚无尽 发表于 2022-3-19 10:42
原来一直觉得 晓晓 最接近人声。
刚刚试了一下  晓辰 更像人声
 楼主| 天空宫阙 发表于 2022-3-19 10:43

有,最前面和最后面都有链接
32K 发表于 2022-3-19 10:47
大佬好思路!
Ironcarrot 发表于 2022-3-19 10:47
换一个语言还可以吗
 楼主| 天空宫阙 发表于 2022-3-19 10:48
Ironcarrot 发表于 2022-3-19 10:47
换一个语言还可以吗

可以,只是实现了下载按钮,其他原有的功能都在
大兵马元帅 发表于 2022-3-19 10:53
天空宫阙 发表于 2022-3-19 10:43
有,最前面和最后面都有链接

我下载了JS文件,怎么使用啊
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-12-9 06:03

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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