傍晚时分,坐在屋檐下,看着天慢慢地黑下去,心里寂寞而凄凉,感到自己的生命被剥夺了。当时我是个年轻人,但我害怕这样生活下去,衰老下去。在我看来,这是比死亡更可怕的事。
刚好今天生日,祝自己生日快乐吧,向22岁出发。
一、准备工作
1. 导入所需的库
1 2 3 4 5 6 7 import jsonimport randomimport requestsfrom Crypto.Cipher import AES from Crypto.Util.Padding import padfrom base64 import b64encode
2. 捕获评论数据包
网站的评论区数据是通过js动态加载的,在XHR中捕获所有数据包,找到评论区数据包。
3. 分析数据包
在标头可以看到是通过post请求发送的数据,载荷中的表单数据能看出params和encSecKey的值是被加密过的。使用正常方式带上者两个发送post请求是肯定得不到评论数据的,因此必须破解加密。
1 2 3 4 5 6 url = "https://music.163.com/weapi/comment/resource/comments/get?csrf_token=" data = { 'params' : ' ' 'encSecKey' : ' ' }
二、逆向评论数据包
1.堆栈分析
在数据包中,数据的加密过程:明文->加密->密文,在上面看到的就是密文。于是我们在启动器中跟踪调用栈查找何时是明文何时被加密为密文
2. 首先在栈顶处的代码打上断点
3.观察网页
刷新网页并观察,发现歌词和评论都消失了,说明都是在这之后加载的,而我们的断点是下在一个函数里面的;可以判断该函数会被多次调用,因此我们要让网页继续运行直到是评论包调用了这个函数
4. 继续执行
执行两到三次后出现歌词
5. 继续执行到评论出现
说明在歌词出现后再次调用这个函数评论出现,因此我们要找的评论数据就在歌词出来后的这个断点里;刷新网页重新运行直到歌词出现前
可以看到一个params=XXXXX参数,结合前面载荷中的参数,判断出这很可能是加密后的内容,因此在调用堆栈中向下寻找,看看能不能找到加密前的内容
6. 堆栈向下查找并分析
到下面的地方时,参数进入了e0x中,可以看到还是加密的内容,继续寻找
到这里时看到data的内容发生了明显的变化,可以隐约看到一些信息;在上面t0x.be0x中是加密的状态,由此判定数据是在t0x.be0x中被加密
7. 找到加密函数
在本地堆栈信息中找到data所处的位置
发现里面的信息又来自bVi6c中,找到上面的bVi6c
1 2 3 4 5 6 7 8 9 10 11 var bVi6c = window .asrsea (JSON .stringify (i0x), bse6Y (["流泪" , "强" ]), bse6Y (Qu1 x.md ), bse6Y (["爱心" , "女孩" , "惊恐" , "大笑" ]));
三、解析加密函数的参数
1. 后三个参数
可以看到在代码中是写死的
我们在控制台尝试执行这三个参数内容
可以看到生成了固定的字符串
1 2 3 bse6Y(["流泪" , "强" ]='010001' bse6Y(Qu1x.md)='00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e' bse6Y(["爱心" , "女孩" , "惊恐" , "大笑" ])='0CoJUm6Qyw8W8jud'
2.
解析JSON.stringify中的i0x参数
在加密函数处打上断点,追踪i0x的值
在网络捕获数据包,直到评论区数据包出现
评论数据包出现说明数据已经加密过了,重新执行到评论区数据包出现的前一次执行
可以找到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" , "threadId" : "R_SO_4_1392772737" , }
四、解析加密函数
1 var bVi6c = window .asrsea (JSON .stringify (i0x), bse6Y (["流泪" , "强" ]), bse6Y (Qu1 x.md ), bse6Y (["爱心" , "女孩" , "惊恐" , "大笑" ]));
1.
在代码中搜索加密函数,找到加密函数的位置
2. 分析加密函数
可以看到,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 = { "csrf_token" : "c5ab7625a12cbf8010af4c1df61b0f6a" , "cursor" : "-1" , "offset" : "0" , "orderType" : "1" , "pageNo" : "1" , "pageSize" : "20" , "rid" : "R_SO_4_34723470" , "threadId" : "R_SO_4_34723470" , } e="010001" f="00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7" g="0CoJUm6Qyw8W8jud"
3. 逐个分析加密函数内的调用
a()函数 :用于生成16位随机字符串
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
加密函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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' ) def b (a, b ): c = b d = '0102030405060708' e = a f = encrypt_aes(e, c, d) return f
c()函数 :RSA加密,b、c分别最为公钥的指数部分和模数部分,中间的私钥为空。d为创建的密钥对对象,用来对i进行加密。
由于e,f的值都是固定的,c里的设置也是固定的,那么我们其实可以将i也设置为固定的值。
i是个16位随机数,它的作用就是为了让每次加密的结果不同,起混淆的作用,如果我们将它给固定住了也不会影响。
实际流程:c()函数对随机数i进行RSA加密,发送到服务器的时候先对i进行解析,然后得到随机数i作为解析encText的密钥,然后再解析出d也就是i0x中关于歌曲的信息。
1)在i处断点,追踪i的变化
2)刷新网页,执行脚本直到评论数据出现
3)重新刷新网页截到评论包前一个包后停止
4)单步跳过函数直到i变化
4)继续调试直到脚本运行到h后观察encSecKey的值
这样,我们就得到了当i="P5rcbOIWOpY8YqGq"时,encSecKey的值
1 >encSecKey="7b3a12df898a7eaf2f0fc1adf83ddb305f0ee4563c0a7cbdad70ecf9c8118f76a211dba2218339cc28bb9647ea3f0f591dae07051b3956f2af8b15111532ccf94fcd3d106f19594e7bddec1002d695aaf00fdd886519e8df24bb044be1d5b868efc2feba0bc7bb19f1b456e5ee2a6098559c993fc3b45c3a022b660cdb065acf"
4. 获得h.encText的值
可以看到进行了两次加密,我们用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 > >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' ) > >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 = "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 jsonimport randomimport requestsfrom Crypto.Cipher import AES from Crypto.Util.Padding import padfrom base64 import b64encodedef a (a ): b = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" c = "" for d in range (a): e = random.randint(0 , len (b) - 1 ) c += b[e] return c 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' ) def b (a, b ): c = b d = '0102030405060708' e = a f = encrypt_aes(e, c, d) return f if __name__ == '__main__' : i = "P5rcbOIWOpY8YqGq" d = { "csrf_token" : "c5ab7625a12cbf8010af4c1df61b0f6a" , "cursor" : "-1" , "offset" : "0" , "orderType" : "1" , "pageNo" : "1" , "pageSize" : "20" , "rid" : "R_SO_4_34723470" , "threadId" : "R_SO_4_34723470" , } e = "010001" f = "00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7" 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 } 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' ])
爬取成功
保存为json文件