देशी मशीन कोड को आसानी से विघटित क्यों नहीं किया जा सकता है?


16

जावा, VB.NET, C #, एक्शनस्क्रिप्ट 3.0 आदि जैसे बायटेकोड-आधारित वर्चुअल मशीन भाषाओं के साथ, आप कभी-कभी सुनते हैं कि इंटरनेट से कुछ डिकंपाइलर को डाउनलोड करना कितना आसान है, एक अच्छा समय इसके माध्यम से बायटेकोड चलाएं, और अक्सर, कुछ सेकंड के मामले में मूल स्रोत कोड से बहुत दूर नहीं आते हैं। माना जाता है कि इस तरह की भाषा विशेष रूप से इसके प्रति संवेदनशील होती है।

मैंने हाल ही में सोचना शुरू किया है कि आप इस बारे में देशी बाइनरी कोड के बारे में अधिक क्यों नहीं सुनते हैं, जब आप कम से कम जानते हैं कि यह मूल रूप से किस भाषा में लिखा गया था (और इस प्रकार, किस भाषा में डिकंपाइल करने की कोशिश की गई है)। एक लंबे समय के लिए, मुझे लगा कि यह सिर्फ इसलिए है क्योंकि देशी मशीन की भाषा बहुत अधिक क्रेज़ी और विशिष्ट बायोटेक की तुलना में अधिक जटिल है।

लेकिन बायटेकोड कैसा दिखता है? यह इस तरह दिख रहा है:

1000: 2A 40 F0 14
1001: 2A 50 F1 27
1002: 4F 00 F0 F1
1003: C9 00 00 F2

और देशी मशीन कोड क्या दिखता है (हेक्स में)? बेशक, यह इस तरह दिखता है:

1000: 2A 40 F0 14
1001: 2A 50 F1 27
1002: 4F 00 F0 F1
1003: C9 00 00 F2

और निर्देश कुछ हद तक समान मन के फ्रेम से आते हैं:

1000: mov EAX, 20
1001: mov EBX, loc1
1002: mul EAX, EBX
1003: push ECX

तो, कुछ देशी बाइनरी को C ++ में अपघटित करने की कोशिश करने के लिए भाषा को देखते हुए, C ++ का कहना है कि इसके बारे में क्या मुश्किल है? केवल दो विचार जो तुरंत दिमाग में आते हैं 1) यह वास्तव में यह है कि बायटेकोड की तुलना में बहुत अधिक जटिल है, या 2) इस तथ्य के बारे में कुछ है कि ऑपरेटिंग सिस्टम पगेट कार्यक्रमों और उनके टुकड़ों को तितर बितर करने के लिए बहुत अधिक समस्याएं पैदा करते हैं। यदि उन संभावनाओं में से एक सही है, तो कृपया समझाएं। लेकिन किसी भी तरह से, आप इसे मूल रूप से क्यों नहीं सुनते हैं?

ध्यान दें

मैं उत्तरों में से एक को स्वीकार करने वाला हूं, लेकिन मैं पहले कुछ का उल्लेख करना चाहता हूं। लगभग हर कोई इस तथ्य का उल्लेख कर रहा है कि मूल स्रोत कोड के विभिन्न टुकड़े एक ही मशीन कोड के लिए मैप कर सकते हैं; स्थानीय चर नाम खो गए हैं, आप नहीं जानते कि मूल रूप से किस प्रकार का लूप इस्तेमाल किया गया था, आदि।

हालाँकि दोनों के उदाहरण जो अभी उल्लेखित थे, मेरी दृष्टि में एक प्रकार के तुच्छ हैं। हालांकि कुछ उत्तर बताते हैं कि मशीन कोड और मूल स्रोत के बीच का अंतर इस तुच्छ चीज़ से बहुत अधिक है।

लेकिन उदाहरण के लिए, जब यह स्थानीय चर नामों और लूप प्रकार जैसी चीजों के लिए आता है, तो बायटेकोड इस जानकारी को खो देता है (कम से कम ActionScript 3.0 के लिए)। मैंने उस सामान को पहले एक डिकम्पॉइलर के माध्यम से वापस खींच लिया है, और मुझे वास्तव में परवाह नहीं थी कि एक चर कहा जाता था strMyLocalString:Stringया loc1। मैं अभी भी उस छोटे, स्थानीय दायरे में देख सकता हूं और देख सकता हूं कि बिना किसी परेशानी के इसका उपयोग कैसे किया जा रहा है। और एक forपाश एक बहुत ही सटीक बात है एक के रूप मेंwhileपाश, अगर आप इसके बारे में सोचते हैं। जब भी मैं irFuscator (जो, SecureSWF के विपरीत, केवल सदस्य चर और फ़ंक्शन नामों को रैंडमाइज़ करने से बहुत अधिक नहीं करता) के माध्यम से स्रोत को चलाएगा, तब भी यह ऐसा लगता था कि आप छोटी कक्षाओं में कुछ चर और कार्यों को अलग करना शुरू कर सकते हैं, आंकड़ा उनका उपयोग कैसे किया जाता है, अपने नाम उन्हें सौंप दें, और वहां से काम करें।

इसके लिए एक बड़ा सौदा होने के लिए, मशीन कोड को उससे बहुत अधिक जानकारी खोने की आवश्यकता होगी, और कुछ उत्तर इस पर जाते हैं।


35
हैम्बर्गर से बाहर एक गाय बनाना मुश्किल है।
काज ड्रैगन

4
मुख्य मुद्दा यह है कि एक देशी बाइनरी कार्यक्रम के बारे में बहुत कम मेटाडेटा रखता है। यह कक्षाओं के बारे में कोई जानकारी नहीं रखता है (C ++ को विशेष रूप से विघटित करना मुश्किल है) और हमेशा कार्यों के बारे में कुछ भी नहीं - यह आवश्यक नहीं है क्योंकि सीपीयू स्वाभाविक रूप से एक काफी रैखिक फैशन में कोड निष्पादित करता है, एक समय में एक निर्देश। इसके अतिरिक्त, कोड और डेटा ( लिंक ) के बीच अंतर करना असंभव है । अधिक जानकारी के लिए, आप RE.SE पर खोज या फिर से पूछने पर विचार कर सकते हैं ।
ntoskrnl

जवाबों:


39

संकलन के प्रत्येक चरण में आप ऐसी जानकारी खो देते हैं जो अपरिवर्तनीय है। मूल स्रोत से आप जितनी अधिक जानकारी खोते हैं, उतने ही कठिन होते हैं।

आप बाइट-कोड के लिए एक उपयोगी डी-कंपाइलर बना सकते हैं क्योंकि अंतिम लक्ष्य मशीन कोड का निर्माण करते समय मूल स्रोत से बहुत अधिक जानकारी संरक्षित होती है।

संकलक का पहला चरण स्रोत को मध्यवर्ती प्रतिनिधित्व के लिए कुछ में बदलना है जो अक्सर एक पेड़ के रूप में दर्शाया जाता है। परंपरागत रूप से इस पेड़ में गैर-अर्थ संबंधी जानकारी नहीं होती है जैसे कि टिप्पणी, श्वेत-स्थान, आदि। एक बार इसे फेंकने के बाद आप उस पेड़ से मूल स्रोत को पुनर्प्राप्त नहीं कर सकते।

अगला कदम पेड़ को मध्यवर्ती भाषा के कुछ रूप में प्रस्तुत करना है जो अनुकूलन को आसान बनाता है। यहां काफी कुछ विकल्प हैं और प्रत्येक संकलक के बुनियादी ढांचे के अपने हैं। आमतौर पर, हालांकि, स्थानीय चर नाम, बड़ी नियंत्रण प्रवाह संरचना (जैसे कि आपने एक लूप के लिए उपयोग किया था) जैसे जानकारी खो जाते हैं। कुछ महत्वपूर्ण अनुकूलन आम तौर पर यहां होते हैं, निरंतर प्रसार, अपरिवर्तनीय कोड गति, फ़ंक्शन इनलाइनिंग आदि, जिनमें से प्रत्येक प्रतिनिधित्व को एक प्रतिनिधित्व में बदल देता है जिसमें समान कार्यक्षमता होती है, लेकिन काफी अलग दिखता है।

इसके बाद एक कदम वास्तविक मशीन निर्देशों को उत्पन्न करना है जिसमें शामिल हो सकते हैं जिन्हें "पीप-होल" अनुकूलन कहा जाता है जो सामान्य अनुदेश पैटर्न के अनुकूलित संस्करण का उत्पादन करते हैं।

प्रत्येक चरण पर आप अधिक से अधिक जानकारी खो देते हैं, अंत में, आप इतना खो देते हैं कि मूल कोड से मिलता-जुलता कुछ भी पुनर्प्राप्त करना असंभव हो जाता है।

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

विभिन्न लक्ष्य मशीन कोड के लिए डिकंपाइलर हैं, लेकिन वे अक्सर उपयोगी परिणाम नहीं देते हैं (कुछ आप संशोधित कर सकते हैं और फिर recompile) क्योंकि मूल स्रोत का बहुत अधिक खो जाता है। यदि आपके पास निष्पादन योग्य के लिए डिबग जानकारी है, तो आप और भी बेहतर काम कर सकते हैं; लेकिन, यदि आपके पास डिबग जानकारी है, तो संभवतः आपके पास मूल स्रोत भी है।


5
तथ्य यह है कि जानकारी को रखा जाता है ताकि जेआईटी बेहतर काम कर सके।
btilly

क्या C ++ DLL आसानी से सड़ने योग्य हैं?
Panzercrisis

1
किसी भी चीज में मैं उपयोगी नहीं समझूंगा।
चकज

1
मेटाडेटा "एक ही बाइट-कोड को कई लक्ष्यों को संकलित करने की अनुमति नहीं है", यह प्रतिबिंब के लिए है। त्यागने योग्य मध्यवर्ती प्रतिनिधित्व के लिए उस मेटाडेटा में से कोई भी होने की आवश्यकता नहीं है।
एसके-लॉजिक

2
वह सत्य नहीं है। अधिकांश डेटा प्रतिबिंब के लिए है, लेकिन प्रतिबिंब केवल उपयोग नहीं है। उदाहरण के लिए, इंटरफ़ेस और वर्ग की परिभाषा का उपयोग लक्ष्य मशीन पर परिभाषित फ़ील्ड ऑफ़सेट, वर्चुअल टेबल बनाने आदि के लिए किया जाता है, जिससे उन्हें लक्ष्य मशीन के लिए सबसे कुशल तरीके से निर्माण करने की अनुमति मिलती है। इन तालिकाओं का निर्माण कंपाइलर और / या लिंकर द्वारा किया जाता है जब देशी कोड का उत्पादन करते हैं। एक बार यह हो जाने के बाद, उनके निर्माण के लिए उपयोग किए गए डेटा को छोड़ दिया जाता है।
18

11

अन्य उत्तरों द्वारा बताई गई जानकारी का नुकसान एक बिंदु है, लेकिन यह डीलब्रेकर नहीं है। आखिरकार, आप मूल कार्यक्रम को वापस पाने की उम्मीद नहीं करते हैं, आप सिर्फ उच्च-स्तरीय भाषा में कोई प्रतिनिधित्व चाहते हैं। यदि कोड इनबिल्ड है, तो आप बस इसे होने दे सकते हैं, या स्वचालित रूप से सामान्य संगणना का कारक बन सकते हैं। आप कई अनुकूलन में सिद्धांत रूप में कर सकते हैं। लेकिन कुछ ऑपरेशन हैं जो सिद्धांत रूप में अपरिवर्तनीय हैं (कम से कम कंप्यूटिंग की अनंत राशि के बिना)।

उदाहरण के लिए, शाखाओं की गणना कूद हो सकती है। इस तरह कोड:

select (x) {
case 1:
    // foo
    break;
case 2:
    // bar
    break;
}

इसमें संकलित हो सकते हैं (क्षमा करें कि यह वास्तविक कोडांतरक नहीं है):

0x1000:   jump to 0x1000 + 4*x
0x1004:   // foo
0x1008:   // bar
0x1012:   // qux

अब, यदि आप जानते हैं कि x 1 या 2 हो सकता है, तो आप जंप देख सकते हैं और इसे आसानी से उलट सकते हैं। लेकिन 0x1012 पते का क्या? क्या आपको case 3इसके लिए भी निर्माण करना चाहिए ? आपको सबसे खराब स्थिति में पूरे कार्यक्रम का पता लगाना होगा कि किन मूल्यों की अनुमति है। इससे भी बदतर, आपको सभी संभावित उपयोगकर्ता इनपुटों पर विचार करना पड़ सकता है! समस्या के मूल में यह है कि आप डेटा और निर्देशों को अलग-अलग नहीं बता सकते।

यह कहा जा रहा है, मैं पूरी तरह से निराशावादी नहीं होगा। जैसा कि आपने ऊपर 'असेंबलर' में देखा होगा, अगर एक्स बाहर से आता है और 1 या 2 होने की गारंटी नहीं है , तो आपके पास अनिवार्य रूप से एक खराब बग है जो आपको कहीं भी कूदने की अनुमति देता है। लेकिन अगर आपका प्रोग्राम इस तरह के बग से मुक्त है, तो इसके बारे में तर्क करना बहुत आसान है। (यह कोई दुर्घटना नहीं है कि सीएलआर आईएल या जावा बाइटकोड जैसी "सुरक्षित" मध्यवर्ती भाषाएं अपघटित करने के लिए बहुत आसान हैं, यहां तक ​​कि मेटाडेटा को अलग करना।) इसलिए व्यवहार में, कुछ निश्चित, अच्छी तरह से व्यवहार करना संभव होना चाहिएकार्यक्रम। मैं व्यक्तिगत, कार्यात्मक शैली दिनचर्या के बारे में सोच रहा हूं, जिसमें कोई दुष्प्रभाव नहीं है और अच्छी तरह से परिभाषित इनपुट हैं। मुझे लगता है कि आस-पास कुछ डिकंपाइलर हैं जो सरल कार्यों के लिए छद्म कोड दे सकते हैं, लेकिन मेरे पास इस तरह के उपकरणों के साथ बहुत अनुभव नहीं है।


9

मशीन कोड आसानी से मूल स्रोत कोड में वापस परिवर्तनीय क्यों नहीं हो सकता इसका कारण यह है कि संकलन के दौरान बहुत सारी जानकारी खो जाती है। विधियों और गैर-निर्यात की गई कक्षाओं को झुकाया जा सकता है, स्थानीय चर नाम खो जाते हैं, फ़ाइल नाम और संरचनाएं पूरी तरह से खो जाती हैं, संकलक गैर-स्पष्ट अनुकूलन कर सकते हैं। एक और कारण यह है कि कई अलग-अलग स्रोत फाइलें सटीक एक ही विधानसभा का उत्पादन कर सकती हैं।

उदाहरण के लिए:

int DoSomething()
{
    return Add(5, 2);
}

int Add(int x, int y)
{
    return x + y;
}

int main()
{
    return DoSomething();
}

संकलित किया जा सकता है:

main:
mov eax, 7;
ret;

मेरी असेंबली बहुत जंग लगी है, लेकिन अगर कंपाइलर यह सत्यापित कर सकता है कि एक अनुकूलन सही ढंग से किया जा सकता है, तो वह ऐसा करेगा। यह संकलित बाइनरी के कारण नामों को जानने की आवश्यकता नहीं है DoSomethingऔर Addसाथ ही इस तथ्य के साथ कि Addविधि में दो नामित पैरामीटर हैं, संकलक भी जानता है कि DoSomethingविधि अनिवार्य रूप से एक निरंतर रिटर्न करती है, और यह विधि कॉल और इनलाइन दोनों को इनलाइन कर सकती है। विधि ही।

संकलक का उद्देश्य एक असेंबली बनाना है, न कि स्रोत फ़ाइलों को बंडल करने का तरीका।


अंतिम निर्देश को सिर्फ retऔर सिर्फ कहने के लिए विचार करें कि आप C बुलाए गए सम्मेलन को मान रहे थे।
चकज

3

यहां सामान्य सिद्धांत कई-से-एक मैपिंग और विहित प्रतिनिधियों की कमी हैं।

कई-से-एक घटना के एक सरल उदाहरण के लिए आप सोच सकते हैं कि क्या होता है जब आप कुछ स्थानीय चर के साथ एक फ़ंक्शन लेते हैं और इसे मशीन कोड पर संकलित करते हैं। चरों के बारे में सारी जानकारी खो जाती है क्योंकि वे सिर्फ मेमोरी एड्रेस बन जाते हैं। छोरों के लिए कुछ ऐसा ही होता है। तुम एक ले जा सकते हैं forया whileपाश और अगर वे संरचित कर रहे सिर्फ सही तो आप के साथ समान मशीन कोड मिल सकता है jumpनिर्देश।

यह मशीन कोड निर्देशों के लिए मूल स्रोत कोड से विहित प्रतिनिधियों की कमी भी लाता है। जब आप लूप को विघटित करने की कोशिश करते हैं, तो आप jumpनिर्देश को कैसे लूपिंग कंस्ट्रक्शन में वापस मैप करते हैं ? क्या आप उन्हें forलूप या whileलूप बनाते हैं ।

इस मुद्दे को इस तथ्य से और बढ़ा दिया गया है कि आधुनिक संकलक विभिन्न प्रकार के फोल्डिंग और इनलाइनिंग करते हैं। तो जब तक आप मशीन कोड को प्राप्त करते हैं, तब यह बताना बहुत असंभव है कि निम्न स्तर का मशीन कोड किस उच्च स्तर का निर्माण करता है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.