文章目錄
- 前言
- 1. 請求分析
- 2. 獲取引數
- 3. 提取資訊
- 結束語
前言
??免責宣告:
????本篇博文的初衷是分享自己學習逆向分析時的個人感悟,所涉及的內容僅供學習、交流,請勿將其用于非法用途!!!任何由此引發的法律糾紛均與作者本人無關,請自行負責!!!
??著作權宣告:
????未經作者本人授權,禁止轉載!!!

??上篇博客已經分析了網易云音樂的加密引數,本篇通過酷狗音樂進行逆向分析,進而加深對逆向分析流程的理解,
??目標:通過輸入歌名或者歌手名,列出相應的音樂資訊,然后通過選擇某一項,將對應的音樂下載到本地指定目錄,
??工具:Google Chrome、PyCharm
??這里依舊以我最喜歡的歌手本兮為例,這里播放了一首下雪的季節,需要RMB才能聽完整版的:


1. 請求分析
??如果想要下載一首歌,我們首先要獲取到這首歌所對應的 u r l url url,隨機選擇一首歌進行播放,打開Chrome的開發者工具,重繪看一下對應的請求,找到我們想要的歌曲檔案的 u r l url url,就是下面這個:

??然后找到該請求對應的 u r l url url,分析一下該請求:

??可知,獲取資料的
u
r
l
url
url 為https://wwwapi.xxxxx.com/yy/index.php?r=play/getdata&callback=jQuery1910989040365354567_1599909353609&hash=1B5C869853B6A7DD39FED655B2155207&album_id=1819925&dfid=3LfODQ2G5XMN0x1liv3DeyjX&mid=61a73ea098eb98e7c6f4fbc66cd7f367&platid=4&_=1599909353610,請求方式為GET,它要提交的引數已經包含在了
u
r
l
url
url 里,而且里面有很多不是必須的引數,
??經過幾次重繪發現,引數callback和_的值是一直在變化的,其他引數是固定的,根據經驗,GET請求方式的
u
r
l
url
url 一般是可以簡化的,即去掉不是必須的引數后仍然可以正常得到資料,簡化后的
u
r
l
url
url 為https://wwwapi.xxxxx.com/yy/index.php?r=play/getdata&hash=1B5C869853B6A7DD39FED655B2155207,但是通程序式訪問這個
u
r
l
url
url 卻是失敗的,這說明,請求需要cookie,
??綜上分析可以猜測到,一首歌對應一個引數hash的值,而且這個引數肯定在搜索結果中,下面要做的就是找到這個hash,
2. 獲取引數
??我們來到搜索界面:

??然后打開Chrome的開發者工具,重繪看一下對應的請求,找到我們想要的搜索結果串列,就是下面這個:

??可以看到,我們想要的hash其實就是FileHash,而且里面還有歌名、歌手以及專輯等資訊,然后找到對應的
u
r
l
url
url,分析一下該請求:

??依舊是個GET請求,
u
r
l
url
url 為https://complexsearch.xxxxx.com/v2/search/song?callback=callback123&keyword=%E6%9C%AC%E5%85%AE&page=1&pagesize=30&bitrate=0&isfuzzy=0&tag=em&inputtype=0&platform=WebFilter&userid=-1&clientver=2000&iscorrection=1&privilege_filter=0&srcappid=2919&clienttime=1599910861467&mid=1599910861467&uuid=1599910861467&dfid=-&signature=51F1A4D0FBB3DE862AD5E87364E6756A,先簡單分析一下它的引數是什么意思,引數keyword就是我們在搜索那里輸入的內容,引數page為頁數,引數pagesize表示每頁顯示多少條資訊,這里依舊是很長的一串,我嘗試這簡化
u
r
l
url
url,然而并沒有成功,錯誤資訊為"error_msg" : "Parameter Error"、"error_msg" : "err signature"和"error_msg" : "err appid(srcappid) or clientver or mid or dfid",可以推測出引數signature應該是很重要的,而且經過重繪發現引數signature、clienttime、mid和uuid每次都會發生變化,且后面三個一直相同,估計引數可能被加密了,全域搜索引數signature,將其定位:

??果然,引數signature被MD5加密了,打上幾個斷點,然后debug看一下:

??引數是20個,但是只有引數clienttime、mid和uuid發生變化,而且它們還相同,找一下它們來自哪里,向上定位到了它們的位置:

??發現是個時間序列,由JavaScript中的getTime()方法生成的,它回傳的是毫秒數,在Python中可以用time模塊的time()方法代替,下面來模擬一下MD5加密,這里可以使用Python的標準庫hashlib:
def MD5Encrypt(self, text):
# 回傳當前時間的時間戳(1970紀元后經過的浮點秒數)
k = time.time()
k = int(round(k * 1000))
info = ["NVPh5oo715z5DIWAeQlhMDsWXXQV4hwt", "bitrate=0", "callback=callback123",
"clienttime={}".format(k), "clientver=2000", "dfid=-", "inputtype=0",
"iscorrection=1", "isfuzzy=0", "keyword={}".format(text), "mid={}".format(k),
"page=1", "pagesize=30", "platform=WebFilter", "privilege_filter=0",
"srcappid=2919", "tag=em", "userid=-1", "uuid={}".format(k), "NVPh5oo715z5DIWAeQlhMDsWXXQV4hwt"]
# 創建md5物件
new_md5 = md5()
info = ''.join(info)
# 更新哈希物件
new_md5.update(info.encode(encoding='utf-8'))
# 加密
result = new_md5.hexdigest()
return result.upper()
??這個加密的結果就是引數signature,為了檢驗結果的正確性,我們將時間序列和上面的保持一致,即clienttime = mid = uuid=1599910861467,運行結果如下:

??結果是正確的,然后我們拼接成
u
r
l
url
url即可:

??我們訪問拼成的
u
r
l
url
url可以正常得到資料:

?? u r l url url小常識:
????+?表示空格
????/?分隔目錄和子目錄
??????分隔實際的URL和引數
????%?表示特殊字符
????#?表示書簽
????&?表示引數間的分隔符
????=?表示引數的值
3. 提取資訊
??兩個請求的
u
r
l
url
url 我們都已經獲得了,下面就是將資料從json格式的文本中提取出來,然后對歌曲檔案
u
r
l
url
url 發起請求,將結果以二進制形式保存,后綴名為.mp3,不廢話,直接上代碼:
# -*- coding: utf-8 -*-
# @Time : 2020/9/12 21:01
# @Author : XiaYouRan
# @Email : youran.xia@foxmail.com
# @File : kugou_music2.py
# @Software: PyCharm
import time
from hashlib import md5
import json
import requests
import re
import os
class KuGouMusic(object):
def __init__(self):
self.headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) '
'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36'}
def MD5Encrypt(self, text):
# 回傳當前時間的時間戳(1970紀元后經過的浮點秒數)
k = time.time()
k = int(round(k * 1000))
info = ["NVPh5oo715z5DIWAeQlhMDsWXXQV4hwt", "bitrate=0", "callback=callback123",
"clienttime={}".format(k), "clientver=2000", "dfid=-", "inputtype=0",
"iscorrection=1", "isfuzzy=0", "keyword={}".format(text), "mid={}".format(k),
"page=1", "pagesize=30", "platform=WebFilter", "privilege_filter=0",
"srcappid=2919", "tag=em", "userid=-1", "uuid={}".format(k), "NVPh5oo715z5DIWAeQlhMDsWXXQV4hwt"]
# 創建md5物件
new_md5 = md5()
info = ''.join(info)
# 更新哈希物件
new_md5.update(info.encode(encoding='utf-8'))
# 加密
signature = new_md5.hexdigest()
url = 'https://complexsearch.kugou.com/v2/search/song?callback=callback123&keyword={0}' \
'&page=1&pagesize=30&bitrate=0&isfuzzy=0&tag=em&inputtype=0&platform=WebFilter&userid=-1' \
'&clientver=2000&iscorrection=1&privilege_filter=0&srcappid=2919&clienttime={1}&' \
'mid={2}&uuid={3}&dfid=-&signature={4}'.format(text, k, k, k, signature.upper())
return url
def get_html(self, url):
# 加一個cookie
cookie = 'kg_mid=61a73ea098eb98e7c6f4fbc66cd7f367; kg_dfid=3LfODQ2G5XMN0x1liv3DeyjX; kg_dfid_collect=d41d8cd98f00b204e9800998ecf8427e; Hm_lvt_aedee6983d4cfc62f509129360d6bb3d=1599906321; Hm_lpvt_aedee6983d4cfc62f509129360d6bb3d=1599922649'.split(
'; ')
cookie_dict = {}
for co in cookie:
co_list = co.split('=')
cookie_dict[co_list[0]] = co_list[1]
try:
response = requests.get(url, headers=self.headers, cookies=cookie_dict)
response.raise_for_status()
response.encoding = 'utf-8'
return response.text
except Exception as err:
print(err)
return '請求例外'
def parse_text(self, text):
count = 0
hash_list = []
print('{:*^80}'.format('搜索結果如下'))
print('{0:{5}<5}{1:{5}<15}{2:{5}<10}{3:{5}<10}{4:{5}<20}'.format('序號', '歌名', '歌手', '時長(s)', '專輯', chr(12288)))
print('{:-^84}'.format('-'))
song_list = json.loads(text)['data']['lists']
for song in song_list:
singer_name = song['SingerName']
# <em>本兮</em> 正則提取
# 先匹配'</em>'這4中字符, 然后將其替換
pattern = re.compile('[</em>]')
singer_name = re.sub(pattern, '', singer_name)
song_name = song['SongName']
song_name = re.sub(pattern, '', song_name)
album_name = song['AlbumName']
# 時長
duration = song['Duration']
file_hash = song['FileHash']
file_size = song['FileSize']
# 音質為HQ, 高品質
hq_file_hash = song['HQFileHash']
hq_file_size = song['HQFileSize']
# 音質為SQ, 超品質, 即無損, 后綴為flac
sq_file_hash = song['SQFileHash']
sq_file_size = song['SQFileSize']
# MV m4a
mv_hash = song['MvHash']
m4a_size = song['M4aSize']
hash_list.append([file_hash, hq_file_hash, sq_file_hash])
print('{0:{5}<5}{1:{5}<15}{2:{5}<10}{3:{5}<10}{4:{5}<20}'.format(count, song_name, singer_name, duration, album_name,
chr(12288)))
count += 1
if count == 10:
# 為了測驗方便, 這里只顯示了10條資料
break
print('{:*^80}'.format('*'))
return hash_list
def save_file(self, song_text):
filepath = './download'
if not os.path.exists(filepath):
os.mkdir(filepath)
text = json.loads(song_text)['data']
audio_name = text['audio_name']
author_name = text['author_name']
album_name = text['album_name']
img_url = text['img']
lyrics = text['lyrics']
play_url = text['play_url']
response = requests.get(play_url, headers=self.headers)
with open(os.path.join(filepath, audio_name) + '.mp3', 'wb') as f:
f.write(response.content)
print("下載完畢!")
if __name__ == '__main__':
kg = KuGouMusic()
search_info = input("請輸入歌名或歌手: ")
search_url = kg.MD5Encrypt(search_info)
search_text = kg.get_html(search_url)
hash_list = kg.parse_text(search_text[12:-2])
while True:
input_index = eval(input("請輸入要下載歌曲的序號(-1退出): "))
if input_index == -1:
break
download_info = hash_list[input_index]
song_url = 'https://wwwapi.kugou.com/yy/index.php?r=play/getdata&hash={}'.format(download_info[0])
song_text = kg.get_html(song_url)
kg.save_file(song_text)
??測驗結果如下:


結束語
??今天分析的搜索結果介面和我以前分析出來的有點不一樣,而且以前的介面還可以正常使用,沒記錯的話以前是不需要cooike的,
url = "https://songsearch.xxxxx.com/song_search_v2?keyword={}&platform=WebFilter".format(song_name)
??與網易云音樂相比,酷狗音樂的請求相對來說簡單了些,基本上沒有什么加密,而且酷狗的音樂著作權還賊多,我喜歡ヾ(^?^)ノ
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/126504.html
標籤:其他
