
This is the main repository for metasm, a free assembler / disassembler / compiler written in 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)।
Ia32 एक विशिष्ट GetProcAddress/dlsym कॉलबैक भी लागू करता है, जो यदि पैरामीटर का बैकट्रेस किया जा सकता है तो सही रिटर्न मान देगा।
पूर्ण डिसअसेंबलर लागू करने वाली स्क्रिप्ट्स samples/disassemble{-gui}.rb हैं। GUI कुंजी बाइंडिंग के लिए टिप्पणियाँ देखें।
ExeFormat हेरफेर:
आप एक ExeFormat को एनकोड/डिकोड कर सकते हैं (यानी अनुभाग, आयात, हेडर आदि डिकोड करें)
कंस्ट्रक्टर: ExeFormat.decode_file(str), ExeFormat.decode_file_header(str) विधियाँ: ExeFormat#encode_file(filename), ExeFormat#encode_string
PE और ELF फ़ाइलों में LoadedPE/LoadedELF समकक्ष हैं, जो उन प्रारूपों के मेमोरी-mmaped संस्करणों के साथ काम करने में सक्षम हैं (उदा. चल रही प्रक्रियाओं को डीबग करने के लिए)
VirtualString:
VirtualString एक String-जैसी ऑब्जेक्ट है: आप इसके स्लाइस पढ़ सकते हैं और पुनः लिख सकते हैं। इसे EncodedData#data के रूप में उपयोग किया जा सकता है, और इस प्रकार अधिकांश Metasm एल्गोरिदम के वर्चुअलाइजेशन की अनुमति देता है। आप VirtualString की लंबाई नहीं बदल सकते। VirtualString का एक स्लाइस लेने पर या तो एक String (छोटे आकारों के लिए) या एक और VirtualString (दूसरे में एक 'विंडो') लौटेगा। आप #dup(offset, length) विधि का उपयोग करके एक छोटा VirtualString प्राप्त करने के लिए बाध्य कर सकते हैं। इस पर कॉल की गई कोई भी अप्रयुक्त विधि एक फ्रोज़न String पर अग्रेषित की जाती है जो VirtualString की एक पूर्ण प्रति है (यदि संभव हो तो इससे बचा जाना चाहिए, अंतर्निहित स्ट्रिंग बहुत बड़ी और एक्सेस करने में धीमी हो सकती है)।
वर्तमान में 3 VirtualStrings लागू हैं:
Win/Lin संस्करण काफी शक्तिशाली हैं, और लाइव प्रक्रिया डिसअसेंबली/पैचिंग जैसी चीजों को आसानी से करने की अनुमति देते हैं (LoadedPE/LoadedELF को ExeFormat के रूप में उपयोग करके)
डिबगिंग:
Metasm में डिबगिंग संभालने के लिए कुछ इंटरफेस शामिल हैं। WinOS और LinOS क्लासेज अंतर्निहित OS प्रक्रियाओं तक पहुंच प्रदान करते हैं (उदा. OS.current.find_process('foobar') फ़ाइल नाम में foobar के साथ एक चल रही प्रक्रिया को पुनः प्राप्त करेगा; फिर process.mem का उपयोग उसकी मेमोरी तक पहुंचने के लिए किया जा सकता है।)
Windows और Linux के निम्न-स्तरीय डिबगिंग API में एक बुनियादी ruby इंटरफेस है (PTrace और WinAPI); जिनका उपयोग एकीकृत उच्च-स्तरीय Debugger क्लास द्वारा किया जाता है। रिमोट डिबगिंग GDB सर्वर वायर प्रोटोकॉल के माध्यम से समर्थित है।
उच्च-स्तरीय डिबगर्स को निम्नलिखित ruby पंक्ति के साथ बनाया जा सकता है: Metasm::OS.current.create_debugger('foo')
किसी समय में केवल एक प्रकार का होस्ट डिबगर क्लास मौजूद हो सकता है; कई प्रक्रियाओं को डीबग करने के लिए, मौजूदा क्लास का उपयोग करके अन्य प्रक्रियाओं से जुड़ें। यह उस तरीके के कारण है जिस तरह से OS डिबगिंग API Windows और Linux पर काम करता है।
निम्न-स्तरीय बैकएंड os/ उपनिर्देशिका में परिभाषित हैं, फ्रंट-एंड debug.rb में परिभाषित है।
एक लिनक्स कंसोल डिबगिंग इंटरफेस samples/lindebug.rb में उपलब्ध है; यह एक (सरलीकृत) SoftICE-जैसा लुक और फील का उपयोग करता है। यह gdb-server सॉकेट से बात कर सकता है; एक [udp:]host:port लक्ष्य का उपयोग करें।
डिसअसेंबलर-gui सैंपल लाइव प्रक्रिया इंटरैक्शन की अनुमति देता है जब लक्ष्य के रूप में 'live:' का उपयोग करते हैं।
C पार्सर:
Metasm में एक हाथ-लिखित C पार्सर शामिल है। यह उन सभी निर्माणों को संभालता है जिनके बारे में मैं जानता हूँ, hex floats को छोड़कर:
C फ़ाइलों को पार्स करना मौजूदा ExeFormat का उपयोग करके parse_c_file विधि के साथ किया जाना चाहिए। यह सुनिश्चित करता है कि प्रारूप-विशिष्ट मैक्रो/ABI सही ढंग से परिभाषित हैं (उदा: 'long' प्रकार का आकार, फ़ंक्शनों को पैरामीटर पास करने के लिए ABI, आदि)
जब आप C::Parser.parse(text) का उपयोग करके एक C String पार्स करते हैं, तो आपको एक Parser ऑब्जेक्ट प्राप्त होता है। इसमें एक #toplevel फ़ील्ड होता है, जो एक C::Block है, जिसमें #structs, #symbols और #statements होते हैं। शीर्ष-स्तरीय फ़ंक्शन #symbol हैश में पाए जाते हैं जिनकी कुंजियाँ प्रतीक नाम हैं, जो फ़ंक्शनों को रखने वाले C::Variable ऑब्जेक्ट से जुड़े होते हैं। फ़ंक्शन पैरामीटर/विशेषताएँ func.type के माध्यम से सुलभ हैं, और कोड func.initializer में है, जो स्वयं एक C::Block है। इसके नीचे आपको C::Statements की एक पेड़-जैसी संरचना मिलेगी (If, While, Asm, CExpressions...)
एक C::Parser को #precompiled किया जा सकता है ताकि इसे एक सरलीकृत संस्करण में बदला जा सके जो संकलित करना आसान हो: typedefs हटा दिए जाते हैं, नियंत्रण अनुक्रम 'if (XX) goto YY;' आदि में बदल दिए जाते हैं।
C प्रोग्राम संकलित करने के लिए, PE/ELF.compile_c का उपयोग करें, जो exe-विशिष्ट मैक्रोज़ के साथ एक C::Parser बनाएगा (जैसे PE या ELF)।
विक्रेता-विशिष्ट हेडरों को या तो #pragma prepare_visualstudio (Microsoft Visual Studio हेडरों को पार्स करने के लिए) या prepare_gcc (gcc के लिए) का उपयोग करने की आवश्यकता हो सकती है, बाद वाला स्वतः पहचाना जा सकता है (या नहीं भी)। परीक्षण किए गए विक्रेता हेडर VS2003 (incl. DDK) और gcc4 हैं; ymmv.
वर्तमान में C कोड का CPU#compilation एक asm स्रोत (टेक्स्ट) उत्पन्न करेगा, जिसे फिर पार्स करके बाइनरी कोड में असेंबल किया जा सकता है।
देखें ExeFormat#compile_c, और samples/exeencode.rb