VMP 的基本逻辑和特征
JavaScript VMP (虚拟机器保护) 是一种代码混淆技术,它将原始JavaScript代码转换为在自定义虚拟机中执行的字节码,使得逆向工程变得困难。
主要特征
字节码执行:
原始JS代码被编译为自定义字节码
运行时通过解释器执行这些字节码
虚拟指令集:
自定义的操作码和操作数
可能包含算术、逻辑、控制流等虚拟指令
堆栈或寄存器架构:
模拟传统虚拟机的堆栈或寄存器操作
反调试技术:
检测开发者工具
检测调试器存在
使用无限循环或异常干扰调试
代码动态生成:
运行时动态构造关键代码片段
可能配合
eval
或Function
构造函数使用
调试方法
1. 静态分析
代码格式化:使用工具美化混淆代码
识别虚拟机结构:查找以下模式:
// 典型的VMP结构 var vm = { stack: [], ip: 0, bytecode: [...], dispatch: function() { while(this.ip < this.bytecode.length) { var opcode = this.bytecode[this.ip++]; switch(opcode) { case 0x01: /* 操作1 */ break; case 0x02: /* 操作2 */ break; // ... } } } };
2. 动态调试
使用Chrome DevTools:
设置断点并单步执行
监控调用栈和变量变化
使用"Blackbox script"功能忽略库代码
Hook关键函数:
// Hook Function构造函数
var originalFunction = Function;
Function = function() {
console.log('Function constructor called with args:', arguments);
return originalFunction.apply(this, arguments);
};
3. 反反调试技巧
禁用调试检测:
// 覆盖常见的调试检测 Object.defineProperty(window, 'console', { get: function() { return {log: function(){}, debug: function(){}, /* 其他方法 */}; } });
修改时间相关检测:
// 干扰基于时间的检测 Date.now = function() { return 0; }; performance.now = function() { return 0; };
还原VMP代码的步骤
识别字节码加载部分:找到字节码数组和解释器主循环
分析指令集:通过交叉引用确定各操作码的功能
重建控制流:
跟踪跳转指令(如JMP、CALL、RET)
重建函数调用关系
模拟执行:
编写脚本模拟虚拟机执行
记录执行路径和数据流
转换为高级代码:
根据模拟结果将字节码转换回JS代码
实用工具
Babel:用于解析和转换JS代码
AST Explorer:可视化分析代码结构
Terser:代码反混淆工具
自定义解析脚本:针对特定VMP实现编写解析器
示例分析
假设遇到如下VMP代码:
var _0xabc = [0x1, 0x2, 0x3, 'push', 'pop', 0x4];
var vm = {
s: [],
p: 0,
run: function() {
while(this.p < _0xabc.length) {
var op = _0xabc[this.p++];
if(typeof op == 'string') {
this[op]();
} else {
this.s.push(op);
}
}
},
push: function() { /* ... */ },
pop: function() { /* ... */ }
};
vm.run();
分析步骤:
识别字节码数组
_0xabc
分析解释器循环
run
方法确定指令含义(数字为数据,字符串为操作)
模拟执行并记录堆栈状态