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

eval और new Function के साथ VM ऑब्फ़स्केशन

eval और new Function के साथ VM ऑब्फ़स्केशन

VM ऑब्फ़स्केशन डायनामिक कोड निर्माण (direct eval और Function कंस्ट्रक्टर) को कैसे संभालता है, किसे बाइटकोड में बदला जाता है और किसे छोड़ दिया जाता है, ऑब्फ़स्केटर कौन-सी चेतावनियाँ देता है, और रनटाइम पर ReferenceError का निदान कैसे करें।

यह क्यों मायने रखता है

VM ऑब्फ़स्केशन फ़ंक्शन बॉडीज़ को बाइटकोड में कंपाइल करता है, जो रनटाइम के भीतर मौजूद एक इंटरप्रेटर के ज़रिए चलता है। साथ ही, आसपास के स्कोप के आइडेंटिफ़ायर्स के नाम भी बदल दिए जाते हैं। ये दोनों बदलाव उस कोड के साथ ठीक से नहीं चलते जो रनटाइम पर किसी स्ट्रिंग से बनाया जाता है: eval(s), new Function(...s) और Function(...s)। अगर रनटाइम पर बना कोड किसी ऐसे आइडेंटिफ़ायर का संदर्भ देता है जिसका नाम ऑब्फ़स्केटर ने बदल दिया है, तो जनरेट किया गया फ़ंक्शन पहली बार चलते ही Uncaught ReferenceError: <renamed-name> is not defined देगा।

ऑब्फ़स्केटर हर पैटर्न को अलग तरह से संभालता है। नीचे दी गई मैट्रिक्स संक्षिप्त रूप है; इस पेज का बाकी हिस्सा हर कोष्ठक को विस्तार से समझाता है।

ऑब्फ़स्केटर क्या करता है — एक नज़र में

आपके सोर्स में पैटर्नक्या होता है
eval('literal string') (body एक स्ट्रिंग लिटरल है)सही चलता है। जिस फ़ंक्शन में यह कॉल है, वह VM बाइटकोडिंग खो देता है (direct eval आसपास के लोकल वैरिएबल्स पढ़ता है, जिन्हें फ़ंक्शन के बाइटकोड में कंपाइल होने के बाद VM बनाए नहीं रखता)।
eval(dynamicExpression)रनटाइम पर ReferenceError के साथ क्रैश हो सकता है। जिस फ़ंक्शन में यह कॉल है — और उसके अंदर परिभाषित हर फ़ंक्शन — वह भी VM बाइटकोडिंग खो देता है।
(0, eval)(s) / window.eval(s) (अप्रत्यक्ष)सही चलता है। जिस फ़ंक्शन में यह कॉल है वह सामान्य रूप से VM-बाइटकोडेड होता है। अप्रत्यक्ष eval आसपास के लोकल वैरिएबल्स नहीं देख सकता, इसलिए नाम बदलना उसे तोड़ नहीं सकता।
new Function('a', 'b', 'return a + b') (सभी आर्ग्युमेंट स्ट्रिंग लिटरल हैं)सही चलता है। जिस फ़ंक्शन में यह कॉल है वह सामान्य रूप से VM-बाइटकोडेड होता है।
new Function(dynamicBody) / Function(dynamicBody)रनटाइम पर ReferenceError के साथ क्रैश हो सकता है। जिस फ़ंक्शन में यह कॉल है — और उसके अंदर परिभाषित हर फ़ंक्शन — वह भी VM बाइटकोडिंग खो देता है।

ये सभी पैटर्न ऑब्फ़स्केशन परिणाम पर ग़ैर-घातक चेतावनियों के रूप में भी सामने आते हैं — चेतावनियों के स्वरूप और एक CI स्निपेट के लिए नीचे रनटाइम से पहले समस्या पकड़ना देखें।

स्टैटिक और डायनामिक के साथ अलग व्यवहार क्यों

eval(s) जिस फ़ंक्शन में कॉल होता है, उसके लोकल वैरिएबल्स को पढ़ और लिख सकता है। जब s एक स्ट्रिंग लिटरल हो, तो ऑब्फ़स्केटर उस body को ऑब्फ़स्केशन के समय पार्स कर सकता है और आसपास के कोड के अनुरूप आइडेंटिफ़ायर्स के नाम बदल सकता है। जब s एक डायनामिक एक्सप्रेशन हो, तो पार्सिंग सिर्फ़ रनटाइम पर होती है — और तब तक आइडेंटिफ़ायर्स के नाम बदल चुके होते हैं, इसलिए रनटाइम पर बना सोर्स उन पुराने नामों का संदर्भ देता है जो अब मौजूद ही नहीं हैं।

new Function(s) अलग तरह से काम करता है: body हमेशा ऐसे चलती है मानो उसे आपकी फ़ाइल के सबसे ऊपर परिभाषित किया गया हो — उसकी पहुँच केवल ग्लोबल वैरिएबल्स तक होती है, कॉल के आसपास के लोकल्स तक कभी नहीं। अपने आप में यह सुरक्षित है — लेकिन अगर आप किसी नाम-बदले आइडेंटिफ़ायर को जोड़कर body बनाते हैं (जैसे ऐसे फ़ंक्शन के func.toString() से जिसके अंदरूनी हिस्से ऑब्फ़स्केटर ने बदल दिए हैं), तो रनटाइम पर कंपाइल हुआ फ़ंक्शन फिर भी उसी तरह का ReferenceError देगा।

स्टैटिक new Function('return 42') में यह जोखिम कभी नहीं होता: body एक साधारण स्ट्रिंग है जिसे नाम बदलने वाला कभी देखता ही नहीं, और रनटाइम पर उसे केवल ग्लोबल्स दिखने चाहिए। ऑब्फ़स्केटर कॉल को वैसे ही रहने देता है और आसपास का फ़ंक्शन VM बाइटकोडिंग के लिए पात्र बना रहता है।

रनटाइम पर दिखने वाली एरर

आम लक्षण यह है कि डायनामिक रूप से बना फ़ंक्शन पहली बार चलते ही ReferenceError देता है:

browser console

एरर

यहाँ TU एक नाम-बदला आइडेंटिफ़ायर है जिसे ऑब्फ़स्केटर ने बंडल के IIFE स्कोप के अंदर जोड़ा था। डायनामिक eval / Function-कंस्ट्रक्टर कॉल एक ऐसी body इवैल्युएट करता है जो उसका संदर्भ देती है, लेकिन वह body ऐसे स्कोप में चलती है जहाँ TU परिभाषित ही नहीं है।

रनटाइम से पहले समस्या पकड़ना

ऑब्फ़स्केटर API के ज़रिए ग़ैर-घातक चेतावनियाँ देता है, ताकि आप शिप करने से पहले ही CI में ये पैटर्न पकड़ सकें। दो चेतावनी प्रकार यहाँ प्रासंगिक हैं:

  • DynamicCodeRenameRisk — किसी फ़ंक्शन में डायनामिक eval / new Function / Function कॉल है जिसकी body रनटाइम पर बनती है।
  • VMDynamicCodeSkipped — ऊपर बताए किसी पैटर्न की वजह से किसी फ़ंक्शन के लिए VM बाइटकोडिंग छोड़ दी गई। इसमें फ़ंक्शन का नाम (अगर उपलब्ध हो) और वह निर्माण-प्रकार शामिल होता है जिसने छूट ट्रिगर की।

ci-build.mjs

JavaScript

अगर आपके बिल्ड में कोई चेतावनी अपेक्षित है और आप उसे CI में फ़िल्टर करने के बजाय स्रोत पर ही बंद करना चाहते हैं, तो warnings विकल्प (v7.8.0+) तय करता है कि getWarnings() क्या दिखाए: 'none' सब कुछ दबा देता है, और { VMDynamicCodeSkipped: false } जैसा प्रति-प्रकार मैप सिर्फ़ एक प्रकार को चुप कराता है, बाकी बरकरार रहते हैं।

वर्कअराउंड

  • अप्रत्यक्ष eval ((0, eval)(s)) पर स्विच करें

    यह केवल eval वाली स्थिति में उपयोगी है। अप्रत्यक्ष eval ग्लोबल स्कोप में चलता है, इसलिए वह आसपास के लोकल वैरिएबल्स नहीं देख सकता — और इसी वजह से वह नाम-बदले आइडेंटिफ़ायर्स का संदर्भ भी नहीं दे सकता। जिस फ़ंक्शन में यह कॉल है वह VM-बाइटकोडेड बना रहता है।

  • body को पूरी तरह स्टैटिक बनाएँ

    new Function के लिए, अगर आप body को बिना किसी इंटरपोलेशन के एक अकेले स्ट्रिंग लिटरल / टेम्पलेट लिटरल के रूप में लिख सकें, तो कॉल में नाम बदलने का कोई जोखिम नहीं रहता और उसके आसपास का फ़ंक्शन बाइटकोड में बदला जाता रहता है। new Function('a', 'b', 'return a + b') ठीक है; new Function('return ' + expr) नहीं।

  • कॉल को उसके अपने टॉप-लेवल फ़ंक्शन में ले जाएँ, और IIFE को खोल दें

    हर टॉप-लेवल फ़ंक्शन की जाँच अलग से होती है। डायनामिक-कोड-निर्माण वाली कॉल को उसके अपने टॉप-लेवल फ़ंक्शन में ले जाने का मतलब है कि केवल वही एक फ़ंक्शन VM बाइटकोडिंग खोएगा — बजाय इसके कि छूट आपके पूरे बंडल को लपेटने वाले टॉप-लेवल IIFE में फैल जाए।

  • vmTargetFunctionsMode: 'comment' पर स्विच करें

    यह ऑप्ट-इन मोड है: केवल /* javascript-obfuscator:vm */ से चिह्नित फ़ंक्शन्स ही बाइटकोड में बदले जाते हैं। जिस फ़ंक्शन में डायनामिक-कोड कॉल है उस पर यह कमेंट न लगाएँ, और बाकी सब बाइटकोड में बदल दें। फ़ंक्शन्स को लक्षित करना देखें।

  • vmForceCompileDynamicCode: true से छूट को दरकिनार करें (v6.14.0+)

    यह अंतिम उपाय वाला एस्केप हैच है। चालू होने पर ऑब्फ़स्केटर आसपास के फ़ंक्शन को फिर भी बाइटकोड में बदल देता है और VMDynamicCodeSkipped चेतावनी दबा देता है। इसे तभी उपयोग करें जब आप गारंटी दे सकें कि रनटाइम पर बनी body कभी किसी ऐसे आइडेंटिफ़ायर का संदर्भ नहीं देगी जिसका नाम ऑब्फ़स्केटर बदलता है — अन्यथा आप साफ़ ऑब्फ़स्केशन के बदले रनटाइम पर ReferenceError मोल ले रहे हैं। DynamicCodeRenameRisk फिर भी आती रहती है, ताकि CI उस पर रोक लगाता रह सके। डैशबोर्ड में यह VM सेक्शन के Overrides समूह के अंतर्गत "Force Compile Dynamic Code" स्विच है।

संबंधित पेज