转载自分析网易云歌曲评论分析加密的JS并且解密,并使用Python抓取歌曲评论 - 『编程语言区』 - 吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn
原文无法显示图片,后来原作者又单独上传了图片的压缩包,我仅将图片插入原文,未做其他修改。
原作者的另一篇类似的文章对某网站进行JS逆向AES实战 - 『编程语言区』 - 吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn
一、首先我们打开网站,找到真实的URL
然后我们打开开发者模式,选中“XHR” 经过我缜密的判断以及六级的英语水平,在Headers得到其真实URL地址为
https://music.163.com/weapi/comment/resource/comments/get?csrf_token=
然后我们点开Preview,分析一下,可以看到hotComments,没错了,就是他。
然后我们再回到headers 我们看到需要两个参数,可惜它加密了,不能直接用
那我就不得不进行解密了
然后我们可以看到需要两个参数params and encSecKey 在接下来我会讲解怎么查找
二、在开发者工具中分析其数据
首先点开这个Initiator,找到这个栈调用,这个栈是从下往上调用的,最上面是最近一次调用
我们点开第一个,然后选择左下角我们,切换我们人类看得懂的代码
然后定位到这一行,打个断点先
打完断点之后刷新一下网页,发现当前的url不是那个comment我们继续下一步
找到想要链接的数据之后 继续点左下角
然后我们看到还是加密过得,继续去栈那边找
后面的我就不演示了,跳过了,经过分析发现之后
annoy ==》b8h.这一步进行了加密,所以我们直接找到这b8h
我们直接给13312这里打个断点
如法炮制,找到那个comment的url
ok,找到
然后我们可以看见这两个加密的参数在这边有出现由此可以推出上一节末尾的留下的问题
#params ==> encText
#encSecKey ==> encSecKey
我们可以看到数据在这边有变化,有一个i8a 右边也有i8a,ok,我们拿出来放到py编译器里面
然后我们把断点打到这里,看看发生了什么事
然后我们一直点下一步,看看在哪里会发生变化
ok,由于我之前走过了,我知道在这里会发生变化,所以我们断点打到这里
我们知道这个参数在window.asrsea函数里发生了变化,我们来搜索看一下
只有两个点,一个是上面那个图片,一个是这里
我们可以看到他把d的值赋值给他了,我们先从上面把那个d函数拿过来先
然后我们回到他的源头
我们可以看到第一个参数就是json:i8a 就是之前那一大长串数据,我们已经提取出来了
然后我们把其他的值丢到console里面运行一下
之后我们得到的数据为:
e = "010001"
f = "00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7"
g = "0CoJUm6Qyw8W8jud"
现在我们还差图中的i,i是什么 是一个16位的随机数,我们可以通过图中的i找到函数里面的a
function a(a a == 16) {
var d, e, b = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", c = "";
# 0....16
for (d = 0; a > d; d += 1)
# 随机数
e = Math.random() * b.length,
#取整
e = Math.floor(e),
#查找字符串
c += b.charAt(e);
return c
}
又因为ef是固定的,i还没拿到,我们需要这个encSecKey的值,定死这个i和encSEcKey后面的事情就好办了
然后我们知道i在这边出现过,我们直接一个断点打过去,看看他真实的值是什么
然后我们可以看到我们之前抓取的参数 我们可以获得i "V5qZ6lHWeg7rHzFJ"
接着我们在98行打个断点,点一下 下一步,我们可以看到98行的运行结果是通过定死i来获取到一个定死的encSecKey:
"80120e0d7626fd139a543169ace736d769ca75684cef835a7fba85d231374e36249bef6458006dcf1b04aa8116f0102c0ecdcae410865156fb06d3d408f592d173f315920c5e49c5809e23a30d4041322a22280f2b2111910dd51b0bbb4c9e317c20037dbda4eec0f67f3b39a7e8b5dbd2bbfc20474ee910ad4db82ad262f8dd"
三、分析数据
ok,我们现在的数据全部拿到了
data = {
"csrf_token": "",
"cursor": "-1",
"offset" : "0",
"orderType": "1",
"pageNo": "1",
"pageSize": "20",
"rid": "R_SO_4_548808659",
"threadId": "R_SO_4_548808659"
}
e = "010001"
f = "00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7"
g = "0CoJUm6Qyw8W8jud"
i = "V5qZ6lHWeg7rHzFJ"
# 定死
def get_encSecKey():
return "80120e0d7626fd139a543169ace736d769ca75684cef835a7fba85d231374e36249bef6458006dcf1b04aa8116f0102c0ecdcae410865156fb06d3d408f592d173f315920c5e49c5809e23a30d4041322a22280f2b2111910dd51b0bbb4c9e317c20037dbda4eec0f67f3b39a7e8b5dbd2bbfc20474ee910ad4db82ad262f8dd"
ief都拿到了,c函数搞定,接下来解决d函数
由于这次我主要是记录怎么分析这个过程,后面的加解密我就不做详细讲述了,大家请看代码
运行结果图为:
import requests
from Crypto.Cipher import AES
from base64 import b64encode
import json
import re
#有发送param and enSecKey
#有加密 得找到没有加密的数据
#解密之后,拿到评论
url = "https://music.163.com/weapi/comment/resource/comments/get?csrf_token="
#params ==> encText
#encSecKey ==> encSecKey
#拿到真实参数
data = {
"csrf_token": "",
"cursor": "-1",
"offset" : "0",
"orderType": "1",
"pageNo": "1",
"pageSize": "20",
"rid": "R_SO_4_548808659",
"threadId": "R_SO_4_548808659"
}
# 处理加密过程 ==》 window.asrsea ==》 window.asrsea = d。接下来我们看加密过程
# d是入口
# 拿到一些常量 也就是定死的值
# var bMl1x = window.asrsea(JSON.stringify(i8a), bwa3x(["流泪", "强"]), bwa3x(RQ4U.md), bwa3x(["爱心", "女孩", "惊恐", "大笑"]));
e = "010001"
f = "00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7"
g = "0CoJUm6Qyw8W8jud"
# i = "8pbpybE55DyevZF4"
i = "V5qZ6lHWeg7rHzFJ"
# 定死i 以拿到以定死的i的encSecKey 不然它变化
def get_encSecKey():
# return "091870ee1d9eaa44f50d8788f77f9c625cafc76c4ade76499875831f8b3ded2f417e4909cb47d8c97bfe5e9eab0466b265e1ad2d96beb0a392f3c54394171f9caba249b01c76630b4c98a63f17236ee783c370a7ff48a6cc7417972afe09a0811027f61bd5c9179deb7174d215e6c3896dc33792d79540b835721ab3e0a95ab0"
return "80120e0d7626fd139a543169ace736d769ca75684cef835a7fba85d231374e36249bef6458006dcf1b04aa8116f0102c0ecdcae410865156fb06d3d408f592d173f315920c5e49c5809e23a30d4041322a22280f2b2111910dd51b0bbb4c9e317c20037dbda4eec0f67f3b39a7e8b5dbd2bbfc20474ee910ad4db82ad262f8dd"
"""
这个函数get_params对应的是 页面里面的 二次加密
#d是抓到的数据
#e是 bwa3x(["流泪", "强"]) ==》固定死的值==》"010001"
#f 很长
#g
function d(d, e, f, g) {
var h = {}
, i = a(16);
#两次加密
d + g ==> b
b + i == >params
h.encText = b(d, g), # g 秘钥
h.encText = b(h.encText, i), # i 秘钥
h.encSecKey = c(i, e, f), # e 和 f 是固定的,再把i固定,encSecKey 也就是固定
return h
}
"""
def get_params(data):#默认收到的是字符串,并非字典
first = enc_params(data, g)
second = enc_params(first, i)
return second
#这个对应的是AES加密转换过程,不做过多讲解 转16倍数,AES加密算法
def to_16(data):
pad = 16 - len(data) % 16
# print("pad: %d , data : %s" %(pad,data))
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)
res = aes.encrypt(data.encode("utf-8"))
# b64处理
#对应toString的过程
return str(b64encode(res), "utf-8")
resp = requests.post(url, data={
"params": get_params(json.dumps(data)),
"encSecKey": get_encSecKey()
})
page = resp.text
obj = re.compile(r'"userId":.*?"nickname":"(?P<name>.*?)",.*?'
r'"commentId":.*?,"content":"(?P<comment>.*?)","', re.S)
# print(page)
res = obj.finditer(page)
for r in res:
print(r.group("name"), ":", r.group("comment").replace("\\", ""))
"""
function a(a a == 16) {
var d, e, b = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", c = "";
# 0....16
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) {#a是需要加密
var c = CryptoJS.enc.Utf8.parse(b) # 秘钥
, d = CryptoJS.enc.Utf8.parse("0102030405060708")
, e = CryptoJS.enc.Utf8.parse(a) # e 是数据
, f = CryptoJS.AES.encrypt(e, c, {# AES加密 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)
}
# var bMl1x = window.asrsea(JSON.stringify(i8a), bwa3x(["流泪", "强"]), bwa3x(RQ4U.md), bwa3x(["爱心", "女孩", "惊恐", "大笑"]));
#d是抓到的数据
#e是 bwa3x(["流泪", "强"]) ==》固定死的值==》"010001"
#f 很长
#g
function d(d, e, f, g) {
var h = {}
, i = a(16);
#两次加密
d + g ==> b
b + i == >params
h.encText = b(d, g), # g 秘钥
h.encText = b(h.encText, i), # i 秘钥
h.encSecKey = c(i, e, f), # e 和 f 是固定的,再把i固定,encSecKey 也就是固定
return h
}
"""