Dokumentasi
/
Resep
/

Menyembunyikan Nama Fungsi dari Analisis LLM

Menyembunyikan Nama Fungsi dari Analisis LLM

Pro

Masalahnya

Anda mengaktifkan vmObfuscation: true, menjalankannya pada berkas yang berisi fungsi seperti validateLicense, lalu mendapati bahwa keluaran hasil obfuskasi masih memuat teks harfiah validateLicense. Isi fungsinya sudah hilang dan diganti bytecode, tetapi namanya sendiri tetap terpampang jelas.

JavaScript

Kalikan itu di seluruh basis kode sungguhan dan Anda mendapatkan daftar nama fungsi seperti validateLicense, decryptPayload, processPayment, checkSubscription. LLM tidak perlu membongkar bytecode untuk memahami apa yang dilakukan program: nama-nama itu saja sudah cukup baginya untuk menghasilkan ringkasan perilaku modul yang yakin dan akurat. Bytecode-nya buram; daftar isinya tidak.

Mengapa obfuskasi VM mempertahankan nama-nama ini

Dengan vmTargetFunctionsMode: 'root' (bawaan), obfuscator mentransformasi isi setiap fungsi tingkat root menjadi bytecode VM, tetapi sengaja membiarkan nama-nya. Secara semantik, deklarasi fungsi tingkat root adalah binding pada cakupan di sekitarnya: untuk skrip artinya objek global, untuk modul artinya cakupan modul (fungsi tingkat atas yang tidak diekspor berada dalam cakupan modul, bukan global, tetapi tetap berada di tingkat root dalam berkas tersebut). Obfuscator tidak dapat mengganti namanya dengan aman karena tidak punya cara untuk mengetahui siapa lagi yang mereferensikannya: bundel lain, <script> inline, atribut HTML onclick="validateLicense(...)", pencarian dinamis window['validateLicense'], dan sebagainya.

Jadi kompromi yang diambil oleh perilaku bawaan adalah: lindungi implementasinya, pertahankan antarmuka publiknya. Hal ini menjaga integrasi tetap utuh, tetapi juga berarti LLM mendapatkan indeks gratis dari setiap titik masuk. Ketika hal ini terjadi, hasil obfuskasi melaporkan peringatan VMGlobalFunctionNamesNotRenamed yang mencantumkan nama-nama yang tetap terbaca.

Mengapa ini penting untuk rekayasa balik berbantuan LLM

Penyerang manusia yang dihadapkan pada beberapa ratus baris dispatch bytecode biasanya akan menyerah. LLM yang diberi berkas yang sama tidak akan repot menyerang bytecode sama sekali: ia akan membaca nama-namanya, mencocokkan beberapa literal string yang terlihat, lalu menghasilkan sesuatu seperti:

"Modul ini membatasi akses ke fitur berbayar. validateLicense memverifikasi token bertanda tangan, checkExpiry menolak lisensi yang kedaluwarsa, dan activateFeature membuka UI setelah pemeriksaan lolos. Fungsi pembantu dekripsinya ada di decryptPayload."

Ringkasan itu sudah cukup bagi penyerang untuk merencanakan cara mengelak yang terarah tanpa pernah menyentuh VM. Nama-nama itulah yang bocor.

Solusinya: bungkus kode Anda dalam IIFE

Cara paling sederhana dan paling andal untuk menghilangkan kebocoran ini adalah mendorong fungsi-fungsi sensitif Anda satu tingkat lebih dalam di pohon cakupan. Fungsi yang dideklarasikan di dalam fungsi lain bukan fungsi tingkat root, sehingga obfuscator bebas mengganti namanya dan menyerap deklarasinya ke dalam bytecode seperti pernyataan lainnya.

IIFE (Immediately-Invoked Function Expression) adalah cara paling ringan untuk melakukannya: ia menambahkan satu fungsi pembungkus yang dijalankan sekali dan tidak mengekspos apa pun berdasarkan nama.

Sebelum - nama terekspos

JavaScript

Setelah obfuskasi VM, validateLicense dan checkExpiry tetap bertahan dengan namanya di keluaran.

Sesudah - nama tersembunyi di balik IIFE

JavaScript

Kini kedua deklarasi fungsi berada di dalam isi IIFE. IIFE itu sendiri adalah satu-satunya konstruksi tingkat root, dan IIFE anonim tidak punya nama yang bisa bocor. Setelah obfuskasi VM, nama-nama fungsi diganti dan isinya dikonversi menjadi bytecode.

Apa yang berubah. Fungsi-fungsi tersebut tidak lagi dapat dijangkau sebagai global (dan justru itulah yang membocorkan namanya). Fungsi-fungsi itu tetap dapat dipanggil, hanya dari dalam IIFE yang sama. Jika memang ada sesuatu yang perlu memanggil validateLicense dari luar, sesuatu itu tidak lagi dapat menjangkaunya; lihat pola trampolin di bawah. Jika tidak ada, Anda tidak kehilangan apa pun.

Nama yang diekspor, pengenal yang dicadangkan, string, dan perilaku yang dapat diamati mungkin tetap terlihat. Uji integrasi setelah membungkus, terutama global, ekspor modul, dan kode yang memeriksa nama fungsi.

Satu konsekuensi yang perlu diketahui: jika isi IIFE berisi direct eval, atau panggilan new Function(...) / Function(...) dengan isi dinamis, obfuskasi VM melewati seluruh fungsi tersebut beserta semua yang bersarang di dalamnya (peringatan VMDynamicCodeSkipped), karena kode sumber yang dibangun saat runtime mungkin merujuk pengenal yang telah diganti namanya oleh obfuscator. Kode yang baru saja Anda pindahkan ke dalam IIFE pun kembali ke obfuskasi biasa dan kehilangan proteksi bytecode-nya. Gunakan indirect eval - (0, eval)(...) - atau lihat direct eval di bawah obfuskasi VM untuk pilihan yang tersedia.

Bagaimana jika sebuah fungsi memang harus menjadi global?

Terkadang sebuah fungsi memang merupakan titik masuk publik: event handler inline, callback JSONP, hook SDK pihak ketiga. Anda punya dua pilihan:

  • Ekspos trampolin tipis, simpan logikanya di dalam IIFE. Deklarasikan pembungkus global kecil yang tugasnya hanya memanggil implementasi yang berada dalam cakupan IIFE. Nama trampolin tetap bocor, tetapi tidak membawa informasi semantik apa pun (beri nama __entry1 atau sejenisnya) dan semua logika yang bermakna tetap tersembunyi.

    JavaScript

  • Tulis ulang titik pemanggilan. Jika global itu hanya ada karena handler inline onclick="validateLicense(...)" membutuhkannya, ganti handler inline tersebut dengan addEventListener dari dalam IIFE. HTML berhenti menyebut nama fungsi, fungsi tidak perlu lagi menjadi global, dan kebocorannya hilang sepenuhnya.

Inisialisasi variabel tingkat atas: vmWrapTopLevelInitializers

Deklarasi fungsi bukan satu-satunya hal yang berada di tingkat root berkas. Inisialisasi variabel tingkat atas (konstanta string, objek konfigurasi, tabel pencarian) sama terbacanya di keluaran selama tetap berupa JavaScript biasa. Baris seperti const API_BASE = '/api/v2/license' memberi tahu LLM sebanyak function validateLicense.

Opsi vmWrapTopLevelInitializers (boolean, bawaan false; preset VM saat ini mengaktifkannya) membungkus inisialisasi tingkat atas yang memenuhi syarat dalam IIFE sehingga nilainya sendiri dihitung oleh bytecode VM saat runtime alih-alih berada di kode sumber sebagai literal. Dalam mode root, inisialisasi yang tetap berupa JavaScript biasa dilaporkan dengan peringatan VMTopLevelInitializerNotVirtualized.

Tanpa opsi ini

JavaScript

Dengan vmWrapTopLevelInitializers: true

JavaScript

Nama binding (MY_STRING) tetap berada di tingkat root dengan alasan yang sama seperti nama fungsi (sesuatu di luar berkas mungkin mereferensikannya), tetapi nilai yang dikandungnya kini dihasilkan oleh VM dan tidak lagi muncul sebagai teks yang dapat dibaca.

Hanya berpengaruh ketika vmTargetFunctionsMode bernilai 'root' (bawaan) dan vmAsyncExecutor nonaktif. Dalam mode komentar, opsi ini tidak berpengaruh dan tidak ada peringatan yang dilaporkan. Dalam mode root dengan vmAsyncExecutor (yang hanya memvirtualisasi fungsi async), opsi ini juga tidak berpengaruh, dan build melaporkan VMTopLevelInitializerNotVirtualized untuk inisialisasi yang tertinggal sebagai JavaScript biasa.

Jika sebuah berkas tidak memiliki fungsi yang dapat divirtualisasi oleh VM, hasilnya melaporkan VMNoFunctionsToVirtualize; bungkus kode yang ingin Anda lindungi dalam sebuah fungsi, atau gunakan mode komentar untuk memilih fungsi sensitif secara eksplisit.

Ketika ini belum cukup

  • Nama yang diimpor dari modul lain. Jika Anda membundel beberapa berkas dan satu modul mengekspor validateLicense untuk diimpor modul lain, bundler akan mempertahankan nama itu tetap terlihat di keluaran bundel, sama seperti fungsi tingkat root terlihat. Bungkus bundel itu sendiri dalam IIFE (kebanyakan bundler dapat melakukannya), atau pindahkan ekspor ke dalam IIFE dan ekspos kembali melalui trampolin yang tidak bermakna.
  • Runtime tetap dapat diamati. Obfuskasi menambah upaya yang dibutuhkan untuk memahami dan memodifikasi kode; ia tidak dapat menjamin bahwa rekayasa balik mustahil dilakukan. Simpan rahasia dan keputusan keamanan yang otoritatif di server.

Jangan langsung meraih renameGlobals. Opsi itu memang ada, dan akan mengganti nama pengenal tingkat root, tetapi tidak punya cara untuk mengetahui pengenal mana yang direferensikan dari luar berkas (bundel lain, HTML inline, pencarian dinamis). Mengaktifkannya sering merusak integrasi dengan cara yang halus. Membungkus dengan IIFE lebih aman: tidak ada global yang diganti namanya, hanya berhenti membuat global yang sebenarnya tidak Anda butuhkan.