概要
本篇文章主要介绍了某音乐平台播放页面的js逆向思路。
抓包分析
选取一首歌曲,进入到播放页面,打开浏览器的开发者模式,点击播放按钮,进入到XHR选项,可以看到有一个https://music.163.com/weapi/song/enhance/player/url/v1?csrf_token=
链接,响应值中包含了本首歌曲的链接,将歌曲链接放到浏览器中打开,可以直接播放。
查看请求链接的请求头和请求的表单数据,未发现请求头中包含有加密字段,而在请求的表单数据中有两个加密参数params
和encSecKey
,下面将对这两个参数进行分析。
逆向分析
我们先搜索加密参数,这里搜索的是encSecKey
,发现有多个结果,挨个查看搜索结果,在第一个搜索结果中就找到了参数位置
在参数位置打上断点,再次点击播放按钮,js在断点处断开,说明参数生成的位置是正确的,在参数上方展示有本次请求的url(表单数据生成方式相同),若url与歌曲生成的url(https://music.163.com/weapi/song/enhance/player/url/v1?csrf_token=
)不同,则继续执行,直到链接相同。
查看bVc8U
的值,发现包含了我们想要的结果,而这个参数就在上一行生成
var bVc8U = window.asrsea(JSON.stringify(i3x), bsF5K(["流泪", "强"]), bsF5K(St0x.md), bsF5K(["爱心", "女孩", "惊恐", "大笑"]));
e3x.data = j3x.cr4v({
params: bVc8U.encText,
encSecKey: bVc8U.encSecKey
})
查看window.asrsea
方法,跳转到了d
方法,此方法有四个参数,分别对应了window.asrsea
的四个参数,在控制台中打印这四个值,经过多次测试,发现后三个参数为固定值,而第一个参数是一个json字符串,其内的ids
为当前页面的歌曲id:
将d
方法复制出来,将四个参数先固定,然后运行代码,报错a
方法未找到:
function d(d, e, f, g) {
var h = {}
, i = a(16);
return h.encText = b(d, g),
h.encText = b(h.encText, i),
h.encSecKey = c(i, e, f),
h
}
const one = '{"ids":"[2077531015]","level":"standard","encodeType":"aac","csrf_token":""}'
const two = '010001'
const three = '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7'
const four = '0CoJUm6Qyw8W8jud'
console.log(d(one, two, three, four))
在浏览器中a
方法打断点,进入到此方法中,将a
方法复制出来,将后续的b
和c
方法都复制出来
function a(a) {
var d, e, b = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", c = "";
for (d = 0; a > d; d += 1)
e = Math.random() * b.length,
e = Math.floor(e),
c += b.charAt(e);
return c
}
function b(a, b) {
var c = CryptoJS.enc.Utf8.parse(b)
, d = CryptoJS.enc.Utf8.parse("0102030405060708")
, e = CryptoJS.enc.Utf8.parse(a)
, f = CryptoJS.AES.encrypt(e, c, {
iv: d,
mode: CryptoJS.mode.CBC
});
return f.toString()
}
function c(a, b, c) {
var d, e;
return setMaxDigits(131),
d = new RSAKeyPair(b, "", c),
e = encryptedString(d, a)
}
从上边代码可以看出b
方法使用了CryptoJS
加密库,于是我们先引入此库(需要nodejs
环境且安装了这个库)
var CryptoJS = require('crypto-js')
再次运行报错
在setMaxDigits
打上断点,进入到setMaxDigits
函数内部,发现这部分为RSA
加解密的函数,将有关函数全部复制出来,再次运行,即可得到加密的参数
结果
以赵雷的成都为例,歌曲的播放页链接为https://music.163.com/#/song?id=436514312
,因此歌曲的id为436514312
,那么就可以下载本首歌曲
在js中定义一个方法用来调用:
function key(music_id) {
// music_id: 歌曲id, 字符串
var second = '010001'
var third = '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7'
var forth = '0CoJUm6Qyw8W8jud'
var json_str = JSON.stringify({"ids": `[${music_id}]`, "level": "standard", "encodeType": "aac", "csrf_token": ""})
console.log(d(json_str, second, third, forth))
return d(json_str, second, third, forth)
}
使用python
代码调用:
import requests
import execjs
from utils import Tool
def main():
# 音乐id, 成都-赵雷
music_id = '436514312'
# 音乐接口url
url = 'https://music.163.com/weapi/song/enhance/player/url/v1?csrf_token='
with open('./js_data.js', 'r', encoding='utf-8') as f:
js = execjs.compile(f.read())
# 调用js加密
js_data = js.call('key', music_id)
data = {
'params': js_data['encText'],
'encSecKey': js_data['encSecKey']
}
# 请求音乐接口
response = requests.post(url, data=data, headers=Tool().headers)
print(response.json())
m4a_url = response.json()['data'][0]['url']
resp = requests.get(m4a_url, headers=Tool().headers)
with open('./成都-赵雷.mp3', 'wb') as mf:
mf.write(resp.content)
if __name__ == '__main__':
main()
代码运行后,就可将此歌曲下载到本地