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ıp | Ne 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:
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 bireval/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.
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
evaldurumunda 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 Functioniç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çinTercihe 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: trueile 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
VMDynamicCodeSkippeduyarı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 birReferenceErrorile takas etmiş olursunuz.DynamicCodeRenameRiskyine 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
- VM Obfuscation — Doğrudan eval Davranışı — atlamanın etki alanını sınırlamak için IIFE açma kalıbı.
- VM Obfuscation — Fonksiyonları Hedefleme — fonksiyon ayrıntı düzeyinde dahil etmek veya hariç tutmak için
vmTargetFunctionsModekullanımı.
