Dokumentasi
/
Resep
/

Obfuskasi VM dengan eval dan new Function

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 AndaApa 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:

browser console

Error

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 pemanggilan eval / new Function / Function dinamis 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.

ci-build.mjs

JavaScript

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 sebuah ReferenceError saat runtime. DynamicCodeRenameRisk tetap terpicu sehingga CI dapat terus menjaganya. Di dasbor, ini adalah sakelar "Force Compile Dynamic Code" di bawah grup Overrides pada bagian VM.

Halaman terkait