(四)JS逆向——中国观鸟网
爬取观鸟网的信息
有sign值,timestamp和requestid,要看这些值是怎么生成的
载荷有加密的数据
返回值也经过加密
搜索requestid,找到了eval加密的代码,通过解密,就能找到生成这些值的代码段
代码格式化后,找到了这几个值的生成位置
requestid的生成是随机值,timestamp是时间戳
e是一些限制条件,通过url编码可知,pointname是地点名称,所以e就是一些限制条件,然后序列化成了字符串
sign值就是这个值的字符串拼接,再通过md5加密
JSEncrypt是一个第三方库,通过npm install jsencrypt
encryptUnicodeLong函数是自定义的方法修改的源代码,所以找到这个函数的位置
这就是自定义的函数,使其支持中文加密,复制到本地的源码内,就可以获取data值
python代码
import json import requests from functools import partial import time import hashlib import subprocess subprocess.Popen = partial(subprocess.Popen, encoding="utf-8") import execjs from urllib.parse import urlencode from Crypto.Cipher import AES import base64 from Crypto.Util.Padding import pad,unpad url = "https://api.birdreport.cn/front/record/activity/search" e = { "limit":"20", "page":"4", "pointname":"梧桐沟" } # url编码 e = urlencode(e) # 读取并执行js代码 with open("bird.js", 'r') as f: jscode = f.read() js = execjs.compile(jscode) res = js.call("jiami",e) # 获取请求头和data headers = res.get('header') data = res.get("data") # 补充请求头 headers.update({ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36", "Content-Type":"application/x-www-form-urlencoded; charset=UTF-8", "Referer":"https://www.birdreport.cn/", "User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36", }) res = requests.post(url=url, data=data, headers=headers) data = res.json().get("data") # base64解码 data = base64.b64decode(data) # aes解密 key = "3583ec0257e2f4c8195eec7410ff1619" iv = "d93c0d5ec6352f20" aes = AES.new(key.encode('utf-8'), AES.MODE_CBC,iv.encode()) ret = aes.decrypt(data) print(ret.decode())
js代码
var JSEncrypt = require('node-jsencrypt'); // 引入md5标准库 const CryptoJS = require('crypto-js'); function MD5(password) { const encryptedPassword = CryptoJS.MD5(password).toString(); return encryptedPassword; } function getUuid() { var s = []; var a = "0123456789abcdef"; for (var i = 0; i < 32; i++) { s[i] = a.substr(Math.floor(Math.random() * 0x10), 1) } s[14] = "4"; s[19] = a.substr((s[19] & 0x3) | 0x8, 1); s[8] = s[13] = s[18] = s[23]; var b = s.join(""); return b } function sort_ASCII(a) { var b = new Array(); var c = 0; for (var i in a) { b[c] = i; c++ } var d = b.sort(); var e = {}; for (var i in d) { e[d[i]] = a[d[i]] } return e } function url2json(a) { var b = /^[^\?]+\?([\w\W]+)$/, reg_para = /([^&=]+)=([\w\W]*?)(&|$|#)/g, arr_url = b.exec(a), ret = {}; if (arr_url && arr_url[1]) { var c = arr_url[1], result; while ((result = reg_para.exec(c)) != null) { ret[result[1]] = result[2] } } return ret } function dataTojson(a) { var b = []; var c = {}; b = a.split('&'); for (var i = 0; i < b.length; i++) { if (b[i].indexOf('=') != -1) { var d = b[i].split('='); if (d.length == 2) { c[d[0]] = d[1] } else { c[d[0]] = "" } } else { c[b[i]] = '' } } return c } const serialize = function (a) { var b = []; for (var p in a) if (a.hasOwnProperty(p) && a[p]) { b.push(encodeURIComponent(p) + '=' + encodeURIComponent(a[p])) } return b.join('&') }; var paramPublicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCvxXa98E1uWXnBzXkS2yHUfnBM6n3PCwLdfIox03T91joBvjtoDqiQ5x3tTOfpHs3LtiqMMEafls6b0YWtgB1dse1W5m+FpeusVkCOkQxB4SZDH6tuerIknnmB/Hsq5wgEkIvO5Pff9biig6AyoAkdWpSek/1/B7zYIepYY0lxKQIDAQAB"; var encrypt = new JSEncrypt(); encrypt.setPublicKey(paramPublicKey); function jiami(text) { var c = Date.parse(new Date()); var d = getUuid(); var e = JSON.stringify(sort_ASCII(dataTojson(text || '{}'))); data = encrypt.encryptUnicodeLong(e); var f = MD5(e + d + c); // 返回需要的数据 return { header:{ timestamp:c.toString(), requestId:d, sign:f }, data:data } }