0x00 背景介绍

在工作后的休闲时间我比较喜欢打开网络电台听一些有声书,大牛实事点评;不知道从什么时候开始网络上突然流行起了付费音频,很多付费音频都是由名家亲自参与制作,质量非常高 很受大众的喜欢;其中以某电台的付费内容最受欢迎,我也购买了好些套音频来跟着名家的脚步学习;名家制作的付费音频在选择的时候不容二说,直接购买就是;但是也不乏其中有很多的标题党 哗众取宠,能不能在不付费的情况下先进行一次试听呢

0x01 分析

为了方便分析,我特意选择了直接在浏览器中打开电台wap版;随便选择一个付费才能收听的音频,使用Chrome浏览器,调试模式,手机模式如下所示

音频播放URL:http://m.xxx.com/69149360/sound/32173409

其中直接访问 http://m.xxx.com/69149360 可以显示当前主播的所有音频,也就是说 69149360 为主播ID, 而后面的 32173409 应该为当前的音频ID。

点击播放按钮通过chrome 调试 Network功能标签中,可以看到客户端会向服务端请求音频的真实地址用来播放; 通过分析发现以下两个请求比较关键。

第一个关键请求:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

GET http://m.XXXX.com/mobile/track/pay/32173409?device=pc&uid=&token=&isBackend=false&_=1490364593769

服务端返回:

{

    "ret": 0,

    "msg"null,

    "errorCode"null,

    "trackId": 32173409,

    "uid": 69149360,

    "albumId": 6294413,

    "title""演说之禅 | 耿人健 0309",

    "domain""http://audio.pay.xx.com",

    "duration": 2752,

    "totalLength": 22278931,

    "sampleDuration": 180,

    "sampleLength": 1702877,

    "isAuthorized"false,

    "apiVersion""1.0.0",

    "buyKey""fe4f133ccbf4b22dfa2a1e704ccbbda8",

    "seed": 3669,

    "fileId""38*14*54*34*10*54*56*59*50*65*47*62*53*65*65*59*26*14*3*48*38*50*59*61*47*50*59*47*46*59*20*52*59*56*23*26*15*61*16*55*60*15*12*43*8*43*30*55*30*60*11*39*29*52*53*40*15*7*48*3*53*53*46*2*12*46*43*",

    "ep""ixdsaY59SiQC2v0Mb4wd414PUk0i1ibGSddPKQ7mX3e0nu+O2qjckr8Kga7ahPJmVbQjgHJRfvE0jPb8wQMSjrkPPC9VE6CqX9LAvCdcqUKio+NbmGgY"

}

第二个关键请求:

1

http://audio.pay.xx.com/download/1.0.0/preview/1702877/group1/M01/04/6E/wKgJMljAJmaxaBjBAVPzE83Jfuo884.m4a?sign=7b2b193f95d330f616013cc1ff709b01&buy_key=fe4f133ccbf4b22dfa2a1e704ccbbda8&timestamp=1490364575567020&token=6391&duration=2752

第二个请求直接就是m4a 音频地址了,其中服务器地址是 audio.pay.xx.com ,可以轻易的看出应该是从第一条GET中返回的json中获取的;那 这一串参数是怎么得来的呢?

1

/download/1.0.0/preview/1702877/group1/M01/04/6E/wKgJMljAJmaxaBjBAVPzE83Jfuo884.m4a?sign=7b2b193f95d330f616013cc1ff709b01&buy_key=fe4f133ccbf4b22dfa2a1e704ccbbda8&timestamp=1490364575567020&token=6391&duration=2752

0x02 调试

作为一个优秀且长相帅气的看雪潜水多年的网友,遇到问题想到的第一解决方案就是 调(tiao)试(xi); 对付这种web型的协议密钥就不用祭出倚天剑(OD)和屠龙刀(IDA)了,直接利用chrome自带的调试功能以及Fiddler进行劫持就够了。过程如下:

从以上两条请求可以分析得出,音频的最终请求参数不是由服务器返回,而是由本地计算得到的;通常在web开发中js往往就承担了计算以及动态控制责任。由于本人对js不是很熟悉,无法快速定位于是使用了一个本办法:删除触发播放按钮的事件来定位,删除一个点击下播放,查看是否能播放,然后再删除一个再点击播放 如此循环;

在不断删除点击下,最终定位到请求w4a的处理时间在all.js中。打开all.js 发现所有js源码都在同一行,这完全无法分析,而且影响下js断点调试;于是我将js 格式化保存到本地,利用Fiddler 的AutoResponder功能劫持到本地来设置。由于站点使用https 来传输js,Fiddler无法劫持https内容,我们索性将整个请求页面一并劫持并修改源码all.js 去请求非https;Fiddler设置如下

all.js格式化之后变得特别清晰,于是乎我们先稍微看一遍all.js 看看哪里可能是解密的关键地方。干逆向这么久,觉得逆向的过程就是一个猜想和推到猜想的过程;以我C语言功力来理解js 在有可以的地方下断点(关于Chrome JS 调试请看这里: http://www.jb51.net/article/58570.htm)

点击播放,最终发现在 success: function(t) { 这个地方停下来

F11一步步分析得出结论如下

解密播放绝对路径:

KEY通过字典o(s, r) 运算由 dg3utf1k6yxdwi09得到 xkt3a41psizxrh9l

1

t = "dg3utf1k6yxdwi09",e = [19, 1, 4, 7, 30, 14, 28, 8, 24, 17, 6, 35, 34, 16, 9, 10, 13, 22, 32, 29, 31, 21, 18, 3, 2, 23, 25, 27, 11, 20, 5, 15, 12, 0, 33, 26]function o(t, e) {        for (var i = [], o = 0; o < t.length; o++) {            for (var n = 0,            n = "a" <= t[o] && "z" >= t[o] ? t[o].charCodeAt(0) - 97 : t[o] - 0 + 26, a = 0; 36 > a; a++) if (e[a] == n) {                n = a;                break            }            i[o] = 25 < n ? n - 26 : String.fromCharCode(n + 97)        }        return i.join("")    }

再将KEY和第一条GET得到的json内容中的ep一起传入 i(p, e(t.ep)) 中,经过取字典位移等运算得出密文,

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

   function e(t) {

        if (!t) return "";

        var e, i, o, n, a, t = t.toString(),

        s = [ - 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1];

        for (n = t.length, o = 0, a = ""; o < n;) {

            do e = s[255 & t.charCodeAt(o++)];

            while (o < n && -1 == e);

            if ( - 1 == e) break;

            do i = s[255 & t.charCodeAt(o++)];

            while (o < n && -1 == i);

            if ( - 1 == i) break;

            a += String.fromCharCode(e << 2 | (48 & i) >> 4);

            do {

                if (e = 255 & t.charCodeAt(o++), 61 == e) return a;

                e = s[e]

            while ( o < n && - 1 == e );

            if ( - 1 == e) break;

            a += String.fromCharCode((15 & i) << 4 | (60 & e) >> 2);

            do {

                if (i = 255 & t.charCodeAt(o++), 61 == i) return a;

                i = s[i]

            while ( o < n && - 1 == i );

            if ( - 1 == i) break;

            a += String.fromCharCode((3 & e) << 6 | i)

        }

        return a

    }

function i(t, e) {

        for (var i, o = [], n = 0, a = "", s = 0; 256 > s; s++) o[s] = s;

        for (s = 0; 256 > s; s++) n = (n + o[s] + t.charCodeAt(s % t.length)) % 256,

        i = o[s],

        o[s] = o[n],

        o[n] = i;

        for (var r = n = s = 0; r < e.length; r++) s = (s + 1) % 256,

        n = (n + o[s]) % 256,

        i = o[s],

        o[s] = o[n],

        o[n] = i,

        a += String.fromCharCode(e.charCodeAt(r) ^ o[(o[s] + o[n]) % 256]);

        return a

    }

使用json返回的_randomSeed通过算法得到字符串A "oRIBQLWKzamwPSuh\C3:1cdMDTYvJVeH_q97fjX58p4nFsgxyr20t.NObi6-GEAlUk/Z"

1

2

3

4

5

6

7

8

9

10

11

12

13

cg_hun: function() {

            this._cgStr = "";

            var t = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/\\:._-1234567890",

            e = t.length,

            i = 0;

            for (i = 0; i < e; i++) {

                var o = this.ran(),

                n = o * t.length,

                a = parseInt(n);

                this._cgStr += t.charAt(a),

                t = t.split(t.charAt(a)).join("")

            }

        },

再使用json中返回的fileId 字段重新排列字符串A 得到最终结果 preview/1702877/group1/M01/04/6E/wKgJMljAJmaxaBjBAVPzE83Jfuo884.m4a

1

2

3

4

5

6

7

cg_fun: function(t) {

            var t = t.split("*"),

            e = "",

            i = 0;

            for (i = 0; i < t.length - 1; i++) e += this._cgStr.charAt(t[i]);

            return e

        },

有了m4a的绝对路径结果已经很清晰了; 播放地址拼凑方式为:domain+"download"+apiVersion+解密后的绝对路径+?+sign+buy_key+timestamp+token+duration

OK截至到这里,我们已经将收费音频中的试听音频的逻辑分析清楚了,下面我们看看必须购买才能听的音频是如何使用上诉逻辑获取真实地址的;

由于上面的分析使用的一条收费且可以试听的音频,下面我们看看如何获取其他收费的音频

复习一下:获取收费音频的播放列表的URL是 http://m.xxxx.com/zhubo/主播ID ,我们去主页随便选个收费且不能试听的主播ID试试吧 如下:

于是我们可以直接拿这个ID 来直接拼凑成我们分析的第一条GET语句(返回播放json) :

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

GET http://m.xxxx.com/mobile/track/pay/33236908?device=pc&uid=&token=&isBackend=false&_=1490366894901

返回:

{

    ret: 0,

    msg: null,

    errorCode: null,

    trackId: 33236908,

    uid: 26457553,

    albumId: 6222157,

    title: "24. 记忆宫殿法之“配图定位法”",

    domain: "http://audio.pay.xx.com",

    duration: 672,

    totalLength: 5447895,

    sampleDuration: 180,

    sampleLength: 1505660,

    isAuthorized: false,

    apiVersion: "1.0.0",

    buyKey: "fe4f133ccbf4b22dfa2a1e704ccbbda8",

    seed: 6381,

    fileId: "48*49*34*23*60*34*58*25*38*31*1*31*13*13*1*25*54*49*37*24*48*38*25*14*1*31*25*1*2*25*43*19*25*58*59*54*65*9*38*16*27*66*47*4*58*58*50*11*35*11*26*14*54*38*21*49*48*57*36*2*62*13*21*18*10*2*0*",

    ep: "ixdsaY59SiQC2v0Mb4wd414PUk0i1ibGSddPKQ7mX3e0nriK36iKm74I2vaK1alhX+8mhnoCf/Bg2KP7wlBEjbcPMCpXGqCqX9LAvCdRrEOip+Jdm2oR"

}

看到如此熟悉的结果相信大家知道应该怎么做了吧?懂js的同学可以直接还原 all.js的算法,我这里就偷个懒用了浏览器来直接替换json值来计算结果

直接在解密函数头,双击t.ep中的值进行修改;关键值为:ep, fileId,seed, fileId,duration;由函数直接计算的结果为:

1

http://audio.pay.xx.com/download/1.0.0/preview/1505660/group1/M05/04/B8/wKgJN1jRD2CwwIAVAFMg19rph74369.m4a?sign=7273c31c689628328ae876e12b41eaf8&buy_key=fe4f133ccbf4b22dfa2a1e704ccbbda8&timestamp=1490369220604020&token=8200&duration=672

0x03 总结

本篇文章得以完成,完全需要感谢此app在架构过程中产生的逻辑漏洞;(服务器太过于信任客户端和未对收费音频进行认证(音频用户关联性认证)); 对此我要对此app表示感谢!

好了我们的本次分析某电台收费app之旅就结束了,懂js或者会调用js的同学,相信你们直接写个脚本遍历下载本app的所有付费音频应该不是什么难事吧; :) ,此篇文章我也会一并发送到app官方技术邮箱中,如果相信看到本文的朋友动手实战时会发现文中所诉根本不成立。

由于本人技术有限,文中所写如有错误地方还请大家指正海涵。如果有没看明白的同学可以在帖子中留言或者直接站内信联系我(请不要问我其他高深的问题,因为我连js都不会。。。)

0x04 感谢

在 VPP Security Lab 小组中论漏洞挖掘能力我不及@ggggwwww,论漏洞分析能力又不及@少仲。如此菜的人在VPP中得以生存下来是源于两位的无私分享,谢谢你们!

感谢看雪平台上所有无私奉献的大牛,没有你们的文章,估计我的技术应该还处于村口放牛的水平!谢谢!

转载于:https://my.oschina.net/u/998410/blog/1120358

某APP收费音频无会员绕过下载过程分析相关推荐

  1. 某音乐软件 收费音频无会员绕过下载过程分析

    0x00 背景介绍 在工作后的休闲时间我比较喜欢打开网络电台听一些有声书,大牛实事点评:不知道从什么时候开始网络上突然流行起了付费音频,很多付费音频都是由名家亲自参与制作,质量非常高 很受大众的喜欢: ...

  2. 苹果:App Store中国区无版号游戏8月1日起下架;美国计划打造量子互联网;HHVM 4.67 发布 | 极客头条...

    整理 | 屠敏 头图 | CSDN 下载自东方 IC 快来收听极客头条音频版吧,智能播报由出门问问「魔音工坊」提供技术支持. 「极客头条」-- 技术人员的新闻圈! CSDN 的读者朋友们早上好哇,「极 ...

  3. 手机网络专业测试软件,3个专业网速测试APP,免费无广告

    3个专业网速测试APP,免费无广告 2021-02-01 22:01:54 332点赞 2352收藏 139评论 创作立场声明:应用市场大部分测速APP不是有内购就是充满广告,分享3个专业又免费无广告 ...

  4. Iphone 开发播放音频无声音

    在做远程遥控汽车项目中,音频无法播放问题. 问题:APP 播放视频无声音?无论是播放流媒体还是播放本地视频文件均无法播放声音,视频显示正常. AVPlayerViewController,AVPlay ...

  5. 4k超清壁纸APP抓包获取所有壁纸下载地址

    4k超清壁纸APP抓包获取所有壁纸下载地址   Lan   2020-05-09 10:38   288 人阅读  0 条评论 额,最近发现手机壁纸似乎有段时间没换了,刚好又看到网上有一个APP叫做4 ...

  6. 手机厂商筑起APP的“垄断”高墙:用户下载选择权“名存实亡”

    来源:消费者报道 作者:黄成宏 ■按: 当我们在谈论"好消费"时,我们在谈论什么?一年一度315国际消费者权益日存在的意义,正是致力于还原"好消费"的本来面貌. ...

  7. [简单逆向]某直播APP 收费直播链接获取-AES解密

    故事的由来 图片看到有人在推广所谓的"不花钱"APP(但是实际上并不是免费) 下载了试了试,如果都一个样的APP 不过这个APP稍微简单点..几秒就定位到了 [健康生活,远离黄赌毒 ...

  8. Python 抖音无水印视频下载

    引言 上篇文章讲到抖音首页视频的爬取和下载,于是我想到能不能下载特定的视频.网上搜索一番,发现有网站提供抖音无水印视频的下载,但是试了几个,发现下载下来都还是有水印的视频.上篇文章已经实现了首页无水印 ...

  9. 无废话xml下载_建立您自己的网站作为完整的初学者,没有废话

    无废话xml下载 I have always wanted to blog and have my own website and I finally did! It feels so good! 我 ...

最新文章

  1. 图论中的知识点(等待补充和更新)
  2. 【Django】安装及配置
  3. java ftp限速_为什么Java FTP客户端的传输速率存在很大差异
  4. Python从入门到精通:Python装饰器详解
  5. 1743. 从相邻元素对还原数组
  6. python3人工智能网盘_《Python3入门人工智能掌握机器学习+深度学习提升实战能力》百度云网盘资源分享下载[MP4/5.77GB]...
  7. LeetCode 632. 最小区间(排序+滑动窗口)
  8. c++第二次上机实验项目二
  9. Spring全家桶,永远滴神!
  10. 浅谈SQL Server中的事物日志(一)
  11. 一个不错的架构图:基于SpringCloud的微服务项目
  12. CTA策略:主力连续、全合约模式下回测结果的差异探讨
  13. 110KV降压变电所电气一次部分及防雷保护设计
  14. java用dockerfile生成镜像_【HAVENT原创】创建 Dockerfile 生成新的镜像,并发布到 DockerHub...
  15. 花开蝶自来——回到梦开始的地方
  16. HTML+CSS系列实战之表格
  17. 码蹄集 - MT2165 - 小码哥的抽卡之旅1
  18. centos 7 查看oracle,Centos7下oracle配置(详细)
  19. 课堂内外杂志课堂内外杂志社课堂内外编辑部2022年第9期目录
  20. P2327 [SCOI2005]扫雷(递推)

热门文章

  1. AC9560网卡等类似驱动无法正常启动解决方法
  2. java 对象序列化工具类
  3. 象棋为什么不多不少刚好有五个兵?
  4. Solidify实现一个智能合约7(固定大小字节数组)
  5. JsonNode常用方法
  6. jtag keil v11驱动_Keil for ARM/ Realview MDK 中用JTAG调试的方法
  7. 【Vue+SpringBoot】超详细!一周开发一个SpringBoot + Vue+MybatisPlus+Shiro+JWT+Redis前后端分离个人博客项目!!!【项目完结】
  8. Swiper插件之Animate动画
  9. iphone11系列的尺寸_苹果iPhone11和iPhone11ProMax究竟什么区
  10. 笔记本java设置ip地址_如何设置苹果笔记本IP地址