Obfuskasi VM dengan eval dan new Function
Bagaimana obfuskasi VM menangani konstruksi kode dinamis (direct eval dan konstruktor Function), apa yang dibytecode versus dilewati, peringatan yang dikeluarkan obfuscator, serta cara mendiagnosis ReferenceError saat runtime.
Mengapa ini penting
Obfuskasi VM mengompilasi tubuh fungsi menjadi bytecode yang di-dispatch melalui sebuah interpreter di dalam runtime.
Pengenal pada scope di sekitarnya juga diganti namanya. Kedua transformasi tersebut berinteraksi buruk dengan kode yang dibangun dari sebuah string saat
runtime: eval(s), new Function(...s), dan Function(...s). Jika kode yang dibangun saat runtime merujuk pengenal yang telah
diganti namanya oleh obfuscator, Anda akan mendapatkan Uncaught ReferenceError: <renamed-name> is not defined pada saat fungsi yang dihasilkan
pertama kali dijalankan.
Obfuscator menangani setiap pola secara berbeda. Matriks di bawah ini adalah versi singkatnya; sisa halaman ini menjelaskan setiap selnya.
Apa yang dilakukan obfuscator, sekilas
| Pola pada sumber Anda | Apa yang terjadi |
|---|---|
eval('literal string') (body-nya berupa literal string) | Berjalan dengan benar. Fungsi yang mengandung pemanggilan ini kehilangan bytecoding VM (direct eval membaca variabel lokal di sekitarnya, yang tidak dipertahankan VM begitu sebuah fungsi dikompilasi menjadi bytecode). |
eval(dynamicExpression) | Bisa crash saat runtime dengan ReferenceError. Fungsi yang mengandung pemanggilan ini — dan setiap fungsi yang didefinisikan di dalamnya — juga kehilangan bytecoding VM. |
(0, eval)(s) / window.eval(s) (tidak langsung) | Berjalan dengan benar. Fungsi yang mengandung pemanggilan ini dibytecode VM secara normal. Indirect eval tidak dapat melihat variabel lokal di sekitarnya, sehingga penggantian nama tidak dapat merusaknya. |
new Function('a', 'b', 'return a + b') (semua argumen berupa literal string) | Berjalan dengan benar. Fungsi yang mengandung pemanggilan ini dibytecode VM secara normal. |
new Function(dynamicBody) / Function(dynamicBody) | Bisa crash saat runtime dengan ReferenceError. Fungsi yang mengandung pemanggilan ini — dan setiap fungsi yang didefinisikan di dalamnya — juga kehilangan bytecoding VM. |
Semua pola ini juga dimunculkan sebagai peringatan non-fatal pada hasil obfuskasi — lihat Mendeteksi masalah sebelum runtime di bawah untuk bentuk peringatannya dan sebuah cuplikan CI.
Mengapa yang statis dan dinamis diperlakukan berbeda
eval(s) dapat membaca dan menulis variabel lokal dari fungsi tempat ia dipanggil. Ketika s berupa literal string,
obfuscator dapat mengurai body tersebut saat obfuskasi dan mengganti nama pengenal secara konsisten dengan kode di sekitarnya. Ketika
s berupa ekspresi dinamis, penguraiannya baru terjadi saat runtime — pada titik itu pengenal sudah
diganti namanya, sehingga sumber yang dibangun saat runtime merujuk nama lama yang tidak lagi ada.
new Function(s) bekerja berbeda: body-nya selalu berjalan seolah-olah didefinisikan di bagian atas berkas Anda, dengan akses
hanya ke variabel global dan tidak pernah ke variabel lokal di sekitar pemanggilan. Itu sendiri aman — tetapi jika Anda membangun body
dengan menggabungkan pengenal yang telah diganti nama ke dalamnya (mis. melalui func.toString() dari sebuah fungsi yang internalnya telah ditulis ulang oleh obfuscator),
fungsi yang dikompilasi saat runtime tetap akan mengalami ReferenceError sejenis.
new Function('return 42') yang statis tidak pernah membawa risiko ini: body-nya adalah string biasa yang tidak pernah diperiksa oleh renamer, dan
saat runtime ia hanya perlu melihat global. Obfuscator membiarkan pemanggilan tersebut apa adanya dan fungsi di sekitarnya tetap
memenuhi syarat untuk bytecoding VM.
Error yang Anda lihat saat runtime
Gejala umumnya adalah sebuah ReferenceError pada saat fungsi yang dibangun secara dinamis pertama kali dijalankan:
TU di sini adalah pengenal yang telah diganti nama yang diperkenalkan obfuscator di dalam scope IIFE bundel. Pemanggilan dynamic eval /
konstruktor Function mengevaluasi sebuah body yang merujuknya, tetapi body tersebut berjalan pada scope di mana TU tidak terdefinisi.
Mendeteksi masalah sebelum runtime
Obfuscator mengeluarkan peringatan non-fatal melalui API sehingga Anda dapat menangkap pola-pola ini di CI sebelum pengiriman. Dua jenis peringatan yang relevan:
DynamicCodeRenameRisk— sebuah fungsi mengandung pemanggilaneval/new Function/Functiondinamis yang body-nya dibangun saat runtime.VMDynamicCodeSkipped— bytecoding VM dilewati untuk sebuah fungsi karena salah satu pola di atas. Menyertakan nama fungsi (jika tersedia) dan jenis konstruksi yang memicu pelewatan tersebut.
Jika suatu jenis peringatan memang diharapkan dalam build Anda dan Anda lebih memilih membisukannya di sumbernya daripada menyaringnya di CI,
opsi warnings (v7.8.0+) mengatur apa yang dikeluarkan getWarnings(): 'none' menekan segalanya, dan peta per-jenis
seperti { VMDynamicCodeSkipped: false } membisukan hanya satu jenis sambil mempertahankan sisanya.
Solusi
Beralih ke indirect eval (
(0, eval)(s))Hanya berguna untuk kasus
eval. Indirect eval berjalan pada scope global, sehingga tidak dapat melihat variabel lokal di sekitarnya — tetapi justru karena itu, ia juga tidak dapat merujuk pengenal yang diganti namanya. Fungsi yang mengandung pemanggilan tersebut tetap dibytecode VM.Jadikan body sepenuhnya statis
Untuk
new Function, jika Anda dapat mengekspresikan body sebagai satu literal string / literal template tanpa interpolasi, pemanggilan tersebut tidak membawa risiko penggantian nama dan fungsi di sekitarnya tetap dibytecode.new Function('a', 'b', 'return a + b')tidak masalah;new Function('return ' + expr)bermasalah.Pindahkan pemanggilan ke fungsi tingkat atasnya sendiri, dan lepaskan IIFE
Setiap fungsi tingkat atas diperiksa secara terpisah. Mengangkat pemanggilan konstruksi kode dinamis ke fungsi tingkat atasnya sendiri berarti hanya satu fungsi itu yang kehilangan bytecoding VM, alih-alih pelewatan yang merambat melalui IIFE tingkat atas yang membungkus seluruh bundel Anda.
Beralih ke
vmTargetFunctionsMode: 'comment'Mode opt-in: hanya fungsi yang ditandai dengan
/* javascript-obfuscator:vm */yang dibytecode. Jangan anotasi fungsi yang mengandung pemanggilan kode dinamis, dan bytecode sisanya. Lihat Menargetkan Fungsi.Timpa pelewatan dengan
vmForceCompileDynamicCode: true(v6.14.0+)Jalan keluar sebagai upaya terakhir. Ketika diaktifkan, obfuscator tetap membytecode fungsi di sekitarnya dan menekan peringatan
VMDynamicCodeSkipped. Gunakan hanya ketika Anda dapat menjamin bahwa body yang dibangun saat runtime tidak pernah merujuk pengenal yang diganti namanya oleh obfuscator — jika tidak, Anda menukar obfuskasi yang bersih dengan sebuahReferenceErrorsaat runtime.DynamicCodeRenameRisktetap terpicu sehingga CI dapat terus menjaganya. Di dasbor, ini adalah sakelar "Force Compile Dynamic Code" di bawah grup Overrides pada bagian VM.
Halaman terkait
- Obfuskasi VM — Perilaku Direct eval — pola pelepasan-IIFE untuk membatasi radius dampak pelewatan.
- Obfuskasi VM — Menargetkan Fungsi — menggunakan
vmTargetFunctionsModeuntuk ikut serta atau keluar pada granularitas fungsi.
