
यह metasm का मुख्य रिपॉजिटरी है, जो ruby में लिखा गया एक मुफ्त असेंबलर / डिसअसेंबलर / कंपाइलर है।
लेखक: Yoann Guillot
मूल अवलोकन:
Metasm आपको निष्पादन योग्य प्रारूपों (ExeFormat) के साथ इंटरैक्ट करने की अनुमति देता है: PE, ELF, Mach-O, Shellcode, आदि। ExeFormat के लिए तीन दृष्टिकोण हैं:
तैयार-उपयोग स्क्रिप्ट्स samples/ उपनिर्देशिका में पाई जा सकती हैं, स्क्रिप्ट्स हेडर में टिप्पणियाँ देखें। यदि आप भाग्यशाली महसूस कर रहे हैं तो आप --help तर्क भी आज़मा सकते हैं।
अधिक जानकारी के लिए, doc/ उपनिर्देशिका देखें। टेक्स्ट फाइलों को misc/txt2html.rb स्क्रिप्ट का उपयोग करके html में संकलित किया जा सकता है।
यहाँ Metasm के आंतरिक भागों का एक संक्षिप्त अवलोकन है।
असेंबली:
संकलन करते समय, आप एक स्रोत टेक्स्ट (ruby String, जिसमें अधिकतर निर्देश/डेटा/पैडिंग डायरेक्टिव का अनुक्रम होता है) से शुरू करते हैं, जिसे पार्स किया जाता है।
इस स्ट्रिंग को एक Preprocessor इंस्टेंस को सौंपा जाता है (जो #if, #ifdef, #include, #define, /* */ आदि को संभालता है, जो gcc -E के साथ 100% संगत होना चाहिए), जो असेंबलर स्रोतों के लिए AsmPreprocessor में एनकैप्सुलेटेड होता है (asm मैक्रो परिभाषाओं, 'equ' और asm ';' टिप्पणियों को संभालने के लिए)। ऐसा करने का इंटरफ़ेस ExeFormat#parse(text[, filename, lineno]) या ExeFormat.assemble है (जो .new, #parse और #assemble को कॉल करता है)।
(Asm)Preprocessor ExeFormat को टोकन लौटाता है, जो उन्हें Data, Padding, Labels या पार्सर डायरेक्टिव के रूप में पार्स करता है। पार्सर डायरेक्टिव हमेशा एक डॉट से शुरू होते हैं। वे सामान्य (.pad, .offset...) या ExeFormat-विशिष्ट (.section, .import, .entrypoint...) हो सकते हैं। इन्हें #parse_parser_instruction() द्वारा नियंत्रित किया जाता है। यदि ExeFormat किसी शब्द को नहीं पहचानता है, तो वह उसे अपने CPU इंस्टेंस को सौंप देता है, जो Instructions को पार्स करने के लिए जिम्मेदार है (या एक अपवाद फेंकता है)। वे सभी टोकन ExeFormat की @source विशेषता में एक या अधिक सरणियों में संग्रहीत होते हैं (Shellcode का @source एक Array है, PE/ELF के लिए यह एक हैश है [सेक्शन नाम] => [पार्स किए गए डेटा की Array]) प्रत्येक तत्काल मान एक मनमाना Expression हो सकता है (बाद में देखें)।
फिर आप ExeFormat#assemble का उपयोग करके स्रोत को बाइनरी अनुभागों में असेंबल कर सकते हैं।
एक बार अनुभाग बाइनरी उपलब्ध हो जाने पर, पूरे बाइनरी निष्पादन योग्य को ExeFormat#encode_file(filename[, format]) का उपयोग करके डिस्क पर लिखा जा सकता है।
PE और ELF में एक ऑटोइम्पोर्ट सुविधा शामिल है जो ज्ञात OS-विशिष्ट कार्यों के लिए आयात-संबंधित डेटा का स्वचालित निर्माण करने की अनुमति देती है (जैसे कि 'strcpy' के अनसुलझे कॉल डेटा उत्पन्न करेंगे ताकि बाइनरी रनटाइम पर libc लाइब्रेरी से लिंक हो जाए)।
samples/{exe,pe,elf}encode.rb एक asm स्रोत फ़ाइल को तर्क के रूप में ले सकता है और उसे एक कार्यशील निष्पादन योग्य में संकलित कर सकता है।
CPU क्लासेज व्यक्तिगत निर्देशों को पार्स करने और एनकोड करने के लिए जिम्मेदार हैं। वर्तमान Ia32 पार्सर Intel सिंटैक्स का उपयोग करता है (जैसे mov eax, 42)। सामान्य पार्सर लेबल को एक पंक्ति की शुरुआत में कोलन के बाद की स्ट्रिंग के रूप में पहचानता है (जैसे 'some_label:')। GCC-शैली के स्थानीय लेबल उपयोग किए जा सकते हैं (जैसे '1:', '1b' (पीछे की ओर) या '1f' (आगे की ओर) का उपयोग करके संदर्भित; आवश्यकता अनुसार कई बार पुनर्परिभाषित किया जा सकता है।) डेटा को 'db'-शैली के संकेतन का उपयोग करके निर्दिष्ट किया जाता है (जैसे 'dd 42h', 'db "blabla", 0') देखें samples/asmsyntax.rb
EncodedData:
Metasm में सभी बाइनरी डेटा EncodedData के रूप में संग्रहीत होता है। EncodedData के 3 मुख्य गुण हैं:
EncodedData में एक #virtsize भी है (जैसे .bss अनुभागों के लिए), और एक #ptr (आंतरिक ऑफसेट जो चीजों को डिकोड करते समय उपयोग होता है)
आप एक Hash चर नाम => मान के साथ EncodedData को फिक्सअप कर सकते हैं (मान एक Expression या एक संख्यात्मक मान होना चाहिए)। जब आप ऐसा करते हैं, तो प्रत्येक रिलोकेशन के लक्ष्य को बाइंडिंग का उपयोग करके बाउंड किया जाता है, और यदि परिणाम गणना योग्य है (Expression में कोई बाहरी चर नाम उपयोग नहीं हुआ है), तो परिणाम रिलोकेशन के आकार/चिह्न/एंडियननेस जानकारी का उपयोग करके एनकोड किया जाता है। यदि यह अतिप्रवाह होता है (128 को 8bit साइन्ड रिलोकेशन में संग्रहीत करने का प्रयास), तो EncodeError अपवाद फेंका जाता है। मौन अतिप्रवाह ट्रंकेशन की अनुमति देने के लिए :a32 प्रकार का उपयोग करें। यदि रिलोकेशन का लक्ष्य संख्यात्मक नहीं है, तो लक्ष्य अपरिवर्तित रहता है यदि आप EncodedData#fixup का उपयोग करते हैं, या इसे #fixup! के साथ बाउंड लक्ष्य से प्रतिस्थापित किया जाता है।
डिसअसेंबली:
यह कोड metasm/decode.rb स्रोत फ़ाइल में पाया जाता है, जो Disassembler क्लास को परिभाषित करता है।
डिसअसेंबलर को एक डिकोडेड ExeFormat की आवश्यकता होती है (यह बताने में सक्षम होने के लिए कि कौन सा डेटा किस वर्चुअल पते पर है) और एक एंट्रीपॉइंट (एक वर्चुअल पता या निर्यात नाम) की आवश्यकता होती है। यह फिर निर्देशों को डिसअसेंबल करना शुरू कर सकता है। जब यह :setip के रूप में चिह्नित एक Opcode का सामना करता है, तो यह CPU से जंप गंतव्य (एक Expression जिसमें रजिस्टर मान शामिल हो सकते हैं, उदा. jmp eax) मांगता है, और निर्देशों का बैकट्रेस करता है जब तक कि उसे संख्यात्मक मान नहीं मिल जाता।
डिकोडिंग पर, Disassembler एक #decoded हैश बनाए रखता है जो पतों (expressions/integer #normalize()d) को DecodedInstructions से जोड़ता है।
डिसअसेंबली एक InstructionBlock ग्राफ उत्पन्न करती है। प्रत्येक ब्लॉक DecodedInstruction की एक सूची रखता है, और अगले/पिछले ब्लॉक के पॉइंटर (पते द्वारा) रखता है।
डिसअसेंबलर निर्देशों द्वारा डेटा एक्सेस का पता भी लगाता है, और उनके लिए Xrefs संग्रहीत करता है। बैकट्रेस पैरामीटर समायोजित किए जा सकते हैं, और विचार करने के लिए अधिकतम गहराई को विशेष रूप से :r/:w बैकट्रेस के लिए #backtrace_maxblocks_data का उपयोग करके बदला जा सकता है। जब एक Expression का बैकट्रेस किया जाता है, तो प्रत्येक देखे गए ब्लॉक को चिह्नित किया जाता है ताकि लूप का पता लगाया जा सके, और ताकि यदि किसी मौजूदा ब्लॉक के लिए एक नया कोड पथ मिलता है, तो बैकट्रेस को इस नए पथ का उपयोग करके फिर से शुरू किया जा सके।
डिसअसेंबलर बहुत कम धारणाएँ बनाता है, और विशेष रूप से यह नहीं मानता कि फ़ंक्शन वापस लौटेंगे; वे केवल तभी लौटेंगे यदि 'ret' निर्देशों का बैकट्रेस निर्णायक हो। यह काफी शक्तिशाली है, लेकिन इसका तात्पर्य यह भी है कि बैकट्रेसिंग प्रक्रिया में कोई भी त्रुटि पूर्ण विराम का कारण बन सकती है; और इसका मतलब यह भी है कि डिसअसेंबलर काफी धीमा है।
विशेष विधि #disassemble_fast का उपयोग इसके आसपास काम करने के लिए किया जा सकता है जब कोड को अच्छी तरह से गठित माना जाता है (यानी यह मानता है कि सभी कॉल वापस लौटते हैं)
जब एक सबफ़ंक्शन पाया जाता है, तो एक विशेष DecodedFunction बनाया जाता है, जो फ़ंक्शन के प्रभावों का सारांश रखता है (जैसे कि स्टेरॉयड पर एक DecodedInstruction)। यह बैकट्रैकर को सबफ़ंक्शनों के ऊपर से 'step over' करने की अनुमति देता है, जो गति में काफी सुधार करता है। DecodedFunctions कॉलबैक-आधारित हो सकते हैं, ताकि एक बहुत ही गतिशील व्यवहार की अनुमति मिल सके। बाहरी फ़ंक्शन कॉल समर्पित DecodedFunctions बनाते हैं, जिसमें कुछ API जानकारी होती है (जैसे स्टैक फिक्सअप जानकारी, बुनियादी पैरामीटर एक्सेस...) यह जानकारी पहले से पार्स किए गए C हेडर से प्राप्त की जा सकती है। यदि कोई C फ़ंक्शन प्रोटोटाइप उपलब्ध नहीं है, तो एक विशेष 'default' प्रविष्टि का उपयोग किया जाता है, जो मानता है कि फ़ंक्शन में एक मानक ABI है।
Ia32 एक विशिष्ट :default प्रविष्टि लागू करता है, जो यह मानकर स्वचालित स्टैक फिक्सअप समाधान को संभालता है कि अंतिम 'call' निर्देश लौटता है। यह अप्रत्याशित परिणाम दे सकता है; अधिकतम सटीकता के लिए सभी बाहरी फ़ंक्शनों के लिए जानकारी रखने वाला एक C हेडर अनुशंसित है (पूर्ण Visual Studio इंस्टॉलेशन और लक्ष्य बाइनरी से ऐसा हेडर उत्पन्न करने के लिए स्क्रिप्ट देखें samples/factorize-headers-peimports)।