Documentação
/
Receitas
/

Ofuscação VM com eval e new Function

Ofuscação VM com eval e new Function

Como a ofuscação VM lida com a construção dinâmica de código (eval direto e o construtor Function), o que vira bytecode e o que é ignorado, os avisos que o obfuscator emite e como diagnosticar ReferenceError em tempo de execução.

Por que isso importa

A ofuscação VM compila os corpos das funções em bytecode despachado por um interpretador embutido no runtime. Os identificadores do escopo ao redor também são renomeados. Ambas as transformações interagem mal com código construído a partir de uma string em tempo de execução: eval(s), new Function(...s) e Function(...s). Se o código construído em tempo de execução referenciar um identificador que o obfuscator renomeou, você recebe Uncaught ReferenceError: <renamed-name> is not defined na primeira vez que a função gerada é executada.

O obfuscator trata cada padrão de forma diferente. A matriz abaixo é a versão resumida; o restante desta página explica cada célula.

O que o obfuscator faz, em resumo

Padrão no seu código-fonteO que acontece
eval('literal string') (o corpo é um literal de string)Executa corretamente. A função que contém essa chamada perde a compilação para bytecode da VM (o eval direto lê as variáveis locais ao redor, que a VM não preserva depois que uma função é compilada para bytecode).
eval(dynamicExpression)Pode falhar em tempo de execução com ReferenceError. A função que contém essa chamada — e toda função definida dentro dela — também perde a compilação para bytecode da VM.
(0, eval)(s) / window.eval(s) (indireto)Executa corretamente. A função que contém essa chamada é compilada para bytecode da VM normalmente. O eval indireto não enxerga as variáveis locais ao redor, então a renomeação não pode quebrá-lo.
new Function('a', 'b', 'return a + b') (todos os argumentos são literais de string)Executa corretamente. A função que contém essa chamada é compilada para bytecode da VM normalmente.
new Function(dynamicBody) / Function(dynamicBody)Pode falhar em tempo de execução com ReferenceError. A função que contém essa chamada — e toda função definida dentro dela — também perde a compilação para bytecode da VM.

Todos esses padrões também são reportados como avisos não fatais no resultado da ofuscação — veja Detectando o problema antes da execução abaixo para os formatos dos avisos e um trecho de CI.

Por que o estático e o dinâmico são tratados de formas diferentes

eval(s) pode ler e escrever variáveis locais da função em que é chamado. Quando s é um literal de string, o obfuscator consegue analisar o corpo no momento da ofuscação e renomear os identificadores de forma consistente com o código ao redor. Quando s é uma expressão dinâmica, a análise só acontece em tempo de execução — quando os identificadores já foram renomeados, então o código construído em tempo de execução referencia nomes antigos que não existem mais.

new Function(s) funciona de outra forma: o corpo sempre executa como se estivesse definido no topo do seu arquivo, com acesso apenas às variáveis globais e nunca às locais ao redor da chamada. Isso, por si só, é seguro — mas, se você monta o corpo concatenando nele um identificador renomeado (por exemplo, via func.toString() de uma função cujo interior o obfuscator reescreveu), a função compilada em tempo de execução ainda vai esbarrar no mesmo tipo de ReferenceError.

O new Function('return 42') estático nunca corre esse risco: o corpo é uma string simples que o renomeador nunca inspeciona e, em tempo de execução, ele só precisa enxergar variáveis globais. O obfuscator mantém a chamada como está e a função ao redor continua elegível para a compilação para bytecode da VM.

O erro que você vê em tempo de execução

O sintoma comum é um ReferenceError na primeira vez que a função construída dinamicamente é executada:

browser console

Error

Aqui, TU é um identificador renomeado que o obfuscator introduziu dentro do escopo da IIFE do bundle. A chamada de eval dinâmico / construtor Function avalia um corpo que o referencia, mas esse corpo executa em um escopo onde TU está indefinido.

Detectando o problema antes da execução

O obfuscator emite avisos não fatais pela API para que você detecte esses padrões na CI antes de publicar. Dois tipos de aviso são relevantes:

  • DynamicCodeRenameRisk — uma função contém uma chamada dinâmica de eval / new Function / Function cujo corpo é construído em tempo de execução.
  • VMDynamicCodeSkipped — a compilação para bytecode da VM foi ignorada em uma função por causa de um dos padrões acima. Inclui o nome da função (quando disponível) e o tipo de construção que provocou o descarte.

ci-build.mjs

JavaScript

Se um tipo de aviso for esperado no seu build e você preferir silenciá-lo na origem em vez de filtrá-lo na CI, a opção warnings (v7.8.0+) controla o que getWarnings() emite: 'none' suprime tudo, e um mapa por tipo como { VMDynamicCodeSkipped: false } silencia apenas um tipo, mantendo os demais.

Alternativas

  • Mude para o eval indireto ((0, eval)(s))

    Útil apenas para o caso do eval. O eval indireto executa no escopo global, então não enxerga as variáveis locais ao redor — mas, por isso mesmo, também não consegue referenciar identificadores renomeados. A função que contém a chamada continua compilada para bytecode da VM.

  • Deixe o corpo totalmente estático

    No caso do new Function, se você conseguir expressar o corpo como um único literal de string / template literal sem interpolações, a chamada não traz risco de renomeação e a função ao redor continua sendo compilada para bytecode. new Function('a', 'b', 'return a + b') está ok; new Function('return ' + expr) não.

  • Mova a chamada para uma função de nível superior própria e desempacote a IIFE

    Cada função de nível superior é verificada de forma independente. Mover a chamada de construção dinâmica de código para uma função de nível superior própria faz com que apenas essa função perca a compilação para bytecode da VM, em vez de o descarte se propagar por uma IIFE de nível superior que envolve todo o seu bundle.

  • Mude para vmTargetFunctionsMode: 'comment'

    Modo opt-in: apenas as funções marcadas com /* javascript-obfuscator:vm */ são compiladas para bytecode. Não anote a função que contém a chamada de código dinâmico e compile o restante para bytecode. Veja Selecionando funções.

  • Sobrescreva o descarte com vmForceCompileDynamicCode: true (v6.14.0+)

    Válvula de escape de último recurso. Quando ativada, o obfuscator compila a função ao redor para bytecode mesmo assim e suprime o aviso VMDynamicCodeSkipped. Use apenas quando você puder garantir que o corpo construído em tempo de execução nunca referencia um identificador que o obfuscator renomeia — caso contrário, você troca uma ofuscação limpa por um ReferenceError em tempo de execução. O DynamicCodeRenameRisk continua sendo emitido, então a CI pode seguir bloqueando com base nele. No painel, este é o switch "Force Compile Dynamic Code" no grupo Sobrescritas da seção VM.

Páginas relacionadas