Documentación
/
Recetas
/

Ofuscación VM con eval y new Function

Ofuscación VM con eval y new Function

Cómo la ofuscación VM maneja la construcción dinámica de código (eval directo y el constructor Function), qué se convierte a bytecode y qué se excluye, las advertencias que emite el ofuscador y cómo diagnosticar un ReferenceError en tiempo de ejecución.

Por qué importa esto

La ofuscación VM compila los cuerpos de las funciones a bytecode despachado a través de un intérprete en el runtime. Los identificadores del ámbito circundante también se renombran. Ambas transformaciones interactúan mal con el código que se construye a partir de una cadena en tiempo de ejecución: eval(s), new Function(...s) y Function(...s). Si el código construido en tiempo de ejecución referencia un identificador que el ofuscador ha renombrado, obtienes Uncaught ReferenceError: <renamed-name> is not defined la primera vez que se ejecuta la función generada.

El ofuscador maneja cada patrón de forma distinta. La matriz de abajo es la versión resumida; el resto de esta página explica cada celda.

Qué hace el ofuscador, de un vistazo

Patrón en tu código fuenteQué ocurre
eval('literal string') (el cuerpo es un literal de cadena)Funciona correctamente. La función que contiene esta llamada pierde el bytecoding de la VM (el eval directo lee las variables locales circundantes, que la VM no conserva una vez que una función se compila a bytecode).
eval(dynamicExpression)Puede fallar en tiempo de ejecución con ReferenceError. La función que contiene esta llamada —y toda función definida dentro de ella— también pierde el bytecoding de la VM.
(0, eval)(s) / window.eval(s) (indirecto)Funciona correctamente. La función que contiene esta llamada se convierte a bytecode de la VM con normalidad. El eval indirecto no puede ver las variables locales circundantes, así que el renombrado no puede romperlo.
new Function('a', 'b', 'return a + b') (todos los argumentos son literales de cadena)Funciona correctamente. La función que contiene esta llamada se convierte a bytecode de la VM con normalidad.
new Function(dynamicBody) / Function(dynamicBody)Puede fallar en tiempo de ejecución con ReferenceError. La función que contiene esta llamada —y toda función definida dentro de ella— también pierde el bytecoding de la VM.

Todos estos patrones también se exponen como advertencias no fatales en el resultado de la ofuscación; consulta Detección del problema antes de la ejecución más abajo para ver las formas de las advertencias y un fragmento para CI.

Por qué se tratan de forma diferente lo estático y lo dinámico

eval(s) puede leer y escribir variables locales de la función en la que se llama. Cuando s es un literal de cadena, el ofuscador puede parsear el cuerpo en tiempo de ofuscación y renombrar los identificadores de forma coherente con el código circundante. Cuando s es una expresión dinámica, el parseo solo ocurre en tiempo de ejecución, momento en el que los identificadores ya han sido renombrados, por lo que el código fuente construido en tiempo de ejecución referencia nombres antiguos que ya no existen.

new Function(s) funciona de otra manera: el cuerpo siempre se ejecuta como si estuviera definido en la parte superior de tu archivo, con acceso solo a las variables globales y nunca a las locales que rodean la llamada. Eso por sí solo es seguro, pero si construyes el cuerpo concatenando en él un identificador renombrado (por ejemplo, mediante func.toString() de una función cuyas entrañas el ofuscador ha reescrito), la función compilada en tiempo de ejecución seguirá topándose con el mismo tipo de ReferenceError.

Un new Function('return 42') estático nunca conlleva este riesgo: el cuerpo es una cadena simple que el renombrador nunca inspecciona, y en tiempo de ejecución solo necesita ver las globales. El ofuscador deja la llamada en su sitio y la función circundante sigue siendo elegible para el bytecoding de la VM.

El error que ves en tiempo de ejecución

El síntoma habitual es un ReferenceError la primera vez que se ejecuta la función construida dinámicamente:

browser console

Error

Aquí TU es un identificador renombrado que el ofuscador introdujo dentro del ámbito de la IIFE del bundle. La llamada al eval dinámico / constructor Function evalúa un cuerpo que lo referencia, pero el cuerpo se ejecuta en un ámbito donde TU no está definido.

Detección del problema antes de la ejecución

El ofuscador emite advertencias no fatales a través de la API para que puedas detectar estos patrones en CI antes de publicar. Dos tipos de advertencia son relevantes:

  • DynamicCodeRenameRisk — una función contiene una llamada dinámica a eval / new Function / Function cuyo cuerpo se construye en tiempo de ejecución.
  • VMDynamicCodeSkipped — se omitió el bytecoding de la VM para una función debido a uno de los patrones anteriores. Incluye el nombre de la función (si está disponible) y el tipo de construcción que provocó la exclusión.

ci-build.mjs

JavaScript

Si un tipo de advertencia es esperado en tu build y prefieres silenciarlo en el origen antes que filtrarlo en CI, la opción warnings (v7.8.0+) controla qué emite getWarnings(): 'none' lo suprime todo, y un mapa por tipo como { VMDynamicCodeSkipped: false } silencia solo un tipo manteniendo el resto.

Soluciones alternativas

  • Cambia a eval indirecto ((0, eval)(s))

    Solo útil para el caso de eval. El eval indirecto se ejecuta en el ámbito global, así que no puede ver las variables locales circundantes, pero por esa misma razón tampoco puede referenciar identificadores renombrados. La función que contiene la llamada permanece convertida a bytecode de la VM.

  • Haz que el cuerpo sea totalmente estático

    Para new Function, si puedes expresar el cuerpo como un único literal de cadena / literal de plantilla sin interpolaciones, la llamada no conlleva riesgo de renombrado y la función que la rodea sigue convirtiéndose a bytecode. new Function('a', 'b', 'return a + b') está bien; new Function('return ' + expr) no lo está.

  • Mueve la llamada a su propia función de nivel superior y desenvuelve la IIFE

    Cada función de nivel superior se comprueba de forma independiente. Sacar la llamada de construcción de código dinámico a su propia función de nivel superior hace que solo esa función pierda el bytecoding de la VM, en lugar de que la exclusión se propague en cascada por una IIFE de nivel superior que envuelve todo tu bundle.

  • Cambia a vmTargetFunctionsMode: 'comment'

    Modo opt-in: solo se convierten a bytecode las funciones marcadas con /* javascript-obfuscator:vm */. No anotes la función que contiene la llamada de código dinámico y convierte a bytecode el resto. Consulta Selección de funciones.

  • Anula la exclusión con vmForceCompileDynamicCode: true (v6.14.0+)

    Vía de escape de último recurso. Cuando está activada, el ofuscador convierte de todos modos a bytecode la función circundante y suprime la advertencia VMDynamicCodeSkipped. Úsala solo cuando puedas garantizar que el cuerpo construido en tiempo de ejecución nunca referencia un identificador que el ofuscador renombra; de lo contrario, cambias una ofuscación limpia por un ReferenceError en tiempo de ejecución. DynamicCodeRenameRisk sigue disparándose para que CI pueda seguir controlándolo. En el panel, este es el interruptor "Force Compile Dynamic Code" dentro del grupo Overrides de la sección VM.

Páginas relacionadas