Python执行JS代码
前言
相信接触过 Javascript
逆向的朋友都知道,通常遇见JS逆向时,会有以下这两种情况。
- 简单 JS 破解:通过 Python 代码轻松实现
- 复杂的 JS 破解:代码不容易重写,使用程序直接调用 JS 运行获取结果。
对于简单的 JS 来说,我们可以通过 Python 代码,直接重写,轻轻松松的就能搞定。 而对于复杂的 JS 代码而言呢,由于代码过于复杂,重写太费时费力,且碰到对方更新这就比较麻烦了。所以,我们一般直接使用程序去调用 JS,在 Python 层面就只是获取一个运行结果,这样做相比于重写而言就方便多了。
那么以下就介绍两种关于Python执行JS的模块。
PyExecJS
PyExecJS ,这个库一个最开始诞生于 Ruby 中的库,后来人被移植到了 Python 上,目前看到一些比较新的文章都是用它来执行 JS 代码的,然后它是有多个引擎可以选择的,我们一般选择 NodeJS 作为它的一个引擎执行代码,毕竟 NodeJS 的速度是比较快的而且配置起来比较简单。
注意:几年前已停止维护,可能有小BUG
PyExecJS的安装
首先我们就是要安装引擎了,这个引擎指的就是 JS 的一个运行环境,这边推荐使用 Node.js 。
注意:虽然 Windows 上有个系统自带的 JScript,可以用来作为 PyExecjs 的引擎,但是这个 JScript 很容易与其他的引擎有一个不一样的地方,容易踩到一些奇奇怪怪的坑。所以还是比较建议直接安装 Node.js 。
安装完 Nodejs 之后,我们就需要执行安装 PyExecjs 了:
pip install pyexecjs
直接使用PIP一键安装即可。
首先检测一下运行环境是否正常 如下:
1 | import execjs |
execjs.get() # 查看调用的环境
用此来看看我们的库能不能检测到 Nodejs,如果不能的话那就需要手动设置一下,不过一般像我上面一样正常输出 Node.js
就可以了。 如果,你检测出来的引擎不是 Node.js
的话,那你就需要手动设置一下了,这里有两种设置形式,如下: 选择不同引擎进行解析
1 | # 长期使用 |
由上边可知,我们有两种形式:一种是长期使用的,通过环境变量的形式,通过把环境变量改成大写的 EXECJS_RUNTIME
然后将其值赋值为 Node。 另一种的话,将它改成临时使用的一种方式,这种是直接使用 get,这种做法的话,你在使用的时候就需要使用 Node 变量了,不能直接导入 PyExecjs 来直接开始使用,相对麻烦一些。
PyExecJs的使用
1 | import execjs |
PyExecjs 最简单的用法就是导入包,然后通过 eval 这个方法并传入简单的 JS 代码来执行。但是我们正常情况下肯定不会这么使用,因为我们的 JS 代码是比较复杂的而且 JS 代码内容也是比较多的。
1 | # -*- coding: utf-8 -*- |
这样的话,我们一般通过使用第二种方式,第二种方式是通过使用 compile 对 JS 字符串进行编译,这个编译操作其实就是把参数(jscode)里面的那段 JS 代码给放到一个叫 Context
的上下文中,它并不是我们平时编译程序所说的编译。然后我们 调用 call 方法进行执行。 第一个参数是我们调用 JS 中的的函数名,也就是 hello。然后后面跟着的 hello niko 就是参数,也就是我们 JS 中需要传入到 str 的参数。
注意:JS 代码不要放在和 Python 代码同一个文件中,尽量放在单独的 js 文件中,因为我们的 JS 文件内容比较多。然后通过读取文件的方式,如下代码:
1 | # -*- coding: utf-8 -*- |
这里我通过读取文件的方式,将 js 文件读取进来,把代码读取到我们的字符串里面,这样一方面方便我们管理,另一方面也可以直接通过代码检测自动补全功能,使用起来会比较方便。 然后,这里我们有一个小技巧,我们可以通过 format 字符串拼接的形式,将 Python 中的变量,也就是上面的变量 c
然后将这个变量写入到 Js 代码中,从而变相的实现了通过调用 JS 函数,在没有参数的情况下修改 JS 代码中的特定变量的值。最后我们拼接好了我我们的 JS 代码(add 和 script)。 拼完 JS 代码之后,我们这边再常规的进行一个操作,调用 Call 方法执行 aesEncrypt 这样一个函数,需要注意的是,这个代码里面 return 出来的 JS,它是一个 object,JS 中的 object 也就是 Python 中的字典。
我们实际使用时,如果需要在 Python 中拿到 object 的话,建议把它转换成一个 json 字符串,而不是直接的把结果 return 出来。 因为,有些时候 PyExecjs 对 object 的转换会出现问题,所以我们可能会拿到一些类似于将字典直接用 str 函数包裹后转为字符串的一个东西,这样的话它是无法通过正常的方式去解析的。 或者说你也可能会遇到其情况的报错,总之大家最好先转一下 json 字符串,然后再 return 避免踩坑。这是我们的一个代码。
接下来我们来说一下,PyExecJS 存在的一些问题主要有以下两点:
- 执行大型 JS 时会有点慢(这个是因为,每次执行 JS 代码的时候,都是从命令行去调用到的 JS,所以 JS 代码越复杂的话,nodejs 的初始化时间就越长,这个基本上是无解的)
- 特殊编码的输入或输出参数会出现报错的情况(因为,是从命令行调用的,所以在碰到一些特殊字符输入或输出参数或者 JS 代码本身就有一些特殊字符的情况下,就会直接执行不了,给你抛出一个异常。不过这个跟系统的命令行默认编码有一定关系,具体的话这里就不深究了,直接就说解决方案吧。)
- 可以把输入或输出的参数使用 Base64 编码一下(如果看报错是 JS 代码部分导致的,那就去看看能不能删除代码中的那部分字符或者你自己 new 一个上下文对象,将那个名叫 tempfile 的参数打开,这样在调用的时候,它就直接去执行那个文件了,不过大量调用的情况下,可能会对磁盘造成一定压力。
而如果参数不充分导致的话,有个很简单的方法:就是把参数使用 Base64 编码一下,因为编码之后出来的字符串,我们知道 Base64 编码之后是生成英文和数字组成的。这样就没有特殊符号了。所以就不会出现问题了。
PyV8
这是 Google 官方将 Chrome V8 引擎用 Python 封装的库,和 PyExecJS
相比,这个库很轻量,不需要额外装 JS 环境,因为 V8 本身就是环境,同时也因为不需要启动外部环境,执行速度很快,但我不建议使用他,原因如下:
- 年久失修,最新版本是 2010 年的(https://pypi.org/project/PyV8/#history)
- 存在内存泄漏问题,所以不建议使用
- 不支持PIP直接安装,需手动安装。
PyV8的安装
Python3 安装不要使用pip,因为官方只支持 Python2,需要在这里下载对应系统的二进制文件:emmetio/pyv8-binaries
然后解压后将 PyV8.py 与 _PyV8.so (注意:如不是这两个文件名需要修改) ,将两文件复制到 Python 的 site-packages 目录下,如 /usr/local/lib/python3.6/site-packages
。
PyV8的使用
使用示例:
1 | import PyV8 |