Hiding Function Names from LLM Analysis
Sorun
vmObfuscation: true seçeneğini etkinleştirdiniz, validateLicense gibi bir fonksiyon içeren bir dosya üzerinde
çalıştırdınız ve obfuscate edilmiş çıktının hâlâ validateLicense düz metnini içerdiğini fark ettiniz - gövde gitti,
yerini bayt kodu aldı, ancak adın kendisi orada açıkça duruyor.
// Input
function validateLicense(token) {
const decoded = decodeBase64(token);
return verifySignature(decoded);
}
// Output - name is preserved, body is bytecode
function validateLicense(b) {
return vmq_1bac70(0x5, [], undefined, undefined, undefined, this);
}
Bunu gerçek bir kod tabanı boyunca çoğaltın ve validateLicense, decryptPayload, processPayment,
checkSubscription gibi bir fonksiyon adları listesi elde edin. Bir LLM'nin, programın ne yaptığını anlamak için bayt
kodunu kırmasına gerek yoktur - modülün davranışına ilişkin kendinden emin, doğru bir özet üretmesi için adlar tek başına
yeterlidir. Bayt kodu anlaşılmazdır; içindekiler tablosu ise değildir.
VM obfuscation bu adları neden korur
vmTargetFunctionsMode: 'root' (varsayılan) ile obfuscator, her kök düzey fonksiyonun gövdesini VM bayt koduna
dönüştürür ancak adını kasıtlı olarak olduğu gibi bırakır. Kök düzey bir fonksiyon bildirimi, anlamsal olarak
çevreleyen kapsam üzerinde bir bağlamadır - bir betik için bu global nesne, bir modül için modül ad alanı anlamına gelir.
Obfuscator bunu güvenli bir şekilde yeniden adlandıramaz çünkü ona başka kimin başvurduğunu bilmesinin bir yolu yoktur:
başka bir paket, satır içi bir <script>, bir HTML onclick="validateLicense(...)" özniteliği, dinamik bir
window['validateLicense'] araması vb.
Yani varsayılanın yaptığı ödünleşim şudur: uygulamayı koru, genel yüzeyi olduğu gibi bırak. Bu, entegrasyonu bozmadan tutar, ancak aynı zamanda bir LLM'nin her giriş noktasının ücretsiz bir dizinini elde etmesi anlamına gelir.
Bu, LLM destekli tersine mühendislik için neden önemli
Birkaç yüz satırlık bayt kodu gönderimiyle karşılaşan insan bir saldırgan genellikle pes eder. Aynı dosya verilen bir LLM, bayt koduna hiç saldırmakla uğraşmaz - adları okur, görebildiği birkaç string değişmezini çapraz referanslar ve şuna benzer bir şey üretir:
Bu özet, bir saldırganın VM'ye hiç dokunmadan hedefli bir atlatma planlaması için yeterlidir. Sızıntı, adlardır.
Çözüm: kodunuzu bir IIFE içine sarın
Bu sızıntıyı gidermenin en basit ve en sağlam yolu, hassas fonksiyonlarınızı kapsam ağacında bir seviye daha derine itmektir. Başka bir fonksiyonun içinde bildirilen fonksiyonlar kök düzey değildir, bu nedenle obfuscator bunları yeniden adlandırmakta ve bildirimlerini diğer herhangi bir ifade gibi bayt koduna dahil etmekte serbesttir.
Bir IIFE (Hemen Çağrılan Fonksiyon İfadesi), bunu yapmanın en hafif yoludur - bir kez çalışan ve adıyla hiçbir şey açığa çıkarmayan tek bir sarmalayıcı fonksiyon ekler.
Öncesi - adlar açığa çıkmış
function validateLicense(token) {
const decoded = decodeBase64(token);
return verifySignature(decoded);
}
function checkExpiry(license) {
return Date.now() < license.expiresAt;
}
document.querySelector('#activate').addEventListener('click', () => {
const token = document.querySelector('#token').value;
if (validateLicense(token)) {
unlockUI();
}
});
VM obfuscation'dan sonra, hem validateLicense hem de checkExpiry çıktıda adlarıyla hayatta kalır.
Sonrası - adlar bir IIFE arkasında gizlenmiş
(function () {
function validateLicense(token) {
const decoded = decodeBase64(token);
return verifySignature(decoded);
}
function checkExpiry(license) {
return Date.now() < license.expiresAt;
}
document.querySelector('#activate').addEventListener('click', () => {
const token = document.querySelector('#token').value;
if (validateLicense(token)) {
unlockUI();
}
});
})();
Artık her iki fonksiyon bildirimi de IIFE'nin gövdesi içinde yaşıyor. IIFE'nin kendisi tek kök düzey yapıdır ve anonim bir IIFE'nin sızdıracak adı yoktur. VM obfuscation'dan sonra tüm gövde - içindeki her bildirim dahil - bayt koduna dönüştürülür ve kodlanır; IIFE'nin içindeki hiçbir şey okunabilir metin olarak hayatta kalmaz.
Bir fonksiyonun gerçekten global olması gerekiyorsa ne olur?
Bazen bir fonksiyon gerçekten genel bir giriş noktasıdır - satır içi bir olay işleyici, bir JSONP geri çağırması, üçüncü taraf bir SDK hook'u. İki seçeneğiniz var:
İnce bir tramplen açığa çıkarın, mantığı IIFE içinde tutun. Tek görevi IIFE kapsamındaki uygulamayı çağırmak olan küçük bir global sarmalayıcı bildirin. Tramplen adı hâlâ sızar, ancak hiçbir anlamsal bilgi taşımaz - ona
__entry1veya benzeri bir ad verin - ve tüm anlamlı mantık gizli kalır.var __entry1; (function () { function validateLicense(token) { /* … */ } __entry1 = validateLicense; })(); // Outside code calls __entry1(token) instead of validateLicense(token).Kontrol etmediğiniz çağrı noktasını yeniden yazın. Global yalnızca satır içi bir
onclick="validateLicense(...)"ona ihtiyaç duyduğu için varsa, satır içi işleyiciyi IIFE'nin içindenaddEventListenerile değiştirin. HTML fonksiyonu adlandırmayı bırakır, fonksiyonun global olmasına gerek kalmaz ve sızıntı tamamen ortadan kalkar.
Üst düzey değişken başlatıcıları: vmWrapTopLevelInitializers
Bir dosyanın kökünde yaşayan tek şey fonksiyon bildirimleri değildir. Üst düzey değişken başlatıcıları - string
sabitleri, yapılandırma nesneleri, arama tabloları - varsayılan olarak çıktıda aynı derecede okunabilir.
const API_BASE = '/api/v2/license' gibi bir satır, bir LLM'ye function validateLicense kadar çok şey söyler.
vmWrapTopLevelInitializers seçeneği (boolean, varsayılan false), uygun üst düzey başlatıcıları bir IIFE içine sarar;
böylece değerin kendisi kaynakta bir değişmez olarak durmak yerine çalışma zamanında VM bayt kodu tarafından hesaplanır.
Seçenek olmadan
// Input
const MY_STRING = 'my-string';
// Output - string is visible
const MY_STRING = 'my-string';
vmWrapTopLevelInitializers: true ile
// Input
const MY_STRING = 'my-string';
// Output - initializer is now a VM call, the string lives inside bytecode
const MY_STRING = (() => {
return vmq_1bac70(0x5, [], undefined, undefined, undefined, this);
})();
Bağlama adı (MY_STRING), fonksiyon adlarıyla aynı nedenle hâlâ kök düzeydir - dosyanın dışındaki bir şey ona
başvurabilir - ancak tuttuğu değer artık VM tarafından üretilir ve okunabilir metin olarak görünmez.
Bu yeterli olmadığında
- Diğer modüllerden içe aktarılan adlar. Birden fazla dosyayı paketliyorsanız ve bir modül başka birinin içe aktarması için
validateLicense'ı dışa aktarıyorsa, paketleyici bu adı, kök düzey fonksiyonların görünür olduğu şekilde paketlenmiş çıktıda görünür tutar. Paketin kendisini bir IIFE içine sarın (çoğu paketleyici bunu yapabilir) veya dışa aktarımı bir IIFE içine taşıyın ve anlamsız bir tramplen aracılığıyla yeniden açığa çıkarın.
