Dokümantasyon
/
Tarifler
/

eval ve new Function ile VM Obfuscation

VM Obfuscation with eval and new Function

VM obfuscation'ın dinamik kod oluşturmayı (doğrudan eval ve Function yapıcısı) nasıl ele aldığı, neyin bayt koda çevrildiği, neyin atlandığı, obfuscator'ın yayınladığı uyarılar ve çalışma zamanındaki ReferenceError'ın nasıl teşhis edileceği.

Bu neden önemli

VM obfuscation, fonksiyon gövdelerini, çalışma zamanı içindeki bir yorumlayıcı aracılığıyla yürütülen bayt koduna derler. Çevreleyen kapsamdaki tanımlayıcılar da yeniden adlandırılır. Her iki dönüşüm de çalışma zamanında bir string'den oluşturulan kodla kötü etkileşime girer: eval(s), new Function(...s) ve Function(...s). Çalışma zamanında oluşturulan kod, obfuscator'ın yeniden adlandırdığı bir tanımlayıcıya başvuruyorsa, üretilen fonksiyon ilk kez çalıştığında Uncaught ReferenceError: <renamed-name> is not defined hatasını alırsınız.

Obfuscator her kalıbı farklı şekilde ele alır. Aşağıdaki matris kısa versiyondur; sayfanın geri kalanı her hücreyi açıklar.

Obfuscator ne yapar, bir bakışta

Kaynağınızdaki kalıpNe olur
eval('literal string') (gövde bir string değişmezidir)Doğru çalışır. Bu çağrıyı içeren fonksiyon VM bayt kodlamasını kaybeder (doğrudan eval, çevreleyen yerel değişkenleri okur; VM ise bir fonksiyon bayt koduna derlendikten sonra bunları korumaz).
eval(dynamicExpression)Çalışma zamanında ReferenceError ile çökebilir. Bu çağrıyı içeren fonksiyon — ve içinde tanımlanan her fonksiyon — de VM bayt kodlamasını kaybeder.
(0, eval)(s) / window.eval(s) (dolaylı)Doğru çalışır. Bu çağrıyı içeren fonksiyon normal şekilde VM bayt koduna dönüştürülür. Dolaylı eval, çevreleyen yerel değişkenleri göremez, bu nedenle yeniden adlandırma onu bozamaz.
new Function('a', 'b', 'return a + b') (tüm argümanlar string değişmezidir)Doğru çalışır. Bu çağrıyı içeren fonksiyon normal şekilde VM bayt koduna dönüştürülür.
new Function(dynamicBody) / Function(dynamicBody)Çalışma zamanında ReferenceError ile çökebilir. Bu çağrıyı içeren fonksiyon — ve içinde tanımlanan her fonksiyon — de VM bayt kodlamasını kaybeder.

Tüm bu kalıplar aynı zamanda obfuscation sonucunda ölümcül olmayan uyarılar olarak da ortaya çıkar — uyarı biçimleri ve bir CI parçacığı için aşağıdaki Sorunu çalışma zamanından önce tespit etme bölümüne bakın.

Statik ve dinamik neden farklı ele alınır

eval(s), çağrıldığı fonksiyondaki yerel değişkenleri okuyabilir ve yazabilir. s bir string değişmezi olduğunda, obfuscator gövdeyi obfuscation sırasında ayrıştırabilir ve tanımlayıcıları çevreleyen kodla tutarlı biçimde yeniden adlandırabilir. s dinamik bir ifade olduğunda ayrıştırma yalnızca çalışma zamanında gerçekleşir — bu noktada tanımlayıcılar zaten yeniden adlandırılmıştır, dolayısıyla çalışma zamanında oluşturulan kaynak, artık var olmayan eski adlara başvurur.

new Function(s) farklı çalışır: gövde her zaman dosyanızın en üstünde tanımlanmış gibi çalışır; yalnızca global değişkenlere erişimi vardır ve çağrının çevresindeki yerel değişkenlere asla erişemez. Bu tek başına güvenlidir — ancak gövdeyi, içine yeniden adlandırılmış bir tanımlayıcı ekleyerek oluşturuyorsanız (örneğin, obfuscator'ın iç yapısını yeniden yazdığı bir fonksiyonun func.toString() çağrısıyla), çalışma zamanında derlenen fonksiyon yine aynı türden ReferenceError ile karşılaşır.

Statik new Function('return 42') bu riski asla taşımaz: gövde, yeniden adlandırıcının hiçbir zaman incelemediği düz bir dizedir ve çalışma zamanında yalnızca global değişkenleri görmesi gerekir. Obfuscator çağrıyı yerinde bırakır ve çevreleyen fonksiyon hâlâ VM bayt kodlamasına uygundur.

Çalışma zamanında gördüğünüz hata

Yaygın belirti, dinamik olarak oluşturulan fonksiyon ilk kez çalıştığında ortaya çıkan bir ReferenceError'dır:

browser console

Hata

Buradaki TU, obfuscator'ın paketin IIFE kapsamı içinde tanıttığı yeniden adlandırılmış bir tanımlayıcıdır. Dinamik eval / Function yapıcı çağrısı, ona başvuran bir gövdeyi değerlendirir, ancak gövde TU'nun tanımsız olduğu bir kapsamda çalışır.

Sorunu çalışma zamanından önce tespit etme

Obfuscator, bu kalıpları göndermeden önce CI'da yakalayabilmeniz için API aracılığıyla ölümcül olmayan uyarılar yayınlar. İlgili iki uyarı türü vardır:

  • DynamicCodeRenameRisk — bir fonksiyon, gövdesi çalışma zamanında oluşturulan dinamik bir eval / new Function / Function çağrısı içerir.
  • VMDynamicCodeSkipped — yukarıdaki kalıplardan biri nedeniyle bir fonksiyon için VM bayt kodlaması atlandı. Fonksiyon adını (varsa) ve atlamayı tetikleyen yapının türünü içerir.

ci-build.mjs

JavaScript

Derlemenizde bir uyarı türü bekleniyorsa ve bunu CI'da filtrelemek yerine kaynağında susturmayı tercih ediyorsanız, warnings seçeneği (v7.8.0+) getWarnings()'in ne yayınladığını denetler: 'none' her şeyi bastırır ve { VMDynamicCodeSkipped: false } gibi tür bazlı bir eşleme, geri kalanı korurken yalnızca bir türü susturur.

Geçici çözümler

  • Dolaylı eval'e geçin ((0, eval)(s))

    Yalnızca eval durumunda yararlıdır. Dolaylı eval global kapsamda çalışır, bu nedenle çevreleyen yerel değişkenleri göremez — ama aynı nedenle yeniden adlandırılmış tanımlayıcılara da başvuramaz. Çağrıyı içeren fonksiyon VM bayt kodlu kalır.

  • Gövdeyi tümüyle statik yapın

    new Function için, gövdeyi hiçbir değer yerleştirmesi içermeyen tek bir string değişmezi / şablon değişmezi olarak ifade edebiliyorsanız, çağrı yeniden adlandırma riski taşımaz ve çevresindeki fonksiyon yine bayt koduna dönüştürülür. new Function('a', 'b', 'return a + b') sorunsuzdur; new Function('return ' + expr) değildir.

  • Çağrıyı kendi üst düzey fonksiyonuna taşıyın ve IIFE'yi açın

    Her üst düzey fonksiyon bağımsız olarak kontrol edilir. Dinamik kod oluşturma çağrısını kendi üst düzey fonksiyonuna taşımak, atlamanın tüm paketinizi saran üst düzey bir IIFE boyunca zincirleme yayılması yerine yalnızca o tek fonksiyonun VM bayt kodlamasını kaybetmesi anlamına gelir.

  • vmTargetFunctionsMode: 'comment' moduna geçin

    Tercihe bağlı mod: yalnızca /* javascript-obfuscator:vm */ ile işaretlenmiş fonksiyonlar bayt koduna dönüştürülür. Dinamik kod çağrısını içeren fonksiyonu işaretlemeyi atlayın ve geri kalanını bayt koduna dönüştürün. Fonksiyonları Hedefleme sayfasına bakın.

  • Atlamayı vmForceCompileDynamicCode: true ile geçersiz kılın (v6.14.0+)

    Son çare niteliğinde bir kaçış yolu. Etkinleştirildiğinde, obfuscator çevreleyen fonksiyonu yine de bayt koduna dönüştürür ve VMDynamicCodeSkipped uyarısını bastırır. Yalnızca, çalışma zamanında oluşturulan gövdenin obfuscator'ın yeniden adlandırdığı bir tanımlayıcıya asla başvurmayacağını garanti edebiliyorsanız kullanın — aksi takdirde temiz bir obfuscation'ı çalışma zamanındaki bir ReferenceError ile takas etmiş olursunuz. DynamicCodeRenameRisk yine de tetiklenir, böylece CI bunun üzerinden geçit kontrolü yapmaya devam edebilir. Kontrol panelinde bu, VM bölümünün Geçersiz Kılmalar grubu altındaki "Force Compile Dynamic Code" anahtarıdır.

İlgili sayfalar