文档
/
VM 混淆
/

直接 eval 的行为

直接 eval() 会禁用 VM 混淆

如果一个函数体内的任何位置(包括嵌套函数中)含有直接 eval(code) 调用,VM 混淆会跳过该函数,并且混淆器会报告一条 VMDynamicCodeSkipped 警告(函数有名称时会指明该函数)。在 root 模式(默认)下,会跳过整个函数及其所有嵌套函数;在 comment 模式下,您单独标记的嵌套函数仍会被虚拟化,除非它自身包含 eval。带有非静态参数的直接 eval 还会发出 DynamicCodeRenameRisk 警告。这是因为直接 eval 可以访问外层函数的局部变量,而函数被编译为 VM 字节码后,这些局部变量就无法再以这种方式访问。

对 IIFE 包裹的代码尤其重要。 如果顶层 IIFE 深处的任何位置含有直接 eval,整个 IIFE(包括您的全部代码)都不会被 VM 混淆。

JavaScript

拆开 IIFE 需要付出一个代价:一旦您的函数位于顶层,只有出问题的那个函数会被跳过,但其余函数现在都是根级函数,因此 VM 混淆会保留它们的名称。如何让这些名称不出现在输出中,请参阅对 LLM 分析隐藏函数名。

(0, eval)(code) 和 window.eval(code) 这类间接形式不会阻止 VM 混淆。eval?.(code) 是个例外:JavaScript 将其作为间接 eval 执行,但混淆器会保守地将其视为直接 eval 并跳过该函数。

当函数体参数是动态值时,Function 构造器(new Function(body) / Function(body))也按同样方式处理。 含有动态 new Function(...) 调用的函数同样会被跳过,并产生 VMDynamicCodeSkipped 警告。与动态的直接 eval 一样,混淆器还会添加 DynamicCodeRenameRisk 警告,因为运行时构建的函数体可能引用被重命名的标识符。new Function('a', 'b', 'return a + b') 这样完全静态的调用不会被跳过。

间接 eval 和 Function 构造器在全局作用域中执行。它们无法读取调用方的局部变量,但如果引用了已被重命名或移除的全局变量,仍然可能失败。像 new Function('a', 'b', 'return a + b') 这样只使用自身参数的静态函数体可以避免这种依赖。请检查警告并测试最终的包;仅仅改变 eval 的写法,并不能让任意动态代码变得安全。

应急开关(v6.14.0+): 设置 vmForceCompileDynamicCode: true(或打开 VM 部分 覆盖设置 分组下的 Force Compile Dynamic Code 开关),即可强制将外围函数编译为字节码,并抑制 VMDynamicCodeSkipped。它无法修复作用域:在被强制编译的函数内部,直接 eval 无法读写该函数的局部变量、参数或被虚拟化的外层函数的变量,即使代码是字符串字面量也是如此。只有当被求值的代码仅引用全局变量时才可使用它。开启该选项后,DynamicCodeRenameRisk 仍会继续触发,因为它所描述的重命名风险与 VM 跳过无关。

完整的对照表、警告格式和规避方法,请参阅 eval 与 new Function 下的 VM 混淆。