共计 9661 个字符,预计需要花费 25 分钟才能阅读完成。
起因
之前购买的一首彩铃,很喜欢,但众所周知,电信的系统就是那么难用,还经常改版开倒车,所以怎么都找不到了。
当然也不能说是找不到,可能是工作人员失误,能搜索到,能在列表中看到,但是参数错误,播放出来的彩铃并不匹配罢了。
我要找的一首彩铃叫:我崩溃了崩溃了(男人专用),列表在这: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 的歌曲信息。
PHP
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 的方式,对字符串解密。
就得到这样的地址:http://music.ctmus.cn/audio.res.v2/music/1061/mp3/00/13/52/1061001352040800.mp3?param=ST&deviceid=10000×tamp=1616777942&sign1=b7c45a083c6bac69637ec0ba8a940384&position=0&sign2=d2a6d03edbd8409fec34740407af8f5d&cc=5282&resid=1061001352
至此,很明显,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,这样我直接就能在前台订购这首彩铃!