JS加密?用虚拟机opcode保护JS源码

JS代码保护,有多种方式,如常规的JS混淆加密、如bytecode化、又或如虚拟机化。

这里简单探讨虚拟机JS保护。

一、原理

虚拟机保护的最终目标,是将JS代码转为opcode,或汇编语言式代码,在虚拟机中执行。

一般是保护重要的函数、算法、当然也可以保护更多更大段的代码。

更详细一些来说,汇编语言式代码,形态会类似:

push a
push b
push c
call fun
pop

这是古老的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)
          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指向指令地址,通过补偿值,获得函数调用前压入的参数
          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代码中,酸爽。

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
实现一个完整的虚拟机是一个非常复杂的任务,需要深入理解计算机系统和操作系统原理。不过,我们可以尝试实现一个简单的虚拟机,来模拟一些基本的计算功能。 以下是一个使用Python实现的简单虚拟机的示例代码: ```python class VirtualMachine: def __init__(self): self.stack = [] self.pc = 0 self.running = False def run(self, program): self.pc = 0 self.running = True while self.running: opcode = program[self.pc] if opcode == "PUSH": value = program[self.pc + 1] self.stack.append(value) self.pc += 2 elif opcode == "ADD": a = self.stack.pop() b = self.stack.pop() self.stack.append(a + b) self.pc += 1 elif opcode == "SUB": a = self.stack.pop() b = self.stack.pop() self.stack.append(b - a) self.pc += 1 elif opcode == "PRINT": value = self.stack.pop() print(value) self.pc += 1 elif opcode == "HALT": self.running = False else: raise Exception("Invalid opcode") # Example program: adds 2 and 3, then prints the result program = ["PUSH", 2, "PUSH", 3, "ADD", "PRINT", "HALT"] vm = VirtualMachine() vm.run(program) ``` 这个虚拟机有一个堆栈和一个程序计数器,它可以运行一系列指令,包括 PUSH、ADD、SUB、PRINT 和 HALT。其中,PUSH 将一个值推入堆栈,ADD 和 SUB 分别弹出两个值进行加减运算,PRINT 弹出一个值并打印,HALT 终止程序。 这只是一个非常简单的示例,真正的虚拟机实现远比这个复杂。如果你有兴趣深入了解虚拟机的实现,可以参考一些经典的虚拟机实现,如Java虚拟机和Python虚拟机等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值