js逆向-网易云音乐爬虫 声明:本文仅供学习交流,请勿暴力爬取数据,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!
记录一次完整的js逆向爬虫过程 js混淆后每次请求函数名变量名可能都不同随便打开一首歌
查看页面源代码,没有评论信息,评论是动态获取的 F12 -> 网络 -> Fetch/XHR刷新抓包。 Ctrl+F查询某个评论,评论在 get?csrf_token= 请求中获取 (后面表示为目标url)
无需登录,去掉csrf_token参数 请求 URL: https://music.163.com/weapi/comment/resource/comments/get 请求方法: POST 查看负载,post中包含两个参数params, encSecKey 观察可知参数被加密了
目标:通过js逆向找到未加密的参数,在python中模拟网页的js程序加密,再post请求
确定参数加密位置 查看请求的请求发起程序调用堆栈 点击栈顶程序进入源代码
点击后自动定位到向目标url发送请求的那一行 加入断点,刷新网页 查看作用域,url不是目标url 恢复脚本执行,执行十几次后,url为目标url
此时data已经被加密,逐个程序查看 观察可知数据在t0x/be0x中被加密
查看源代码
1 2 3 4 5 var bVi2x = window .asrsea (JSON .stringify (i0x), bse8W (["流泪" , "强" ]), bse8W (Qu7 n.md ), bse8W (["爱心" , "女孩" , "惊恐" , "大笑" ]));e0x.data = j0x.cr0x ({ params : bVi2x.encText , encSecKey : bVi2x.encSecKey })
params对应bVi2x.encText,encSecKey对应bVi2x.encSecKey bVi2x由window.asrsea函数生成
1 2 3 4 5 6 7 8 9 10 11 i0x = { "rid" : "R_SO_4_1397345903" , "threadId" : "R_SO_4_1397345903" , "pageNo" : "1" , "pageSize" : "20" , "cursor" : "-1" , "offset" : "0" , "orderType" : "1" , "csrf_token" : "" }
现在只需要确定window.asrsea的四个参数和该函数的执行逻辑
四个参数 第一个参数是json对象i0X转为字符串 观察源代码并在控制台验证 其它三个参数为定值
1 2 3 e = '010001' f = '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7' g = '0CoJUm6Qyw8W8jud'
函数的执行逻辑 Ctrl+F查找window.asrsea
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 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" ) , e = CryptoJS .enc .Utf8 .parse (a) , f = CryptoJS .AES .encrypt (e, c, { iv : d, mode : CryptoJS .mode .CBC }); return f.toString () } 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 } window .asrsea = d,
加入断点,执行 确定i和与i对应的encSecKey(这里用已经生成好的一对(i,encSecKey),也可以模拟c函数进行RSA加密随机生成一对)
1 2 i = "iD3QreYEDyh0VaJS" encSecKey = "0a0d995ab03722dc085ef73fe0a067b29bcfdf01c7837c4e118380c6ba2aedbdee2dd8f193fce9d0a6fa7bd27246226e57cfcd9555cfc412b3b460022700e7ac0468232661eef7505ad8df3f5bb69687334075fbd32405d1eb4b264c1c39bde86e004c8637cddba3a7c106e5edab21015654a4c14f686ea40720fd541ad7f4a0"
此时已经确定好所有参数,只需要模拟b函数和d函数
完整的python代码实现 这里只获取了一首歌的评论
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 import requestsimport base64from Crypto.Cipher import AESfrom Crypto.Util.Padding import padimport rsaimport jsonheaders = {"User-Agent" :"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0" } url = "https://music.163.com/weapi/comment/resource/comments/get" bgdata = { "rid" : "R_SO_4_1397345903" , "threadId" : "R_SO_4_1397345903" , "pageNo" : "1" , "pageSize" : "20" , "cursor" : "-1" , "offset" : "0" , "orderType" : "1" , "csrf_token" :"" } e = '010001' f = '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7' g = '0CoJUm6Qyw8W8jud' i = "iD3QreYEDyh0VaJS" encSecKey = "0a0d995ab03722dc085ef73fe0a067b29bcfdf01c7837c4e118380c6ba2aedbdee2dd8f193fce9d0a6fa7bd27246226e57cfcd9555cfc412b3b460022700e7ac0468232661eef7505ad8df3f5bb69687334075fbd32405d1eb4b264c1c39bde86e004c8637cddba3a7c106e5edab21015654a4c14f686ea40720fd541ad7f4a0" def b (data, key ): key = key.encode('utf-8' ) data = data.encode('utf-8' ) iv = b'0102030405060708' cipher = AES.new(key, AES.MODE_CBC, iv) padded_data = pad(data, AES.block_size) encrypted_data = cipher.encrypt(padded_data) encrypted_data_base64 = base64.b64encode(encrypted_data).decode('utf-8' ) return encrypted_data_base64 data = { "params" : b(b(json.dumps(bgdata), g), i), "encSecKey" :encSecKey } resp = requests.post(url=url, headers=headers,data=data) comment = resp.json() print (resp.text)
网易加密底层逻辑 (个人猜想) 生成一个16位随机数 用公钥和 i 进行RSA非对称加密算法生成 encSecKey 用 i 和原参数进行AES对称加密算法生成 encText 向服务器发送 encSecKey 和 encText 服务器用私钥对 encSecKey 进行RSA解密得到 i 再用 i 和 encText 进行AES解密得到 原参数