0x00 查看所要数据是否在网页源代码
例:选取歌曲马思唯《不怪她》,进入有该歌曲评论的页面。
Ctrl+U查看网页源代码,复制一条评论:不怪她 怪我太爱吃豆瓣酱

在网页源代码中Ctrl+F搜索此条评论发现:评论不存在于网页源代码中。
0x01 查看XHR中是否有所要数据
XMLHttpRequest
(XHR)对象用于与服务器交互。通过XMLHttpRequest可以在不刷新页面的情况下请求特定的URL,获取数据。这允许网页在不影响用户操作的情况下,更新页面的局部内容。XMLHttpRequest
在AJAX编程中被大量使用。
尽管名称如此,XMLHttpRequest
可以用于获取任何类型的数据,而不仅仅是XML它甚至支持。HTTP以外的协议(包括文件://和FTP),尽管可能受到更多出于安全等原因的限制。
如果您的通信流程需要从服务器端接收事件或消息数据,请考虑通过EventSource
接口使用服务器发送的事件。对于全双工的通信,WebSocket可能是更好的选择。
- 在页面中
F12/Ctrl+Shift+I
进入开发者工具调试页面 - Network
- XHR
- Ctrl+R刷新
- 在
get?csrf_token=
中找到评论信息

- 记录需要请求的url:”https://music.163.com/weapi/comment/resource/comments/get?csrf_token=”
- 请求方式为POST
0x03 寻找网易云数据加密方法的过程
- 找到未加密的参数
- 想办法把参数进行加密(必须参考网易的逻辑),params,ensSecKey
- 请求到网易,拿到评论信息

在Initiator中找到Request call stack

Requests call stack中的请求是自下而上逐个发送的。进入第一行即最后一个请求的地址:

不要点击页面中代码,点击大括号{}
切换代码显示格式:

切换后显示如下,在send函数处设置断点,刷新页面,观察右侧:

F8放开请求直到此处为get?csrf_token=

观察e8e中的request的data中存在params表示此处的请求被加密:

逐个往回查看请求中的参数,发现到此处的(anoymous)时,request中的data为未加密的:data: "rid=R_SO_4_1462875618&threadId=R_SO_4_1462875618&pageNo=1&pageSize=20&cursor=-1&offset=0&orderType=1"

但t8l.be8W中的data为加密的:data: "params=sFxBk2Z6Y%2BTq3Gje92YzL4Scro9s4WF4BVXSdAJrzfJmc3HRArb6DWX0XNfdulAGKUTelfF9DQ7nlOjHMxknan4PDoP1VOkt3gB3tMZccgaKWVPiu9xHOBE0aY%2B50%2FRtvs5KcoLEMMa8ozNV5swlUzUg6VXZ2PvYOuvAiEd3BvPY%2BSd%2FqVKWwLnTN4f4vp6KbEdjJb74S6AI8H6wLDoQzrYG%2B%2B%2FDwm7lU%2BKyO%2BRFZrRlO9YKfOzoe9QpOEZEGA606MaRb8yPxW%2BGUdy02VI%2B%2BSuq5w173i6TrxEgwm0r6kI%3D&encSecKey=b63324d2d3fd967f3b060670d968c68ad71897f0e1d3b9f8a6c3d9824307c9be67619bba00ca60ec0384d5865732047f71099b572c1214da786aa1f3f434bf1508673497f1a4d960f23e94a0242becf27ccf9a91945f5128497eedb76b7455001f2e0feac78306cc6d1a2a51e3255a6d94ceee0695c97721dc91cae61f95ebe6"
可知:数据在倒数第四个请求中被加密。且在代码中可以发现实现加密的代码段为:

在这个函数的入口设置一个断点并在var bWv6p = window.asrsea()处设置一个断点后发现:

真正的参数是i8a。F10向下执行一步在执行window.asrsea后,Local中多了bMv6p:

继续向下观察代码,发现bWv6p的encText和encSecKey被赋值给了e8e.data:

最后总结出:网易云的加密策略是:
var bWv6p = window.asrsea(JSON.stringify(i8a), bsK3x(["流泪", "强"]), bsK3x(XR5W.md), bsK3x(["爱心", "女孩", "惊恐", "大笑"]));
e8e.data = j8b.cr9i({
params: bWv6p.encText,
encSecKey: bWv6p.encSecKey
})
真实的参数是:
csrf_token: ""
cursor: "-1"
offset: "0"
orderType: "1"
pageNo: "1"
pageSize: "20"
rid: "R_SO_4_1462875618"
threadId: "R_SO_4_1462875618"
0x04 破解网易云数据加密的过程
加密的参数:
- params => encText
- encSecKey => encSecKey
加密过程:
function a(a) {
var d, e, b = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", c = "";
for (d = 0; a > d; d += 1) # 循环16次
e = Math.random() * b.length, # 随机数 ep:1.2345
e = Math.floor(e), # 取整 ep:1
c += b.charAt(e); # 取字符串中的xxx位置 ep:取b 会取得16个字母或数字
return c
}
function b(a, b) { # a是要加密的内容
var c = CryptoJS.enc.Utf8.parse(b) # c的数据是b传来的 b就是密钥
, d = CryptoJS.enc.Utf8.parse("0102030405060708")
, e = CryptoJS.enc.Utf8.parse(a) # e是数据
, f = CryptoJS.AES.encrypt(e, c, { # AEC加密方法:c是加密的密钥
iv: d, # AES加密中的iv是偏移量
mode: CryptoJS.mode.CBC # mode:CBC模式
});
return f.toString()
}
function c(a, b, c) { # c不产生随机数
var d, e;
return setMaxDigits(131),
d = new RSAKeyPair(b,"",c),
e = encryptedString(d, a)
}
function d(d, e, f, g) { # d:数据 e = 010001 f=很长 g=0CoJUm6Qyw8W8jud
var h = {} # 空对象
, i = a(16); # i是一个16位的随机数 把i设置成定值
return h.encText = b(d, g), # b的数据是g传过来的 g就是密钥
h.encText = b(h.encText, i), # 返回的就是params i也是密钥 d是变化的数据
h.encSecKey = c(i, e, f), # 得到的就是encSecKey e和f是定值 c函数的随机只与i有关,如果此时把i固定就可以确定c返回的结果
h
}
function e(a, b, d, e) {
var f = {};
return f.encText = c(a + e, b, d),
f
}
window.asrsea = d, # 入口是d
window.ecnonasr = e
# 两次加密:
数据+g -> b加密 -> 第一次加密+i -> b加密 -> params
加密过程中只有i是随机的,且c函数中不产生随机数,所以除了d(数据)之外唯一的变量就是i的值。
若要获取此首歌的评论,只需提前抓取此首歌中i的值,并在程序中将i设置为常量。
通过在d函数中的h.encText = b(h.encText, i)
处设置断点,放开请求,获取到此首歌中i的值为i: "LVpsmYgr1qUy4XoO"

得到了参数i,则服务于d()函数的参数全部获取完毕,下一步处理加密过程:
报错:TypeError: Object type class ‘str’ cannot be passed to C code
解决:明文、密钥、IV都要编码。并且根据AES加密算法的加密规则:加密的内容必须是16的倍数,所以定义将内容转化为16的倍数的方法:
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的倍数
def to_16(data):
pad = 16 - len(data) % 16
data += chr(pad) * pad
return data
0x05 程序源代码
- pycryptodome 3.10.1 whl文件下载地址
- pip install pycryptodome==3.10.1
from Crypto.Cipher import AES
from base64 import b64encode
import requests
import json
url = "https://music.163.com/weapi/comment/resource/comments/get?csrf_token="
# 服务于d的参数
f = "00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7"
g = "0CoJUm6Qyw8W8jud"
e = "010001"
i = "LVpsmYgr1qUy4XoO" # 手动固定(在网易中是随机的)
data = {
"csrf_token": "",
"cursor": "-1",
"offset": "0",
"orderType": "1",
"pageNo": "1",
"pageSize": "20",
"rid": "R_SO_4_1462875618",
"threadId": "R_SO_4_1462875618"
}
def get_encSecKey(): # 由于i是固定的 所以encSecKey是固定的 c()函数的结果就是固定的
return "9f6b28c353754e75b7dcc4f93d17d28ba9c7bde876182bbfcab4f47b4c57fbc682fbdb7959139f27978171b4f0f731f6c4d1f3200988d4bbcbff204dda7d4171d6c08f864f26e25bb3f030d3e8bd3cc19821e9a699cc5c268b7eb3da4cf1a67589f132dc98f26ac6c269d1a7cbf101d8f4dc16db1ee91429cbf3f795f4f049d4"
# 把参数进行加密
def get_params(data): # 默认这里接受的是字符串
first = enc_params(data, g)
second = enc_params(first, i)
return second # 返回的是params
# 转化成16的倍数 为加密过程的加密算法服务
def to_16(data):
pad = 16 - len(data) % 16
data += chr(pad) * pad
return data
# 加密过程
def enc_params(data, key):
iv = "0102030405060708"
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的倍数
return str(b64encode(bs), "utf-8") # 转化成字符串返回
# 发送请求 得到评论结果
resp = requests.post(url, data={
"params": get_params(json.dumps(data)), # 此处需把data数据从字符串转化为json类型
"encSecKey": get_encSecKey()
})
print(resp.text)