LLM विश्लेषण से फ़ंक्शन नाम छिपाना
समस्या
आपने vmObfuscation: true चालू किया, उसे एक ऐसी फ़ाइल पर चलाया जिसमें validateLicense जैसा फ़ंक्शन है, और देखा कि
ऑब्फ़स्केटेड आउटपुट में validateLicense शब्द अब भी ज्यों का त्यों मौजूद है — बॉडी तो चली गई और उसकी जगह बाइटकोड आ गया,
लेकिन नाम साफ़-साफ़ वहीं दिख रहा है।
// 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);
}
इसे एक असली कोडबेस पर लागू करके देखें, तो आपको validateLicense, decryptPayload, processPayment,
checkSubscription जैसे फ़ंक्शन नामों की पूरी सूची मिल जाती है। किसी LLM को यह समझने के लिए बाइटकोड तोड़ने की ज़रूरत ही नहीं
कि प्रोग्राम क्या करता है — मॉड्यूल के व्यवहार का आत्मविश्वासपूर्ण और सटीक सारांश बनाने के लिए अकेले नाम ही काफ़ी हैं।
बाइटकोड अपारदर्शी है; विषय-सूची नहीं।
VM ऑब्फ़स्केशन ये नाम क्यों बनाए रखता है
vmTargetFunctionsMode: 'root' (डिफ़ॉल्ट) के साथ ऑब्फ़स्केटर हर रूट-स्तरीय फ़ंक्शन की बॉडी को VM बाइटकोड में बदल देता है,
लेकिन नाम को जान-बूझकर नहीं छूता। अर्थ की दृष्टि से, रूट-स्तरीय फ़ंक्शन घोषणा आसपास के स्कोप पर एक बाइंडिंग होती है —
स्क्रिप्ट के लिए इसका मतलब ग्लोबल ऑब्जेक्ट है, मॉड्यूल के लिए मॉड्यूल नेमस्पेस। ऑब्फ़स्केटर उसका नाम सुरक्षित रूप से नहीं बदल
सकता, क्योंकि उसके पास यह जानने का कोई तरीका नहीं कि और कौन उसका संदर्भ देता है: कोई दूसरा बंडल, कोई इनलाइन <script>,
कोई HTML onclick="validateLicense(...)" एट्रिब्यूट, कोई डायनामिक window['validateLicense'] लुकअप, वगैरह।
तो डिफ़ॉल्ट व्यवहार का ट्रेड-ऑफ़ यह है: क्रियान्वयन सुरक्षित करो, सार्वजनिक सतह बनाए रखो। इससे इंटीग्रेशन टूटता नहीं, लेकिन इसका मतलब यह भी है कि किसी LLM को हर एंट्री पॉइंट की मुफ़्त सूची मिल जाती है।
LLM-सहायता प्राप्त रिवर्स इंजीनियरिंग के लिए यह क्यों मायने रखता है
बाइटकोड डिस्पैच की कुछ सौ पंक्तियाँ देखकर कोई मानव हमलावर आमतौर पर हार मान लेता है। वही फ़ाइल किसी LLM को दें, तो वह बाइटकोड पर हमला करने की कोशिश ही नहीं करेगा — वह नाम पढ़ेगा, जो थोड़े-बहुत स्ट्रिंग लिटरल दिखते हैं उनसे मिलान करेगा, और कुछ ऐसा लिख देगा:
हमलावर के लिए यह सारांश ही काफ़ी है कि वह VM को छुए बिना एक लक्षित बायपास की योजना बना ले। असली लीक नाम ही हैं।
समाधान: अपने कोड को IIFE में रैप करें
इस लीक को हटाने का सबसे सरल और सबसे भरोसेमंद तरीका है अपने संवेदनशील फ़ंक्शन्स को स्कोप ट्री में एक स्तर और नीचे धकेल देना। किसी दूसरे फ़ंक्शन के अंदर घोषित फ़ंक्शन रूट-स्तरीय नहीं होते, इसलिए ऑब्फ़स्केटर उनके नाम बदलने और उनकी घोषणाओं को किसी भी अन्य स्टेटमेंट की तरह बाइटकोड में समाहित करने के लिए स्वतंत्र होता है।
IIFE (Immediately-Invoked Function Expression) ऐसा करने का सबसे हल्का तरीका है — यह सिर्फ़ एक रैपिंग फ़ंक्शन जोड़ता है, जो एक बार चलता है और नाम से कुछ भी उजागर नहीं करता।
पहले — नाम उजागर
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 ऑब्फ़स्केशन के बाद validateLicense और checkExpiry, दोनों नाम आउटपुट में बचे रह जाते हैं।
बाद में — नाम IIFE के पीछे छिपे हुए
(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();
}
});
})();
अब दोनों फ़ंक्शन घोषणाएँ IIFE की बॉडी के अंदर हैं। अकेला IIFE ही रूट-स्तरीय निर्माण है, और अनाम IIFE के पास लीक करने के लिए कोई नाम ही नहीं होता। VM ऑब्फ़स्केशन के बाद पूरी बॉडी — उसके अंदर की हर घोषणा सहित — बाइटकोड में बदल दी जाती है और एन्कोड हो जाती है; IIFE के अंदर कुछ भी पढ़ने योग्य टेक्स्ट के रूप में नहीं बचता।
अगर किसी फ़ंक्शन का ग्लोबल होना वाक़ई ज़रूरी हो तो?
कभी-कभी कोई फ़ंक्शन सचमुच सार्वजनिक एंट्री पॉइंट होता है — कोई इनलाइन इवेंट हैंडलर, कोई JSONP कॉलबैक, किसी थर्ड-पार्टी SDK का हुक। आपके पास दो विकल्प हैं:
एक पतला ट्रैम्पोलिन उजागर करें, लॉजिक IIFE के अंदर रखें। एक छोटा ग्लोबल रैपर घोषित करें जिसका काम सिर्फ़ IIFE-स्कोप वाले क्रियान्वयन को कॉल करना हो। ट्रैम्पोलिन का नाम अब भी लीक होता है, लेकिन उसमें कोई अर्थपूर्ण जानकारी नहीं होती — उसे
__entry1या ऐसा ही कोई नाम दें — और सारा सार्थक लॉजिक छिपा रहता है।var __entry1; (function () { function validateLicense(token) { /* … */ } __entry1 = validateLicense; })(); // Outside code calls __entry1(token) instead of validateLicense(token).जिस कॉल साइट पर आपका नियंत्रण नहीं है, उसे बदल दें। अगर वह ग्लोबल सिर्फ़ इसलिए मौजूद है क्योंकि कोई इनलाइन
onclick="validateLicense(...)"उसे चाहता है, तो इनलाइन हैंडलर की जगह IIFE के अंदर सेaddEventListenerलगाएँ। HTML फ़ंक्शन का नाम लेना बंद कर देता है, फ़ंक्शन को ग्लोबल होने की ज़रूरत नहीं रहती, और लीक पूरी तरह ख़त्म हो जाता है।
टॉप-लेवल वैरिएबल इनिशियलाइज़र: vmWrapTopLevelInitializers
फ़ाइल के रूट पर सिर्फ़ फ़ंक्शन घोषणाएँ ही नहीं होतीं। टॉप-लेवल वैरिएबल इनिशियलाइज़र — स्ट्रिंग कॉन्स्टेंट, कॉन्फ़िगरेशन ऑब्जेक्ट,
लुकअप टेबल — डिफ़ॉल्ट रूप से आउटपुट में उतने ही पढ़ने योग्य रहते हैं। const API_BASE = '/api/v2/license' जैसी एक पंक्ति
किसी LLM को उतना ही बता देती है जितना function validateLicense बताता है।
vmWrapTopLevelInitializers विकल्प (बूलियन, डिफ़ॉल्ट false) पात्र टॉप-लेवल इनिशियलाइज़र्स को एक IIFE में लपेट देता है,
ताकि वैल्यू सोर्स में लिटरल के रूप में पड़ी रहने के बजाय रनटाइम पर VM बाइटकोड द्वारा गणना की जाए।
विकल्प के बिना
// Input
const MY_STRING = 'my-string';
// Output - string is visible
const MY_STRING = 'my-string';
vmWrapTopLevelInitializers: true के साथ
// 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);
})();
बाइंडिंग नाम (MY_STRING) उन्हीं कारणों से अब भी रूट-स्तरीय रहता है जिनसे फ़ंक्शन नाम रहते हैं — फ़ाइल के बाहर की कोई चीज़ उसका
संदर्भ दे सकती है — लेकिन उसमें रखी वैल्यू अब VM द्वारा बनाई जाती है और पढ़ने योग्य टेक्स्ट के रूप में नहीं दिखती।
जब इतना काफ़ी न हो
- दूसरे मॉड्यूल्स से इम्पोर्ट किए गए नाम। अगर आप कई फ़ाइलें बंडल करते हैं और एक मॉड्यूल
validateLicenseको एक्सपोर्ट करता है ताकि दूसरा उसे इम्पोर्ट कर सके, तो बंडलर उस नाम को बंडल किए गए आउटपुट में उसी तरह दिखने देगा जैसे रूट-स्तरीय फ़ंक्शन दिखते हैं। पूरे बंडल को ही एक IIFE में रैप करें (अधिकांश बंडलर यह कर सकते हैं), या एक्सपोर्ट को IIFE के अंदर ले जाकर उसे किसी अर्थहीन ट्रैम्पोलिन के ज़रिए फिर से उजागर करें।
