计算密集型JavaScript代码的虚拟化

发布于 2023-09-21  774 次阅读


总结前文的方法设计过程,我们可以发现,由于WebAssembly目前不支持对JavaScript中DOM对象的操作,本文实现的解释器在执行时必须借助JavaScript实现Handler中对属性的操作部分。这样频繁的在WebAssembly和JavaScript之间交互肯定会带来性能的影响,降低执行效率。同时还需要附带大量额外的胶接代码负责维护两个模块的通信,这势必会带来新的空间开销。为了能够解决这一问题,本文在虚拟化过程中特别针对计算密集型的目标代码做出方案的改进。本文对计算密集型代码的定义是:不包含DOM对象的操作,含有大量集中的数值类的算数和逻辑运算操作的JavaScript代码。这类代码可以受到WebAssembly的完全支持,不仅是核心调度结构和关键数据结构,所有解释程序的核心功能均可以通过C语言来实现,因此虚拟解释器的的所有组件都可以编译到生成的“.wasm”文件中。减去在执行过程中和JavaScript不必要的交互,所有解释过程在WASM模块中完成,既可以使攻击者难以调试分析和跟踪观察执行的中间过程变化,还可以有效的利用其效率优势降低虚拟执行带来的时空开销。因此,特殊的对于计算密集型目标代码的虚拟化保护过程设计如下。

虚拟化过程

JavaScript代码在虚拟化过程中由于含有DOM对象等操作,还有一些字符串常量等数据,需要进行特殊的字符转移操作。对于计算密集型的目标代码,由于不包含DOM对象操作和字符串数据,单纯由大量的数值计算代码组成,有清晰的计算过程和逻辑结构,因此更加容易通过抽象语法树进行指令的拆分,进而得到可用中间代码进行虚拟映射和编码过程。如图16展示了一串计算密集型目标代码指令拆分、虚拟转化以及编码的完整保护过程。

A. 目标代码指令拆分和虚拟化
拿到计算密集型目标代码后,首先分析提取目标代码的抽象语法树,处理其中的高级结构,将条件分支和循环分支平铺拆分,然后以语句块作为一个单独子树进行指令拆分处理,将代码转化成如图16中所示的基于栈架构的中间代码表示。然后根据设计实现的虚拟指令集完成虚拟映射过程,将中间代码转化成相应的虚拟指令序列,最后根据自定义的编码规则将虚拟指令序列编码成图中所示的字节码程序。具体的指令拆分过程参考3.2.1节,首先进行代码结构的拆分获得独立的语句块,进一步对语句块进行拆分提取相应的原子操作。同时,计算密集型代码的虚拟化不需要执行字符转移操作,因此不会生成字符串数组这一关键数据。

B. 虚拟指令和Handler设计
计算密集型的目标代码中不包含DOM对象和字符操作,从抽象语法树中提取原子操作的过程不涉及“ElementGet”和“FunctionCall”等节点类型的处理,同时也无法处理外部函数调用操作,因此本文在此处根据需求设计实现三类计算密集型虚拟指令:数据转移指令、控制转移指令和算数逻辑运算指令,表3给出了计算密集型代码虚拟化过程中虚拟指令和相关解释程序的分类设计示例。

对于常见的常量和计算结果转移操作,由数据转移指令对操作数进行压栈处理和结果保存,算术逻辑运算指令则负责映射中间代码中的运算符,对于指令拆分产生的分支标记中间代码,则通过相应的控制转移操作来进行替换。虚拟指令和解释程序的主要逻辑和通用JavaScript代码虚拟化过程中的设计基本相同,主要围绕栈来实现,解释时,首先将所需的操作数压栈,然后在栈顶执行相应的运算操作,最后将保存在栈顶的运算结果保存到相应的存储单元。之前的分类设计中Handler需要内联JavaScript语言实现特殊的属性操作,而计算密集型代码不包含这种类型的操作,因此这里的虚拟指令对应的Handler可以完全采用C语言实现,省去与JavaScript的交互过程,便于实现整个虚拟解释器向WebAssembly的编译转化。


C. 指令编码
得到虚拟指令序列之后,需要按照一定的编码规则对虚拟指令进行编码,最终将其编码成字节码程序的形式并作为关键数据保存到虚拟解释器中。具体的编码过程和编码规则设计参考3.2节的指令编码过程描述,主要将虚拟指令按照操作码和操作数的形式划分并分别编码,同样的也可以在编码过程中加入多样性的编码规则,通过保护结果的多样性增加虚拟化保护的强度。


点击体验一键VMP加密 |下滑查看JSVMP相关文章