javascript
网易云音乐JS逆向
手把手教你網易云音樂JS逆向
1. 目標數據
- 1.搜索音樂,獲取音樂id
- 2.獲取url,下載音樂
2. 文章目錄
目錄
- 手把手教你網易云音樂JS逆向
- 1. 目標數據
- 2. 文章目錄
- 3. 頁面分析
- 4. 模擬加密,獲取參數
- 5.獲取url,下載音樂
- 6. 歌曲搜索
- 完美收工~~~
- [下一篇 打造網易云圖形界面](https://blog.csdn.net/zly717216/article/details/113548197)
3. 頁面分析
用chrome瀏覽器進入網易云音樂官網,找到一首你喜歡的歌
進入歌曲頁面后,url中有一個參數id,因此可以猜想:如果當前頁面中能找到歌曲的url,只需要用id構造url,就能獲取歌曲的鏈接,但事實上沒有那么簡單(后面會詳細分析)
按F12 ,打開調試面板,刷新。發現,當前頁面是一個html頁面
搜索歌曲id,但是網頁源代碼中并沒有(所以前面的猜想不成立,得另尋他法)
既然這樣,歌曲可能是動態加載的,需要到js中找,點擊 XHR,從第一個到最后一個連接,發現并沒有當前頁面的id(1398663411)
既然是動態加載,再點擊播放試試。功夫不負有心人,果然找到了
既然找到了,那就打開看看它的廬山站面目,看到url,并且是m4a后綴,是不是很驚喜
果然沒錯,就是它
既然這樣我們就試試吧,結果發現,能拿到數據
但是問題來了,params和encSeckey是啥玩意兒?這么長,一看就知道是加過密的,接下來要開啟解密模式了
點擊面板右上角的全局搜索,搜索encSeckey
有3個js文件,一個一個去找吧
先別著急找,像下面這樣,結合起來,在第一個js文件中找到的可能性大一些,既然這樣,那就開始找吧
小插曲:上面為什要搜encSeckey,而不搜params呢?因為用params命名的使用范圍要比encSeckey廣,所以用encSeckey搜好一些(經驗)
點擊第一個js,進去后點擊格式化,搜索encSeckey,發現有3處,
滑到第二處,為什么是第二處呢?很簡單,因為params和encSeckey在一起嘛,不在這你說在哪?好了,到這里,另外的兩個js可以忽略了。
接下來就要分析參數了,我們往上嫖,發現兩個參數分別是由bWv7o的encText、encSecKey兩個屬性。
接下來才是真正的js逆向了,敲黑板了!!!
既然params和encKeckey是由bWv7o產生的,那么我們就要分析bWv7o是什么了。不難發現bWv7o是由window.asrsea(JSON.stringify(i1x), bsK8C(["流淚", "強"]), bsK8C(XR1x.md), bsK8C(["愛心", "女孩", "驚恐", "大笑"]));產生的,那么接下來就找找window.asrsea是啥玩意兒
搜索一下window.asrsea,window.asrsea=d
這里d就不能去搜索了,不現實,原因你懂的。這里的d肯定是和window.asrsea在同一作用域內,要不然咋賦值,稀里嘩啦寶一大堆錯,既然這樣就往上翻吧(因為是賦值,所以不會在下面,沒人這么干)
在同一作用域內的就這么點,就一個d函數
!function() {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)}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}function e(a, b, d,, e) {var f = {};return f.encText = c(a + e, b, d),f}window.asrsea = d,window.ecnonasr = e }();接下來分析d函數是干嘛的,看來看去該函數不就返歸一個h對象嘛,不就相當于window.asrsea=h嘛,而params就是encText ,encSecKey 就是encSecKey;encText 是b(h.encText, i)產生的,encSecKey是由c(i, e, f)產生的,接下來打上斷點,開始調試吧
刷新,點擊播放,發現有4個參數
再點一首歌,發現e,f,g三個參數是固定的
綜上,d參數是變化的,而且變化的地方是id,分析的時候就當它是固定的,接著往后走
h={},i=a(16),那么a(16)是什么?點擊去,一探究竟
經過調試和分析,a函數返回一個16位隨機字符串(既然是隨機的,那么就可以讓它固定)
接著往后走
由此可見b函數,是用來加密的,而且是encText經過了兩次加密,跳進去,看看這個加密函數
原來是AES加密,熟悉AES的,肯定敲開心,不熟悉也沒關系,很容易
接下來分析encSecKey參數,跳入到c函數,長這樣
由此可知,encSeckey是經過RSA加密的,但是這里的a,b,c三個參數是固定值,因此這個參數也可以用固定值
好了,到這里js逆向分析就結束了
4. 模擬加密,獲取參數
class Encrypt:def __init__(self, text):self.data = {'encSecKey': '01ec48cb405730aa77f993a988cc1f5bc1938511d75f49eddc581f2fe2aaf18988853200564b2d4b1312cf6e0bb344425addce5a4c81b38b89a5973900946bd100b0f1865d22d2a8e5dd8be208eb5d6eb2f71309a165daeffe95355e1e44edd65bdf28088fe4f5e835a7d9f7569fc2530f9d17c00b51cfafbe421eb462247ea3'}self.text = textself.key = '0CoJUm6Qyw8W8jud'def get_form_data(self):"""生成表單參數"""# 隨機秘鑰參數,可以用固定值i = "4JknCzx6uEXUwxpU"# 兩次加密first_encrypt = self.AES_encpyt(self.text, self.key)self.data['params'] = self.AES_encpyt(first_encrypt, i)return self.datadef AES_encpyt(self, text, key):"""AES加密"""# AES加密明文必須為16的整數倍padding = 16 - len(text.encode()) % 16text += padding * chr(padding)aes = AES.new(key.encode(), AES.MODE_CBC, b'0102030405060708')enctext = aes.encrypt(text.encode())return b64encode(enctext).decode('utf-8')5.獲取url,下載音樂
class NeteaseCloudMusic:def __init__(self, song):self.url = 'https://music.163.com/weapi/song/enhance/player/url/v1?csrf_token='reqstr = '''authority: music.163.commethod: POSTpath: /weapi/song/enhance/player/url/v1?csrf_token=scheme: httpsaccept: */*accept-encoding: gzip, deflate, braccept-language: zh-CN,zh;q=0.9cache-control: no-cachecontent-length: 434content-type: application/x-www-form-urlencodedcookie: __root_domain_v=.163.com; _qddaz=QD.28yaab.3jymc6.kf0ihnaf; _ntes_nnid=e7c5f90265b4d5a5bcb511efebf7a890,1600596980395; _ntes_nuid=e7c5f90265b4d5a5bcb511efebf7a890; _iuqxldmzr_=32; WM_TID=OlHvFOuIVclAQFQUAEJvJZyLuh3MwtGb; NMTID=00ODCot1Uq8CvcXIUIMmKBlPfRiyfoAAAF3NHwibw; WM_NI=%2BWiHzgkFWg%2BON3YYI0rQzlpsOW8x4BPGt%2FWRNpkD3r2Utv8U1gx6RZgvmmJQ0IpSBgdk1GvY9uIQW6BfIN7lVoHo8z1BIoa%2FdLUgKwpx6twUKJtgDlexKOu7LqWGuYApZzg%3D; WM_NIKE=9ca17ae2e6ffcda170e2e6eeb9c844a3b1aba3b24489eb8eb6d15b929a9baaaa5cace70087b64e8ab18299d02af0fea7c3b92ae989a7a9f96da99a9988aa458eed97bacc3cb28fb68df3798d89f899b74a9499bcd0d65a8eb0a5a5b27af28bbc97bb5ff3b9b8d7d152a5aaa38ec95bf497c0b4c16da8b5ffa8f553fbab87b2d63e82ba87afb66896b18890bb72f39e8790e425a8949b88ca7db4a8fa95f65f8996bc88c768a7a885b0f83d90af99a8f85383b0969be637e2a3; hb_MA-9F44-2FC2BD04228F_source=www.baidu.com; JSESSIONID-WYYY=bERBG86BVbD29X%5C35acjg8ndIoGYPEZvQ8fc0t7WUnMu3KTujvG1zqfSMIG%2By4%2FZRz9hC%2FwBN0Mf%2B%2B1RJBK2TeR96X7l%2BmS%2FHhuuqBwl7yxwe4jQ%5ChzFoFgKylb3ZdOnw6%2FqsqaUYUrJ12EVVy0m66JVlQez0T5ijmgZuOsk0KcMnUe4%3A1611553513123; WEVNSM=1.0.0; WNMCID=kctjbv.1611551714155.01.0origin: https://music.163.compragma: no-cachereferer: https://music.163.com/sec-fetch-dest: emptysec-fetch-mode: corssec-fetch-site: same-originuser-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36'''self.headers = HeaderPrettyDict().pretty(reqstr)self.text = '{"ids":"[' + str(song['song_id']) + ']","level":"standard","encodeType":"aac","csrf_token":""}'self.name = song['song_name']self.singer = song['singer']def music(self):"""獲取音樂的url"""data = Encrypt(self.text).get_form_data()res = requests.post(self.url, headers=self.headers, data=data)song_url = res.json()['data'][0]['url']self.save(self.download(song_url))def download(self, url):"""下載音樂"""headers = {'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36'}res = requests.get(url, headers=headers)return res.contentdef save(self, content):"""保存音樂"""# 當前文件目錄path = os.path.dirname(__file__)# 檢查'data'目錄是否存在,不存在則創建目錄if not os.path.exists(path+'\\data'):os.mkdir(path+'\\data')# 音樂保存路徑music_path = path+'\\data'+f'\\{self.name} {self.singer}.m4a'# 保存if not os.path.exists(music_path):with open(music_path, 'wb') as f:f.write(content)6. 歌曲搜索
以上只能實現單曲下載,下面將實現歌曲搜索功能
先分析一下url的結構:url有連個參數,一個是s(歌曲名),一個是type(類型:單曲、視頻、歌詞等)
type類型總結如下:
| 1 | 單曲 |
| 10 | 專輯 |
| 100 | 歌手 |
| 1014 | 視頻 |
| 1006 | 歌詞 |
| 1000 | 歌單 |
| 1009 | 聲音主播 |
| 1002 | 用戶 |
步驟和前面一樣,先找到數據來源
接下來就要分析請求網址和參數了,乍一看,和前面一模一樣啊
開心吧!!!但是事與愿違,雖然參數一樣,但是用前面的生成參數卻無法get到數據,別慌,方法還是一樣,斷點調試
經過調試之后,我們發現就是d參數不同(d={"hlpretag":"<span class=\"s-fc7\">","hlposttag":"</span>","s":"冬眠","type":"1","offset":"0","total":"true","limit":"30","csrf_token":""}),因此只要構造d參數就能請求到數據了
直接上代碼吧
完美收工~~~
下一篇 打造網易云圖形界面
總結
- 上一篇: Javascript和android原生
- 下一篇: gradle idea java ssm