news 2026/6/4 13:43:03

Python 爬虫反爬突破:vmp 混淆 JS 逆向还原加密请求生成逻辑

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python 爬虫反爬突破:vmp 混淆 JS 逆向还原加密请求生成逻辑

前言

现代站点反爬体系中,JS 代码 VMP 虚拟化混淆是当前防护等级最高的前端加密方案之一,区别于普通 JS 压缩、变量名混淆、控制流平坦化,VMP 依托自定义虚拟指令集把原生 JS 代码翻译为虚拟机字节码,借助内置解释器动态执行指令,直接格式化、断点调试、AST 语法树解析均会失效。大量资讯、电商、数据查询类站点将请求签名、接口参数、Cookie 加密逻辑全部嵌入 VMP 混淆脚本,爬虫直接复刻加密算法、模拟请求参数的常规逆向手段完全无法落地。

在合规逆向、仅用于公开数据采集的前提下,本文从 VMP 混淆实现原理、字节码执行机制切入,梳理 VMP 脚本分层结构,分调试脱壳、指令还原、算法剥离三个阶段拆解实战案例,依托 Node.js、PyExecJS、JS 逆向调试工具完成加密请求逻辑还原,最终用 Python 复刻加密函数,实现无浏览器环境直接生成合法加密参数,绕过前端 VMP 校验。

本文涉及开发依赖与官方资源链接汇总:

  1. Node.js 官方下载地址:JS 运行环境,用于脚本调试与脱壳运行
  2. pyexecjs 官方文档:Python 调用 Node.js 执行 JS 代码依赖库
  3. jsbeautifier 开源美化工具:JS 代码格式化库
  4. Chrome DevTools 官方调试文档:浏览器断点调试工具说明
  5. vmp-core 原理参考:开源 JS-VMP 虚拟化项目源码仓库

一、JS-VMP 混淆底层原理与代码特征识别

1.1 VMP 虚拟化混淆核心运行架构

JS-VMP 全称 JavaScript Virtual Machine Protect,核心思想是构建一套独立于 ECMAScript 标准的自定义虚拟机,原始明文 JS 代码经过编译后转换为专属 Opcode 虚拟机指令,最终打包由内置解释器逐行解析执行,整体分为三层架构: 第一层:壳引导代码,负责初始化虚拟机内存、寄存器、全局变量、指令集映射表; 第二层:虚拟解释器主体,包含指令解码函数、堆栈管理器、异常捕获模块,是 VMP 运行核心; 第三层:加密业务字节码,原始加密算法编译后的二进制字符串 / 数组,无任何可读 JS 逻辑。 页面加载时先运行引导代码初始化虚拟机,再载入字节码,解释器循环读取 Opcode 并映射为原生 JS 逻辑运算,前端加密、签名等业务逻辑全部在虚拟环境内闭环执行。

1.2 VMP 混淆代码标志性特征清单

实战逆向中可通过代码特征快速判定目标脚本是否为 VMP 加固,整理特征对照表:

表格

代码特征分类具体代码表现
全局变量特征大量超长无意义变量名,如_0x12aa3d_0xffff00,全局预定义超大数组存储字节码
执行逻辑特征主逻辑为无限 while+switch 分支结构,switch 的 case 对应不同虚拟机指令编码
数据存储特征核心加密代码以十六进制字符串、Base64 编码数组形式存放,无原生函数结构
调用特征所有函数调用、数学运算、字符串操作均通过虚拟机指令跳转实现,无直观业务代码
调试特征开启 Chrome 格式化后代码结构仍高度混乱,断点跟随虚拟机堆栈频繁跳转,无法定点拦截加密函数

1.3 VMP 造成爬虫拦截的业务逻辑

目标站点把接口请求所需的 sign 签名、timestamp 时间戳、deviceId 设备标识、header 加密字段全部交由 VMP 内 JS 生成,前端页面点击查询、翻页时由虚拟机执行加密逻辑生成合法参数,后端接口校验参数合法性,参数伪造、固定签名的爬虫请求直接返回 403、参数错误,这也是 VMP 反爬的核心防护目的。

二、环境配置与混淆样本预处理

2.1 依赖安装命令

Python 侧逆向运行依赖批量安装,Node.js 需提前在服务器与本地环境完成安装并配置环境变量:

bash

运行

pip install pyexecjs==1.5.1 jsbeautifier==1.15.1 urllib3==2.0.8 requests==2.31.0

2.2 VMP 样本获取与基础格式化

从目标站点 Network 面板提取后缀.js 的 VMP 混淆脚本源码,首先通过 jsbeautifier 进行基础代码格式化,去除代码压缩换行,便于区分壳代码与业务字节码,格式化代码示例:

python

运行

import jsbeautifier def format_vmp_js(raw_js:str): opts = jsbeautifier.default_options() opts.indent_size = 4 format_code = jsbeautifier.beautify(raw_js, opts) with open("vmp_format.js","w",encoding="utf-8") as f: f.write(format_code) return format_code # 传入抓取到的原始混淆JS字符串 if __name__ == '__main__': with open("raw_vmp.js","r",encoding="utf-8") as f: raw = f.read() format_vmp_js(raw)
代码原理

jsbeautifier 仅完成代码换行、缩进优化,无法还原 VMP 虚拟机字节码对应的原生逻辑,仅作为逆向前期代码梳理使用,是逆向的前置操作。

三、VMP 逆向三大主流方案优劣对比

逆向 VMP 脚本分为动态调试脱壳、Opcode 指令映射还原、内存 dump 运行时源码三种方案,不同方案适配不同混淆强度的 VMP 脚本,适用场景整理表格:

表格

逆向方案实现难度适用 VMP 类型优缺点说明
Chrome 动态断点调试 dump轻量自定义 VMP、商业低版本 JSVMP优点:不用解析指令集,直接导出运行明文函数;缺点:高强度加固加反调试陷阱时断点失效
手动解析 Opcode 映射还原全版本标准 VMP 加固脚本优点:通用性最强,不受反调试限制;缺点:需要逐条整理指令与原生 JS 运算映射关系,工作量大
Node.js 沙箱剥离壳代码无反调试检测的简单 VMP优点:代码改动量小,快速提取加密函数;缺点:站点内置时间校验、环境检测时无法运行

生产爬虫逆向优先选用「动态 dump + 沙箱剥离」组合方案,遇到强反调试加固脚本再切换 Opcode 指令解析方案。

四、实战一:Node 沙箱剥离壳代码提取加密函数(简易 VMP)

4.1 剥离原理

VMP 脚本分为虚拟机壳代码与业务字节码,业务字节码是最终加密算法的载体,壳代码仅用于构建虚拟机运行环境,在 Node.js 中通过注释、删除无用引导代码,保留虚拟机初始化逻辑与字节码数据,单独导出加密入口函数,实现脱离浏览器环境独立运行。 假设目标 VMP 脚本最终导出getSign(params)加密函数,该函数接收请求参数字典,返回接口所需 sign 签名字符串。

4.2 处理后可独立运行的 JS 精简代码示例(save_vmp_dec.js)

javascript

运行

// 保留VMP虚拟机初始化核心代码,剔除浏览器环境检测、DOM相关无用代码 (function(){ // 原VMP壳:寄存器、指令集、堆栈初始化(精简后) var _vm_reg = []; var _vm_stack = []; var _opcode_map = {0x01:add,0x02:sub,0x03:str_concat}; // 原始VMP字节码数组(从混淆JS中完整提取) var _byte_code = [0x12,0x33,0xaa,0x55,0x99,...]; // VMP内置解释器循环逻辑 function vm_run(code_arr){ let pc = 0; while(pc<code_arr.length){ let op = code_arr[pc++]; switch(op){ case 0x01: let a = _vm_stack.pop(); let b = _vm_stack.pop(); _vm_stack.push(a+b);break; case 0x02: let c = _vm_stack.pop(); let d = _vm_stack.pop(); _vm_stack.push(d-c);break; // 其余Opcode指令映射省略 } } } // 执行字节码,生成原生加密函数 vm_run(_byte_code); // 对外暴露加密入口 window.getSign = function(req_params){ // 内部经过虚拟机运算生成签名 let timestamp = new Date().getTime().toString(); let raw_str = req_params.uid + req_params.page + timestamp; return md5(raw_str); } })()
代码原理拆解
  1. 删除原脚本中document/window.navigator浏览器环境检测代码,规避 Node 沙箱运行时报环境不存在异常;
  2. 完整保留 Opcode 指令映射表与原始字节码数组,保证虚拟机可正常解释运行业务逻辑;
  3. 手动锁定加密入口函数getSign并挂载至全局,实现外部代码调用。

4.3 Python 调用 JS 加密函数生成请求参数

依托 PyExecJS 调用本地 Node 环境执行解密后的 JS 文件,动态生成接口加密签名,实现爬虫参数自动加密:

python

运行

import execjs import requests # 加载剥离壳后的VMP解密JS with open("save_vmp_dec.js","r",encoding="utf-8") as f: js_code = f.read() ctx = execjs.compile(js_code) def get_encrypt_params(uid:str,page:int): # 调用JS中getSign加密函数 params_dict = {"uid":uid,"page":page} sign = ctx.call("getSign", params_dict) headers = { "User-Agent":"Mozilla/5.0 Chrome/122.0.0.0", "sign":sign } return headers,params_dict # 爬虫请求测试 if __name__ == '__main__': headers,payload = get_encrypt_params(uid="U20250601",page=3) resp = requests.get("https://api.example.com/list",headers=headers,params=payload) print("接口返回数据:",resp.text[:300])
运行逻辑

PyExecJS 自动调用本机 Node.exe 创建独立 JS 运行环境,载入解密脚本后,Python 参数序列化为 JS 对象传入加密函数,加密结果回传至 Python,全程无浏览器启动开销,适配分布式爬虫批量调用。

五、实战二:Chrome 动态内存 Dump 提取明文加密代码(中强度 VMP)

5.1 反调试绕过前置操作

高强度 VMP 脚本内置 Debugger 反调试陷阱,打开 Chrome 开发者工具自动触发无限 debugger 阻塞调试,首先通过 Chrome 断点黑盒绕过反调试:打开 Sources 面板→勾选 Never pause here 屏蔽 debugger 断点,关闭 JS 断点自动暂停。

5.2 Dump 时机选择原理

VMP 虚拟机运行时,字节码经过解释器运算后会在 V8 引擎内存中生成原生明文 JS 函数,在前端页面发起接口请求的瞬间,加密函数完成内存实例化,此时通过 Chrome 控制台console.dir(加密函数名)、copy 函数源码完成明文导出,是绕过字节码解析最快的手段。

5.3 Dump 后纯原生 JS 加密代码(无 VMP 虚拟机)

dump 后的代码彻底脱离 VMP 虚拟机依赖,仅剩原始加密逻辑,示例:

javascript

运行

// dump导出明文加密函数,无任何虚拟机壳与字节码 function getSign(req){ let ts = Date.now()+""; let raw = req.uid + "&page="+req.page + "&ts="+ts + "&salt=sk927s@!d"; return hex_md5(raw); }

该代码可直接丢入 PyExecJS 执行,省去维护 VMP 虚拟机壳的成本,是逆向收益最高的方式。

六、实战三:Opcode 指令集全解析还原算法(高强度商业 VMP)

当站点开启强反调试、禁止内存 dump 时,只能从底层 Opcode 指令逐条逆向,核心步骤为:提取指令映射表、标注每条 Opcode 对应的原生运算、批量替换字节码为可读 JS。

6.1 指令映射整理规则

从 VMP 解释器 switch-case 结构中提取:case 值 = Opcode 编码,case 内部代码 = 原生 JS 运算逻辑,建立键值映射:

plaintext

0x01 → 数值加法 a+b 0x02 → 数值减法 a-b 0x05 → 字符串拼接 str+str 0x09 → 数组入栈 push

6.2 字节码转译逻辑

原字节码数组[0x05,"uid","123","page","1"]按照映射表转译为"uid"+"123"+"page"+"1",循环遍历全量字节码后,完整还原原始加密字符串拼接逻辑,逐步还原 MD5、SHA 等加密链路。

七、VMP 逆向落地爬虫避坑优化细则

7.1 时间动态校验坑处理

部分 VMP 加密逻辑内置前端时间戳校验,JS 内部获取本地时间参与签名,Python 运行环境时间和前端偏差超 5 秒即签名失效,解决方案:Python 生成时间戳后传入 JS 作为参数,统一时间基准。

python

运行

# Python控制时间戳,避免两端时间不一致 import time now_ts = int(time.time()*1000) sign = ctx.call("getSign",params_dict,now_ts)

7.2 环境特征检测处理

VMP 内置navigator.userAgent、浏览器内核检测,JS 中读取 UA 参与加密,在 JS 脚本顶部固定全局 UA 变量,屏蔽环境读取:

javascript

运行

// JS脚本头部强制固定UA,绕过环境检测 Object.defineProperty(window,"navigator",{ value:{userAgent:"Mozilla/5.0 Chrome/122.0.0.0"} })

7.3 分布式爬虫 JS 缓存优化

多爬虫进程频繁加载 JS 文件会造成 IO 损耗,Python 侧采用单例模式全局缓存 execjs 编译后的上下文,项目启动仅编译一次 JS,全任务共用加密环境。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/4 13:43:02

任天堂2DS XL上屏幕更换全攻略:从工具准备到铰链排线处理

1. 项目概述与核心价值手头的这台Nintendo 2DS XL&#xff0c;上屏幕被家里的小家伙不小心摔出了一片“雪花”和几条触目惊心的亮线&#xff0c;彻底没法看了。直接换新机&#xff1f;对于一款已经停产的掌机来说&#xff0c;不仅成本高&#xff0c;也失去了那份亲手修复的乐趣…

作者头像 李华
网站建设 2026/6/4 13:42:57

如何在Windows上高效运行Linux图形应用程序:VcXsrv完整配置指南

如何在Windows上高效运行Linux图形应用程序&#xff1a;VcXsrv完整配置指南 【免费下载链接】vcxsrv VcXsrv Windows X Server (X2Go/Arctica Builds) 项目地址: https://gitcode.com/gh_mirrors/vc/vcxsrv VcXsrv Windows X Server是一款专为Windows系统设计的X11服务器…

作者头像 李华
网站建设 2026/6/4 13:42:56

终极黑苹果安装指南:5步让你的PC运行macOS系统

终极黑苹果安装指南&#xff1a;5步让你的PC运行macOS系统 【免费下载链接】Hackintosh 国光的黑苹果安装教程&#xff1a;手把手教你配置 OpenCore 项目地址: https://gitcode.com/gh_mirrors/hac/Hackintosh 你是否曾梦想在普通PC上体验macOS的流畅与优雅&#xff1f;…

作者头像 李华
网站建设 2026/6/4 13:42:00

豆包100个真实功能实测:AI如何把操作压缩到一次滑动距离

1. 项目概述&#xff1a;这不是功能清单&#xff0c;而是一份“人话版豆包使用说明书”“实测豆包100个功能&#xff0c;每一个都简单好用”——看到这个标题&#xff0c;我第一反应不是点开&#xff0c;而是放下手机&#xff0c;泡了杯茶。因为过去三年里&#xff0c;我亲手测…

作者头像 李华