抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

Dekel'Blog

奔赴山海,保持热爱

傍晚时分,坐在屋檐下,看着天慢慢地黑下去,心里寂寞而凄凉,感到自己的生命被剥夺了。当时我是个年轻人,但我害怕这样生活下去,衰老下去。在我看来,这是比死亡更可怕的事。

刚好今天生日,祝自己生日快乐吧,向22岁出发。

一、准备工作

1. 导入所需的库

1
2
3
4
5
6
7
import json
import random
import requests
# 实现AES加密需要的三个模块
from Crypto.Cipher import AES # AES加密
from Crypto.Util.Padding import pad
from base64 import b64encode

2. 捕获评论数据包

网站的评论区数据是通过js动态加载的,在XHR中捕获所有数据包,找到评论区数据包。

image-20240715005559056

3. 分析数据包

在标头可以看到是通过post请求发送的数据,载荷中的表单数据能看出params和encSecKey的值是被加密过的。使用正常方式带上者两个发送post请求是肯定得不到评论数据的,因此必须破解加密。

image-20240715005821999

image-20240715005939746

1
2
3
4
5
6
# 请求评论的url地址及发送的请求数据,空串为我们后面需要破解然后填入的位置
url = "https://music.163.com/weapi/comment/resource/comments/get?csrf_token="
data = {
'params': ' '
'encSecKey': ' '
}

二、逆向评论数据包

1.堆栈分析

在数据包中,数据的加密过程:明文->加密->密文,在上面看到的就是密文。于是我们在启动器中跟踪调用栈查找何时是明文何时被加密为密文

image-20240715125143532

2. 首先在栈顶处的代码打上断点

3.观察网页

刷新网页并观察,发现歌词和评论都消失了,说明都是在这之后加载的,而我们的断点是下在一个函数里面的;可以判断该函数会被多次调用,因此我们要让网页继续运行直到是评论包调用了这个函数

image-20240715125440902image-20240715125453519

4. 继续执行

执行两到三次后出现歌词

image-20240715125732003

5. 继续执行到评论出现

说明在歌词出现后再次调用这个函数评论出现,因此我们要找的评论数据就在歌词出来后的这个断点里;刷新网页重新运行直到歌词出现前

image-20240715130247608

可以看到一个params=XXXXX参数,结合前面载荷中的参数,判断出这很可能是加密后的内容,因此在调用堆栈中向下寻找,看看能不能找到加密前的内容

6. 堆栈向下查找并分析

image-20240715130502259

image-20240715130703700

到下面的地方时,参数进入了e0x中,可以看到还是加密的内容,继续寻找

image-20240715134259733

到这里时看到data的内容发生了明显的变化,可以隐约看到一些信息;在上面t0x.be0x中是加密的状态,由此判定数据是在t0x.be0x中被加密

7. 找到加密函数

image-20240715134737321

在本地堆栈信息中找到data所处的位置

image-20240715134901819

发现里面的信息又来自bVi6c中,找到上面的bVi6c

1
2
3
4
5
6
7
8
9
10
11
// 加密函数
var bVi6c = window.asrsea(JSON.stringify(i0x), bse6Y(["流泪", "强"]), bse6Y(Qu1x.md), bse6Y(["爱心", "女孩", "惊恐", "大笑"]));

/*
构成:
windows.asrsea() 自定义的加密函数
JSON.stringify() 将字典转换为json字符串类型
bse6Y(["流泪", "强"]) 自定义的函数对象
bse6Y(Qu1x.md) 自定义的函数对象
bse6Y(["爱心", "女孩", "惊恐", "大笑"]) 自定义的函数对象
*/

三、解析加密函数的参数

1. 后三个参数

可以看到在代码中是写死的

image-20240715140241230

我们在控制台尝试执行这三个参数内容

image-20240715140409108

可以看到生成了固定的字符串

1
2
3
bse6Y(["流泪", "强"]='010001'
bse6Y(Qu1x.md)='00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e'
bse6Y(["爱心", "女孩", "惊恐", "大笑"])='0CoJUm6Qyw8W8jud'

2. 解析JSON.stringify中的i0x参数

在加密函数处打上断点,追踪i0x的值

image-20240715143804839

在网络捕获数据包,直到评论区数据包出现

image-20240715144012137

评论数据包出现说明数据已经加密过了,重新执行到评论区数据包出现的前一次执行

image-20240715144140623

可以找到i0x的数据内容

1
2
3
4
5
6
7
8
9
10
i0x = {
"csrf_token": "c5ab7625a12cbf8010af4c1df61b0f6a",
"cursor": "-1",
"offset": "0",
"orderType": "1",
"pageNo": "1",
"pageSize": "20",
"rid": "R_SO_4_1392772737", # 每首歌不同的歌曲id
"threadId": "R_SO_4_1392772737", # 每首歌不同进程id
}

四、解析加密函数

1
var bVi6c = window.asrsea(JSON.stringify(i0x), bse6Y(["流泪", "强"]), bse6Y(Qu1x.md), bse6Y(["爱心", "女孩", "惊恐", "大笑"]));

1. 在代码中搜索加密函数,找到加密函数的位置

image-20240715145310311

2. 分析加密函数

image-20240715151754371

可以看到,d()函数就是加密的主体函数,它分别调用a()、b()、c()函数,最后返回h,而h中就含有encText和encSecKey

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# d需要转为json字符串
# i0x
d = {
"csrf_token": "c5ab7625a12cbf8010af4c1df61b0f6a",
"cursor": "-1",
"offset": "0",
"orderType": "1",
"pageNo": "1",
"pageSize": "20",
"rid": "R_SO_4_34723470", # 不同歌曲id
"threadId": "R_SO_4_34723470", # 不同歌曲id
}
# bse6Y(["流泪", "强"])
e="010001"
# bse6Y(Qu1x.md)
f="00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7"
# bse6Y(["爱心", "女孩", "惊恐", "大笑"])
g="0CoJUm6Qyw8W8jud"

3. 逐个分析加密函数内的调用

a()函数:用于生成16位随机字符串

image-20240715152211429

1
2
3
4
5
6
7
8
# 网站为随机生成,后面我们设置成固定值
def a(a):
b = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
c = ""
for d in range(a):
e = random.randint(0, len(b) - 1)
c += b[e]
return c

b()函数:AES加密函数,对a进行加密,密钥为b,初始化向量为0102030405060708,,采用的是CBC模式;用python按照上面的样子直接构建一个相同的AES加密函数

image-20240715152259888

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# AES-CBC加密
def encrypt_aes(text, key, iv):
cipher = AES.new(key.encode('utf-8'), AES.MODE_CBC, iv.encode('utf-8')) # 设置密钥和初始化向量
padded_text = pad(text.encode('utf-8'), AES.block_size) # 设置加密文本

ciphertext = cipher.encrypt(padded_text) # 应用加密
return b64encode(ciphertext).decode('utf-8') # 返回字符串


# 网站源码的function b(a,b)加密函数
def b(a, b):
c = b
d = '0102030405060708'
e = a
f = encrypt_aes(e, c, d)
return f

c()函数:RSA加密,b、c分别最为公钥的指数部分和模数部分,中间的私钥为空。d为创建的密钥对对象,用来对i进行加密。

image-20240715152501730

由于e,f的值都是固定的,c里的设置也是固定的,那么我们其实可以将i也设置为固定的值。

i是个16位随机数,它的作用就是为了让每次加密的结果不同,起混淆的作用,如果我们将它给固定住了也不会影响。

实际流程:c()函数对随机数i进行RSA加密,发送到服务器的时候先对i进行解析,然后得到随机数i作为解析encText的密钥,然后再解析出d也就是i0x中关于歌曲的信息。

1)在i处断点,追踪i的变化

image-20240715153549144

2)刷新网页,执行脚本直到评论数据出现

image-20240715153914000

3)重新刷新网页截到评论包前一个包后停止

image-20240715155332319

4)单步跳过函数直到i变化

image-20240715155417666

image-20240715155448233

1
2
# i的值
i="P5rcbOIWOpY8YqGq"

4)继续调试直到脚本运行到h后观察encSecKey的值

image-20240715155601199

这样,我们就得到了当i="P5rcbOIWOpY8YqGq"时,encSecKey的值

1
>encSecKey="7b3a12df898a7eaf2f0fc1adf83ddb305f0ee4563c0a7cbdad70ecf9c8118f76a211dba2218339cc28bb9647ea3f0f591dae07051b3956f2af8b15111532ccf94fcd3d106f19594e7bddec1002d695aaf00fdd886519e8df24bb044be1d5b868efc2feba0bc7bb19f1b456e5ee2a6098559c993fc3b45c3a022b660cdb065acf"

4. 获得h.encText的值

image-20240715155923446

可以看到进行了两次加密,我们用python模拟即可。需要将字典d转换为json,在python中使用json.dumps(i0x)即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
># AES-CBC加密
>def encrypt_aes(text, key, iv):
cipher = AES.new(key.encode('utf-8'), AES.MODE_CBC, iv.encode('utf-8')) # 设置密钥和初始化向量
padded_text = pad(text.encode('utf-8'), AES.block_size) # 设置加密文本

ciphertext = cipher.encrypt(padded_text) # 应用加密
return b64encode(ciphertext).decode('utf-8') # 返回字符串


># 网站源码的function b(a,b)加密函数
>def b(a, b):
c = b
d = '0102030405060708'
e = a
f = encrypt_aes(e, c, d)
return f

>d_json = json.dumps(d)

>encText = b(d_json, g)
>encText = b(encText, i)

至此便解析了所有加密函数的内容,将获得的encText和encSecKey填入前面提到的params中和encSecKey中:

1
2
3
4
5
6
# 请求评论的url地址及发送的请求数据,空串为我们后面需要破解然后填入的位置
url = "https://music.163.com/weapi/comment/resource/comments/get?csrf_token="
data = {
'params': encText
'encSecKey': encSecKey
}

五、完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
import json
import random

import requests

# 实现AES加密需要的三个模块
from Crypto.Cipher import AES # AES加密
from Crypto.Util.Padding import pad
from base64 import b64encode


# 网站为随机生成,这里设置成固定值
def a(a):
b = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
c = ""
for d in range(a):
e = random.randint(0, len(b) - 1)
c += b[e]
return c


# AES-CBC加密
def encrypt_aes(text, key, iv):
cipher = AES.new(key.encode('utf-8'), AES.MODE_CBC, iv.encode('utf-8')) # 设置密钥和初始化向量
padded_text = pad(text.encode('utf-8'), AES.block_size) # 设置加密文本

ciphertext = cipher.encrypt(padded_text) # 应用加密
return b64encode(ciphertext).decode('utf-8') # 返回字符串


# 网站源码的function b(a,b)加密函数
def b(a, b):
c = b
d = '0102030405060708'
e = a
f = encrypt_aes(e, c, d)
return f


if __name__ == '__main__':
i = "P5rcbOIWOpY8YqGq"
# d需要转为json字符串
# i0x
d = {
"csrf_token": "c5ab7625a12cbf8010af4c1df61b0f6a",
"cursor": "-1",
"offset": "0",
"orderType": "1",
"pageNo": "1",
"pageSize": "20",
"rid": "R_SO_4_34723470", # 不同歌曲编号
"threadId": "R_SO_4_34723470", # 不同歌曲编号
}
# bse6Y(["流泪", "强"])
e = "010001"
# bse6Y(Qu1x.md)
f = "00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7"
# bse6Y(["爱心", "女孩", "惊恐", "大笑"])
g = "0CoJUm6Qyw8W8jud"

d_json = json.dumps(d)

encText = b(d_json, g)
encText = b(encText, i)

url = 'https://music.163.com/weapi/comment/resource/comments/get?csrf_token='
encSecKey = "7b3a12df898a7eaf2f0fc1adf83ddb305f0ee4563c0a7cbdad70ecf9c8118f76a211dba2218339cc28bb9647ea3f0f591dae07051b3956f2af8b15111532ccf94fcd3d106f19594e7bddec1002d695aaf00fdd886519e8df24bb044be1d5b868efc2feba0bc7bb19f1b456e5ee2a6098559c993fc3b45c3a022b660cdb065acf"

header = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36"
}
data = {
'params': encText,
'encSecKey': encSecKey
}
# print(encText)
# print(encSecKey)
res = requests.post(url, headers=header, data=data)

print(res.text)
res_json = res.json()
with open('东京不太热评论.json', 'w', encoding='utf-8') as f:
json.dump(res.json(), f, ensure_ascii=False, indent=4)
comments_list = res_json['data']['comments']
for comment in comments_list:
print(comment['content'])

爬取成功

image-20240715161534690

保存为json文件

image-20240715161616586

评论

看完了不如留下点什么吧