Dokümantasyon
/

Sorun giderme

/

eval ve new Function ile VM Obfuscation

eval ve new Function ile VM Obfuscation

VM obfuscation işleminin dinamik kod oluşturmayı (doğrudan eval ve Function yapıcısı) nasıl ele aldığı, neyin bayt koduna çevrilip neyin atlandığı, obfuscator aracının ürettiği uyarılar ve çalışma zamanındaki ReferenceError hatalarının nasıl teşhis edileceği.

Bu neden önemli

VM obfuscation, fonksiyon gövdelerini çalışma zamanındaki bir yorumlayıcı üzerinden 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şir: eval(s), new Function(...s) ve Function(...s). Çalışma zamanında oluşturulan kod, obfuscator aracının yeniden adlandırdığı bir tanımlayıcıya başvurursa, üretilen fonksiyon ilk kez çalıştığında Uncaught ReferenceError: <renamed-name> is not defined hatası alırsınız.

Obfuscator her kalıbı farklı şekilde ele alır. Aşağıdaki tablo kısa özettir; sayfanın geri kalanı her satırı açıklar.

Obfuscator ne yapar: bir bakışta

Kaynak kodunuzdaki kalıpNe olur
eval('literal string') (gövde bir string değişmezi)Doğru çalışır (vmForceCompileDynamicCode açık değilse; bkz. Geçici çözümler). Bu çağrıyı içeren fonksiyon - ve içinde tanımlanan her fonksiyon - VM bayt koduna çevrilmez (doğrudan eval çevreleyen yerel değişkenleri okur; VM ise bir fonksiyon bayt koduna derlendikten sonra bu değişkenleri korumaz).
eval(dynamicExpression)Çalışma zamanında ReferenceError ile çökebilir. Bu çağrıyı içeren fonksiyon ve içinde tanımlanan her fonksiyon da VM bayt koduna çevrilmez.
(0, eval)(s) / window.eval(s) (dolaylı)Bu çağrıyı içeren fonksiyon normal şekilde VM bayt koduna çevrilir. Dolaylı eval global kapsamda çalışır ve çevreleyen yerel değişkenleri göremez; bu nedenle yeniden adlandırılmış yerel değişkenler onu bozamaz. Değerlendirilen kod yeniden adlandırılmış veya kaldırılmış bir global değişkene başvurursa yine de hata verebilir.
new Function('a', 'b', 'return a + b') (tüm argümanlar string değişmezi)Doğru çalışır. Bu çağrıyı içeren fonksiyon normal şekilde VM bayt koduna çevrilir.
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 da VM bayt koduna çevrilmez.

Derleyici ayrıca eval?.(code) ifadesini ihtiyatlı biçimde tespit eder ve doğrudan eval gibi ele alır. JavaScript bu isteğe bağlı çağrı biçimini dolaylı eval olarak tanımlar; derleyicinin tespiti bu dil davranışını değiştirmez.

Bu kalıpların yalnızca bazıları ölümcül olmayan uyarılar üretir ve yalnızca çağrı bir fonksiyonun içindeyse: dinamik bir eval, new Function veya Function çağrısı hem DynamicCodeRenameRisk hem de VMDynamicCodeSkipped bildirir (VMDynamicCodeSkipped yalnızca VM'in aksi halde derleyeceği bir fonksiyon için: hariç tutulmamış ve comment modundaysa işaretlenmiş; iç içe bir fonksiyon için atlanan en üst düzey üst fonksiyonu adlandırır), statik bir eval('...') yalnızca VMDynamicCodeSkipped bildirir, dolaylı eval ve tamamen statik bir new Function ise hiçbir şey bildirmez. Bir dosyanın en üst düzeyinde, herhangi bir fonksiyonun dışındaki dinamik bir çağrı hiçbir zaman bildirilmez. Uyarı türleri ve bir CI kod parçası için aşağıdaki Sorunu çalışma zamanından önce tespit etme bölümüne bakın.

Atlama IIFE'ler boyunca yayılır. En üst düzeydeki bir IIFE paketinizin tamamını sarmalıyorsa ve içindeki herhangi bir fonksiyon doğrudan bir eval (statik veya dinamik) ya da dinamik bir new Function / Function çağrısı kullanıyorsa, IIFE'nin tamamı VM bayt koduna çevrilmekten muaf tutulur. Etki alanını sınırlayan IIFE'yi açma kalıbı için Doğrudan eval() VM Obfuscation İşlemini Devre Dışı Bırakır sayfasına bakın.

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

eval(s), çağrıldığı fonksiyonun yerel değişkenlerini 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; o 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) ve dolaylı eval farklı çalışır: gövde her zaman global kapsamda çalışır; yalnızca global değişkenlere erişebilir, çağrının çevresindeki yerel değişkenlere asla erişemez. Yeniden adlandırılmış yerel değişkenlere bağımlı olamazlar, ancak yeniden adlandırılmış veya kaldırılmış global değişkenlere başvururlarsa ya da gövdeyi yeniden adlandırılmış bir tanımlayıcıyı birleştirerek oluşturursanız (örn. iç yapısı obfuscator tarafından yeniden yazılmış bir fonksiyonun func.toString() çıktısı aracılığıyla) yine de hata verebilirler.

new Function('a', 'b', 'return a + b') gibi yalnızca kendi parametrelerini kullanan statik bir gövdenin böyle bir bağımlılığı yoktur: gövde, yeniden adlandırıcının hiç incelemediği düz bir string'dir. Obfuscator çağrıyı olduğu yerde bırakır ve çevreleyen fonksiyon VM bayt koduna çevrilmeye uygun olmaya devam eder. Yalnızca eval söz dizimini değiştirmek rastgele dinamik kodu güvenli hale getirmez; uyarıları inceleyin ve son paketi test edin.

Ç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 hatasıdır:

Metin

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

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

Obfuscator, bu kalıpların bazılarını yayınlamadan önce CI'da yakalayabilmeniz için API üzerinden ölümcül olmayan uyarılar üretir. İki uyarı türü ilgilidir. İkisi de yalnızca bir fonksiyonun içindeki çağrılar için bildirilir; bunun istisnası .toString() durumudur: serileştirilen fonksiyon başka bir fonksiyonun içinde iç içe olduğunda veya çözümlenemediğinde, çağrı nerede olursa olsun bildirilir (yalnızca VM derlemelerinde):

  • DynamicCodeRenameRisk - bir fonksiyon çalışma zamanında bir string'den kod oluşturur: doğrudan bir eval veya gövdesi statik olmayan bir new Function / Function çağrısı ya da bir <script> veya Worker içine enjekte edilen fn.toString(). Bir ReferenceError hatasını öngören uyarı budur.
  • VMDynamicCodeSkipped - bir fonksiyon doğrudan bir eval veya dinamik bir new Function / Function çağrısı içerdiği için o fonksiyon için VM bayt koduna çevirme atlanmıştır. Root modunda (varsayılan) içinde tanımlanan her fonksiyon da atlanır; comment modunda ayrıca işaretlediğiniz iç içe bir fonksiyon, çağrının kendisini içermediği sürece yine sanallaştırılır. Güvenli, statik bir eval('literal') için de tetiklenir; bu nedenle bir çalışma zamanı çökmesine değil, kaybedilen VM korumasına işaret eder. Fonksiyon adını (varsa) ve atlamayı tetikleyen yapıyı içerir.

javascript-obfuscator npm paketi uyarıları sonucunda sunmaz; bu nedenle bir CI kapısı onları API yanıtından okur: result ve chunk_end mesajları bir warnings dizisi taşır. Aşağıdaki örnek, API Referansı sayfasındaki akış okuyucusu olan readObfuscationResponse() fonksiyonunu kullanır ve derlemeyi yalnızca DynamicCodeRenameRisk durumunda başarısız kılar; bir fonksiyonda VM korumasının kaybedilmesi de sürümü engellemeliyse filtreye VMDynamicCodeSkipped ekleyin.

Kod

Bir uyarı türü derlemenizde bekleniyorsa ve onu CI'da filtrelemek yerine kaynağında susturmayı tercih ediyorsanız, warnings seçeneği (v7.8.0+) hangi uyarıların üretileceğini denetler: 'none' her şeyi bastırır, { VMDynamicCodeSkipped: false } gibi tür bazında bir eşleme ise diğerlerini korurken yalnızca bir türü susturur.

Geçici çözümler

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

    Yalnızca eval durumu için işe yarar. Dolaylı eval global kapsamda çalışır; bu nedenle çevreleyen yerel değişkenleri göremez ve aynı nedenle yeniden adlandırılmış yerel değişkenlere de başvuramaz. Çağrıyı içeren fonksiyon VM bayt koduna çevrilmeye devam eder. Değerlendirilen kod yine de obfuscator aracının yeniden adlandırdığı global değişkenlere bağımlı olmamalıdır.

  • Gövdeyi tamamen statik yapın

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

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

    Her en üst düzey fonksiyon bağımsız olarak denetlenir. Dinamik kod çağrısını kendi en üst düzey fonksiyonuna taşımak, atlamanın paketinizin tamamını saran en üst düzey bir IIFE boyunca yayılması yerine yalnızca o fonksiyonun VM bayt koduna çevrilmemesi anlamına gelir.

  • vmTargetFunctionsMode: 'comment' moduna geçin

    Katılım tabanlı mod: yalnızca /* javascript-obfuscator:vm */ ile işaretlenmiş fonksiyonlar bayt koduna çevrilir. Dinamik kod çağrısını içeren fonksiyonu işaretlemeyin, geri kalanları işaretleyin. Bu yorumları obfuscation adımına kadar koruyun. Bkz. Belirli Fonksiyonları Hedefleme.

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

    Son çare olarak kullanılacak bir kaçış yolu. Etkinleştirildiğinde obfuscator çevreleyen fonksiyonu yine de bayt koduna çevirir ve VMDynamicCodeSkipped uyarısını bastırır. Kapsamı onaramaz: zorla derlenen bir fonksiyonun içinde doğrudan eval, kod bir string değişmezi olsa bile fonksiyonun yerel değişkenlerini veya parametrelerini ya da yine sanallaştırılan çevreleyen bir fonksiyonun değişkenlerini okuyamaz ve yazamaz. Okumalar ReferenceError fırlatır, yazmalar ise sessizce kaybolur. Bu durumdaki statik bir eval('...') hiçbir uyarı bildirmez, dolayısıyla bir CI kapısı onu yakalamaz; DynamicCodeRenameRisk yine yalnızca dinamik bir gövde için üretilir. Yalnızca değerlendirilen kod globaller dışında hiçbir şeye başvurmuyorsa kullanın. Panelde bu, VM bölümündeki Geçersiz Kılmalar grubunun altındaki Force Compile Dynamic Code anahtarıdır.

İlgili sayfalar