网易云JS逆向分析
网易云JS逆向分析
前言
本文章中所有内容仅供学习交流,严禁用于商业用途和非法用途,否则由此产生的一切后果均与文章作者无关,若有侵权,请联系我立即删除!
阅读本篇文章,需要一定的爬虫基础,和js逆向思维,否则无法继续
首先找到一个歌单
找到这个api接口
api传参为这两个
找到启动器
发现这两个参数和刚刚api接口是一模一样的,那么大概率源码就在这里
打断点,分析加密参数
刷新页面,已经卡在这里了
分析每个api参数
X1x为后端api地址
i1x为csrf_token
e1x为请求方式,该值为post
我们的目标是抓取歌单评论信息,一直放行,直到这个接口
分析这个参数,如下
分析rid:A_PL_0_3865036,这后面的就是歌单id,threadId:A_PL_0_3865036,后面也是歌单id信息,后面的参数基本没用了,页面大小,偏移量和歌单属性
那么就可以得到python代码向后端传的参数为这个
def get_data():
id=3865036 # 歌单id
url = "https://music.163.com/weapi/comment/resource/comments/get"
data = {
'csrf_token': "",
"cursor": "-1", # 0为精彩评论,-1为最新评论
"offset": "0",
"orderType": "1",
"pageNo": "1",
"pageSize": "20",
"rid": f"A_PL_0_{id}",
"threadId": f"A_PL_0_{id}"
}
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36'
}
resp = requests.post(url, data=data, headers=headers)
print(resp.text)
但是呢没有任何响应结果,说明参数加密了
对比源参数即可看到,这些值都是乱码,显然是进行了加密的
那么就要找js的加密节点,怎么找?回到这个界面
把这段代码复制下来仔细分析
var bVi3x = window.asrsea(JSON.stringify(i1x), bse0x(["流泪", "强"]), bse0x(Qu1x.md), bse0x(["爱心", "女孩", "惊恐", "大笑"]));
分别对应如下值
这里我们就得到了每个参数的值
使用变量进行替换
# 服务于函数d
g = "0CoJUm6Qyw8W8jud" # bse0x(["爱心", "女孩", "惊恐", "大笑"])
f = "00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7"
e = "010001" # bse0x(["流泪", "强"])
再放行,找到加密节点
鼠标移动到这里,即可看到,使用了一个方法,方法名称叫 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") // AES加密算法中需要的偏移量
, 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
}();
仔细分析AES加密特征,打断点
放行
打印这些值
此时即可得到参数 i ,如果你多次执行就会发现这个 i 值是变化的,调用的是a函数传了个16
a 函数这就是一个随机计算结果,可以直接定死
而 i 已经定死了,i = "2l8EuJuvLirixMmc" ,这里我执行了第二遍,就用我第二遍的 i 值,得到了已有的值
g = "0CoJUm6Qyw8W8jud" # bse0x(["爱心", "女孩", "惊恐", "大笑"])
f = "00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7"
e = "010001" # bse0x(["流泪", "强"])
i = "2l8EuJuvLirixMmc" # 手动固定,函数中是随机的
偏移量之前已经在js代码的b函数找到了,这张图就可以很清楚的看到解密后的值就是 h.encText 的值
发现这个函数是RSA加密,我们已经得到了这个值,由上图可以看到,i 值为偏移量,e为 010001,f 未知,那么就先放一放
查看这个参数,断点
记得放行查看值
这样就能得到encSecKey的值,分析得到python逆向代码
def get_encSecKey(): # 由于i是固定的,所以经过c函数输出的encSecKey也是固定的
return "288bca7fd6e42e24d4549f155b4eb6317ba323b914c9ee8aaccb786cabcd27773aea34d9413b8902f3a1a86e50863cec7e1f872092585cddd432951f210ae078e2f2c6b486c83c210820d5c09c73e2d3f225401ce1f5543f7a6f903c7bdc65b8ea27894c0299f52d801f05aaa6ec61e34082bec0135b9c74dabcabd1e1dba1ab"
# 把参数进行加密,相当于
def get_params(data): # 默认收到的是字符串
first = enc_paramas(data, g) # 第一次加密,再将第二次结果与i一起加密
second = enc_paramas(first, i) # 第二次加密
return second # 返回的就是paramas
# 将数据长度转化为16的倍数, 为下面加密服务
def to_16(data): #
pad = 16 - len(data) % 16
data += chr(pad) * pad
return data
def enc_paramas(data, key): # 相当于js中的b(a, b)函数
iv = "0102030405060708" # AES加密算法中需要的偏移量
data = to_16(data)
aes = AES.new(key=key.encode('utf-8'), IV=iv.encode('utf-8'), mode=AES.MODE_CBC) # 创建加密器
bs = aes.encrypt(data.encode('utf-8')) # 加密,加密的长度必须是16的倍数,”123456789abcchr(4)chr(4)chr(4)chr(4)"
return str(b64encode(bs), "utf-8") # 转化成字符串
此时将刚刚没有任何结果的代码进行传参即可,半成品代码如下
import json
from base64 import b64encode
from Crypto.Cipher import AES # pip install pycryptodome
import requests
# 设置好已知的值
g = "0CoJUm6Qyw8W8jud" # bse0x(["爱心", "女孩", "惊恐", "大笑"])
f = "00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7"
e = "010001" # bse0x(["流泪", "强"])
i = "2l8EuJuvLirixMmc" # 手动固定,函数中是随机的
def get_data():
id=3865036 # 歌单id
url = "https://music.163.com/weapi/comment/resource/comments/get"
data = {
'csrf_token': "",
"cursor": "-1", # 0为精彩评论,-1为最新评论
"offset": "0",
"orderType": "1",
"pageNo": "1",
"pageSize": "20",
"rid": f"A_PL_0_{id}",
"threadId": f"A_PL_0_{id}"
}
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36'
}
resp = requests.post(url, data={
"params": get_params(json.dumps(data)), # 通过函数的加密得到服务器需要的params参数
"encSecKey": get_encSecKey()
}, headers=headers)
print(resp.text)
if __name__ == '__main__':
get_data()
完整数据清洗后的代码
import random
import json
from base64 import b64encode
from Crypto.Cipher import AES # pip install pycryptodome
import requests
# 服务于函数d
g = "0CoJUm6Qyw8W8jud"
f = "00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7"
e = "010001"
i = "2l8EuJuvLirixMmc" # 手动固定,函数中是随机的
def get_encSecKey(): # 由于i是固定的,所以经过c函数输出的encSecKey也是固定的
return "288bca7fd6e42e24d4549f155b4eb6317ba323b914c9ee8aaccb786cabcd27773aea34d9413b8902f3a1a86e50863cec7e1f872092585cddd432951f210ae078e2f2c6b486c83c210820d5c09c73e2d3f225401ce1f5543f7a6f903c7bdc65b8ea27894c0299f52d801f05aaa6ec61e34082bec0135b9c74dabcabd1e1dba1ab"
# 把参数进行加密
def get_params(data): # 默认收到的是字符串
first = enc_paramas(data, g) # 第一次加密,再将第二次结果与i一起加密
second = enc_paramas(first, i) # 第二次加密
return second # 返回的就是paramas
# 将数据长度转化为16的倍数, 为下面加密服务
def to_16(data):
pad = 16 - len(data) % 16
data += chr(pad) * pad
return data
def enc_paramas(data, key):
iv = "0102030405060708" # AES加密算法中需要的偏移量
data = to_16(data)
aes = AES.new(key=key.encode('utf-8'), IV=iv.encode('utf-8'), mode=AES.MODE_CBC) # 创建加密器
bs = aes.encrypt(data.encode('utf-8')) # 加密,加密的长度必须是16的倍数,”123456789abcchr(4)chr(4)chr(4)chr(4)"
return str(b64encode(bs), "utf-8") # 转化成字符串
def get_data():
id=3865036 # 歌单id
url = "https://music.163.com/weapi/comment/resource/comments/get"
data = {
'csrf_token': "",
"cursor": "-1", # 0为精彩评论,-1为最新评论
"offset": "0",
"orderType": "1",
"pageNo": "1",
"pageSize": "20",
"rid": f"A_PL_0_{id}",
"threadId": f"A_PL_0_{id}"
}
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36'
}
resp = requests.post(url, data={
"params": get_params(json.dumps(data)), # 通过函数的加密得到服务器需要的params参数
"encSecKey": get_encSecKey()
}, headers=headers)
print(resp.text)
dict = json.loads(resp.text) # 将得到的内容转化为字典模式
user_obj = dict['data']['hotComments']
comment_data = []
comment_sum = dict['data']['totalCount']
if not user_obj:
user_obj = dict['data']['comments']
for _ in user_obj:
content = _['content']
nickname = _['user']['nickname']
photo = _['user']['avatarUrl']
timeStr = _['timeStr']
like_count = _['likedCount']
addres = _['ipLocation']['location']
list = {
'nickname': nickname,
'content': content,
'photo': photo,
'timeStr': timeStr,
'addres': addres,
'like_count': like_count,
'com_count': comment_sum
}
comment_data.append(list)
print(comment_data)
if __name__ == '__main__':
get_data()
得到评论结果
其他api接口也是如此,还可以搞一个扫码登录的,下面是我23年8月写的项目
项目推荐
这是我写的一个完整的爬虫django项目,集成了网易云扫码登录,歌单信息,歌曲/歌单评论,歌单收藏,歌曲播放等等运用了aiohttp异步爬虫,requests库,IP池,线程池,redis,中间件mysql数据库,等等等等,项目仅供学习使用,严禁商业用途!!!!记得点个关注,感谢
扫码登录
网易云首页
我的首页
歌单列表
源列表
评论
歌曲播放效果
。。。。。。。
总结
本文章中所有内容仅供学习交流,严禁用于商业用途和非法用途,否则由此产生的一切后果均与文章作者无关,若有侵权,请联系我立即删除!,重要的事儿说三遍!!!
回忆往昔
111