20. JS逆向
一、什么是JS逆向
JavaScript 是一种轻量级的编程语言。JavaScript 是可插入 HTML 页面的编程代码。JavaScript 插入 HTML 页面后,可由所有的现代浏览器执行。
我们在使用爬虫获取网页数据时,可能会遇到通过 JavaScript 代码实现的反爬措施。JS 反爬技术的实现方式包括动态渲染、异步加载、验证码、IP限制等多种方式。这些技术可以有效地防止爬虫的抓取,保护网站的数据安全。
此时,我们需要通过通过分析和破解 JavaScript 代码,获取反爬措施的规则和实现方式,从而绕过反爬措施,实现爬虫的抓取。JS 逆向需要具备一定的 JavaScript 编程能力和代码分析能力。
二、Node.js的安装与配置
Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。Node.js 使用了一个事件驱动的、非阻塞式 I/O 的模型,轻量又高效,它的底层是用 C/C++ 编写的。
2.1、Node.js的安装
我们可以从官网上下载 Node.js,网址如下:https://nodejs.org/en/download/。它的安装步骤什么简单,一路下一步即可。



2.2、Node.js的配置
在执行例如 npm install webpack -g 等命令全局安装的时候,默认会将模块安装在 C:\Users\用户名\AppData\Roaming 路径下的 npm 和 npm_cache 中,不方便管理且占用 C 盘空间。因此,我们需要配置自定义的全局模块安装目录,在 Node.js 安装目录下新建两个文件夹 【node_global】 和 【node_cache】,然后在终端中执行如下两个命令:
npm config set prefix "D:\Nodejs\nodejs\node_global"
npm config set cache "D:\Nodejs\nodejs\node_cache"
其中,
D:\Nodejs\nodejs是 Node.js 的安装目录,node_global和node_cache是刚才创建的文件夹;
执行成功之后,然后在环境变量中新建一个变量名为 【NODE_PATH】, 值为D:\Nodejs\nodejs\node_modules 的变量。【NODE_PATH】 变量就是 Node.js 中用来寻找模块所提供的路径注册环境变量。

最后编辑环境变量 【Path】,将相应 【npm】 的路径改为:D:\Nodejs\nodejs\node_global;


三、补充浏览器环境
现在越来越多的 JavaScript 代码都加入了浏览器的特征,如果你用 Node.js 去运行扣下来的 JavaScript 源代码,可能会报错,也可能得到的结果与浏览器上的不一致,因此也就无法通过服务器的参数校验。因此,我们需要补充浏览器环境。
我们可以通过 Node.js 的 jsdom 模块补充浏览器环境。在终端中通过 npm 安装 jsdom。
npm install -g jsdom
npm install -g node-gyp
当我们安装 jsdom 出现如下问题时,可能是文件权限问题。

这是由于对文件夹操作的权限不够,右击 【Node.js 安装目录】 -> 【属性】-> 【安全】,点击 【编辑】,将所有权限都勾选即可。


有时候由于网络问题,npm 默认使用的是官方源下载 jsdom 模块,但有时候官方源可能会有问题。这时,我们可以尝试切换到其他的镜像源,比如淘宝镜像。你可以使用以下命令进行切换:
npm config set registry https://registry.npmmirror.com
切换完镜像源之后,我们还需要清除 npm 缓存。有时候 npm 缓存中的一些损坏文件可能会导致安装失败。
npm cache verify
JS 代码如下:
const jsdom = require('jsdom');
const { JSDOM } = jsdom;
const resourceLoader = new jsdom.ResourceLoader({
userAgent:"Mozilla/5.0 (Macintosh; Intel Mac 0S X 10_15_7) AppleWebKit/537.36 (KHTML,1ikeGecko) Chrome/96.0.4664.55 Safari/537.36"
});
// 创建一个模拟的浏览器窗口
const html = "<!DOCTYPE html><p>Hello world</p>";
const dom = new JSDOM(html,{
url: "https://www.toutiao.com",
referrer:"https://example.com/",
contentType: "text/html",
// resources: resourceLoader,
});
// window = {}
window = global;
const params = {
location: {
hash: "",
host: "www.toutiao.com",
hostname: "www.toutiao.com",
href: "https://www.toutiao.com",
origin: "https://www.toutiao.com",
pathname: "/",
port: "",
protocol: "https:",
search: "",
},
navigator: {
appCodeName: "Mozilla",
appName: "Netscape",
appVersion: "5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, likeGecko) Chrome/93.0.4577.82 Safari/537.36",
cookieEnabled: true,
deviceMemory: 8,
doNotTrack: null,
hardwareConcurrency: 4,
language: "zh-CN",
languages: ["zh-cN", "zh"],
maxTouchPoints: 0,
onLine: true,
platform: "MacIntel",
product: "Gecko",
productSub: "20030107",
userAgent: "Mozi1la/5.0 (Macintosh; Intel Mac 0S X 10_15_7) AppleWebKit/537.36 (KHTML,1ike Gecko) Chrome/93.0.4577.82 Safari/537.36",
vendor: "Google Inc."
}
}
Object.assign(global,params);
location = window.location
function func(arg){
return arg + "\n" + location.href + "\n" + window.navigator.userAgent
}
Python 代码如下:
import execjs
if __name__ == "__main__":
with open("v1.js", mode="r", encoding="utf-8") as f:
js_string = f.read()
JS = execjs.compile(js_string, cwd=r"D:\Nodejs\nodejs\node_global\node_modules")
sign = JS.call("func", "Sakura")
print(sign)
四、Python执行JS代码
在 Python 中,我们可以通过 pyexecjs 模块执行 JS 代码。但执行 JavaScript 的功能还需要依赖 JavaScript 的运行环境。这里,推荐使用 Node.js 环境。我们可以在终端中通过 pip 安装 pyexecjs 模块。
pip install pyexecjs
Python 内容如下:
import execjs
print(execjs.get().name)
with open("v1.js", mode="r", encoding="utf-8") as f:
js_string = f.read()
JS = execjs.compile(js_string)
sign= JS.call("func", "Sakura")
print(sign)
JS 代码如下:
function func(arg){
return arg;
}
var result = func("Sakura");
console.log(result);
如果配置完 Node.js 后, print(execjs.get().name) 输出的还是 JScript 不是 Node.js (V8) 的话,可能是环境还没切换好,我们可以重启以下项目。
五、用户密码登录
5.1、逻辑分析
这里,我们登录基础教育教师培训网站, https://xuexi.chinabett.com,会发现它发送一个 Ajax 请求,携带请求参数。


这里,我们可以发现,该网站对账号和密码进行了加密。我们需要跳转到源码,寻找它加密的函数。


此时,我们可以向上寻找加密函数,并打上断点。

重新执行,定位到 base64code() 加密函数。


5.2、代码实现
我们在终端中通过 pip 安装相关的模块。
pip install requests
pip install pyexecjs
pip install bs4
pip install lxml
pip install ddddocr
pip install pillow==9.5.0
pip install numpy
我们可以把 base64encode() 函数的内容 和密码再次加密的逻辑扣下来,保存到 v1.js 文件中。
function base64encode(str) {
var base64EncodeChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
var base64DecodeChars = new Array(
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
-1, 0, 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, -1, -1, -1, -1, -1,
-1, 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, -1, -1, -1, -1, -1);
var out, i, len;
var c1, c2, c3;
len = str.length;
i = 0;
out = "";
while (i < len) {
c1 = str.charCodeAt(i++) & 0xff;
if (i == len) {
out += base64EncodeChars.charAt(c1 >> 2);
out += base64EncodeChars.charAt((c1 & 0x3) << 4);
out += "==";
break;
}
c2 = str.charCodeAt(i++);
if (i == len) {
out += base64EncodeChars.charAt(c1 >> 2);
out += base64EncodeChars.charAt(((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4));
out += base64EncodeChars.charAt((c2 & 0xF) << 2);
out += "=";
break;
}
c3 = str.charCodeAt(i++);
out += base64EncodeChars.charAt(c1 >> 2);
out += base64EncodeChars.charAt(((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4));
out += base64EncodeChars.charAt(((c2 & 0xF) << 2) | ((c3 & 0xC0) >> 6));
out += base64EncodeChars.charAt(c3 & 0x3F);
}
return out;
}
function s1() {
var data = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"];
var r = Math.floor(Math.random() * 62);
return data[r];
}
function encryptPwd(password){
var newPwd = [];
var pwdlength = password.length;
for (i = 0; i < pwdlength; i++) {
newPwd.push(password[i]);
if (i < pwdlength - 1)
newPwd.push(s1());
}
password = newPwd.join('');
return password
}
import requests
import execjs
import ddddocr
from bs4 import BeautifulSoup
from urllib.parse import urljoin
if __name__ == "__main__":
# 1,访问首页,获取Cookie
url = "https://xuexi.chinabett.com"
cookie_dict = {}
response = requests.get(url)
cookie_dict.update(response.cookies.get_dict())
# 2.获取验证码
soup = BeautifulSoup(response.text, features="html.parser")
image_tag = soup.find(name="img", attrs={"id": "imgVerifity"})
code_src = image_tag.attrs["src"]
# 3.识别验证码
response = requests.get(url=urljoin(url, code_src), cookies=cookie_dict)
cookie_dict.update(response.cookies.get_dict())
ocr = ddddocr.DdddOcr()
code = ocr.classification(response.content)
# 4.JS逆向加密逻辑
with open("v1.js", mode="r", encoding="utf-8") as f:
js_string = f.read()
JS = execjs.compile(js_string)
# 5.用户名和密码进行加密
username = JS.call("base64encode", "sakura")
password = JS.call("base64encode", "sakura")
password = JS.call("encryptPwd", password)
# 6.登录
response = requests.post(
url="https://xuexi.chinabett.com/Login/Entry",
data={
"userAccount": username,
"password": password,
"returnUrl": "/PersonalCenter",
"proVing": code
},
cookies=cookie_dict
)
print(response.text)