在当今Web安全领域,JavaScript虚拟机保护(JSVMP)技术被广泛应用于前端代码的保护和反爬机制中。作为前端逆向工程师,掌握JSVMP逆向技术已成为必备技能。本文将深入剖析JSVMP的工作原理,并分享实用的逆向破解思路。
JSVMP(JavaScript Virtual Machine Protection)是一种通过自定义虚拟机执行JavaScript代码的保护技术。它将原始JavaScript代码编译为自定义的字节码,然后通过解释器执行,从而:
隐藏原始业务逻辑
增加逆向分析难度
防止直接调试和Hook
将原始JS代码转换为自定义字节码序列:
// 原始JS
function add(a, b) {
return a + b;
}
// 编译后字节码可能类似
[0x01, 0x02, 0x03, 0x04, ...]
解释执行自定义字节码的虚拟机核心:
function VM(bytecode) {
this.pc = 0; // 程序计数器
this.stack = []; // 操作数栈
this.registers = {}; // 寄存器
this.run = function() {
while(this.pc < bytecode.length) {
const opcode = bytecode[this.pc++];
this.execute(opcode);
}
}
this.execute = function(opcode) {
switch(opcode) {
case 0x01: // PUSH
this.stack.push(bytecode[this.pc++]);
break;
case 0x02: // ADD
const a = this.stack.pop();
const b = this.stack.pop();
this.stack.push(a + b);
break;
// ...其他操作码
}
}
}
提供与原生JavaScript环境的交互接口:
const runtime = {
getCookie: function(name) {
// 获取cookie的实现
},
sendRequest: function(url, data) {
// 发送请求的实现
}
// ...其他运行时方法
};
通过特征识别目标是否使用JSVMP:
存在大量switch-case
结构
有明显的字节码序列
代码包含解释执行循环
使用eval
或Function
动态执行
寻找字节码加载和解释器初始化的位置:
// 常见初始化模式
const bytecode = [0x01, 0x02, ...];
const vm = new VM(bytecode);
vm.run();
确定字节码的编码方式和指令集:
操作码 | 指令 | 描述 |
---|---|---|
0x01 | PUSH | 压栈 |
0x02 | ADD | 加法 |
0x03 | CALL | 调用函数 |
... | ... | ... |
使用Chrome DevTools进行动态分析:
// 在关键位置插入调试语句
console.log("PC:", vm.pc, "Opcode:", opcode, "Stack:", vm.stack);
// 或使用debugger语句
if(vm.pc === targetPC) debugger;
目标:破解_signature
参数生成算法
步骤:
通过XHR断点定位加密位置
回溯调用栈找到VM入口
分析字节码中的加密逻辑
提取关键操作模拟执行
// 还原后的加密逻辑
function generateSign(params) {
const vm = new VM(encryptBytecode);
vm.registers.input = JSON.stringify(params);
vm.run();
return vm.stack.pop();
}
挑战:
动态变化的操作码映射表
自修改字节码
反调试检测
解决方案:
使用Object.defineProperty
Hook关键函数
记录操作码执行轨迹
构建操作码到原始JS的映射关系
// Hook示例
const originalRun = VM.prototype.run;
VM.prototype.run = function() {
console.log("VM started with bytecode:", this.bytecode);
return originalRun.apply(this, arguments);
};
通过符号执行还原原始逻辑:
# 使用PyExZ3等符号执行工具
from pyexz3 import *
def analyze_bytecode(bytecode):
vm = VM(bytecode)
vm.run()
return vm.stack
将字节码转换回高级JavaScript代码:
function decompile(bytecode) {
let jsCode = "";
for(let i = 0; i < bytecode.length; ) {
const opcode = bytecode[i++];
switch(opcode) {
case 0x01:
jsCode += `stack.push(${bytecode[i++]});\n`;
break;
// 其他操作码转换...
}
}
return jsCode;
}
通过内存dump获取运行时信息:
// 获取VM内存状态
function dumpVM(vm) {
return {
pc: vm.pc,
stack: [...vm.stack],
registers: {...vm.registers}
};
}
应对JSVMP的反逆向措施:
反爬技术 | 破解方法 |
---|---|
代码混淆 | AST分析 |
环境检测 | 纯净环境 |
动态加载 | 请求拦截 |
定时检测 | 断点绕过 |
静态分析工具:
AST Explorer
Babel Parser
WebStorm
动态调试工具:
Chrome DevTools
Fiddler
Charles
专用逆向工具:
WasmDec
JEB JavaScript
Node.js VM
《JavaScript高级程序设计》- VM实现章节
Chrome V8引擎源码
WebAssembly虚拟机规范
Babel插件开发手册
JSVMP逆向是一个需要耐心和技术积累的过程。通过本文介绍的方法论和实战案例,相信读者已经对JSVMP逆向有了系统性的认识。记住,逆向工程的本质是与开发者的智力博弈,保持学习和技术更新才是制胜关键。
重要声明:本文所有技术仅限学习交流,请勿用于非法用途。实际逆向操作前请确保已获得相关授权。