起因
之前购买的一首彩铃,很喜欢,但众所周知,电信的系统就是那么难用,还经常改版开倒车,所以怎么都找不到了。
当然也不能说是找不到,可能是工作人员失误,能搜索到,能在列表中看到,但是参数错误,播放出来的彩铃并不匹配罢了。
我要找的一首彩铃叫:我崩溃了崩溃了(男人专用),列表在这:https://www.imusic.cn/#/singer/detail/2078n-23-15。
点击跳转后,我们可以看到,列表中有两首,而如果点击播放的话,两首都是女生版,是存在明显错误的。
折腾
第一直觉是研究歌曲ID,看看每次点击播放,发送的是什么,返回的是什么,播放的是什么,应该能找到规律,找到突破口。
请求
如上图,每次点击,所有的Network都在这里,他们分别是什么呢?
两个post请求
这里的两个post请求,分别以contentId和contentId向https://www.imusic.cn/content/getSongContent.html,发送请求,两个请求分别给与了不同的值,但返回的内容是一致的。
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 |
[{songId: "54839", newSongId: 36844, qqPrice: "0", isVipFree: "1",…}] 0: {songId: "54839", newSongId: 36844, qqPrice: "0", isVipFree: "1",…} collected: 0 crbtMdmcId: "810036104184" cring: "imusic://QUFodHRwOi8vbXVzaWMuY3RtdXMuY24vYXVkaW8ucmVzLnYyL211c2ljLzEwNjEvbXAzLzAwLzEzLzUyLzEwNjEwMDEzNTIwNDA4MDAubXAzP3BhcmFtPVNUJmRldmljZWlkPTEwMDAwJnRpbWVzdGFtcD0xNjE2ODUzNDk0JnNpZ24xPTRhNjE2YTNlOTQ2NmM3OGQwYTE5ZmFmMzA1MjA4MDM4JnBvc2l0aW9uPTAmc2lnbjI9NWYzNmVhZTM1NmE0ZDE4ZWExOTNkYTQyODE2MGM3ZGEmY2M9NTI4MiZyZXNpZD0xMDYxMDAxMzUyWlo=" feeMode: "0" free: 0 haspay: 0 isNewSongId: 0 isVipFree: "1" lyric: "[00:00.92]工作总是特别累 崩溃崩溃 ↵[00:01.75]票子总是不够好 崩溃崩溃 ↵[00:02.13]车子总是不够好 崩溃崩溃 ↵[00:02.51]房子总是不够大 崩溃崩溃 ↵[00:02.89]崩溃啊 ↵[00:03.00]十年前我开开心心走路有风 ↵[00:03.41]十年后我无金打睬若不惊风 ↵[00:03.82]从前看到朋友找个地方喝酒 ↵[00:04.24]如今见面点个头他就算招呼 ↵[00:04.65]客户越来越多 朋友越来越少 ↵[00:05.07]年龄越来越大 工作越来越忙 ↵[00:05.48]如今手机上他不停的想 ↵[00:05.82]有哪一个电话是来捞了家长 ↵[00:06.24]工作总是特别累 崩溃崩溃 ↵[00:06.62]票子总是不够好 崩溃崩溃 ↵[00:07.00]车子总是不够好 崩溃崩溃 ↵[00:07.38]房子总是不够大 崩溃崩溃 ↵[00:07.76]崩溃啊 ↵" member: "NONE" mp3: "imusic://QUFodHRwOi8vbXVzaWMuY3RtdXMuY24vYXVkaW8ucmVzLnY2L211c2ljLzEwNjEvbXAzLzAwLzEzLzUyLzEwNjEwMDEzNTIwMDA4MDAubXAzP3BhcmFtPVNUJmRldmljZWlkPTEwMDAwJnRpbWVzdGFtcD0xNjE2ODUzNDk0JnNpZ24xPTRiMjRkOWEwM2FmNzRiMGM5OWYxNGRkYmIyZWFlMWVmJnBvc2l0aW9uPTAmc2lnbjI9ZWYyZmI1N2M5NDlhMzc1ZmZlM2YxOGYyYzE3NWFhNzQmY2M9NTI4MiZyZXNpZD0xMDYxMDAxMzUyWlo=" newSongId: 36844 orderStatu: 2 qqMdmcId: "106100135100" qqPrice: "0" resourceId: "1061001352" singerName: "酷兔兔" songId: "54839" songImg: "//pic.ctmus.cn/pic.nets.v1/nets/upfile/singer/1/cb/3f05c3a4-5b9a-487d-ba2a-3a38a5af9e33.jpg?param=150y150" songName: "我崩溃了崩溃了(男人专用)" |
当然,我们也可以通过第三方工具,看看这个传递与返回。
postman
看了一下这首彩铃上下的post参数,并没有发现什么有价值有规律的事情,但直觉告诉我,可以通过遍历的形式,尝试前后ID是否能找到。
一个小型遍历POST
于是我尝试按照页面的两种请求,尝试获取前后ID的歌曲信息。
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 |
public function post() { for ($x=36800; $x<=46800; $x++) { $post_data = array( 'contentId' => $x.'n' ); $url = 'https://www.imusic.cn/content/getSongContent.html'; $postdata = http_build_query($post_data); $options = array( 'http' => array( 'method' => 'POST', 'header' => 'Content-type:application/x-www-form-urlencoded', 'content' => $postdata, 'timeout' => 15 * 60 // 超时时间(单位:s) ) ); $context = stream_context_create($options); $result = file_get_contents($url, false, $context); echo PHP_EOL; echo $result; } } |
形式大概就是这样,很好理解,就是不断的更新ID,不断的post并且echo。
这样的效率不会很高,1秒钟大概能post100个地址。
但是主要是方便,我要查询的量不是很大,并不是要把整个爱音乐“爬”下来。
如果大的话,自然用别的方法比较好。
我前后遍历了几万条数据,很遗憾,我并没有找到有用的信息。
音乐是怎么播放的
播放的音乐是怎么来的?刚开始不知道怎么也找不到两个很关键的参数,sign1和sign2,如果没有这两个参数,是没办法播放音乐的,请求会被直接拒绝。
那么这个参数是怎么来的呢?
难道是后端处理?但是前端也没有看到任何痕迹表明向后台请求了这个东西。
请求方法
查看播放器JS代码,https://www.imusic.cn/res/js/player/musicPlayer.js
可以看到在一开始就定义了请求接口:
1 |
var musicSingleUrl = '/content/getSongContent.html', //单曲接口 |
请求方法如下:
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 |
//根据歌曲id查找歌曲信息 $.ajax({ url: musicSingleUrl, async: false, type: 'POST', data: {"contentId" : songId}, dataType: "json", success: function(data){ if(data && data.length==0){ base.alert(0,"无法试听!"); return; } if(data){ songInfo.mp3 = data[0].mp3; songInfo.cring = data[0].cring; songInfo.songImg = data[0].songImg; songInfo.lyric = data[0].lyric; songInfo.collected = data[0].collected; songInfo.free = data[0].free; songInfo.feeMode = data[0].feeMode; songInfo.haspay = data[0].haspay; songInfo.crbtMdmcId = data[0].crbtMdmcId; songInfo.newSongId = data[0].newSongId; _this.playNow(songInfo, quanqu, songPriority); } }, error: function(){ songName.text('歌曲加载失败'); setTimeout(function(){if(list_content.children().length > 1) playerMap.next();}, 2000); } }); |
对应的,从返回数据中取值,但是,在这里任然没有看到任何签名的操作。
反思
既然没有向后台请求,那么肯定是浏览器端进行的签名,再找找看是怎么对音乐进行签名的。
我注意到一个参数,中英文混写果然是国内开发者显著特点:
1 |
playerACR.src = playerUI.jiemi(data[0].cring); |
播放器的播放地址,进行了解密,解密数据是返回数据的cring字段,再看看cring是什么内容,上文我们已经通过post查看过这个内容,他就是:
1 |
imusic://QUFodHRwOi8vbXVzaWMuY3RtdXMuY24vYXVkaW8ucmVzLnY2L211c2ljLzEwNjEvbXAzLzAwLzEzLzUyLzEwNjEwMDEzNTIwMDA4MDAubXAzP3BhcmFtPVNUJmRldmljZWlkPTEwMDAwJnRpbWVzdGFtcD0xNjE2ODUzNDk0JnNpZ24xPTRiMjRkOWEwM2FmNzRiMGM5OWYxNGRkYmIyZWFlMWVmJnBvc2l0aW9uPTAmc2lnbjI9ZWYyZmI1N2M5NDlhMzc1ZmZlM2YxOGYyYzE3NWFhNzQmY2M9NTI4MiZyZXNpZD0xMDYxMDAxMzUyWlo= |
是不是咋一看很神奇,其实不然,这种加密方式防君子不防我这样的小人,直接在前端就能看到解密过程,当然,哪怕隐藏了解密过程,也很好破解。
前端JS中有这样一段代码:
1 2 3 4 5 6 7 |
jiemi:function(str) { if (str.search(/^imusic/i)!=-1) { str=str.replace("imusic://",""); str=playerUI.base64decode(str).replace(/^AA|ZZ$/gi,""); return str } } |
翻译过来就,把imusic://删掉,把AA|ZZ也删掉,然后通过base64decode的方式,对字符串解密。
至此,很明显,sign并没有在前端完成,而且后台给定,所以,哪怕我知道resid,也没有办法去获取资源。
好玩的小技巧
分页
在前面的分页列表中,我们能看到,每页的分页数据是15,这个时候,如果我们改变一下URL的值,就能看到更多数据,更方便搜索。
把15改成50即可,通过尝试,发现这里最大值就是50。
https://www.imusic.cn/#/singer/detail/2078n-23-50
播放自定义歌曲
如果想要播放自定义歌曲或订购指定彩铃,可以通过修改DOM对象实现。
1 |
<a href="javascript:;" class="audition" title="试听铃声" songid="2457672" data-newsongid="1376400n" p-type="1"></a> |
将songid=”2457672″改成自己知道的ID,点一下播放或者订购,可以看到效果。
最后的推断
在前面的返回值中的,resourceId: “1061001352“
上一首歌是1061001353,而1061001352出现了两次,接着就是1061001350。
而我们已经得知,解密后的音乐参数中,传递的一个重要值,就是resourceId,将其替换到链接中,可以看到是这样的:
http://music.ctmus.cn/audio.res.v2/music/1061/mp3/00/13/52/resourceId040800.mp3?param=ST&deviceid=10000×tamp=1616777942&sign1=b7c45a083c6bac69637ec0ba8a940384&position=0&sign2=d2a6d03edbd8409fec34740407af8f5d&cc=5282&resid=resourceId
也就是,如果有缘的情况下,我能模拟出sign1和sign2,然后通过resourceId: “1061001351“应该就能拿到这首彩铃。
但是想想也没用,不光需要下载,我更多的是希望知道contentId,这样我直接就能在前台订购这首彩铃!
原创文章,作者:蓝洛水深,如若转载,请注明出处:https://blog.lanluo.cn/10069