दस्तावेज़ीकरण
/
रेसिपीज़
/

LLM विश्लेषण से फ़ंक्शन नाम छिपाना

LLM विश्लेषण से फ़ंक्शन नाम छिपाना

Pro

समस्या

आपने vmObfuscation: true चालू किया, उसे ऐसी फ़ाइल पर चलाया जिसमें validateLicense जैसा फ़ंक्शन है, और देखा कि ऑब्फ़स्केटेड आउटपुट में अब भी validateLicense टेक्स्ट ज्यों का त्यों मौजूद है। फ़ंक्शन की बॉडी गायब हो चुकी है और उसकी जगह बाइटकोड आ गया है, लेकिन नाम खुद साफ़ दिखाई दे रहा है।

JavaScript

इसे किसी असली कोडबेस के पैमाने पर देखें, तो आपको validateLicense, decryptPayload, processPayment, checkSubscription जैसे फ़ंक्शन नामों की पूरी सूची मिल जाती है। प्रोग्राम क्या करता है, यह समझने के लिए LLM को बाइटकोड तोड़ने की ज़रूरत नहीं है। सिर्फ़ नामों से ही वह मॉड्यूल के व्यवहार का आत्मविश्वास भरा और सटीक सारांश बना सकता है। बाइटकोड अपारदर्शी है, लेकिन विषय-सूची नहीं।

VM ऑब्फ़स्केशन ये नाम क्यों रखता है

vmTargetFunctionsMode: 'root' (डिफ़ॉल्ट) के साथ ऑब्फ़स्केटर हर रूट-लेवल फ़ंक्शन की बॉडी को VM बाइटकोड में बदलता है, लेकिन जान-बूझकर उसके नाम को नहीं छूता। अर्थ के हिसाब से रूट-लेवल फ़ंक्शन डिक्लेरेशन आसपास के स्कोप पर एक बाइंडिंग है: script के लिए इसका मतलब ग्लोबल ऑब्जेक्ट है, और मॉड्यूल के लिए मॉड्यूल स्कोप (एक्सपोर्ट न किया गया टॉप-लेवल फ़ंक्शन ग्लोबल नहीं, बल्कि मॉड्यूल-स्कोप वाला होता है, लेकिन फ़ाइल में वह फिर भी रूट-लेवल ही है)। ऑब्फ़स्केटर इसका नाम सुरक्षित रूप से नहीं बदल सकता, क्योंकि उसके पास यह जानने का कोई तरीका नहीं है कि इसे और कौन रेफ़र करता है: कोई दूसरा बंडल, कोई इनलाइन <script>, कोई HTML onclick="validateLicense(...)" एट्रिब्यूट, कोई डायनामिक window['validateLicense'] लुकअप, आदि।

इसलिए डिफ़ॉल्ट यह समझौता करता है: इम्प्लीमेंटेशन की सुरक्षा करें, सार्वजनिक सतह को बनाए रखें। इससे इंटीग्रेशन नहीं टूटता, लेकिन इसका मतलब यह भी है कि LLM को हर एंट्री पॉइंट की मुफ़्त सूची मिल जाती है। ऐसा होने पर ऑब्फ़स्केशन परिणाम VMGlobalFunctionNamesNotRenamed चेतावनी देता है, जिसमें पढ़ने योग्य रह गए नामों की सूची होती है।

LLM-सहायता वाली रिवर्स इंजीनियरिंग के लिए यह क्यों मायने रखता है

बाइटकोड डिस्पैच की कुछ सौ पंक्तियों के सामने एक मानव हमलावर आम तौर पर हार मान लेता है। वही फ़ाइल मिलने पर LLM बाइटकोड पर हमला करने की ज़हमत ही नहीं उठाएगा। वह नाम पढ़ेगा, जो थोड़े-बहुत स्ट्रिंग लिटरल्स दिखते हैं उनसे मिलान करेगा, और कुछ इस तरह का नतीजा देगा:

"यह मॉड्यूल एक पेड फ़ीचर को नियंत्रित करता है। validateLicense एक साइन किए गए टोकन की पुष्टि करता है, checkExpiry समाप्त हो चुके लाइसेंस अस्वीकार करता है, और जाँच पास होने पर activateFeature UI अनलॉक करता है। डिक्रिप्शन हेल्पर decryptPayload में है।"

यह सारांश हमलावर के लिए VM को छुए बिना एक लक्षित बायपास की योजना बनाने को काफ़ी है। लीक नामों में ही है।

समाधान: अपने कोड को IIFE में रैप करें

इस लीक को हटाने का सबसे सरल और सबसे भरोसेमंद तरीका है अपने संवेदनशील फ़ंक्शन्स को स्कोप ट्री में एक स्तर नीचे ले जाना। किसी दूसरे फ़ंक्शन के अंदर घोषित फ़ंक्शन रूट-लेवल नहीं होते, इसलिए ऑब्फ़स्केटर उनके नाम बदलने और उनके डिक्लेरेशन को किसी भी अन्य स्टेटमेंट की तरह बाइटकोड में समाहित करने के लिए स्वतंत्र होता है।

IIFE (Immediately-Invoked Function Expression) ऐसा करने का सबसे हल्का तरीका है: यह एक ही रैपिंग फ़ंक्शन जोड़ता है जो एक बार चलता है और नाम से कुछ भी उजागर नहीं करता।

पहले: नाम उजागर हैं

JavaScript

VM ऑब्फ़स्केशन के बाद validateLicense और checkExpiry दोनों अपने नाम के साथ आउटपुट में बचे रहते हैं।

बाद में: नाम IIFE के पीछे छिपे हैं

JavaScript

अब दोनों फ़ंक्शन डिक्लेरेशन IIFE की बॉडी के अंदर हैं। IIFE खुद ही एकमात्र रूट-लेवल संरचना है, और अनाम IIFE के पास लीक करने को कोई नाम नहीं होता। VM ऑब्फ़स्केशन के बाद फ़ंक्शन नाम बदल दिए जाते हैं और बॉडी बाइटकोड में बदल जाती है।

क्या बदला। फ़ंक्शन अब ग्लोबल के रूप में उपलब्ध नहीं हैं (और यही उनके नाम लीक कर रहा था)। उन्हें अब भी कॉल किया जा सकता है, बस उसी IIFE के अंदर से। अगर किसी चीज़ को वाकई बाहर से validateLicense कॉल करने की ज़रूरत थी, तो अब वह उस तक नहीं पहुँच सकती; नीचे ट्रैम्पोलिन पैटर्न देखें। अगर किसी को ज़रूरत नहीं थी, तो आपने कुछ नहीं खोया।

एक्सपोर्ट किए गए नाम, रिज़र्व्ड आइडेंटिफ़ायर, स्ट्रिंग्स और देखा जा सकने वाला व्यवहार फिर भी दिख सकते हैं। रैप करने के बाद इंटीग्रेशन का परीक्षण करें, खासकर ग्लोबल्स, मॉड्यूल एक्सपोर्ट्स और ऐसे कोड का जो फ़ंक्शन नामों की जाँच करता है।

एक समझौता जो जानना ज़रूरी है: अगर IIFE की बॉडी में direct eval, या डायनेमिक बॉडी वाला new Function(...) / Function(...) कॉल हो, तो VM ऑब्फ़स्केशन उस पूरे फ़ंक्शन और उसके अंदर नेस्टेड हर चीज़ को छोड़ देता है (VMDynamicCodeSkipped चेतावनी), क्योंकि रनटाइम पर बना सोर्स ऐसे आइडेंटिफ़ायर्स का संदर्भ दे सकता है जिनका नाम ऑब्फ़स्केटर ने बदला है। तब जो कोड आपने अभी IIFE के अंदर रखा था, वह सामान्य ऑब्फ़स्केशन पर लौट आता है और अपनी बाइटकोड सुरक्षा खो देता है। indirect eval - (0, eval)(...) - का उपयोग करें, या विकल्पों के लिए Direct eval का व्यवहार देखें।

अगर किसी फ़ंक्शन का वाकई ग्लोबल होना ज़रूरी हो तो?

कभी-कभी कोई फ़ंक्शन सचमुच सार्वजनिक एंट्री पॉइंट होता है: कोई इनलाइन इवेंट हैंडलर, कोई JSONP कॉलबैक, किसी थर्ड-पार्टी SDK का हुक। आपके पास दो विकल्प हैं:

  • एक पतला ट्रैम्पोलिन उजागर करें, लॉजिक IIFE के अंदर रखें। एक छोटा ग्लोबल रैपर घोषित करें जिसका एकमात्र काम IIFE-स्कोप वाले इम्प्लीमेंटेशन को कॉल करना हो। ट्रैम्पोलिन का नाम फिर भी लीक होता है, लेकिन उसमें कोई अर्थपूर्ण जानकारी नहीं होती (उसे __entry1 या ऐसा ही कुछ नाम दें), और सारा सार्थक लॉजिक छिपा रहता है।

    JavaScript

  • कॉल साइट बदलें। अगर ग्लोबल सिर्फ़ इसलिए मौजूद है क्योंकि किसी इनलाइन onclick="validateLicense(...)" को उसकी ज़रूरत है, तो इनलाइन हैंडलर की जगह IIFE के अंदर से addEventListener का उपयोग करें। HTML फ़ंक्शन का नाम लेना बंद कर देता है, फ़ंक्शन को ग्लोबल होने की ज़रूरत नहीं रहती, और लीक पूरी तरह खत्म हो जाता है।

टॉप-लेवल वेरिएबल इनिशियलाइज़र: vmWrapTopLevelInitializers

फ़ाइल के रूट पर सिर्फ़ फ़ंक्शन डिक्लेरेशन ही नहीं होते। टॉप-लेवल वेरिएबल इनिशियलाइज़र (स्ट्रिंग कॉन्स्टैंट, कॉन्फ़िगरेशन ऑब्जेक्ट, लुकअप टेबल) भी सामान्य JavaScript बने रहने पर आउटपुट में उतने ही पढ़ने योग्य होते हैं। const API_BASE = '/api/v2/license' जैसी पंक्ति LLM को उतना ही बताती है जितना function validateLicense।

vmWrapTopLevelInitializers विकल्प (boolean, डिफ़ॉल्ट false; मौजूदा VM प्रीसेट इसे चालू करते हैं) योग्य टॉप-लेवल इनिशियलाइज़र को IIFE में रैप करता है, ताकि मान सोर्स में लिटरल के रूप में पड़ा न रहे, बल्कि रनटाइम पर VM बाइटकोड से उसकी गणना हो। root मोड में, जो इनिशियलाइज़र सामान्य JavaScript में ही रह जाते हैं, उनकी रिपोर्ट VMTopLevelInitializerNotVirtualized चेतावनी के साथ की जाती है।

विकल्प के बिना

JavaScript

vmWrapTopLevelInitializers: true के साथ

JavaScript

बाइंडिंग का नाम (MY_STRING) उसी कारण से अब भी रूट-लेवल है जिस कारण से फ़ंक्शन नाम हैं (फ़ाइल के बाहर की कोई चीज़ उसे रेफ़र कर सकती है), लेकिन उसमें रखा मान अब VM बनाता है और वह पढ़ने योग्य टेक्स्ट के रूप में नहीं दिखती।

यह केवल तभी प्रभावी है जब vmTargetFunctionsMode 'root' (डिफ़ॉल्ट) हो और vmAsyncExecutor बंद हो। comment मोड में इस विकल्प का कोई असर नहीं होता और कोई चेतावनी रिपोर्ट नहीं होती। root मोड में vmAsyncExecutor के साथ (जो केवल async फ़ंक्शनों को वर्चुअलाइज़ करता है) भी इसका कोई असर नहीं होता, और बिल्ड सादे JavaScript में छोड़े गए इनिशियलाइज़र्स के लिए VMTopLevelInitializerNotVirtualized रिपोर्ट करता है।

अगर किसी फ़ाइल में VM के लिए वर्चुअलाइज़ करने लायक कोई फ़ंक्शन नहीं है, तो परिणाम VMNoFunctionsToVirtualize रिपोर्ट करता है; जिस कोड की सुरक्षा करनी है उसे किसी फ़ंक्शन में रैप करें, या संवेदनशील फ़ंक्शन्स को स्पष्ट रूप से चुनने के लिए comment मोड का उपयोग करें।

जब इतना काफ़ी न हो

  • दूसरे मॉड्यूल्स से इम्पोर्ट किए गए नाम। अगर आप कई फ़ाइलें बंडल करते हैं और एक मॉड्यूल किसी दूसरे के इम्पोर्ट के लिए validateLicense एक्सपोर्ट करता है, तो बंडलर उस नाम को बंडल किए गए आउटपुट में उसी तरह दिखता रहने देगा जैसे रूट-लेवल फ़ंक्शन दिखते हैं। पूरे बंडल को ही IIFE में रैप करें (ज़्यादातर बंडलर ऐसा कर सकते हैं), या एक्सपोर्ट को IIFE के अंदर ले जाएँ और उसे किसी अर्थहीन ट्रैम्पोलिन के ज़रिए फिर से उजागर करें।
  • रनटाइम अब भी देखा जा सकता है। ऑब्फ़स्केशन कोड को समझने और बदलने के लिए ज़रूरी मेहनत बढ़ाता है; यह गारंटी नहीं दे सकता कि रिवर्स इंजीनियरिंग असंभव है। सीक्रेट्स और आधिकारिक सुरक्षा निर्णय सर्वर पर रखें।

सबसे पहले renameGlobals की ओर न जाएँ। यह विकल्प मौजूद है और रूट-लेवल आइडेंटिफ़ायर के नाम बदल देगा, लेकिन इसके पास यह जानने का कोई तरीका नहीं है कि उनमें से किन आइडेंटिफ़ायर को फ़ाइल के बाहर से (दूसरे बंडल, इनलाइन HTML, डायनामिक लुकअप) रेफ़र किया जाता है। इसे चालू करने से अक्सर इंटीग्रेशन सूक्ष्म तरीकों से टूट जाता है। IIFE रैप ज़्यादा सुरक्षित है: यह किसी ग्लोबल का नाम नहीं बदलता, बस उन ग्लोबल्स को बनाना बंद कर देता है जिनकी आपको ज़रूरत नहीं थी।