JS加密?⽤虚拟机opcode保护JS源码
JS代码保护,有多种⽅式,如常规的JS混淆加密、如bytecode化、⼜或如虚拟机化。
这⾥简单探讨虚拟机JS保护。
⼀、原理
虚拟机保护的最终⽬标,是将JS代码转为opcode,或汇编语⾔式代码,在虚拟机中执⾏。
⼀般是保护重要的函数、算法、当然也可以保护更多更⼤段的代码。
更详细⼀些来说,汇编语⾔式代码,形态会类似:
push a
push b
push c
call fun
js argumentspop
这是古⽼的asm语法,没错,js代码可以转为此种形式,⽽且,可以更进⼀步,转为opcode,如上述asm代码,如果将push、pop等字符替换为数字的操作码,假设push为20,call为30,pop为40,形态可以变成:
20,1,20,20,3,30,4,40
如果我们的JS代码,变成了这样的数字,谁能理解它的代码逻辑和作⽤吗?
很显然,这样起到了对代码加密保护的作⽤。如果再与JShaman之类的混淆加密⼯具配合使⽤,JS代码的安全性将得到极⼤的提升。
⼆、开发⼀个JS虚拟机
⼀个简单的堆栈虚拟机,并不会⼗分复杂,⽤JS数组模拟堆栈,⽤数组的push⽅法模拟压栈,⽤数组索实现堆栈指针、指令指针、栈帧。本例中,汇编指令,则实现⼀部分操作,如:
const I = {
CONST: 1,
ADD: 2,
PRINT: 3,
HALT: 4,
CALL: 5,
RETURN: 6,
LOAD: 7,
JUMP_IF_ZERO: 8,
JUMP_IF_NOT_ZERO: 9,
SUB: 10,
MUL: 11,
};
虚拟机的核⼼的部分,则是根据指令进⾏相应的堆栈操作,如:
//循环执⾏
switch (instruction) {
//常量
case I.CONST: {
//常量值
const op_value = code[ip++];
//存放到堆栈
stack[++sp] = op_value;
console.log("const",stack)
console.log("const",stack)
break;
}
case I.ADD: {
const op1 = stack[sp--];
const op2 = stack[sp--];
stack[++sp] = op1 + op2;
break;
}
//减法
case I.SUB: {
//减数
const op1 = stack[sp--];
//被减数,都放在堆栈⾥
const op2 = stack[sp--];
//相减的结果,放到堆栈
stack[++sp] = op2 - op1;
break;
}
case I.PRINT: {
const value = stack[sp--];
builtins.print(value);
break;
}
case I.HALT: {
return;
}
//函数调⽤
case I.CALL: {
//函数地址
const op1_address = code[ip++];
//参数个数
const op2_numberOfArguments = code[ip++];
console.log(".....",op1_address,op2_numberOfArguments)
/
/参数个数⼊栈
stack[++sp] = op2_numberOfArguments;
//旧栈帧⼊栈
stack[++sp] = fp;
//指令指针
stack[++sp] = ip;
//console.log("call",stack);return
//独⽴的栈帧,从当前堆栈指针处开始
fp = sp;
//指令指针变化,开始执⾏call函数
ip = op1_address;
break;
}
case I.RETURN: {
const returnValue = stack[sp--];
sp = fp;
ip = stack[sp--];
fp = stack[sp--];
const number_of_arguments = stack[sp--];
sp -= number_of_arguments;
stack[++sp] = returnValue;
break;
}
case I.LOAD: {
//补偿地址,ip指向指令地址,通过补偿值,获得函数调⽤前压⼊的参数
//补偿地址,ip指向指令地址,通过补偿值,获得函数调⽤前压⼊的参数
const op_offset = code[ip++];
const value = stack[fp + op_offset];
//console.log(value);return
stack[++sp] = value;
break;
}
case I.JUMP_IF_NOT_ZERO: {
const op_address = code[ip++];
const value = stack[sp--];
if (value !== 0) {
ip = op_address;
}
break;
}
default:
throw new Error(`Unknown instruction: ${instruction}.`);
}
三、实例
JS虚拟机已简单实现。然后,准备⼀段JS代码⽣成的opcode,如下:
1, 10,  5, 7,  1,  3, 4, 7, -3,1,  1, 10, 9, 17,  1, 1, 6,  7, -3,  7, -3, 1,  1, 10, 5, 7,  1,  11,  6
看起来仅仅是些数字,先看效果,在虚拟机中执⾏:
如上图,输出是⼀个数值。那么,这段opcode究竟是什么呢?
其实,它是这样⼀段JS源代码转化⽽来:
function factorial(n) {
if (n === 1) {
return 1;
}
return n * factorial(n - 1);
}
const result = factorial(10);
console.log(result);
将上述opcode转换⼀个形式,把数字替换为前⾯讲到过的汇编指令,会得到如下形式的类asm代码:
I.CONST,
10,
I.CALL,
/* factorial */ 7,
1,
I.PRINT,
I.HALT,
I.LOAD, // factorial start,7指向的即是这⾥
-3,
I.CONST,
1,
I.SUB,
I.JUMP_IF_NOT_ZERO,
17,
I.CONST,
1,
I.RETURN,
/* n */ I.LOAD,
-3,
/* factorial(n - 1) */ I.LOAD,
-3,
I.CONST,
1,
I.SUB,
I.CALL,
/* factorial */ 7,
1,
I.MUL,
I.RETURN, // factorial end
对照JS源码、虚拟机代码,仔细阅读,⽅能理解此段汇编代码的含意,相应的,也就可以理解opcode。
但如果未得到得虚拟机代码,或是虚拟机代码⼜被进⾏了加密,如:使⽤JShaman对虚拟机代码进⾏了混淆加密。那,想要理解opcode,则是万难。
最后,请再来欣赏这段优雅的JS代码:
1, 10,  5, 7,  1,  3, 4, 7, -3,1,  1, 10, 9, 17,  1, 1, 6,  7, -3,  7, -3, 1,  1, 10, 5, 7,  1,  11,  6
仅是⼀⾏,如果是⼤段⼤段的,或是夹杂在混淆加密保护过的JS代码中,酸爽。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。