关于使用PyExecJS+nodejs使用与js反混淆
来源:https://cuiqingcai.com/5024.html
梳理这篇博客的时候出问题,我默认的是jscript作为pyexcJs的引擎,问题很大,大部分的js都无法加载,各种包用不了,只能处理及其低端的。
安装nodejs,环境变量配好后还是不行,cmd里可以就是引擎还是jscrapy,尝试卸载jscrapy发现比较难,官网不提供改选引擎的方法,陷入困境。
再之后修改安装选项:
之后还是不行,重启电脑后可以了。
本节来说明一下 JavaScript 加密逻辑分析并利用 Python 模拟执行 JavaScript 实现数据爬取的过程。在这里以中国空气质量在线监测分析平台为例来进行分析,主要分析其加密逻辑及破解方法,并利用 PyExecJS 来实现 JavaScript 模拟执行来实现该网站的数据爬取。
疑难杂症
中国空气质量在线监测分析平台是一个收录全国各大城市天气数据的网站,包括温度、湿度、PM 2.5、AQI 等数据,链接为:https://www.aqistudy.cn/html/city_detail.html,预览图如下:
通过这个网站我们可以获取到各大城市任何一天的天气数据,对数据分析还是非常有用的。
然而不幸的是,该网站的数据接口通信都被加密了。经过分析之后发现其页面数据是通过 Ajax 加载的,数据接口地址是:https://www.aqistudy.cn/apinew/aqistudyapi.php,是一个 POST 形式访问的接口,这个接口的请求数据和返回数据都被加密了,即 POST 请求的 Data、返回的数据都被加密了,下图是数据接口的 Form Data 部分,可见传输数据是一个加密后的字符串:
下图是该接口返回的内容,同样是经过加密的字符串:
遇到这种接口加密的情况,一般来说我们会选择避开请求接口的方式进行数据爬取,如使用 Selenium 模拟浏览器来执行。但这个网站的数据是图表展示的,所以其数据会变得难以提取。
那怎么办呢?刚啊!
一刚到底
之前的老法子都行不通了,那就只能上了!接下来我们就不得不去分析这个网站接口的加密逻辑,并通过一些技巧来破解这个接口了。
首先找到突破口,当我们点击了这个搜索按钮之后,后台便会发出 Ajax 请求,说明这个点击动作是被监听的,所以我们可以找一下这个点击事件对应的处理代码在哪里,这里可以借助于 Firefox 来实现,它可以分析页面某个元素的绑定事件以及定位到具体的代码在哪一行,如图所示:
这里我们发现这个搜索按钮绑定了三个事件,blur、click、focus,同时 Firefox 还帮助我们列出来了对应事件的处理函数在哪个代码的哪一行,这里可以看到 click 事件是在 city_detail.html 的第 139 行处理的,而且是调用了 getData() 函数。
接下来我们就可以顺藤摸瓜,找到 city_detail.html 文件的 getData() 函数,然后再找到这个函数的定义即可,很容易地,我们在 city_detail.html 的第 463 行就找到了这个函数的定义:
经过分析发现它又调用了 getAQIData() 和 getWeatherData() 两个方法,而这两个方法的声明就在下面,再进一步分析发现这两个方法都调用了 getServerData() 这个方法,并传递了 method、param 等参数,然后还有一个回调函数很明显是对返回数据进行处理的,这说明 Ajax 请求就是由这个 getServerData() 方法发起的,如图所示:
所以这里我们只需要再找到 getServerData() 方法的定义即可分析它的加密逻辑了。继续搜索,然而在原始 html 文件中没有搜索到该方法,那就继续去搜寻其他的 JavaScript 文件有没有这个定义,终于经过一番寻找,居然在 jquery-1.8.0.min.js 这个文件中找到了:
有的小伙伴可能会说,jquery.min.js 不是一个库文件吗,怎么会有这种方法声明?嗯,我只想说,最危险的地方就是最安全的地方。
好了,现在终于找到这个方法了,可为什么看不懂呢?这个方法名后面怎么直接跟了一些奇怪的字符串,而且不符合一般的 JavaScript 写法。其实这里是经过 JavaScript 混淆加密了,混淆加密之后,代码将变为不可读的形式,但是功能是完全一致的,这是一种常见的 JavaScript 加密手段。
那到这里了该怎么解呢?当然是接着刚啊!
反混淆
JavaScript 混淆之后,其实是有反混淆方法的,最简单的方法便是搜索在线反混淆网站,这里提供一个:http://www.bm8.com.cn/jsConfusion/,我们将 jquery-1.8.0.min.js 中第二行 eval 开头的混淆后的 JavaScript 代码复制一下,然后粘贴到这个网站中进行反混淆,就可以看到正常的 JavaScript 代码了,搜索一下就可以找到 getServerData() 方法了,可以看到这个方法确实发出了一个 Ajax 请求,请求了刚才我们分析到的接口:
那么到这里我们又可以发现一个很关键的方法,那就是 getParam(),它接受了 method 和 object 参数,然后返回得到的 param 结果就作为 POST Data 参数请求接口了,所以 param 就是加密后的 POST Data,一些加密逻辑都在 getParam() 方法里面,其方法实现如下:
JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
var getParam = (function () {
function ObjectSort(obj) {
var newObject = {};
Object.keys(obj).sort().map(function (key) {
newObject[key] = obj[key]
});
return newObject
}
return function (method, obj) {
var appId = '1a45f75b824b2dc628d5955356b5ef18';
var clienttype = 'WEB';
var timestamp = new Date().getTime();
var param = {
appId: appId,
method: method,
timestamp: timestamp,
clienttype: clienttype,
object: obj,
secret: hex_md5(appId + method + timestamp + clienttype + JSON.stringify(ObjectSort(obj)))
};
param = BASE64.encrypt(JSON.stringify(param));
return AES.encrypt(param, aes_client_key, aes_client_iv)
}
})();
|
可以看到这里使用了 Base64 和 AES 加密。加密之后的字符串便作为 POST Data 传送给服务器了,然后服务器再进行解密处理,然后进行逻辑处理,然后再对处理后的数据进行加密,返回了加密后的数据,那么 JavaScript 再接收到之后再进行一次解密,再渲染才能得到正常的结果。
所以这里还需要分析服务器传回的数据是怎样解密的。顺腾摸瓜,很容易就找到一个 decodeData() 方法,其定义如下:
JavaScript
1
2
3
4
5
6
|
function decodeData(data) {
data = AES.decrypt(data, aes_server_key, aes_server_iv);
data = DES.decrypt(data, des_key, des_iv);
data = BASE64.decrypt(data);
return data
}
|
嗯,这里又经过了三层解密,才把正常的明文数据解析出来。
所以一切都清晰了,我们需要实现两个过程才能正常使用这个接口,即实现 POST Data 的加密过程和 Response Data 的解密过程。其中 POST Data 的加密过程是 Base64 + AES 加密,Response Data 的解密是 AES + DES + Base64 解密。加密解密的 Key 也都在 JavaScript 文件里能找到,我们用 Python 实现这些加密解密过程就可以了。
所以接下来怎么办?接着刚啊!
接着刚才怪!
何必去费那些事去用 Python 重写一遍 JavaScript,万一二者里面有数据格式不统一或者二者由于语言不兼容问题导致计算结果偏差,上哪里去 Debug?
那怎么办?这里我们借助于 PyExecJS 库来实现 JavaScript 模拟就好了。
PyExecJS
PyExecJS 是一个可以使用 Python 来模拟运行 JavaScript 的库。大家可能听说过 PyV8,它也是用来模拟执行 JavaScript 的库,可是由于这个项目已经不维护了,而且对 Python3 的支持不好,而且安装出现各种问题,所以这里选用了 PyExecJS 库来代替它。
首先我们来安装一下这个库:
1
|
pip install PyExecJS
|
使用 pip 安装即可。
在使用这个库之前请确保你的机器上安装了以下其中一个JS运行环境:
- JScript
- JavaScriptCore
- Nashorn
- Node
- PhantomJS
- PyV8
- SlimerJS
- SpiderMonkey
PyExecJS 库会按照优先级调用这些引擎来实现 JavaScript 执行,这里推荐安装 Node.js 或 PhantomJS。
接着我们运行代码检查一下运行环境:
Python
1
2
|
import execjs
print(execjs.get().name)
|
运行之后,由于我安装了 Node.js,所以这里会使用 Node.js 作为渲染引擎,结果如下:
1
|
Node.js (V8)
|
接下来我们将刚才反混淆的 JavaScript 保存成一个文件,叫做 encryption.js,然后用 PyExecJS 模拟运行相关的方法即可。
首先我们来实现加密过程,这里 getServerData() 方法其实已经帮我们实现好了,并实现了 Ajax 请求,但这个方法里面有获取 Storage 的方法,Node.js 不适用,所以这里我们直接改写下,实现一个 getEncryptedData() 方法实现加密,在 encryption.js 里面实现如下方法:
1
2
3
4
5
6
7
8
|
function getEncryptedData(method, city, type, startTime, endTime) {
var param = {};
param.city = city;
param.type = type;
param.startTime = startTime;
param.endTime = endTime;
return getParam(method, param);
}
|
接着我们模拟执行这些方法即可:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
import execjs
# Init environment
node = execjs.get()
# Params
method = 'GETCITYWEATHER'
city = '北京'
type = 'HOUR'
start_time = '2018-01-25 00:00:00'
end_time = '2018-01-25 23:00:00'
# Compile javascript
file = 'encryption.js'
ctx = node.compile(open(file).read())
# Get params
js = 'getEncryptedData("{0}", "{1}", "{2}", "{3}", "{4}")'.format(method, city, type, start_time, end_time)
params = ctx.eval(js)
|
这里我们首先定义一些参数,如 method、city、start_time 等,这些都可以通过分析 JavaScript 很容易得出其规则。
然后这里首先通过 execjs(即 PyExecJS)的 get() 方法声明一个运行环境,然后调用 compile() 方法来执行刚才保存下来的加密库 encryption.js,因为这里面包含了一些加密方法和自定义方法,所以只有执行一遍才能调用。
接着我们再构造一个 js 字符串,传递这些参数,然后通过 eval() 方法来模拟执行,得到的结果赋值为 params,这个就是 POST Data 的加密数据。
接着我们直接用 requests 库来模拟 POST 请求就好了,也没必要用 jQuery 自带的 Ajax 了,当然后者也是可行的,只不过需要加载一下 jQuery 库。
接着我们用 requests 库来模拟 POST 请求:
1
2
3
|
# Get encrypted response text
api = 'https://www.aqistudy.cn/apinew/aqistudyapi.php'
response = requests.post(api, data={'d': params})
|
这样 response 的内容就是服务器返回的加密的内容了。
接下来我们再调用一下 JavaScript 中的 decodeData() 方法即可实现解密:
1
2
3
|
# Decode data
js = 'decodeData("{0}")'.format(response.text)
decrypted_data = ctx.eval(js)
|
这样 decrypted_data 就是解密后的字符串了,解密之后,实际上是一个 JSON 字符串:
1
|
{'success': True, 'errcode': 0, 'errmsg': 'success', 'result': {'success': True, 'data': {'total': 22, 'rows': [{'time': '2018-01-25 00:00:00', 'temp': '-7', 'humi': '35', 'wse': '1', 'wd': '东北风', 'tq': '晴'}, {'time': '2018-01-25 01:00:00', 'temp': '-9', 'humi': '38', 'wse': '1', 'wd': '西风', 'tq': '晴'}, {'time': '2018-01-25 02:00:00', 'temp': '-10', 'humi': '40', 'wse': '1', 'wd': '东北风', 'tq': '晴'}, {'time': '2018-01-25 03:00:00', 'temp': '-8', 'humi': '27', 'wse': '2', 'wd': '东北风', 'tq': '晴'}, {'time': '2018-01-25 04:00:00', 'temp': '-8', 'humi': '26', 'wse': '2', 'wd': '东风', 'tq': '晴'}, {'time': '2018-01-25 05:00:00', 'temp': '-8', 'humi': '23', 'wse': '2', 'wd': '东北风', 'tq': '晴'}, {'time': '2018-01-25 06:00:00', 'temp': '-9', 'humi': '27', 'wse': '2', 'wd': '东北风', 'tq': '多云'}, {'time': '2018-01-25 07:00:00', 'temp': '-9', 'humi': '24', 'wse': '2', 'wd': '东北风', 'tq': '多云'}, {'time': '2018-01-25 08:00:00', 'temp': '-9', 'humi': '25', 'wse': '2', 'wd': '东风', 'tq': '晴转多云转多云间晴'}, {'time': '2018-01-25 09:00:00', 'temp': '-8', 'humi': '21', 'wse': '3', 'wd': '东北风', 'tq': '晴转多云转多云间晴'}, {'time': '2018-01-25 10:00:00', 'temp': '-7', 'humi': '19', 'wse': '3', 'wd': '东北风', 'tq': '晴转多云转多云间晴'}, {'time': '2018-01-25 11:00:00', 'temp': '-6', 'humi': '18', 'wse': '3', 'wd': '东北风', 'tq': '多云'}, {'time': '2018-01-25 12:00:00','temp':'-6','humi':'17','wse':'3','wd':'东北风','tq':'多云'},{'time':'2018-01-25 13:00:00','temp':'-5','humi':'17','wse':'2','wd':'东北风','tq':'多云'},{'time':'2018-01-25 14:00:00','temp':'-5','humi':'16','wse':'2','wd':'东风','tq':'多云'},{'time':'2018-01-25 15:00:00','temp':'-5','humi':'15','wse':'2','wd':'北风','tq':'多云'},{'time':'2018-01-25 16:00:00','temp':'-5','humi':'16','wse':'2','wd':'东北风','tq':'多云'},{'time':'2018-01-25 17:00:00','temp':'-5','humi':'16','wse':'2','wd':'东风','tq':'多云'},{'time':'2018-01-25 18:00:00','temp':'-6','humi':'18','wse':'2','wd':'东风','tq':'晴间多云'},{'time':'2018-01-25 19:00:00','temp':'-7','humi':'19','wse':'2','wd':'东风','tq':'晴间多云'},{'time':'2018-01-25 20:00:00','temp':'-7','humi':'19','wse':'1','wd':'东风','tq':'晴间多云'},{'time':'2018-01-25 21:00:00','temp':'-7','humi':'19','wse':'0','wd':'南风','tq':'晴间多云'}]}}}
|
大功告成!
这样我们就可以成功获取温度、湿度、风力、天气等信息了。
另外这部分数据其实不全,还有 PM 2.5、AQI 等数据需要用另外一个 method 参数 GETDETAIL,修改一下即可获取这部分数据了。
再往后的数据就是解析和存储了,这里不再赘述。
关于使用PyExecJS+nodejs使用与js反混淆的更多相关文章
- js混淆代码还原-js反混淆:利用js进行赋值实现
js混淆代码还原-js反混淆:利用js进行赋值实现 [不想用工具的直接看方法二] 本文地址:http://www.cnblogs.com/vnii/archive/2011/12/14/22875 ...
- js反混淆
var esprima = require('esprima') var escodegen = require('escodegen') content = "function _0x35 ...
- js反混淆工具
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <html> <head ...
- js混淆 反混淆 在线
js反混淆地址:http://www.bm8.com.cn/jsConfusion/ 在线javascript 混淆http://www.moralsoft.com/jso-online/hdojso ...
- 使用VBA进行JS加密的反混淆,还原JS代码。
本文地址:http://www.cnblogs.com/Charltsing/p/JSEval.html 联系QQ:564955427 类似下面的代码是登陆 全国企业信用信息公示系统(安徽)(网址:h ...
- 反爬虫之JS反编译:PyExecJS
PyExecJS是python运行JavaScript代码的包 安装: pip install PyExecJS (或者easy_install PyExecJS) 依赖: 需要有执行JS的程序, ...
- js 反显查询。
反显就是点击查询之后,查询条件消失,正常解决很容易,就是查询的表单value等于返回过来的值 ,比如这样,初始化时候的devNumber是空的,点击查询的时候会有值,我们在返回来显示就可以了. < ...
- NodeJS 实现 客户端 js 加密
NodeJS 实现 客户端 js 加密 思路: 服务端渲染业务代码js => 前后端约定加密算法 => 业务代码进行签名 => 客户端解密业务代码 => eval 执行 Nod ...
- 通过C#调用,实现js加密代码的反混淆,并运行js函数
前一篇我测试了vba调用htmlfile做反混淆,并执行js加密函数的代码.本文换成C#实现. 联系QQ:564955427 C#操作JS函数,可以通过ScriptControl组件,但这个组件只能在 ...
随机推荐
- RabbitMQ(6) 集群部署
单节点部署 rabbitmq单节点部署比较简单,可以使用apt-get等工具快速安装部署. wget -O- https://www.rabbitmq.com/rabbitmq-release-sig ...
- iOS笔记杂记
Google Mobile Ads SDK更新至7.2.1不能编译,添加依赖库QuartzCore.framework后正常编译 imageName会把image缓存到手机内存里,不适合大量图片浏览会 ...
- JWT的详细简介
什么是JWT Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准.该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场 ...
- python3与python2中的string.join()函数
在python2中,string 模块中有一个join()函数,用于以特定的分隔符分隔源变量中的字符串,将其作为新的元素加入到一个列表中,例如: body=string.join(( "Fr ...
- New Concept English there (3)
25words/ minutes Some time ago,an interesting discovery was made by archaeologists on the Aegean isl ...
- Corosync+pacemaker实现集群的高可用
一.Corosync和pacemaker的了解: Corosync是集群管理套件的一部分,他在传递信息的时候可以通过一个简单的配置文件来定义信息传递的方式和协议等.也就是说,corosync是Mess ...
- IE11降级到IE8
- HDU - 5628:Clarke and math (组合数&线性筛||迪利克雷卷积)
题意:略. 思路:网上是用卷积或者做的,不太会. 因为上一题莫比乌斯有个类似的部分,所以想到了每个素因子单独考虑. 我们用C(x^p)表示p次减少分布在K次减少里的方案数,由隔板法可知,C(x^p)= ...
- stm32寄存器版学习笔记10 SPI
SPI(Serial Peripheral Interface),串行外围设备接口.SPI是一种高速的.全双工.同步的通信总线. SPI接口一般使用4条线通信: MISO 主设备数据输入,从设备数据输 ...
- 正则同时获取a标签里的href,text 2项
Regex regex2 = new Regex(@"<a[^>]+href=\s*(?:'(?<href>[^']+)'|""(?<hre ...