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ıp | Ne 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:
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 birevalveya gövdesi statik olmayan birnew Function/Functionçağrısı ya da bir<script>veya Worker içine enjekte edilenfn.toString(). BirReferenceErrorhatasını öngören uyarı budur.VMDynamicCodeSkipped- bir fonksiyon doğrudan birevalveya dinamik birnew 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 bireval('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.
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çinYalnızca
evaldurumu 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 Functioniç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çinKatı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: trueile 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
VMDynamicCodeSkippeduyarısını bastırır. Kapsamı onaramaz: zorla derlenen bir fonksiyonun içinde doğrudaneval, 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. OkumalarReferenceErrorfırlatır, yazmalar ise sessizce kaybolur. Bu durumdaki statik bireval('...')hiçbir uyarı bildirmez, dolayısıyla bir CI kapısı onu yakalamaz;DynamicCodeRenameRiskyine 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
- Doğrudan eval() VM Obfuscation İşlemini Devre Dışı Bırakır - atlamanın etki alanını sınırlamak için IIFE'yi açma kalıbı.
- Belirli Fonksiyonları Hedefleme - fonksiyon düzeyinde katılmak veya hariç tutmak için
vmTargetFunctionsModekullanımı.
