इस लेख में, हम विभिन्न सॉफ़्टवेयर "ट्रिक्स" का उपयोग करके एप्लिकेशन प्रोटेक्शन बनाने पर विचार करेंगे, जैसे कि एंट्री पॉइंट को शून्य पर रीसेट करना, फ़ाइल बॉडी को एन्क्रिप्ट करना और एक कचरा पॉलीमॉर्फ़ द्वारा कवर किया गया डिस्क्रिप्टर, वर्चुअल मशीन बॉडी में एप्लिकेशन एल्गोरिथ्म एक्जीक्यूशन के तर्क को छिपाता है।
दुर्भाग्य से, लेख एक नियमित अनुप्रयोग प्रोग्रामर के लिए काफी कठिन होगा जो सॉफ्टवेयर सुरक्षा विषयों में रुचि नहीं रखता है, लेकिन कुछ भी नहीं किया जाना है।
लेख की अधिक या कम पर्याप्त धारणा के लिए, न्यूनतम कोडांतरक ज्ञान (इसमें बहुत कुछ होगा) के साथ-साथ डिबगर के साथ काम करने के कौशल की आवश्यकता होगी।
लेकिन जो लोग उम्मीद करते हैं कि इस प्रकार के संरक्षण को लागू करने के लिए कुछ सरल कदम उठाए जाएंगे, उन्हें निराश होना पड़ेगा। लेख पहले से ही लागू कार्यक्षमता पर विचार करेगा, लेकिन ... इसके हैकिंग के दृष्टिकोण से और एल्गोरिथ्म के पूर्ण रिवर्स।
मुख्य लक्ष्य जो मैं अपने लिए निर्धारित करता हूं, यह सामान्य विचार देना है कि इस तरह के सॉफ़्टवेयर सुरक्षा सामान्य रूप से कैसे काम करती है, लेकिन सबसे महत्वपूर्ण बात यह है कि एक व्यक्ति जो आपकी सुरक्षा को हटा देगा, वह इस पर संपर्क करेगा, क्योंकि एक पुराना नियम है - आप एक सक्षम सुरक्षा उपकरण एल्गोरिथ्म को लागू नहीं कर सकते हैं। इसके विश्लेषण और हैकिंग के तरीकों की कल्पना करना।
एक प्राप्तकर्ता के रूप में, एक काफी सक्षम कॉमरेड की सलाह पर, मैंने कुख्यात सुश्री-रेम से कीगेनमे की थोड़ी पुरानी (लेकिन प्रदर्शन की गुणवत्ता के कारण इसकी प्रासंगिकता नहीं खोई)।
यहाँ मूल लिंक है जहाँ यह दिखाई दिया:
http://exelab.ru/f/index.php?action=vthread&forum=1&topic=4732और फिर वह यहां आया:
http://www.crackmes.de/users/ms_rem/keygenme_by_ms_rem/जहां keygenme को 10 में से 8 पर सेट किया गया था (* बहुत बहुत * कठिन)।
हालांकि, ईमानदार होने के लिए, यह थोड़ा कम स्कोर है - मैं 5-6 अंक के क्षेत्र में डालूंगा।
चलिए शुरू करते हैं।
0. आवश्यकताएँ
इस keygenme की पूर्ण डिबगिंग के लिए अच्छे के लिए, सबसे सुविधाजनक प्लेटफॉर्म विंडोज एक्सपी 32 बिट्स होगा, यह आम तौर पर सबसे इष्टतम वातावरण है, इसलिए इसे वर्चुअल मशीन के रूप में लगातार मेरे वर्कस्टेशन पर तैनात किया जाता है।
विंडोज 7 के तहत - 32 बिट्स (जिस पर डिबगिंग वास्तव में इस लेख के लेखन के दौरान किया गया था) इसमें छोटी कठिनाइयां होंगी, लेकिन उन्हें हल किया जा सकता है (इसका उल्लेख लेख के चौथे अध्याय में किया जाएगा)।
64-बिट OCs पर गंभीर कठिनाइयाँ शुरू हो जाएंगी क्योंकि डीबगिंग के दौरान मुख्य टूल (OllyDebug) के रूप में उपयोग किया जाने वाला कीजनमे बूट लोडर स्टेज पर भी त्रुटियां उत्पन्न करेगा। OllyDebug संस्करण 2 इन त्रुटियों में नहीं फेंकेगा, लेकिन एक और कठिनाई है, इसके लिए कोई आवश्यक प्लग-इन अभी तक नहीं है (या शायद मैं बुरी तरह से देख रहा था)।
Keygenme को स्वयं इस लिंक से डाउनलोड करना होगा:
http://exelab.ru/f/files/3635_03.05.2006_CRACKLAB.rU.tgz (पंजीकरण आवश्यक)।
लेख के पाठ के साथ पूरी तरह से काम करने के लिए, यदि आप इसमें वर्णित सभी चरणों से गुजरने का निर्णय लेते हैं, तो आपको एक छोटे से उपकरण की आवश्यकता होगी।
मैं यहां उनके बारे में विस्तार से वर्णन नहीं करूंगा, जो कुछ भी करने की आवश्यकता है वह लेख के उदाहरण के साथ पुरालेख के मूल में स्थित फ़ाइल "used_tools.txt" में वर्णित है:
http://rouse.drkb.ru/blog/vm_alalize.zipयह बहुत जरूरी है कि "सुश्री-रेम" और "C38FB7A0CF38F73B1159" जैसे पहले लिंक द्वारा प्रदान की गई लॉगिन और सीरियल नंबर की सही जोड़ी को याद रखें। यह डेटा कीजेनम को पार्स करने की प्रक्रिया में बहुत मदद करेगा।
एक बार जब सब कुछ स्थापित हो जाता है - आप शुरू कर सकते हैं।
1. प्रारंभिक विश्लेषण
शुरू करने के लिए, यह तय करने के लायक है कि हम वास्तव में किसके साथ काम कर रहे हैं।
हम PEiD शुरू करते हैं और इसमें keygenme.exe खोलते हैं।
सबसे निचले निचले बटन को दबाएं और मेनू में हार्डकोर स्कैन प्रकार चुनें।
मुसीबत तुरंत शुरू होती है, सबसे पहले, प्रवेश बिंदु "एंट्रीपॉइंट" शून्य पर सेट होता है, जो सामान्य निष्पादन योग्य फ़ाइल में नहीं हो सकता है, दूसरी बात, स्कैन से पता चला कि "UPolyX v0.5 *" मौजूद है।
दूसरा सिर्फ डरावना नहीं है - यह "एक्जिप्टॉर" या "थेमिडा" जैसा कुछ होगा - कुछ वाणिज्यिक संरक्षक, तो हाँ, लेकिन यहाँ, जाहिर है, कुछ उपयुक्त हस्ताक्षर अभी-अभी मिले थे।
हम दाईं ओर दूसरा निचला बटन दबाते हैं और दिखाई देने वाले संवाद में दाईं ओर तीन बटन दबाते हैं।
वह कहते हैं कि फ़ाइल पैक है और एन्ट्रापी पहले से ही 7.56 है।
ठीक है, चलो कहते हैं, हालांकि यह अभी भी कुछ भी मतलब नहीं है। महान एन्ट्रापी न केवल पैक्ड में होती है, बल्कि एन्क्रिप्टेड फाइलों में भी होती है।
संवाद बंद करें और "सबसिस्टम:" के दाईं ओर स्थित बटन पर क्लिक करें।
प्रवेश बिंदु के अलावा, कोड और डेटा बेस मारे गए थे, लोडिंग बेस मानक 4000000 है।
ठीक है, ठीक है - हमारे हाथों पर हमारे पास एक फ़ाइल है जिसे हैंडल से थोड़ा ठीक किया गया था।
चलो डिबगर में यह सब महसूस करने की कोशिश करते हैं।
2. हम एंट्रीपॉइंट = 0 के साथ आवेदन के व्यवहार का विश्लेषण करते हैं
OllyDebug खोलें, "विकल्प" मेनू पर जाएं, वहां "डीबगिंग विकल्प" चुनें, और "ईवेंट" टैब पर चेकबॉक्स सेट करें "पहले विराम दें: -> सिस्टम ब्रेकपॉइंट"।
इस प्रकार, जब हम डीबग किए गए एप्लिकेशन के शरीर पर नियंत्रण स्थानांतरित करने से पहले पहला डीबग संदेश प्राप्त करता है, तो हम डीबगर को बाधित करने का कारण बनेंगे।
यह प्रवेश बिंदु को शून्य पर फेंकने के कारण किया जाता है।
हम खुद ही keygenme.exe खोलते हैं और तुरंत ntdll.dll के अंदर कहीं से तोड़ देते हैं
एंट्री पॉइंट क्या है (एप्लिकेशन के लिए) इसके लोड बेस (hInstance) से ऑफसेट है, जिसके लिए लोडर ट्रांसफर होने के तुरंत बाद कंट्रोल ट्रांसफर कर देता है।
डाउनलोड बेस में हमेशा एक पीई हेडर होता है, जहां _IMAGE_DOS_HEADER संरचना पहले आती है।
क्योंकि keygenme का प्रवेश बिंदु शून्य है, जिसका अर्थ है कि नियंत्रण सीधे उसके hInstance में स्थानांतरित किया जाएगा।
यह जानते हुए, आइए देखें कि हमारे पास क्या है।
"Ctrl + G" दबाएं और आधार के पते में ड्राइव करें "400000", यह कुछ इस तरह होना चाहिए:
मानक हेडर के बजाय यह काफी सभ्य कोड है, लेकिन हेडर जगह में होना चाहिए, अन्यथा आवेदन शुरू नहीं हुआ होगा, इसलिए सीधे _IMAGE_DOS_HEADER में परिवर्तन किए गए थे।
हम देखते हैं कि वास्तव में क्या बदल गया है:
_IMAGE_DOS_HEADER = record e_magic: Word; e_cblp: Word; e_cp: Word; e_crlc: Word; e_cparhdr: Word; e_minalloc: Word;
फ़ील्ड e_magic - इसे छुआ नहीं जाना चाहिए और इसमें हमेशा मार्क Zbikowski 'MZ' (0x4D, 0x5A) के शुरुआती अक्षर होने चाहिए।
दरअसल, इसे छुआ नहीं जाता है, और इन दोनों प्रतीकों को निर्देशों के रूप में व्याख्या की जाती है:
DEC EBP
दूसरे e_cblp फ़ील्ड का मान 0x45, 0x52 में बदल जाता है, जिसके परिणामस्वरूप पहले दो निर्देशों द्वारा किए गए परिवर्तनों को रद्द करता है, स्टैक की सही स्थिति को पुनर्स्थापित करता है।
शेष 4 क्षेत्रों का उपयोग MOV + JMP कमांड को लागू करने के लिए किया जाता है।
यहाँ इस तस्वीर में यह अधिक स्पष्ट रूप से दिखाया गया है।
_IMAGE_DOS_HEADER और एक रीसेट प्रविष्टि बिंदु के साथ इस तरह के जोड़तोड़ का पूरा बिंदु 4053B6 पर एप्लिकेशन बॉडी के अंदर कहीं न कहीं नियंत्रण का हस्तांतरण है।
यानी सिद्धांत रूप में, हम अभी keygenme.exe खोल सकते हैं और प्रवेश बिंदु के रूप में 53B6 निर्दिष्ट कर सकते हैं (फ़ाइल हेडर में परिवर्तनों की अनदेखी), लेकिन क्या यह सही प्रविष्टि बिंदु है?
3. हम एप्लिकेशन बॉडी के डिक्रिप्टर कोड को पार्स करते हैं और एप्लिकेशन को अनपैक करते हैं
हम संक्रमण "Ctrl + G" 4056B6 के पते पर जाते हैं और वहां हम यह देखते हैं:
आम तौर पर ठोस कचरा। लाइन जो भी हो, फिर कचरा निर्देश।
उदाहरण के लिए, सभी सशर्त शाखा ऑपरेटर (JG / JPE / JCXZ / JE) पूर्ण कचरा हैं, क्योंकि इससे कोई फर्क नहीं पड़ता कि क्या शर्त पूरी हुई या नहीं, संक्रमण हमेशा अगली पंक्ति में होगा (कूद पते पर ध्यान दें)।
निर्देश LEA, MOV, XCNG अपने राज्य - कचरे में कोई बदलाव किए बिना एक ही रजिस्टर के साथ काम करते हैं।
मैट्सप्रोसेसर (FCLEX / FFREE) के साथ काम करने के निर्देश अपवाद हैं (जो कि नहीं हैं, क्योंकि मैट्सप्रोसेसर के साथ काम अभी तक नहीं किया गया है) रिलीज रजिस्टर (जो वास्तव में व्यस्त नहीं हैं) - कचरा।
कोड के माध्यम से अंत तक स्क्रॉल करें कि यह कचरा कहां समाप्त होता है।
जब तक हम कोड को केवल शून्य से युक्त नहीं कर लेते, तब तक नीचे स्क्रॉल करें:
हाँ, लेकिन ऐसा लगता है कि हमें पते की आवश्यकता है 401000, जो कि कूदना है, जो सैद्धांतिक रूप से मूल प्रवेश बिंदु हो सकता है।
आइए देखते हैं क्या है:
और हमारे पास कोड है जो स्पष्ट रूप से एक Win32 एप्लिकेशन में नहीं होना चाहिए, जो कि IN और OUT निर्देशों द्वारा स्पष्ट रूप से इंगित किया गया है जो कि निष्पादित होने पर अपवाद को फेंक देंगे।
तो यह पता चला है कि मूल प्रविष्टि बिंदु (OEP - मूल प्रविष्टि बिंदु) पर कोड एन्क्रिप्ट किया गया है और प्रक्रिया में कोड 53B6 अंतिम छलांग लगाने से पहले इसे डिक्रिप्ट करना चाहिए।
लेकिन !!!
लेकिन प्रक्रिया में 53B6, जैसा कि पहले दिखाया गया है, कचरा।
वास्तव में, केवल कचरा नहीं होना चाहिए। जाहिरा तौर पर हम तथाकथित कचरा पॉलीमॉर्फिक और इसके सरल कार्यान्वयन में काम कर रहे हैं।
पॉलीमॉर्फ़िक इंजन का कार्य मूल निर्देशों को उनके एनालॉग्स (या एनालॉग के समूह) के साथ बदलकर मूल कोड को परिवर्तित करना है। परिणामी कोड का विश्लेषण करना मुश्किल बनाने के लिए, कचरा निर्देश ब्लॉक आमतौर पर जोड़े जाते हैं।
यहां कोई ब्लॉक नहीं हैं, बस कचरा निर्देश उत्पन्न होते हैं, साथ ही अंत में, भले ही एनालॉग्स के साथ निर्देशों का प्रतिस्थापन हो, मैंने केवल एक मामले में इस पर ध्यान दिया। यह पूरी तरह से संभव है कि एक कचरा जनरेटर का उपयोग यहां किया गया था, इसे उपयोगी निर्देशों के बीच बहुतायत से cramming, कैसे पता करें ...
हालांकि, एक कार्य दिखाई दिया - इस सभी कचरे के बीच, यह आवश्यक है कि पते से उपयोगी निर्देश 4053B6 से 406839 (5251 बाइट्स - हालांकि) जो एप्लिकेशन बॉडी को डिक्रिप्ट करें।
ऐसा करने के दो तरीके हैं।
पहला यह है कि पूरे कोड को अपनी आंखों से देखें और इस तरह के निर्देशों को खोजने की कोशिश करें। मैंने भी रुचि के लिए लगभग 7 मिनट की कोशिश की और खर्च किया, परिणामस्वरूप मुझे दो ऐसे निर्देश भी मिले जो कचरा नहीं हैं। यह सच है, जैसा कि बाद में पता चला, मैं उनके बीच में से एक से चूक गया, और दूसरे के बाद भी जो मुझे मिला, उससे मैं थोड़ा बीमार हो गया - बहुत कठिन काम।
इसलिए, चलो दूसरा रास्ता बनाते हैं, और एक छोटी सी स्क्रिप्ट लिखते हैं जो सभी कचरे को साफ करने में मदद करेगा और केवल पेलोड को छोड़ देगा।
स्क्रिप्ट स्वयं
संग्रह में स्थित है
, जो निम्नलिखित पथ पर
लेख के साथ जाती है: "। \ _ लिपियों \ fill_trash_by_nop.txt"।
इसे चलाने के लिए, OllyScript प्लगइन स्थापित होना चाहिए।
स्क्रिप्ट इस तरह से चलती है: आपको डीबगर में कीगेनमे को फिर से शुरू करने और NTDLL के अंदर काम करने के लिए पहले BP की प्रतीक्षा करने की आवश्यकता है, फिर "प्लगइन्स" मेनू में "ODbgScript-> रन स्क्रिप्ट ..." चुनें, संवाद में स्क्रिप्ट के साथ फाइल चुनें (ऊपर निर्दिष्ट पथ) और चलाएं। यह।
जैसे ही स्क्रिप्ट अपना काम शुरू करती है, आप अपने लिए कुछ चाय बना सकते हैं, आपके पास लगभग पांच मिनट का खाली समय होगा।
स्क्रिप्ट का तर्क सरल है:
क्योंकि चूंकि कचरा निर्देश रजिस्टरों के मूल्यों में परिवर्तन नहीं करता है (EIP के अपवाद के साथ), कचरा निर्देश को उसके निष्पादन से पहले और बाद में रजिस्टरों की स्थिति की जांच करके पता लगाया जाता है, यदि रजिस्टरों को बदल दिया जाता है, तो निर्देश कुछ उपयोगी करता है, अन्यथा निर्देश को कचरा माना जाता है और इसके बजाय NOP को रखा जाता है।
जब स्क्रिप्ट अपना काम पूरा कर लेती है और संदेश प्रदर्शित करती है, तो आप इसके काम के परिणाम देख सकते हैं (डीबगर को स्वयं रोकें नहीं - यह अभी भी आवश्यक है)।
सभी कचरा एनओपी द्वारा प्रतिस्थापित किया जाएगा और हमारे पास केवल हाथ पर निम्नलिखित निर्देश होंगे (हमें 4053B6 से 406839 पर जाने और सब कुछ लिखने की ज़रूरत है जो एक नोटबुक में एनओपी नहीं है):
पहली दो पंक्तियों में थोड़ा कचरा होगा (नींद को शून्य विलंब के साथ कहा जाता है)।
0040548B PUSH 0 0040548D CALL DWORD PTR DS:[<&kernel32.Sleep>] ; kernel32.Sleep
खैर, अधिक सटीक रूप से, यह कैसे-वास्तव में कचरा नहीं है, यह लाइन बूटलोडर को पहले से लोड किए गए ntdll.dll के साथ समानांतर में प्रक्रिया शुरू होने पर पता प्रक्रिया के पते स्थान में लोड करने के लिए मजबूर करती है, क्योंकि यह लाइब्रेरी keygenme इंपोर्ट टेबल (सिर्फ एक स्लीप फंक्शन के रूप में) में घोषित की गई है।
इसके बाद, डिक्रिप्टर कोड स्वयं जाएगा:
004054B4 MOV ESI,keygenme.00401000
और सीधे OEP पर स्विच करना, जिस पर अब डिबगर बंद हो गया है।
00406839 JMP keygenme.00401000
मोटे तौर पर, यदि आप डिक्रिप्टर के निर्देशों को देखते हैं, तो keygenme बॉडी को इस सरल एल्गोरिथ्म के साथ डिक्रिप्ट किया जाता है:
uses Classes, Winsock; var I, A: Integer; M: TMemoryStream; begin M := TMemoryStream.Create; try M.LoadFromFile('keygenme.exe'); M.Position := 512;
अब, हर बार फ़ाइल के डिक्रिप्शन के लिए इंतजार न करने के लिए, आपको परिणाम को डंप करने की आवश्यकता है (ओलीडम्प प्लगइन स्थापित होना चाहिए)।
ऐसा करने के लिए, OEP ("Ctrl + G" 401000) पर जाएं और वहां ब्रेकपॉइंट लगाएं, और फिर प्रोग्राम को जारी रखें।
जैसे ही डीबगर इंस्टॉल किए गए बीपी पर रुकता है, "प्लगइन्स" मेनू पर जाएं, "ओलीडंप-> डंप डिबग प्रक्रिया" का चयन करें, यह डायलॉग खुल जाएगा:
"वर्चुअल ऑफसेट" कॉलम पर ध्यान केंद्रित करते हुए, 1000 के बराबर कोड बेस सेट करें, और 7000 के बराबर डेटाबेस, "रीइम्पोर्ट आयात" को अनचेक करें और "डंप" बटन दबाएं।
दिखाई देने वाले संवाद में, नया नाम "keygen_unpacked.exe" निर्दिष्ट करें।
वास्तव में सब कुछ - इसलिए हमने पहला लिफाफा हटा दिया।
थोड़ी चाल:सामान्य तौर पर, डिक्रिप्टर और अन्य चीजों के स्रोत कोड पर विचार किए बिना, डंप करना बहुत आसान था, लेकिन चूंकि मैंने सब कुछ अच्छी तरह से विचार करने का फैसला किया, इसलिए मुझे भी उस पर ध्यान देना पड़ा।
दूसरा अनपैकिंग विकल्प इस प्रकार है।
1. डिबगर चलाएँ और NTDLL के अंदर काम करने के लिए पहले BP की प्रतीक्षा करें।
2. मेमोरी कार्ड "Alt + M" के टैब पर जाएं और 401000 पते पर हमने MBP को रिकॉर्ड पर रखा:
3. रिकॉर्डिंग के संचालन के बाधित होते ही निष्पादन के लिए कार्यक्रम चलाएं (यह STOS DWORD निर्देश होगा), फिर से हम मेमोरी कार्ड पर जाते हैं और MBP को हटाते हैं, जिसके बाद हम OEP (401000) पर जाते हैं और वहां सामान्य ब्रेकआउट लगाते हैं।
4. खैर, जैसे ही हम इस पर टूटते हैं, आपको प्रक्रिया को डंप करने के लिए पहले से वर्णित चरणों का पालन करने की आवश्यकता है।
वैसे, यदि आप चाहते हैं, तो आप PEID के तहत परिणामी फ़ाइल की जांच कर सकते हैं, एन्ट्रापी जादुई रूप से 6.95 हो गई - और बस डेटा ब्लॉक को डिक्रिप्ट किया।
4. अनपैक्ड फ़ाइल का प्रारंभिक विश्लेषण और विस्टा और उच्चतर के तहत लॉन्च समस्या को दरकिनार करना।
अब हम पहले से ही अनपैक्ड फ़ाइल के साथ काम करेंगे।
चूंकि इसमें प्रवेश बिंदु सही ढंग से सेट किया गया है, अनावश्यक इशारों को बनाने के लिए, आपको एनटीडीएलएल में पहला स्टॉप अब और नहीं बल्कि सीधे प्रवेश बिंदु पर बनाने के लिए ऑली को कॉन्फ़िगर करना होगा।
OllyDebug खोलें, "विकल्प" मेनू पर जाएं, वहां "डीबगिंग विकल्प" चुनें, और "ईवेंट" टैब पर चेकबॉक्स सेट करें "मुख्य ठहराव यहां: -> मुख्य मॉड्यूल का प्रवेश बिंदु"।
Keygenme_unpacked.exe खोलें और पहले एन्क्रिप्ट किए गए कोड को देखें:
हम तुरंत पहला "उपद्रव" देखते हैं, कॉल का पहला कॉल खुद के अंदर जाता है (पता 401004 कहा जाता है, जबकि अगला निर्देश केवल 4010005 से शुरू होता है)।
मैंने इस लेख में पहले ही ऐसे छलांग लगाने के बारे में बात की थी:
"डिबगर सीखना, भाग दो"इस चाल का सार असंतुष्ट को भ्रमित करना है और इसे गलत कोड प्रदर्शित करना है जो वास्तव में निष्पादित होगा। इस तरह के "ट्रिक" में कुछ भी गलत नहीं है, बस इस कॉल को निष्पादित करने के लिए F7 दबाएं और तुरंत सही कोड देखें:
आप अभी इस प्रक्रिया को फिर से डंप कर सकते हैं + इस "फ़ोकस" को निष्क्रिय करने के लिए POP EBX निर्देश को हटा सकते हैं, लेकिन तब से यह हस्तक्षेप नहीं करेगा - हम इसे वैसे ही छोड़ देंगे और विश्लेषण करना शुरू करेंगे।
पहले पीईबी (प्रोसेस एनवायरनमेंट ब्लॉक) के कुछ आंकड़ों को पढ़ते हुए पांच निर्देशों का एक ब्लॉक आता है, जिसका पता हमेशा एफएस: [30 जून] में होता है।
यदि आप PEB संरचना को खोलते हैं और देखते हैं कि कोड में दिखाए गए ऑफ़सेट का क्या मतलब है, तो हम निम्नलिखित के बारे में प्राप्त करते हैं:
0040100A MOV EAX,DWORD PTR FS:[30]
इस प्रकार, ये पाँच निर्देश hInstance के लिए खोजते हैं "kernel32.dll", जो कि इस पते पर स्थित होगा, हालाँकि विस्टा और उच्चतर के तहत, hInstance "k गिर्बनेश। dll" इस पते पर स्थित होगा और एक अप्रिय त्रुटि इसके साथ जुड़ी होगी।
LEA ईएसआई निर्देश ईएसआई में एक संकेतक को 004012DE पर स्थित एएनएसआई स्ट्रिंग्स के एक छोटे सरणी में रखता है। ये तीन पंक्तियाँ हैं जो शून्य से अलग होती हैं: "LoadLibraryA", "ExitProcess" और "VirtualAlloc"।
वैसे, मैं पहले इस बात का उल्लेख करना पूरी तरह से भूल गया, यदि आप keygenme.exe आयात तालिका को देखते हैं, तो आप देखेंगे कि यह एक सिंगल कर्नेल 32. स्लीप फ़ंक्शन को आयात करता है, बाकी गायब हैं। तो काम के लिए आवश्यक बाकी के पते, आवेदन अपने दम पर खोजना होगा।
निम्न LEA EDI निर्देश EDI में एक सूचक को बफर में रखता है जिसमें पाए गए कार्यों के पते रखे जाएंगे (यह कर्नेल 32 के लिए एक आभासी आयात तालिका होगी), जिसके बाद यह प्रक्रिया 401198 पर कॉल की जाती है।
वास्तव में, LEA EDI / ESI दोनों कॉल कचरा हैं, जैसे जब ये प्रक्रिया 401198 पर कॉल की जाती है, तब इन रजिस्टरों को ओवरराइट किया जाएगा, लेकिन इनका उपयोग उसी तरीके से किया जाएगा, जैसा कि मैंने ऊपर वर्णित किया है (EDI, अधिक सटीक EBP + 305, अंत में इसमें फ़ंक्शन के पते होंगे)।
संक्षेप में, प्रक्रिया 401198 का कार्य ईएसआई रजिस्टर तैयार करना है, जिसमें वांछित फ़ंक्शन के नाम के साथ एक संकेतक होता है, साथ ही ईडीआई रजिस्टर, जिसमें लाइब्रेरी एक्सपोर्ट टेबल के लिए एक पॉइंटर होता है, जिसका hInstance हमें PEB से डेटा पढ़ने पर प्राप्त होता है,
फिर फ़ंक्शन को 4011E2 पर कॉल करें, जो नाम से खोज करेगा।
और यहाँ हम दूसरी मुसीबत की प्रतीक्षा कर रहे हैं, बहुत शुरुआत में कॉल के साथ चाल की तुलना में अधिक गंभीर है।
बहुत पहले "LoadLibraryA" के लिए खोज की जाएगी, जो "kernelbase.dll" निर्यात नहीं करता है।
इसका मतलब यह है कि विंडोज विस्टा और इसके बाद के संस्करण में, यह keygenme काम नहीं करेगा और स्टार्टअप पर क्रैश हो जाएगा।
आप देख सकते हैं, अपवाद यहां उठाया जाएगा:
004011EB CMPS BYTE PTR DS:[ESI],BYTE PTR ES:[EDI]
चारों ओर पाने के लिए यह काफी सरल है, निर्देश पर बीपी लगाने के लिए कीजेनमे शुरू करने के बाद यह पर्याप्त है:
0040101B LEA ESI,DWORD PTR SS:[EBP+2DE]
यानी लाइब्रेरी लोड पता प्राप्त करने के तुरंत बाद, और "k गिने जाने वाले 3200 kll" लाइब्रेरी के मूल्य के साथ EAX रजिस्टर में मूल्य को बदलें (सही पता Alt + M प्रक्रिया मेमोरी कार्ड में देखा जा सकता है)।
इस तरह के जोड़तोड़ के बाद, कीगेनमे सामान्य रूप से शुरू हो जाएगा।
हर बार जब आप एप्लिकेशन शुरू करते हैं तो ऐसा नहीं करने के लिए, यह स्क्रिप्ट को "। \ Scripts \ run_at_vista.txt" फ़ोल्डर से चलाने के लिए पर्याप्त होगा, जो हर बार जब आप प्रोग्राम को शुरू करते हैं और त्रुटियों के बिना प्रोग्राम चलाते हैं, तो EAX मान को स्वचालित रूप से सही से बदल देगा।
5. लॉगिन और सीरियल नंबर पढ़ना
अब यह देखने का समय है कि एप्लिकेशन की मेमोरी में लॉगिन और सीरियल नंबर कैसे पढ़े जाते हैं और उनके लिए क्या संशोधन किए जाते हैं।
आमतौर पर, EDIT से डेटा पढ़ना GetWindowsText या GetDlgItemText फ़ंक्शन का उपयोग करके होता है, लेकिन तब से दूसरा फ़ंक्शन अंततः पहले वाले को वैसे भी कॉल करता है, फिर हम ब्रेकपॉइंट को "गेटवॉन्ड टेक्स्ट" पर सेट करेंगे।
ऐसा करने के लिए, keygenme शुरू होने के बाद (और user32.dll लाइब्रेरी ने लोड किया है) और इसका डायलॉग बॉक्स दिखाई देता है, डिबगर पर जाएं और सभी मॉड्यूल में सभी उपलब्ध कार्यों की तलाश करें:
दिखाई देने वाले संवाद में, निर्यात किए गए फ़ंक्शन "GetWindowsTextA" के नाम की तलाश करें, और मेनू से "फॉलो डिस्सेम्बलर में" चुनें:
उसके बाद, बीपी डालें, कीगेनमे के साथ बातचीत में जाएँ और उचित फ़ील्ड में यूज़रनेम “Ms-Rem” और सीरियल नंबर “C38FB7A0CF38F73B1159” चलाएं।
"चेक" बटन पर क्लिक करें और ... "GetWindowsTextA" फ़ंक्शन की शुरुआत में उपयोगकर्ता 32 के अंदर टूटने पर रोकें।
अब आपको उसके कॉल की जगह पर लौटने की जरूरत है।
प्रेस:
1. Ctrl + Shift + F9 - GetWindowsTextA फ़ंक्शन के अंत में कूदना
2. F8 - "GetDlgItemText" फ़ंक्शन पर जाएं
3. Ctrl + Shift + F9 - GetDlgItemText फ़ंक्शन के अंत में कूदना
4. F8 - कॉल करने की जगह पर जाएं
नीले फ्रेम में "GetDlgItemText" के लिए बस एक ही कॉल है, जिसमें निम्न पैरामीटर हैं:
hDlg = ESI
nIDDlgItem = 65
lpString = EAX
nMaxCount = $ 10
हमने 16 बाइट्स के EAX रजिस्टर द्वारा इंगित बफर में इस लॉगिन मूल्य को पढ़ा, और 32 बाइट्स के EDI (EBP + 414E) द्वारा इंगित बफर को सीरियल नंबर पढ़ने के लिए कॉल को एक लाल फ्रेम में हाइलाइट किया गया है।
सबसे दिलचस्प हिस्सा सीरियल नंबर पढ़ने के ठीक बाद शुरू होता है।
10 पुनरावृत्तियों का एक दिलचस्प चक्र निम्न है, ईएसआई रजिस्टर बफर को इंगित करने के लिए सीरियल नंबर के साथ पढ़ा जाता है, और ईडीआई रजिस्टर बफर को इंगित करता है जहां परिणाम रखा गया है:
00401111 MOV ECX,0A
यानी फ़ंक्शन 40117B में सीरियल नंबर पर, कुछ रूपांतरण किए जाते हैं, जिसका परिणाम ईडीआई में रखा गया है।
यह फ़ंक्शन निम्नानुसार है:
यह कुछ ऐसा है जैसे दो वर्णों को एक स्ट्रिंग HEX प्रतिनिधित्व से बाइट्स में परिवर्तित करना।
यदि यह अशिष्ट है, तो यह परिणाम का एक एनालॉग है: = StrToInt ('$' + मूल्य);
उदाहरण के लिए मूल ज्ञात सीरियल नंबर लें: "C38FB7A0CF38F73B1159"
10 पुनरावृत्तियों के बाद, इसे निम्नलिखित सामग्रियों के साथ एक सरणी में बदल दिया जाएगा:
var sn: array [0..9] of Byte = ($C3, $8F, $B7, $A0, $CF, $38, $F7, $3B, $11, $59);
लेकिन कब से फ़ंक्शन सीमाओं के लिए जांच नहीं करता है जिसके आगे स्ट्रिंग प्रारूप में HEX मान नहीं जाना चाहिए, अर्थात, मान्य मानों की एक बहुत बड़ी श्रेणी, जो इस तरह के कलाकारों के बाद एक ही नंबर देगी।
उदाहरण के लिए, कि "सी 3" कि "एस 1" इस तरह के रूपांतरण के परिणामस्वरूप 195 (या $ सी 3) के बराबर होगा। इसलिए, यह भी, काफी मान्य सीरियल नंबर होगा: "
s1 8FB7A0CF38F73B1159"।
इस प्रकार, हम निष्कर्ष निकालते हैं: दर्ज किया गया लॉगिन इस प्रकार पढ़ा जाता है, और पढ़ने के बाद क्रम संख्या को बाइट प्रतिनिधित्व में बदल दिया जाता है, इसके अलावा, क्योंकि पुनरावृत्तियां केवल 10 हैं, फिर सीरियल नंबर की लंबाई 20 एचईएक्स वर्णों से अधिक नहीं होनी चाहिए (बाकी को ध्यान में नहीं रखा जाएगा)।
मूल रूप से, यह सब जानकारी जो हमें चाहिए, अब समय आ गया है कि आप keygenme source कोड को थोड़ा अलग कोण से देखें।
6. हम आईडीए प्रो के तहत कीगेनमे का विश्लेषण करते हैं, हम वीएम को सॉर्ट करते हैं और हम इसका पी-कोड प्राप्त करते हैं
हम आईडीए प्रो शुरू करते हैं और इसमें keygenme_unpacked.exe खोलते हैं, खोलने के ठीक बाद हम टैब "फ़ंक्शन" पर जाते हैं।
केवल 8 टुकड़े:
और लगभग हम सभी जानते हैं:
401000 OEP है, यह दिलचस्प नहीं है
401096 - और यह प्रारंभिक प्रविष्टि बिंदु प्रतीत होता है, जो कि किसी भी एन्क्रिप्शन से पहले था और वर्चुअल IAT के निर्माण को वहां लटका दिया गया था, लेकिन, वैसे, अब हमें इसकी आवश्यकता नहीं है।
40117B HexToInt है, देखा गया ...
401198 - वर्चुअल IAT को भरने, देखा ...
4011E2 - नाम, देखा द्वारा फ़ंक्शन खोज पता ...
सब_401204 कुछ दिलचस्प है (ग्राफ को देखते हुए), हम शायद इसके साथ शुरू करेंगे, वैसे, यह दो शेष कार्यों को उप_401257 और सब_401276 कहता है।
और आईडीए प्रो ने EDITs के 004010F5 और 00401107 पते पर रीड फंक्शंस के कॉल को नहीं पहचाना, साथ ही 00401111 पर लूप, क्योंकि उनके सामने कचरा निर्देश आ रहा था (और महत्वपूर्ण नहीं)।
तो, हम फ़ंक्शन ग्राफ उप_401204 को देखते हैं:
क्या आपने कभी देखा है कि आईडीए में वीएम ग्राफ कैसा दिखता है?
यदि नहीं, तो देखें - यह आभासी मशीन का शरीर है, और काफी सरल है।
वर्चुअल मशीन क्या है?
मोटे तौर पर ... एक साधारण प्रोसेसर इसे ज्ञात निर्देशों का एक सेट निष्पादित करता है (एक मशीन कोड जिसे डिसाइड किया जा सकता है)।
एक वर्चुअल मशीन अनिवार्य रूप से एक प्रोसेसर भी है, यह केवल निर्देशों के अपने स्वयं के सेट को निष्पादित करता है जो कि तथाकथित पी-कोड के रूप में सुलभ मेमोरी क्षेत्र में कहीं और उत्पन्न होते हैं।
यह बिल्कुल आवश्यक नहीं है कि ये निर्देश उन लोगों के साथ मेल खाते हैं जो वास्तविक प्रोसेसर निष्पादित कर सकते हैं (अधिक सटीक रूप से, इसके विपरीत - ज्यादातर मामलों में वे संयोग नहीं करेंगे)।
सुरक्षा की प्रक्रिया में, वाणिज्यिक रक्षक कोड के संरक्षित ब्लॉकों को इकट्ठा करते हैं, उनमें से प्रत्येक के लिए एक अद्वितीय तर्क और निर्देशों का एक सेट के साथ एक वर्चुअल मशीन उत्पन्न करते हैं, असंतुष्ट कोड को एक पिकोड में अनुवाद करते हैं जो एक विशिष्ट वर्चुअल मशीन शरीर में कहीं भी बफर के रूप में परिणाम को निष्पादित और बचा सकती है। आवेदन। प्रत्येक वीएम, एक कार्यक्रम के निष्पादन के दौरान नियंत्रण प्राप्त करने पर, क्रमिक रूप से केवल इसके लिए इरादा पिकोड निर्देशों को पढ़ता है और उन्हें निष्पादित करता है।
इस प्रकार, संरक्षित एल्गोरिथ्म का तर्क, जिसे वह डिबगर के तहत पार्स कर सकता है, पटाखा से छिपा हुआ है।
इस तरह के संशोधनों के बाद, प्रत्येक वीएम का पहले विश्लेषण करना आवश्यक होगा, और केवल इसके पूर्ण विश्लेषण के बाद, इसे निष्पादित करने वाले पिकोड से एल्गोरिथ्म को बाहर निकालना, इसे वापस एक मशीन कोड में परिवर्तित करना जो सामान्य प्रोसेसर के लिए समझ में आता है। वह काम अभी भी है ...ऊपर की तस्वीर में हम केवल वीएम कार्यान्वयन में से एक को देखते हैं, जो केवल 8 निर्देशों को ज्ञात कर सकता है।आइए करीब से देखें:यह सभी ईबीएक्स रजिस्टर के आरंभीकरण के साथ शुरू होता है, जो वर्चुअल मशीन के लिए एक picode के साथ बफर की शुरुआत को इंगित करता है, साथ ही साथ EAX रजिस्टर, जो कर्सर (निष्पादन योग्य निर्देश का सूचकांक) है।VM ऑपरेशन loc_40120D प्रक्रिया से शुरू होता है।इसका कार्य पहले सब_401276 फ़ंक्शन को कॉल करके निष्पादित निर्देश के ओपकोड को प्राप्त करना है, जिसका कोड संकेत में दिया गया है।इस कोड को देखते हुए, यह समझा जा सकता है कि पिको खुद भी एन्क्रिप्टेड है, और प्रत्येक बाइट को पढ़ने के तुरंत बाद, यह लगभग इस एल्गोरिथ्म के साथ डिक्रिप्ट करता है: var A, B: Byte; ... A := PicodeBuff[I]; B := A; B := B shr 4; A := A xor B; Result := A and 7;
ओपकोड प्राप्त करने के बाद, यह जाँच की जाती है, यदि यह शून्य है, तो नियंत्रण यहां स्थानांतरित किया जाता है:जहां कुछ वैरिएबल की इकाई को बस जोड़ा जाता है (चलो इसे arg_4 कहते हैं जैसा कि यह है)और अंत में, नियंत्रण अंतिम ब्लॉक में जाता है, जो EAX रजिस्टर को बढ़ाते हुए, अगले ओपोड के निष्पादन को शुरू करता है।और इसमें हम पिकोकोड के समग्र आकार का पता लगा सकते हैं, यह $ 3DA2 (15778 बाइट्स) के बराबर है।इसलिए, हम सभी निर्देशों का क्रम में विश्लेषण करेंगे:0. (loc_4012CA): चर arg_41. (loc_4012C5) का मान बढ़ाता है: चर arg_42 का मान घटता है । 2. (loc_401012BE): arg_43. ( द्वारा इंगित किया गया मान बढ़ता है) loc_4012B7): arg_44. द्वारा इंगित मूल्य को घटाता है (loc_4012A8): arg_4 द्वारा इंगित की गई मान को arg_8 द्वारा इंगित की गई मेमोरी में रखता है, फिर चर arg_85 (loc_401299) के मान को बढ़ाता है:। arg_C द्वारा बताए गए मान को arg_4 द्वारा बताई गई मेमोरी में डालता है, और फिर चर gg.C का मान बढ़ाता है6. (loc_401284): arg_4 द्वारा बताए गए मान की जाँच करता है, और यदि यह शून्य है, तो प्रक्रिया शुरू करता है sub_401257 1 से EDX7. (0040123E) मान पास करते हुए: arg_4 द्वारा निर्दिष्ट मान की जाँच करता है, और यदि यह शून्य के बराबर नहीं है, यह प्रक्रिया "सब_401257" शुरू करता है, जो मान -1 से EDX तक जाता हैऔर इस प्रक्रिया में "सब_401257" निम्न होता है, EDX पिको कोड के स्कैन की दिशा है, यदि मान धनात्मक है, तो यह opcode नंबर 7 को खोजता है, जो कि opcode number 6 से संबंधित है। ई। यदि 066770 जाता है, तो जब पहली ओपकोड 6 से बुलाया जाता है, तो EAX कर्सर शून्य पर सेट हो जाएगा, और जब दूसरे ओपकोड 6 से बुलाया जाता है, तो EAX दूसरे सात को इंगित करेगा)।और अगर EDX = -1, तो स्कैन विपरीत दिशा में जाता है और साथ ही साथ नेस्टिंग को भी ध्यान में रखता है।यह वास्तव में पूरे वी.एम.क्या कुछ भी समान नहीं है?
हाँ - यह वास्तव में Brainfuck है जैसा कि यह है।http://ru.wikipedia.org/wiki/Brainfuckयानी
यह पता चला है कि कीगेनमे के अंदर एक एन्क्रिप्टेड P-Code है, जिसे Brainfuck दुभाषिया पर निष्पादित किया जाना चाहिए, जो वास्तव में एक वर्चुअल मशीन के रूप में कार्य करता है (ठीक है, क्यों नहीं?)वैसे, यदि आप विकिपीडिया पर BF के विवरण और हैंडलर के कार्यान्वयन पर ध्यान देते हैं। दुभाषिया का दिया गया संस्करण, आप देखेंगे कि चौथा और पाँचवा ओपकोड (पढ़ना और लिखना) मिला हुआ है।ठीक है, यदि ऐसा है, तो हमारे लिए जो कुछ बचा है वह कीजेनम बॉडी से पिकोड को हटाने के लिए है और हमें अब केगेनमे की जरूरत नहीं है, तो हम खुद।एक picode के साथ बफर के स्थान को निर्धारित करने के लिए, VM की शुरुआत में BP लगाएं और उस पते को देखें जो EBX को इनिशियलाइज़ करता है।हम ओलीडबग शुरू करते हैं और इसमें हमने 401208 पते पर बीपी लगाया है।बीपी ट्रिगर होने के बाद, हम ईबीएक्स मूल्य को देखते हैं, यह पता 401380 है।हम इसे डंप विंडो में जाते हैं और एचईएक्स मूल्यों को देखते हैं, पहले 8 बाइट्स "सीई 44 4 ई 53101708 डी" के बराबर हैं।अब किसी भी HEX संपादक में keygenme_unpacked.exe खोलें और इन 8 बाइट्स को देखें।VM का आकार, जैसा कि हमें पहले पता चला, 15778 बाइट्स है।हम "vm.mem" फ़ाइल में पाए गए 15778 बाइट्स की प्रतिलिपि बनाते हैं।खैर, अब हमारे पास हाथ में एक वर्चुअल मशीन का पी-कोड है, जिसके साथ हमारे पास करने के लिए एक लंबा और कठिन काम है, और हमें अब ओलिवेबग और आईडीए प्रो के साथ कीजेनम की आवश्यकता नहीं है, उन्होंने अपना काम किया।पुनश्च: पहले से ही कॉपी की गई फ़ाइल "vm.mem" लेख के लिए उदाहरण के साथ संग्रह में उपलब्ध है और "। \ Data \ vm.mem" फ़ोल्डर में स्थित है।वैसे।
लेख को प्रूफ़ करने की प्रक्रिया में, उन्होंने मुझे ऐसे क्षण की ओर इशारा किया: एक वास्तविक युद्ध अनुप्रयोग में, कार्यों की संख्या में परिमाण के कई क्रम अधिक होंगे, लेकिन इस मामले में आभासी मशीन के शरीर का निर्धारण कैसे किया जाए?इस स्थिति में, बाहरी डेटा (लॉगिन और सीरियल नंबर) को पढ़ने के लिए बीपी सेट करने के लिए पर्याप्त होगा, जिसमें से एक वर्चुअल मशीन के हैंडलर में संक्रमण को ट्रैक करना काफी आसान होगा, या आउटपुट बफर के साथ एक ही क्रिया करना होगा (क्योंकि वीएम को कुछ करना चाहिए और होना चाहिए) बाहरी वातावरण के साथ बातचीत)।लेकिन यह सब, ज़ाहिर है, वीएम के विशिष्ट कार्यान्वयन पर निर्भर करता है, और यह दृष्टिकोण हमेशा लागू नहीं होता है।7. हम अपने स्वयं के दुभाषिया ब्रेनफक लिखते हैं
सबसे पहले, परिणामी "vm.mem" को इस तरह से कुछ के साथ सामान्य प्रतिनिधित्व में डिकोड करें: const BrainFuckOpcode: array [0..7] of AnsiChar = ('>', '<', '+', '-', ',', '.', '[', ']'); const PicodeBuffSize = 15778; var PicodeBuff: array [0..PicodeBuffSize - 1] of Byte; M: TMemoryStream; I: Integer; A, B: Byte; begin M := TMemoryStream.Create; try M.LoadFromFile('..\..\data\vm.mem'); M.ReadBuffer(PicodeBuff[0], PicodeBuffSize); for I := 0 to PicodeBuffSize - 1 do begin A := PicodeBuff[I]; B := A; B := B shr 4; A := A xor B; PicodeBuff[I] := Byte(BrainFuckOpcode[A and 7]); end; M.Clear; M.WriteBuffer(PicodeBuff[0], PicodeBuffSize); M.SaveToFile('..\..\data\vm.brainfuck'); finally M.Free; end;
यह एक "vm.brainfuck" फ़ाइल का उत्पादन करेगा, जिसमें यह कोड आमतौर पर लिखा गया है, और यहाँ पहले से ही निर्देश दिए गए हैं। और "," भ्रमित हैं।यह फ़ाइल, सिद्धांत रूप में, पहले से ही किसी भी बीएफ दुभाषिया को खिलाया जा सकता है, और यदि आप लॉगिन और सीरियल नंबर के साथ सही बफर को पर्ची करते हैं, तो यह निष्पादित भी करेगा।वैसे, लॉगिन और सीरियल नंबर के साथ बफर के बारे में - मैं इसका उल्लेख करना पूरी तरह से भूल गया। यह 20 बाइट्स के ब्लॉक के रूप में वर्चुअल मशीन के इनपुट पर जाता है, जहां पहले 10 बाइट्स लॉगिन वर्णों से भरे होते हैं (यदि लॉगिन 10 बाइट्स से कम है, तो शेष बाइट्स शून्य हैं), और उनके तुरंत बाद एक स्ट्रिंग HEX प्रतिनिधित्व से बाइट में परिवर्तित सीरियल नंबर के 10 बाइट्स हैं।यानी
ज्ञात "Ms-Rem" और "C38FB7A0CF38F73B1159" बफर इस तरह होगा:('एम', 'एस', '-', 'आर', 'ई', 'एम', 0, 0, 0, 0)। $ C3, $ 8F, $ B7, $ A0, $ CF, $ 38, $ F7, $ 3B, $ 11, $ 59)यह तब देखा जा सकता है जब डिबगर के तहत वर्चुअल मशीन को शुरू करना, पता द्वारा स्थित डेटा को देखते हुए चर द्वारा इंगित किया गया " arg_C ”।बीएफ दुभाषिया काम करने के लिए, 300,000 बाइट्स के बफर की आवश्यकता होती है (विकी में वर्णित शर्तों के तहत), लेकिन वास्तव में, कोड का यह संस्करण 300,000 से केवल 221 बाइट्स का उपयोग करता है।वास्तव में, हम कोड लिखते हैं।हमें VM कार्यक्षेत्र, इनपुट और आउटपुट बफ़र्स के लिए, पिकोड के लिए 4 बफ़र्स की आवश्यकता है। const PicodeBuffSize = 15778; var
स्टार्टअप पर, पिको कोड को लोड करना और लॉगिन और सीरियल नंबर के साथ बफर को सही ढंग से प्रारंभ करना आवश्यक है: procedure InitVM; var M: TMemoryStream; begin M := TMemoryStream.Create; try M.LoadFromFile('..\..\data\vm.brainfuck'); M.Read(PicodeBuff[0], PicodeBuffSize); finally M.Free; end; end; procedure InitLoginAndPwd(const Login, Password: AnsiString); var I: Integer; A, B: Byte; begin
फिर आपको दुभाषिया कोड खुद लिखने की आवश्यकता है: procedure RunVM; var I: Integer; Count: Integer; begin repeat case PicodeBuff[PicodeIndex] of Byte('>'): Inc(WorkBuffIndex); Byte('<'): Dec(WorkBuffIndex); Byte('+'): Inc(WorkBuff[WorkBuffIndex]); Byte('-'): Dec(WorkBuff[WorkBuffIndex]); Byte('.'): begin OutputBuff[OutputBuffIndex] := AnsiChar(WorkBuff[WorkBuffIndex]); Inc(OutputBuffIndex); end; Byte(','): begin WorkBuff[WorkBuffIndex] := Byte(LoginAndPwd[LoginAndPwdIndex]); Inc(LoginAndPwdIndex); end; Byte('['): begin if WorkBuff[WorkBuffIndex] <> 0 then begin Inc(PicodeIndex); Continue; end; Count := 1; for I := PicodeIndex + 1 to PicodeBuffSize - 1 do begin if PicodeBuff[I] = Byte('[') then begin Inc(Count); Continue; end; if PicodeBuff[I] = Byte(']') then begin Dec(Count); if Count = 0 then begin PicodeIndex := I; Break; end; end; end; end; Byte(']'): begin if WorkBuff[WorkBuffIndex] = 0 then begin Inc(PicodeIndex); Continue; end; Count := 1; for I := PicodeIndex - 1 downto 0 do begin if PicodeBuff[I] = Byte(']') then begin Inc(Count); Continue; end; if PicodeBuff[I] = Byte('[') then begin Dec(Count); if Count = 0 then begin PicodeIndex := I; Break; end; end; end; end; end; Inc(PicodeIndex); until PicodeIndex = PicodeBuffSize; end;
यह केवल यह सब चलाना है: InitVM; InitLoginAndPwd('Ms-Rem', 'C38FB7A0CF38F73B1159'); RunVM; Writeln(PAnsiChar(@OutputBuff[0])); Readln;
हम शुरू करते हैं और परिणाम को देखते हैं:ठीक है, सब कुछ सही किया जा रहा है और यह काम करना चाहिए।( उदाहरण के साथ संग्रह में दुभाषिया के स्रोत कोड '। \ Tools \ bf_execute \')इस प्रकार - दूसरा लिफाफा हटा दिया जाता है।लेकिन अब हम यह सब क्या करते हैं?यह पिकोकोड माथे का विश्लेषण करने के लिए काम नहीं करेगा - कोई उपकरण नहीं हैं, केवल एक चीज जो देखी जा सकती है वह है कोशिकाओं की संख्या जिसमें लॉगिन और पासवर्ड दर्ज किए जाते हैं और जिससे परिणाम प्राप्त होता है।हम बीपी को पढ़ने और लिखने की प्रक्रियाओं पर डालते हैं और देखते हैं कि हमारे साथ क्याहोता है ... कुछ भी अच्छा नहीं है, उस समय जब लॉगिन और सीरियल नंबर डेटा पढ़ा जाता है, प्रत्येक बाइट को हमेशा काम करने वाले बफर के सेल नंबर छह में पढ़ा जाता है।यही बात VM से डेटा के आउटपुट के साथ होती है, अगला चरित्र भी सेल नंबर छह से लिया गया है।केवल एक चीज जो कम से कम किसी भी तरह से चित्र को स्पष्ट कर सकती है वह है डेटा आउटपुट के समय काम करने वाले बफर का एक डंप, इसे देखें:यहां, कम से कम यह स्पष्ट है कि उपयोगकर्ता नाम और पासवर्ड वीएम के काम करने वाले बफर में हैं, साथ ही साथ नीचे यह "अच्छे संदेश" और "खराब" के साथ पहले से ही तैयार दो लाइनें हैं।और काम करने वाले बफर के छठे सेल में (शीर्ष दाएं से दूसरा) आउटपुट संदेश "बधाई" से तैयार चरित्र "सी" !!! यह मान्य धारावाहिक है! ”यह एक ऐसा माहौल है।8. हम ब्रेनफॉक डीकॉम्पेलर लिखते हैं
डीकॉम्पिलेटर क्या है?वास्तव में, यह एक उपयोगिता है जो एक प्रोसेसर के लिए मशीन कोड के एक सेट को विधानसभा निर्देशों के एक सेट में परिवर्तित करता है जिसे एक प्रोग्रामर समझता है। प्रत्येक प्रोसेसर के लिए, यह अपना स्वयं का कोडांतरक (32/64 / ARM, आदि) होगा। खैर, एक वर्चुअल मशीन (जैसा कि पहले उल्लेख किया गया है) एक ही प्रोसेसर है जिसमें निर्देशों का अपना सेट है, जिसे पिको कोड के रूप में व्यक्त किया गया है।अब कार्य 32-बिट असेंबलर में एक पिकोड डिकम्पॉइलर लिखना है ताकि आप किसी तरह परिणाम के साथ काम कर सकें, क्योंकि इस मामले में हमारे पास पहले से ही विश्लेषण के लिए एक उपकरण है - यह एक डिबगर है।इसके अलावा, यह कार्य अनिवार्य रूप से सरल है, किसी भी उपसर्ग को ध्यान में रखने की आवश्यकता नहीं है, ModRM / SIB पार्सिंग - सिर्फ आठ निर्देश, लेकिन पहले आपको यह पता लगाने की आवश्यकता है कि यह कैसा दिखेगा।माथे को अलग करना एक अच्छा विचार नहीं होगा, आपको बीएफ कोड के दोहराया निर्देशों के ब्लॉक को कम करने की आवश्यकता है।उदाहरण के लिए, हमारे पास इस तरह की एक दिमाग़ी पटकथा है: ">>> +++ << ----"यहां हम चौथे सेल में जाते हैं, इसके मूल्य में तीन की वृद्धि करते हैं, दूसरे पर जाते हैं और इसके मूल्य को चार से घटाते हैं।यदि आप माथे में जुदा करते हैं, तो आपको कुछ धीमा पड़ता है: inc eax inc eax inc eax inc byte ptr [eax] inc byte ptr [eax] inc byte ptr [eax] dec eax dec eax dec byte ptr [eax] dec byte ptr [eax] dec byte ptr [eax] dec byte ptr [eax]
निर्देशों के दोहराए गए सेटों को संक्षिप्त करना बहुत आसान है: add eax, 3 add byte ptr [eax], 3 sub eax, 2 sub byte ptr [eax], 4
इनपुट और आउटपुट को काफी आसानी से इकट्ठा किया जाता है।हमें केवल यह जानना होगा कि कहां पढ़ना है और कहां से प्राप्त करना है। इसके अलावा, यह ध्यान में रखा जाना चाहिए कि हर बार जब डेटा पढ़ते या आउटपुट करते हैं, तो आपको उस स्थिति को शिफ्ट करने की आवश्यकता होती है, ताकि जो पहले से पढ़ा गया है या जो पहले से लिखा गया है, उसे पढ़ने के लिए नहीं।केवल कोष्ठक के साथ विकल्प रहता है।बीएफ में कोष्ठक, लूप के एक एनालॉग को निष्पादित करते हैं, अर्थात। जब तक सेल शून्य नहीं होता है, तब तक लूप बॉडी निष्पादित की जाती है।उद्घाटन ब्रैकेट "[" लूप बॉडी में प्रवेश करने के लिए जिम्मेदार है, अगले पुनरावृत्ति के लिए संक्रमण के लिए, "]" को बंद करना।यानी
मोटे तौर पर कोडांतरक में हमें दो सशर्त संक्रमण प्रदान करने की आवश्यकता होती है, एक लूप में प्रवेश करने के लिए, एक अगले पुनरावृत्ति पर जाने के लिए।ढेर से पहले कम से कम एक और चीज की आवश्यकता है, यह वह पता है जहां आपको जाने की आवश्यकता है यदि लूप में प्रवेश करने की शर्त पूरी नहीं हुई है, या यदि लूप के अगले पुनरावृत्ति में जाने के लिए शर्त पूरी नहीं हुई है।अगला चरण यह पता लगाना है कि छोरों को कैसे अलग करना है, ऊपर वर्णित तीन छलांगों को ध्यान में रखते हुए।उदाहरण के लिए, इस तरह की एक तस्वीर है:Disassembling के लिए अधिक सुविधाजनक (कम से कम मेरे लिए) मुख्य कोड के बाहर लूप के शरीर को हटाने का काम होगा, कुछ इस तरह से:यदि इस तरह से संपर्क किया जाता है, तो डिस्सेम्बलर से आपको केवल प्रत्येक प्रक्रिया की शुरुआत और अंत को जानना होगा और प्रत्येक शरीर को एक लूप में विघटित करना होगा, सही स्थानों पर आंतरिक छोरों में कूदता है।पुनश्च: सच है, अब मैं इस तस्वीर को देखता हूं और समझता हूं कि शायद यह इसके लायक नहीं रहा होगा, क्योंकि एक अतिरिक्त कूद दिखाई दी, लेकिन ... फिर से लिखने के लिए बहुत आलसी।ठीक है, ठीक है, अब काम करने वाले बफर और इनपुट / आउटपुट बफ़र्स के बारे में: ईएसआई रजिस्टर को काम करने वाले बफर के लिए एक संकेतक के रूप में इस्तेमाल किया जाएगा, यह कर्सर भी होगा, अर्थात्। संचालन पर ">" या "<" यह रजिस्टर बढ़ेगा या घटेगा।लॉगिन और सीरियल नंबर के साथ बफर के लिए एक पॉइंटर EBX रजिस्टर को स्टोर करेगा, और EDI रजिस्टर आउटपुट बफर के लिए जिम्मेदार होगा।हम एक कोड लिख रहे हैं।डीकंपॉइलर को एक पकोड और एक छोटी सूची के साथ एक बफर की आवश्यकता होगी जिसमें प्रत्येक लूप की शुरुआत और अंत संग्रहीत किया जाएगा। var PicodeBuffSize: Integer; PicodeBuff: array of Byte; type TWhileSubProc = record StartAddr, EndAddr: Integer; SubProcEndLabel, SubProcStartLabel: string; end; TWhileSubProcList = TList<TWhileSubProc>;
अगला, कई उपयोगितावादी प्रक्रियाओं की आवश्यकता है।पहला सूची को स्वयं बनाता है, और दूसरा और तीसरा, StartAddr और EndAddr फ़ील्ड पर ध्यान केंद्रित करते हुए सूची आइटम देता है। function GetWhileSubProcList: TWhileSubProcList; var I, A, Z: Integer; Item: TWhileSubProc; begin Result := TWhileSubProcList.Create; for I := 0 to PicodeBuffSize - 1 do if PicodeBuff[I] = Byte('[') then begin Item.StartAddr := I; Z := 1; for A := I + 1 to PicodeBuffSize - 1 do begin if PicodeBuff[A] = Byte('[') then begin Inc(Z); Continue; end; if PicodeBuff[A] = Byte(']') then begin Dec(Z); if Z = 0 then begin Item.EndAddr := A; Break; end; end; end; Result.Add(Item); end; end; function IndexAtStart(List: TWhileSubProcList; Value: Integer): Integer; var I: Integer; begin Result := -1; for I := 0 to List.Count - 1 do if List[I].StartAddr = Value then Exit(I); end; function IndexAtEnd(List: TWhileSubProcList; Value: Integer): Integer; var I: Integer; begin Result := -1; for I := 0 to List.Count - 1 do if List[I].EndAddr = Value then Exit(I); end;
अब मुख्य प्रक्रिया ब्लॉक के डिकंपाइलर को बाहर से इंगित किया जाता है जबकि: procedure DecodeSubRoutine(List: TWhileSubProcList; Index: Integer); function GetCharSimbol(Value: Integer): string; begin if Value < 32 then Result := ' // #' + IntToHex(Abs(Value), 2) else Result := string(' // char "' + AnsiChar(Value) + '"'); end; const LabelPfx = '@vm_code_'; var Count, I: Integer; SubRoutineName: string; Item: TWhileSubProc; begin if Index >= 0 then begin
यह ब्लॉक रहते हुए प्रत्येक को विघटित करने के लिए पाश रहता है: procedure DecodeVM; var I: Integer; List: TWhileSubProcList; begin MakeAsmCode('procedure RunBrainfuck(pWorkBuff, pInBuf, pOutBuf: Pointer);'); MakeAsmCode('asm'); MakeAsmCode(' pusha'); MakeAsmCode(' mov esi, pWorkBuff'); MakeAsmCode(' mov ebx, pInBuf'); MakeAsmCode(' mov edi, pOutBuf'); List := GetWhileSubProcList; try DecodeSubRoutine(List, -1); for I := 0 to List.Count - 1 do DecodeSubRoutine(List, I); finally List.Free; end; MakeAsmCode('end;'); end;
और इस सब के लिए स्टार्टअप कोड लिखें: function InitPicode: Boolean; var M: TMemoryStream; begin Result := False; if (ParamCount = 0) or not FileExists(ParamStr(1)) then begin Writeln('Brainfuck file not found.'); Exit; end; M := TMemoryStream.Create; try M.LoadFromFile(ParamStr(1)); PicodeBuffSize := M.Size; SetLength(PicodeBuff, PicodeBuffSize); M.ReadBuffer(PicodeBuff[0], PicodeBuffSize); Result := True; finally M.Free; end; end; begin if InitPicode then begin AsmCode := TFileStream.Create(ChangeFileExt(ParamStr(1), '.inc'), fmCreate); try DecodeVM; finally AsmCode.Free; end; Writeln('Done.'); end; Readln; end.
( उदाहरण के साथ संग्रह में डिकम्पाइलर का स्रोत कोड '। \ Tools \ bf_decompiler')अब आपको किसी चीज़ पर इसके संचालन की जांच करने की आवश्यकता है।इस सामग्री के साथ विकी से हैलो वर्ल्ड का उदाहरण लें:+++++++++++ [> ++++++++++++++++++++++++++++++ <<<< -]> ++
।> +। +। +++++++ .. +++।> ++। << ++++++++++++++++++++&।> +++
------.-------->> +>।
और देखते हैं कि यह क्या होता है: procedure RunBrainfuck(pWorkBuff, pInBuf, pOutBuf: Pointer); asm pusha mov esi, pWorkBuff mov ebx, pInBuf mov edi, pOutBuf add byte ptr [esi], 10
ऐसा लग रहा है। मैं सहमत हूं, यह अनुकूलन करना संभव है, लेकिन कार्य नहीं।हम जांचते हैं कि यह कैसे काम करेगा और एक परीक्षण लिखेगा: program hello_world_test; {$APPTYPE CONSOLE} {$R *.res} {$I ..\..\data\helloworld.inc} var WorkBuff: array [0..300000] of Byte; OutBuf: array [0..100] of Byte; begin RunBrainfuck(@WorkBuff[0], nil, @OutBuf[0]); Writeln(PAnsiChar(@OutBuf[0])); Readln; end.
हम परिणाम को देखते हैं:
खैर, बुरा नहीं है।यह पता चला कि क्या इरादा था, अब हम स्वयं वीएम के शरीर को विघटित करते हैं।आउटपुट शुद्ध असेंबलर की 8153 लाइनें थी :)( उदाहरण के साथ संग्रह में वीएम को विघटित करने का परिणाम '। \ Data \ vm.inc')उसकी आंखों के माध्यम से एक त्वरित नज़र, आप तुरंत vm_code_001B_001D के अंदर 'अच्छा' और 'खराब' लाइनों के आरंभीकरण को नोटिस कर सकते हैं: @vm_code_001B_001D: add esi, 105 add byte ptr [esi], 67
लेकिन ... सभी समान, 8 हजार से अधिक लाइनें, उन्हें एक स्नैप से विश्लेषण नहीं किया जा सकता है।अतिरिक्त उपकरण लिखना आवश्यक है।लेकिन इसके लिए आपको एक छोटा डेमो एप्लिकेशन लिखना होगा जो विघटित वीएम कोड को निष्पादित करता है, जिसके साथ हम शेष लेख में काम करेंगे। program decompiled_vm_test; {$APPTYPE CONSOLE} {$R *.res} uses Classes, Math; var
डेमो एप्लिकेशन सेटिंग्स में, आपको एमएपी फ़ाइल (विस्तृत मोड में) की पीढ़ी को सक्षम करने की आवश्यकता है, यह हमारे लिए अगले अध्याय में उपयोगी होगा।9. हम एक अनुरेखक लिखते हैं
यह समझने के लिए कि वास्तव में विघटित कोड में क्या हो रहा है, निष्पादन ट्रेस का निर्माण करना आवश्यक है। इसके अलावा, इस बारे में पर्याप्त जानकारी होगी कि कौन से ब्लॉक निष्पादित किए जा रहे हैं और कहां से संक्रमण हो रहा है।मार्ग का सार एक निर्देशित ग्राफ बनाना है, जिसका विश्लेषण करके आप वेक्टर शाखाओं में तार्किक शाखाओं को रोक सकते हैं (मोटे तौर पर, ब्लॉक आरेख की तरह)।ऐसा करने के लिए, आपको एक डिबगर की आवश्यकता है: http://habrahabr.ru/post/178159/एक ट्रेस के निर्माण की प्रक्रिया में, डिबगर प्राप्तकर्ता के कोड का अनुसरण करेगा, जो हमें पहले से उत्पन्न स्रोत में लाइन नंबर की ओर ले जा सकता है।अस्म लिस्टिंग में लाइन नंबर पर पता डालने के लिए, आपको एक छोटे मॉड्यूल की आवश्यकता होती है जो MAP फ़ाइल को पार्स करेगा और यह जानकारी लौटाएगा।आपको एक अन्य मॉड्यूल की भी आवश्यकता होगी, जो कि asm लिस्टिंग में लाइन नंबर द्वारा, सबप्रोडेक्ट के नाम को लौटा देगा जो बंद हो गया है।मैं पिछले दो मॉड्यूल के स्रोत कोड नहीं दूंगा - वे बहुत सरल हैं, किसी भी मामले में, कोड को संग्रह में निम्न पथ के साथ उदाहरण के लिए संग्रह के साथ देखा जा सकता है : '। \ Tools \ tracer \'।आपको एक वर्ग की भी आवश्यकता होगी जो हटाए गए ट्रैक पर डेटा संग्रहीत करेगा।मोटे तौर पर, उनका पूरा काम इस तरह के रिकॉर्ड की एक श्रृंखला को संग्रहीत करना है: type TTraceItem = record SubName: string; InList, OutList: TStringList; CustomData: Pointer; end;
और उसके साथ काम करने के तरीके प्रदान करते हैं।यह भी बहुत सरल है, कोड पर नज़र जा सकती है, संग्रहीत रास्ते पर: '\ उपकरण \ आम \। Trace_data.pas'प्रदर्शन ट्रैक 4 अलग प्रकार में 4 रन में दिखाई देगा।1. कार्यक्रम निष्पादन का एक पूरा ट्रैक।2. आंशिक ट्रेस, लॉगिन और सीरियल नंबर के साथ बफर पढ़ने की एक पुनरावृत्ति से।3. परिणाम के आउटपुट के एक पुनरावृत्ति से एक आंशिक ट्रेस।4. उन प्रक्रियाओं का पता लगाना जिसमें कोशिकाएं लॉगिन और पासवर्ड के साथ दर्ज की जाती हैं।थोड़ी देर बाद, ट्रैसर को जोड़ना होगा, ट्रेस को पूरी तरह से हटाने के लिए इसमें एक पांचवीं मोड जोड़ देगा, लेकिन डेमो उदाहरण से हटा दिया जाएगा, जिसमें सीरियल नंबर को शून्य के अनुक्रम में फेंक दिया जाएगा (स्रोत कोड में, इस मोड को ttWrongSN कहा जाएगा)।लेकिन उस बारे में और बाद में, और अब ...ट्रेसर का कार्यान्वयन काफी आम है:1. हम डिबगर के OnCreateProcess को ब्लॉक करते हैं और इसे उपयुक्त BP सेट करके शुरू करते हैं: procedure TTracer.OnCreateProcess(Sender: TObject; ThreadIndex: Integer; Data: TCreateProcessDebugInfo); begin Writeln('process start'); case FTraceType of
2. उसके बाद, हम हार्डवुड ब्रेकर के प्रोसेसर में ऑपरेटिंग मोड सेट करते हैं: procedure TTracer.OnHardwareBreakpoint(Sender: TObject; ThreadIndex: Integer; ExceptionRecord: Windows.TExceptionRecord; BreakPointIndex: THWBPIndex; var ReleaseBreakpoint: Boolean); var CurrentName: string; ThreadData: TThreadData; I: Integer; begin Inc(FTotalStepCount); ThreadData := FDebuger.GetThreadData(ThreadIndex); Writeln(ThreadData.Breakpoint.Description[BreakPointIndex]); case FTraceType of ttFull: begin if BreakPointIndex = 0 then begin FDebuger.ResumeAction := raTraceInto; ReleaseBreakpoint := True; end else begin ReleaseBreakpoint := True; CurrentName := FSrc.GetProcedureNameAtLine(FMap.LineAtAddr(DWORD(ExceptionRecord.ExceptionAddress))); FTrace.AddTace(FPreviousName, CurrentName); FTrace.SaveToFile('..\..\data\full.trace'); Writeln('Trace done.'); Writeln('Total instructions traced: ', FTotalStepCount); Writeln('Traced subroutine added: ', FTrace.Count); Writeln('Time elapsed: ', GetTickCount - FStart, 'ms'); end; end; ttIn, ttOut: begin if FProcList.Count > 0 then begin ReleaseBreakpoint := True; CurrentName := FSrc.GetProcedureNameAtLine(FMap.LineAtAddr(DWORD(ExceptionRecord.ExceptionAddress))); FTrace.AddTace(FPreviousName, CurrentName); if FTraceType = ttIn then FProcList.SaveToFile('..\..\data\in.proclist') else FProcList.SaveToFile('..\..\data\out.proclist'); Writeln('Trace done.'); Writeln('Total instructions traced: ', FTotalStepCount); Writeln('Traced subroutine added: ', FProcList.Count); Writeln('Time elapsed: ', GetTickCount - FStart, 'ms'); FDebuger.ResumeAction := raRun; end else FDebuger.ResumeAction := raTraceInto; end; ttCheckLoginBuff: begin ReleaseBreakpoint := True; if BreakPointIndex = 0 then begin FWorkBuffAddr := PByte(FDebuger.GetContext(0).Eax); for I := 0 to 9 do FDebuger.SetMemoryBreakpoint(FWorkBuffAddr + 28 + (I * 2), 1, True, 'Login' + IntToStr(I)); for I := 0 to 9 do FDebuger.SetMemoryBreakpoint(FWorkBuffAddr + 48 + (I * 2), 1, True, 'SN' + IntToStr(I)); end else begin FProcList.SaveToFile('..\..\data\change_buff.proclist'); Writeln('Trace done.'); Writeln('Total instructions traced: ', FTotalStepCount); Writeln('Traced subroutine added: ', FProcList.Count); Writeln('Time elapsed: ', GetTickCount - FStart, 'ms'); end; end; end; end;
संक्षेप में, पूर्ण डंप मोड (ttFull) में, दूसरा HBP का अर्थ ट्रेसिंग प्रक्रिया को पूरा करना होगा, ttIn, ttOut मोड तब बंद हो जाएगा जब दूसरा HBP ट्रिगर होता है (एक I / O पास), लेकिन ttCheckLoginBuff MBP का उपयोग करके ट्रेसिंग प्रदर्शन करेगा। पूरे वीएम चक्र को निष्पादित किया जाएगा।3. ट्रेस परिणाम इन दो हैंडलर में एकत्र किए जाएंगे: procedure TTracer.OnSingleStep(Sender: TObject; ThreadIndex: Integer; ExceptionRecord: Windows.TExceptionRecord); var CurrentName: string; begin Inc(FTotalStepCount); CurrentName := FSrc.GetProcedureNameAtLine(FMap.LineAtAddr(DWORD(ExceptionRecord.ExceptionAddress))); if FTraceType = ttFull then begin if FPreviousName = '' then FPreviousName := CurrentName; if FPreviousName <> CurrentName then begin FTrace.AddTace(FPreviousName, CurrentName); FPreviousName := CurrentName; end; end else FProcList.Add(CurrentName); FDebuger.ResumeAction := raTraceInto; end; procedure TTracer.OnMemoryBreakpoint(Sender: TObject; ThreadIndex: Integer; ExceptionRecord: Windows.TExceptionRecord; BreakPointIndex: Integer; var ReleaseBreakpoint: Boolean); begin Inc(FTotalStepCount); FProcList.Add( FSrc.GetProcedureNameAtLine(FMap.LineAtAddr(DWORD(ExceptionRecord.ExceptionAddress)))); end;
पहला बदलावों की शाखा की निगरानी करता है, और दूसरा उन प्रक्रियाओं के नाम एकत्र करता है जिनसे बफर एक्सेस किया गया था, जिसमें लॉगिन और सीरियल नंबर स्थित हैं।ट्रैसर के काम के परिणामस्वरूप, हमारे पास 4 फाइलें होंगी, जिसके आधार पर हम वीएम के सभी कार्यों का आसानी से विश्लेषण कर सकते हैं, लेकिन ...लेकिन इसके लिए किसी तरह से प्राप्त डेटा को उस रूप में प्रदर्शित करना आवश्यक है जिसमें आप उनके साथ काम कर सकते हैं ...10. हम एक ग्राफ के रूप में निष्पादन का पता लगाते हैं
आईडीए प्रो एक उत्कृष्ट उपकरण है, उन्हें दिए गए ग्राफ़ स्रोत कोड के विश्लेषण की प्रक्रिया में बहुत मदद करते हैं, लेकिन ... लेकिन यह सार्वभौमिक नहीं है।विशेष रूप से, इस मामले में आईडीए द्वारा निर्मित विघटित वीएम कोड के ग्राफ के साथ काम करना बहुत सुविधाजनक नहीं है।यही कारण है कि मैं अपने हाथों से ट्रैक को हटाने के लिए गया (अंतिम अध्याय में वर्णित) और एक उपकरण लिख रहा हूं जो हमें इसे कल्पना करने की अनुमति देता है।उपकरण का सार इस प्रकार है:1. सॉफ़्टवेयर निष्पादन ट्रेस विज़ुअलाइज़्ड ग्राफ में गिर जाता है। 2. ग्राफ में तार्किक निष्पादन ब्लॉकों को प्रदर्शित करें3. परिणामस्वरूप, शो। कोड विश्लेषण के लिए दिलचस्प पते ब्लॉक करें।पहले चरण में ट्रैसर से दिशात्मक ग्राफ में डेटा का रूपांतरण होता है।यह काफी सरल रूप से बनाया गया है - हम वर्तमान ब्लॉक लेते हैं और इसे पुनरावर्ती रूप से आउटगोइंग संक्रमण बनाते हैं। मुख्य बात यह है कि छोरों के बारे में भूल जाते हैं, जहां प्रक्रिया के अंत में इसकी शुरुआत में संक्रमण पहले से ही जोड़े गए तत्व को निर्देशित किया जाएगा।इसलिए, जब एक ग्राफ का निर्माण करते हैं, तो प्रत्येक तत्व के लिए एक कस्टम संपत्ति जोड़ना आवश्यक होता है जिसमें ग्राफ का नोड संग्रहीत किया जाएगा, जिस पर पहली कॉल के दौरान लिंक को उजागर किया जाना चाहिए।बीएफ कोड का विश्लेषण करते हुए, हम निम्नलिखित निष्कर्षनिकाल सकते हैं : 1. प्रत्येक लूप में, केवल 2 इनपुट हो सकते हैं (बाहरी कोड ब्लॉक से और लूप के अंत से संक्रमण)।2. प्रत्येक चक्र के अंत में केवल 2 आउटपुट होते हैं (यदि चक्र पूरा हो गया है, तो कोड में उच्च स्तर पर जाएं, अन्यथा चक्र की शुरुआत में वापस आ जाएं)।3. जबकि ब्लॉक के शरीर में कई संख्या में आउटपुट हो सकते हैं, बशर्ते कि इसके शरीर में इफ्थेन ब्लॉक हैं (लेकिन यह हमारा मामला नहीं है)।कोड कनवल्शन का अर्थ निम्न है:1. यदि प्रक्रिया में एक इनपुट और एक आउटपुट (ट्रेस के परिणामों के आधार पर) है, तो इसे वर्तमान कन्वेक्शन ब्लॉक में जोड़ा जाता है।2. प्रत्येक कन्वेंशन ब्लॉक की शुरुआत किसी भी प्रक्रिया होगी जिसमें दो इनपुट होते हैं (जबकि लूप)।3. कन्वेंशन ब्लॉक का अंत एक पहले से ही जोड़े गए कोड (अंत में) या किसी अन्य कोड ब्लॉक में संक्रमण होगा, जिसमें ट्रेस के परिणामों के आधार पर दो आउटपुट होते हैं (जो पिछले पैराग्राफ 3 के आधार पर लूप के समान छोर का अर्थ है)।सभी चरणों के लिए एक काफी सरल पुनरावर्ती प्रक्रिया ('। \ Tools \ ट्रेस_व्यूसर \ ट्रेस_ग्राफ.पास') जिम्मेदार है: procedure TTraceGraph.LoadItem(Index: Integer; AParent: TExecutionBlock); var Block: TExecutionBlock; Item: TTraceItem; begin
विघटित कोड के पथ को संसाधित करने का परिणाम (जिसमें कुछ समय से कई निकास के रूप में कोई जटिल मामले नहीं हैं) यह चित्र होगा:यह प्रोग्राम के निष्पादन ट्रैक को प्रदर्शित करता है, निष्पादन ब्लॉक ध्वस्त हो गया है।इस तथ्य पर ध्यान न दें कि सब कुछ इतनी खूबसूरती से ऊर्ध्वाधर स्तंभों के साथ व्यवस्थित किया गया है - मैंने इस तरह की व्यवस्था के लिए एक एल्गोरिथ्म नहीं लिखा और पेन के साथ सब कुछ बिखेर दिया (क्योंकि यह आईडीए के रूप में एक ही विज़ुअलाइज़ेशन इंजन लिखने की तुलना में बहुत तेज़ है।मैं इस उपयोगिता का स्रोत कोड नहीं दूंगा। , आप इसे आर्काइव में देख सकते हैं "। \ Tools \ ट्रेस_व्यूसर \"।इसका मुख्य कार्य एक शुरुआती बिंदु देना है जिससे आप वीएम कोड का विश्लेषण करते समय शुरू कर सकते हैं।और यहां दो चित्र हैं जो काम के तर्क का हिस्सा दिखाएंगे।लॉगिन पढ़ने के दौरान यह होता है । और सीरियल नंबर:लेकिन परिणाम प्रदर्शित होने पर ये प्रक्रियाएं की जाती हैं:सर्किट के केंद्र में प्रक्रियाओं के निष्पादन पर ध्यान दें, वे एक हार्डवेयर हैं और वीएम में छिपे एल्गोरिदम के तर्क के काम में भाग नहीं लेते हैं।मैं तुरंत एक आरक्षण करूँगा - यह निष्कर्ष कोड अनुसंधान की प्रक्रिया में पहले से ही बनाया गया था।प्रारंभ में, मेरे हाथों में ये दो चित्र प्राप्त हुए, मैं थोड़ा हैरान हुआ और सुझाव दिया कि अंदर एक और मिनी-वीएम (नीचे बाईं ओर से हरे रंग का ऊर्ध्वाधर ब्लॉक) छिपाया जा सकता है, जो मेरे स्वयं के निर्देशों के आधार पर तर्क को कुंद करता है, लेकिन ... यह धारणा गलत थी।और इसलिए, हमारे पास मौजूद चित्रों को देखते हुए:1. सर्किट के ऊपरी बाएं ब्लॉक डेटा पढ़ रहा है।2. निचला दायाँ - परिणाम का निष्कर्ष।3. केंद्रीय शाखा कुछ प्रकार के हार्डवेयर है (परिणामस्वरूप, आपको इसका विश्लेषण भी नहीं करना है)।वह सब जो हरे रंग में हाइलाइट नहीं किया गया है वह तीसरा लिफाफा (वीएम के तहत छिपा हुआ तर्क) है।उन लोगों के लिए जो इस दर्शक के स्रोत कोड का अध्ययन करेंगे, मैं पहले से माफी मांगता हूं - उपकरण कोड बहुत कच्चा है और दो शाम को सचमुच मेरे घुटने पर लिखा गया था, इसलिए उपयोगिता में बहुत अधिक कार्यक्षमता नहीं है। यह केवल ग्राफ को ही प्रदर्शित कर सकता है, इसके संशोधन के लिए एक तंत्र प्रदान करता है (पढ़ें - आप ब्लॉकों को स्थानांतरित कर सकते हैं क्योंकि यह अधिक सुविधाजनक होगा)।ZOOM मोड को नहीं जोड़ा गया है, इसके लिए एक अलग बटन है जो एक सामान्य पूर्वावलोकन (जिससे ये स्क्रीनशॉट लिया गया था) उत्पन्न करता है।लेकिन एक ग्राफ विश्लेषण उपकरण की भूमिका खराब नहीं है।लेख के साथ संग्रह में पहले से ही व्यवस्थित ग्राफ ('। \ Data \ current.graph') है जो पिछले अध्याय ('। \ Data \ full.trace') में लिए गए ट्रेस से प्राप्त किया गया था।हालांकि ... हमारी भेड़ों को।विघटित वीएम कोड का विश्लेषण करने के लिए उपकरण तैयार है, चलो इसके विश्लेषण के लिए सीधे आगे बढ़ें ...11. चर पढ़ने के लिए एल्गोरिथ्म का विश्लेषण और पता लगाना।
वीएम का विश्लेषण करना शुरू करना, मुझे पहले से ही कुछ विचार था कि मुझे क्या काम करना होगा।मेमोरी कार्ड पर भरोसा करना जिसके साथ वीएम काम करता है (अध्याय 7 में दिखाया गया है), मुझे पता था कि परिणाम के साथ "सही" और "सही नहीं" लाइनें ऑफसेट स्थित हैं, साथ ही साथ ऑफसेट, जिस पर लॉगिन और सीरियल नंबर के साथ बफर स्थित है।यह जानते हुए कि बीएफ दुभाषिया कैसे काम करता है, मैंने डेटा के साथ काम करने के लिए एक अनुमानित एल्गोरिथ्म का भी पता लगाया, इस तथ्य को ध्यान में रखते हुए कि लॉगिन और एसएन पर कुछ ऑपरेशन किए जाने चाहिए।उदाहरण के लिए, यदि आप दो फ़ील्ड Z और X जोड़ना चाहते हैं, तो Brainfuck के पास एक अतिरिक्त ऑपरेशन नहीं है, इसके लिए आपको थोड़ी सी लूप लिखने की आवश्यकता होगी जिसमें सेल X का मान पुनरावृत्त हो जाएगा और सेल Y का मान बढ़ जाएगा।इस प्रकार, सेल वाई में दोनों कोशिकाओं का योग होगा, और सेल एक्स शून्य हो जाएगा।लेकिन जब डेटा को आउटपुट किया जाता है, जैसा कि ऊपर दिखाया गया है, तो आप देख सकते हैं कि लॉगिन और सीरियल नंबर के साथ बफर वह जगह है जहां यह होना चाहिए और इसका एक भी फ़ील्ड नहीं बदला गया है। इसलिए VM डेटा पढ़ने के लिए कुछ अन्य दृष्टिकोण अपनाता है।एक अजीब बारीकियाँ जिस पर मैंने तुरंत गौर किया - वह लॉगिन, कि क्रम संख्या, कि मेमोरी कार्ड में "सही" और "सही नहीं" लाइनें उस रूप में नहीं हैं जैसा कि वे हैं, लेकिन प्रत्येक बाइट को अगले से शून्य से अलग किया जाता है। ऐसा लगता है कि हम यूनिकोड के साथ काम कर रहे हैं, हालांकि वास्तव में हम एएनएसआई के साथ काम कर रहे हैं।ये दो बिंदु मेरे लिए स्पष्ट नहीं थे।हालाँकि, कुछ के साथ शुरू करने की आवश्यकता अभी भी है, और मैंने इसके अंत से एल्गोरिथ्म का विश्लेषण शुरू करने का फैसला किया, यह मानते हुए कि परिणाम को आउटपुट करने का निर्णय प्रक्रियाओं के इस ब्लॉक के साथ किया जाता है (जहां परिणाम प्रदर्शित होता है):इसके अलावा, ग्राफ इस ब्लॉक को एक प्रविष्टि बिंदु और एक आउटपुट के साथ एकल फ़ंक्शन के रूप में दिखाता है।(वैसे, यह धारणा कि इस ब्लॉक में एक निर्णय किया जाता है, अंत में गलत हो गया)।डीबगिंग के लिए, मैंने स्वयं डेल्फी से सीधे सीपीयू-व्यू मोड का उपयोग किया (यह आंखों के लिए पर्याप्त था)।डीबग किया गया अनुप्रयोग "decompiled_vm_text.exe" था जिसमें से निष्पादन ट्रैक हटा दिया गया था।वॉच लिस्ट में सहायक संकेत के रूप में तीन चर जोड़े गए थे:1. वर्किंग बफर कर्सर (ईएसआई रजिस्टर) का वर्तमान मूल्य, जिसकी गणना वर्किंग बफर (पॉवरबफ) की शुरुआत के पते और रजिस्टर के वर्तमान मूल्य के अंतर से की गई थी। (esi- $ 4E5008)2. वर्तमान सेल pWorkBuff का मूल्य (pbyte (esi) ^)3. यह केवल HEX मोड में ही है।4. डंप विंडो को कार्यशील वीएम बफर ($ 4 ई 5008) शुरू करने के लिए कॉन्फ़िगर किया गया था।पुनश्च: आपका काम करने वाला बफर पता अलग हो सकता है, इसलिए आपको pWorkBuff चर में इस मान को देखकर डिबगिंग शुरू करते समय संख्या 4E5008 अग्रिम में गणना करनी चाहिए।इस प्रकार, कॉन्फ़िगर डिबगर, "@ vm_code_39D7" प्रक्रिया की शुरुआत में इस तरह से देखा गया:वास्तव में इस प्रक्रिया के साथ और आगे बढ़ें।यदि आप ग्राफ़ की तस्वीर को देखते हैं, तो ध्वस्त निष्पादन ब्लॉक में यह पहली प्रक्रिया है जो खुद को कॉल करता है (लूप के समय)।इस प्रक्रिया को कॉल करने के समय, काम करने वाले बफर का कर्सर सेल नंबर 27 पर सेट होता है (लाल रंग में हाइलाइट किए गए मेमोरी कार्ड के साथ तस्वीर में), जिसमें एक निश्चित संख्या (40 या $ 28) होती है। यह सेल केवल लॉगिन और सीरियल नंबर (ऑफसेट 28 पर स्थित) के साथ बफर के सामने स्थित है, जिसमें पहला लॉगिन प्रतीक संग्रहीत है - "एम"।इस सेल की संख्या याद रखें - यह वीएम लॉजिक के केंद्रीय तत्वों में से एक है।आइए देखें एक ढह चुके ब्लॉक का स्रोत कोड: @vm_code_39D7: cmp byte ptr [esi], 0
यहां मैंने उनके निष्पादन के क्रम में निष्पादन ब्लॉकों का निर्माण किया (स्रोत कोड में वे जिस रूप में जाते हैं) नहीं, ताकि कार्य से तर्क का विश्लेषण करना आसान हो।तो यहाँ क्या हो रहा है?यह एल्गोरिथ्म इकाइयों द्वारा ज़ीरोस (जो लॉगिन / सीरियल नंबर के प्रत्येक तत्व को अलग करता है, साथ ही साथ "खराब" और "अच्छी" लाइनें) को इनिशियलाइज़ करता है।यदि आप चक्र को अंतिम रूप देने पर रुक जाते हैं और परिणाम को देखते हैं, तो आप इस तस्वीर को देख सकते हैं:सेल # 27 शून्य पर रीसेट हो जाएगा, लेकिन इसके परिणामस्वरूप, आप देख सकते हैं कि बफर को पहले वीएम आउटपुट "अच्छी लाइन" चरित्र, अर्थात् "सी" चरित्र से ठीक पहले समाप्त होने वाली इकाइयों के साथ शुरू किया जाएगा।कृपया ध्यान दें कि संशोधित किए जा रहे डेटा की शुरुआत और अंत में बदलाव कितने दिलचस्प तरीके से किए गए थे (प्रक्रियाओं @ vm_code_39D8 और @ vm_code_39DD)। अंत ब्लॉकों का पता लगाने के रूप में, शून्य के साथ वर्तमान सेल के मूल्य का एक सामंजस्य का उपयोग किया जाता है, बाईं ओर शून्य सेल नंबर 27 (एक बदलाव के साथ) की स्थिति का संकेत देगा, और सबसे दाएं शून्य का मतलब एक सेल होगा जो अभी तक एक द्वारा आरंभीकृत नहीं किया गया है।इस प्रकार, इस ब्लॉक का सार उस अनुक्रमणिका (इकाइयों के रूप में) के निर्माण के लिए कम हो जाता है जिसके साथ अगली, जबकि लूप काम करना शुरू कर देगा, @ vm_code_39EB प्रक्रिया से शुरू होगा।वह सेल के साथ अपना काम शुरू करेगा, जो कि पहले बनाए गए सूचकांक की अंतिम इकाई द्वारा इंगित किया गया है, अर्थात् सेल नंबर 108 के साथ, जहां प्रतीक "सी" स्थित है (नीले रंग में हाइलाइट किया गया है), जिसे वीएम को अब बाहरी बफर में आउटपुट करना चाहिए।इस प्रक्रिया को कॉल करने से पहले, वीएम कुछ तैयारी क्रियाओं को करने में कामयाब रहा, जिनमें से एक इकाई के साथ सेल नंबर 27 का इनिशियलाइज़ेशन था, अर्थात। सबसे बाईं ओर सेल, जिस पर उस क्षण से सूचकांक के बाएं छोर की खोज करने के लिए एल्गोरिदम जाएगा, सेल नंबर 26 होगा।तो:
@vm_code_39EB: dec esi
दूसरे चक्र का तर्क इस प्रकार है: यह सेल नंबर 108 के मान को सेल नंबर 108 से सेल नंबर 26 और नंबर 6 पर स्थानांतरित करता है (ध्यान दें कि यह छठा सेल है जिसमें से वह प्रतीक जिसे मैंने अध्याय सात में बताया है)।और वास्तव में, डेटा के साथ काम करने का बहुत ही तर्क पहले से ही उभरने लगा है (जब मूल्य को पढ़ते हैं, तो मूल्य को दो पते पर दोहराया जाता है)।मेमोरी कार्ड इस तरह दिखने लगता है:हरे रंग की आयतें उन स्थानों को इंगित करती हैं जिनके साथ परिवर्तन हुआ, सेल नंबर 108 जिसमें से मूल्य लिया गया था, लाल रंग में हाइलाइट किया गया है।ग्राफ में अगला लूप प्रक्रिया "@ vm_code_3A2A" से शुरू होता है।वह सेल नंबर 26 से अपना काम शुरू करेगा, जो पहले सेल नंबर 108 में रखी गई संख्या की एक प्रति संग्रहीत करता है।कोड निम्नानुसार है: @vm_code_3A2A: inc esi
इस कोड का कार्य सेल नंबर 26 की आईडी को सेल नंबर 108 पर वापस स्थानांतरित करना है, इस प्रकार सेल के मूल मूल्य को बहाल करना जिसके साथ एल्गोरिदम ने अपना काम शुरू किया।आइए एल्गोरिथम के काम करने के बाद मेमोरी कार्ड देखें:और कोशिकाओं के साथ काम करने के इस थोड़े भ्रमित तर्क में अंतिम चरण "@ vm_code_3A41" प्रक्रिया में अंतिम रूप से स्थित होगा,यह काफी सरल है: @vm_code_3A41: dec byte ptr [esi] sub esi, 2 cmp byte ptr [esi], 0 jne @vm_code_3A41
उसका पूरा कार्य एक के बराबर बहुत अंतिम सेल के साथ काम करना शुरू करना है (जिसके द्वारा सूचकांक बनाया गया है) और सूचकांक की सभी इकाइयों को हटा दें।उसके काम का नतीजा होगा ऐसा नक्शा:हरे सूचकांक क्षेत्रों को इंगित करता है कि अंतिम प्रक्रिया हटा दी गई है, और लाल सभी चार ध्वस्त ब्लॉकों के परिणाम को इंगित करता है।उलझन में?
कुछ भी अजीब नहीं है, लेकिन अगर आप नवीनतम तस्वीर को करीब से देखते हैं, तो आप समझ सकते हैं कि चार चरणों के सभी चालित-आउट तर्क अनिवार्य रूप से एक साधारण ऑपरेशन करते हैं।और यह ऑपरेशन सेल "ए" से सेल "बी" के मूल्य का असाइनमेंट है।इसके अलावा, एल्गोरिथ्म काफी लचीला है और इस प्रकार आप किसी भी लॉगिन / सीरियल नंबर प्रतीक या "अच्छा / बुरा" लाइनों के मूल्य को पढ़ने के लिए अनुमति देता है, इसके लिए यह सेल नंबर 27 को प्रतीक संख्या के साथ आरंभ करने और उपरोक्त सभी चार चरणों को पूरा करने के लिए पर्याप्त है।एक सुंदर दृष्टिकोण - आप कुछ भी नहीं कह सकते हैं, कोशिकाओं के साथ काम करने का तर्क बल्कि ठोस रूप से धब्बा है, हालांकि एक छोटी मात्रा है।यह समझने में कि सेल वैल्यू कैसे प्राप्त की जाती है और हाथ पर निष्पादन ग्राफ होने के बाद, इस क्षण से कार्य के पूरे तर्क का विश्लेषण करने में एक-डेढ़ घंटा लगेगा।एक ग्राफ के साथ शीर्ष छवि से बाकी प्रक्रियाएं समझ में नहीं आती हैं, वीएम ऑपरेशन के लिए आवश्यक किसी प्रकार के आंतरिक उपकरण को छिपाया जाता है, कोड के स्पष्ट रूप से कचरा टुकड़े होते हैं (उदाहरण के लिए, एक चक्र में, दो / तीन कोशिकाओं के मूल्य को स्थानों में बदलना और अंततः अपनी मूल स्थिति में सब कुछ वापस करना) ।12. घटकों में तर्क तत्वों का विश्लेषण।
इस प्रकार, यह पता चला कि सेल मूल्य तीन चक्रों को निष्पादित करके प्राप्त किया जाता है, जिसे ग्राफ और अंतिम रूप के अंतिम ब्लॉक पर स्पष्ट रूप से देखा जा सकता है, जो तीसरे चक्र के अंत के तुरंत बाद सूचकांकों के सरणी को हटा देता है।यहाँ इस तस्वीर में, मैंने तुरंत उन्हें लाल आयतों के साथ हाइलाइट किया और वीएम लॉजिक के विश्लेषण में इस छवि से हटा दिया:दो नीले आयतें पढ़ने वाले सेल मानों के समान चक्रों को उजागर करती हैं, लेकिन चूंकि वर्णों के मान लॉगिन [0] और लॉगिन [1] उनमें पढ़े जाते हैं (मैंने एल्गोरिथम के सभी चक्रों का विश्लेषण करके यह सीखा है), तो इकाइयों के सूचकांक सरणी को इन दो मामलों में नहीं बनाया गया है, पहले चक्र में मूल्य कोशिकाओं संख्या 26 और नंबर 6 में पढ़ा जाता है, और दूसरे में, सेल नंबर 26 से मूल्य वापस आ गया है।चित्र बहुत स्पष्ट रूप से दिखाई नहीं देता है (ज़ूम प्रभावित करता है), ताकि यह अधिक स्पष्ट हो, यहां दूसरा विकल्प है, जो उस सिद्धांत को दिखाता है जिसके द्वारा मैंने आयतों के साथ ब्लॉकों को परिचालित किया:मेरी धारणा को जांचने के लिए, मैंने खुद को चेक किया और ग्राफ पर उन ब्लॉकों को प्रदर्शित किया जिसमें लॉगिन और सीरियल नंबर फ़ील्ड बदलते हैं (बटन "लॉगिन और एसएन के साथ बफर तक पहुंच दिखाएं")। इस बटन का उपयोग करके, दर्शक ttCheckLoginBuff मोड में ट्रेसर द्वारा पहले बनाई गई ऐसी प्रक्रियाओं की सूची को लोड करेगा और इस तस्वीर को दिखाएगा:पिछली छवि में सब कुछ अपेक्षित है, शीर्ष बाईं ओर से एकमात्र ब्लॉक (नीले आयत द्वारा परिचालित) को तर्क से बाहर खटखटाया जाता है, यह कोड का एक भाग है जो बाहरी बफर से लॉगिन और सीरियल नंबर मूल्यों को पढ़ता है, इसलिए इसमें आश्चर्य की बात नहीं है कि ये फ़ील्ड बदलते हैं।यहां मैंने निष्कर्ष निकाला कि, जाहिरा तौर पर, प्रत्येक ऊर्ध्वाधर ब्लॉक सीरियल नंबर की जांच करने के चरणों में से एक है (यह धारणा भी सच हो गई)।अगला चरण मेरे लिए दिलचस्प था, और जहां सीरियल नंबर की शुद्धता पर निर्णय लिया गया था, इसके लिए मैंने "decompiled_vm_text.exe" उदाहरण को फिर से दिया, जिसमें मैंने सीरियल नंबर के मूल्य को शून्य में बदल दिया, और फिर से ttWrongSN मोड में ट्रेस लिया।इन सभी चरणों के बाद, मैंने मार्ग के अंतरों को देखा (बटन "गलत एसएन के साथ मार्ग के अंतर दिखाएं") परिणामस्वरूप मैंने निम्नलिखित देखा:आप बिंगो कह सकते हैं।पहले ऑपरेशन में (दाएं ऊर्ध्वाधर ब्लॉक), एक निकास हुआ, और सभी प्रक्रियाएं नहीं की गईं (लाल रंग में चिह्नित ब्लॉक)। तो यह उसके अंदर है कि निर्णय लॉगिन और सीरियल नंबर के साथ बफर पर पहले ऑपरेशन के परिणामों के अनुसार किया जाता है।अधिक सटीक रूप से, अंत में कई ऐसे ब्लॉक होंगे, प्रत्येक ऑपरेशन के लिए अपने स्वयं के, हालांकि, आपको ऑपरेशन के उनके तर्क को अलग नहीं करना होगा, क्योंकि सभी परिचालनों का अर्थ काफी पारदर्शी होगा।अगला चरण मैंने प्रत्येक ऊर्ध्वाधर ब्लॉक की शुरुआत में डिबगर में ब्रेक लगाया (जो मैंने पहले आयतों के साथ चिह्नित किया था), यह समझने के लिए कि संचालन किस क्रम में किया जाता है।निम्नलिखित सूची निकली:- vm_code_17AB
- vm_code_1A63
- vm_code_1E74
- vm_code_2333
- vm_code_28DA —
- vm_code_2AF3
- vm_code_2D27
- vm_code_2F41
यह रहता है, ग्राफ़ पर ध्यान केंद्रित करते हुए, बस सभी चयनित स्थानों (पहले से ज्ञात लॉजिक तत्वों को छोड़ना जिसमें लॉगिन प्रतीक / एसएन लिया गया है) को पार करना और इन प्रतीकों पर किए गए कार्यों का विश्लेषण करना।मैं उन प्रक्रियाओं की श्रेणी नहीं दूंगा जिनमें सूचकांक बनाया जाता है, मूल्य को पढ़ा जाता है, मान लौटाया जाता है और सूचकांक हटा दिया जाता है। सादगी के लिए, मैं केवल पहले चक्र की प्रक्रिया का नाम दिखाऊंगा (जिसमें इकाइयों से सूचकांक सरणी का निर्माण)।आइए तर्कका विश्लेषण शुरू करें : 1. vm_code_17AB1.1 vm_code_1815 - सेल नंबर 6 में SN [0] का मान पढ़ें1.2 1.2 vm_code_17AB_1880 सेल नंबर 6 में नंबर 121.3 mm_code_1903 जोड़ें - सेल नंबर 7 में एसएन [4] का मूल्य पढ़ें।नतीजतन, दोनों कोशिकाओं (6, 7) में हमें एक ही नंबर मिलता है, इसलिए हम निष्कर्ष निकालते हैं कि पहला गणितीय ऑपरेशन निम्नलिखित जांच करता है: एसएन [4] = एसएन [0] + 122. vm_code_1A632.1 vm_code_1AD4 - मान पढ़ें SN [0] सेल नंबर 6 में2.2 vm_code_1B58 इस मान को सेल नंबर 72.3 vm_code_1BC8 पर स्थानांतरित किया जाता है - सेल नंबर 6 में मूल्य SN [3] पढ़ें2.4 vm_code_1C4C सेल नंबर 6 का मूल्य सेल नंबर 7 में जोड़ा जाता है (7 में परिणाम)2.5 vm_code_1A63_1C5A - सेल। №7 संख्या 84 की वृद्धि हुई2.6 vm_code_1D12 - पढ़ने के मूल्य एस.एन. [2] एक सेल №6नतीजतन, दोनों कोशिकाओं (6, 7) एक प्राप्त की और एक ही संख्या है, जिससे डे है amu निष्कर्ष यह है कि दूसरे की गणितीय प्रक्रिया निम्न परीक्षण का उत्पादन: एसएन [2] = एस.एन. [0] + एस.एन. [3] + 84Vm_code_1E74 3.3.1 vm_code_1EE8 - पढ़ने के मूल्य लॉग इन [0] सेल №6 में3.2 vm_code_1F5B - 6 से 7 सेल करने के लिए मूल्य हस्तांतरण3.3 vm_code_1FDD - पढ़ने के मूल्य लॉग इन [1] सेल №6 में3.4 vm_code_204D - №6 सेल मूल्य एक सेल ढेर करने के लिए № 7 (7 में परिणाम)3.5 vm_code_20CB - सेल नंबर 63.6 vm_code_214F में एसएन [4] का मूल्य पढ़ें - सेल नंबर 7 में सेल नंबर 6 का मूल्य जोड़ा जाता है (7 में परिणाम)3.7 vm_code_21C3 - सेल नंबर 8बी में एसएन [1] का मूल्य पढ़ें। दोनों कोशिकाओं (7, 8) में परिणाम एक ही नंबर मिलेगा, इसलिए हम निष्कर्ष निकालते हैं कि तीसरा गणितीय ऑपरेशन निम्नलिखित जांच करता है: एसएन [1] = लॉगिन [0] + लॉगिन [1] + एसएन [4]4 । v m_code_23334.1 vm_code_239B - सेल नंबर 7 में लॉगिन [8] का मान पढ़ें [4.2] सेल नंबर 6 में लॉगिन [4] का मूल्य पढ़ें [4.3 ] सेल नंबर 7 में सेल नंबर 7 (परिणाम 7)4.4 vm_code_2586 - सेल नंबर 6 के साथ अभिव्यक्त किया गया है। 2] सेल नंबर 64.5 4.5 vm_code_260A के लिए - सेल नंबर 6 का मूल्य सेल नंबर 7 से घटाया जाता है (परिणाम 7 है)4.6 vm_code_2689 - हम सेल नंबर में एसएन [5] के मूल्य को पढ़ते हैं।परिणामस्वरूप, दोनों कोशिकाओं (6, 7) में हमें एक ही चीज मिलती है। -zhe संख्या है, इसलिए हम निष्कर्ष है कि चौथे गणितीय प्रक्रिया निम्न परीक्षण का उत्पादन: एसएन [5] = लॉगिन [8] + लॉग इन [4] - लॉग इन [2]5. vm_code_28DA - पाश 10 पारित सभी प्रतीकों लो इन की और उन्हें एक सेल के साथ संक्षेप में प्रस्तुत №7मोटे तौर पर पांचवें गणितीय ऑपरेशन के परिणामस्वरूप, निम्नलिखित कोड निष्पादित किया गया है: Tmp := 0; for I := 0 to 9 do Inc(Tmp, Login[I]);
सभी लॉगिन वर्णों का योग कक्ष संख्या 7 में रहेगा।6. vm_code_2AF36.1 vm_code_2bc5 - कोशिका संख्या में SN [8] का मूल्य पढ़ें 6.परिणामस्वरूप, दोनों कोशिकाओं (6, 7) में हमें एक ही संख्या मिलती है, इसलिए हम निष्कर्ष निकालते हैं कि छठा गणितीय ऑपरेशन निम्नलिखित जांच करता है: एसएन [8] = टीएमपी (सभी लॉगिन संख्याओं का योग)7. vm_code_2D277.1 vm_code_2D27_2D29 - संख्या 727.2 सेल नंबर 7 vm -code_2DD5 में जोड़ा जाता है - हम सेल नंबर 8 में एसएन [9] का मूल्य पढ़ते हैं, परिणामस्वरूप, दोनों कोशिकाओं में। 7, 8) हमें एक ही नंबर मिलता है, इसलिए हम निष्कर्ष निकालते हैं कि सातवां गणितीय ऑपरेशन निम्नलिखित जांच करता है: एसएन [9] = टीएमपी (सभी लॉगिन संख्याओं का योग) +v२ 8. vm_code_2F41(इस प्रक्रिया के समय में कॉल नंबर के लिए लॉग इन राशि सेल №18 में झूठ होगा)8.1 vm_code_2FA1 - एस.एन. पढ़ [6] एक सेल №6 को8.2 vm_code_2F41_300C - №6 सेल नंबर 51 की वृद्धि हुई हैसेल №7 में Tmp टॉस 8.3 vm_code_3071_307D + vm_code_308B8.4 vm_code_30AA - सेल नंबर 7 का मान सेल नंबर 6 के साथसम्मिलित है (परिणाम 6 है) 8.5 vm_code_311D - हम सेल नंबर 7 में एसएन [7] का मूल्य पढ़ते हैंनतीजतन, दोनों कोशिकाओं (6, 7) में हमें एक ही नंबर मिलता है, इसलिए हम एक ही नंबर प्राप्त करते हैं, इसलिए हम करते हैं निष्कर्ष यह है कि आठवां गणितीय ऑपरेशन निम्नलिखित जांच करता है: एसएन [7] = एसएन [६] + ५१ + टीएमपीयहाँ संपूर्ण एल्गोरिथ्म है, जो VI में छिपा है। अनुष्ठान मशीन, पूरे दृश्य में।इस प्रकार इसका सोर्स कोड दिखेगा: program keygenme_source; {$APPTYPE CONSOLE} {$R *.res} uses Windows, Math; function CheckSerial(const ALogin, ASerial: AnsiString): string; const ValidSN = 'Congratulations!!! It is valid serial!'; InvalidSN = 'Serial invalid :('; var Login: array [0..9] of Byte; Serial: array [0..9] of Byte; I, A, B, Tmp: Byte; Checked: Boolean; begin ZeroMemory(@Login[0], 10); Move(ALogin[1], Login[0], Min(10, Length(ALogin)));
खैर ... यह व्यावहारिक रूप से, पिछले तीसरे लिफाफे को हटा दिया गया है।बस थोड़ा सा बचा है।
13. हम एक सीरियल नंबर जनरेटर लिख रहे हैं।
कीजेनमे टास्क में एक शर्त थी - इसे कीनज करना जरूरी है, यानी। एक एल्गोरिथ्म लिखें जो दर्ज किए गए लॉगिन के आधार पर एक सीरियल नंबर उत्पन्न करेगा।ऐसा करने के लिए सीरियल नंबर की जांच के लिए एल्गोरिथ्म को हाथ में लेना काफी तुच्छ है।यदि आप क्रम संख्या के क्षेत्रों को देखते हैं, तो आप देख सकते हैं कि एसएन [0], एसएन [3] और एसएन [6] की जांच नहीं की जाती है, वे केवल अन्य क्षेत्रों के मूल्यों की जांच में भाग लेते हैं। इसलिए, इन तीन क्षेत्रों में कोई भी मान हो सकता है, और शेष फ़ील्ड की गणना उनके आधार पर पहले से ही की जाएगी।इस प्रकार, जनरेटर कोड इस तरह दिखेगा: program serial_generator; {$APPTYPE CONSOLE} {$R *.res} uses Windows, Math, SysUtils; function GetSN(const ALogin: AnsiString): string; var Login: array [0..9] of Byte; Serial: array [0..9] of Byte; I, Tmp: Byte; begin ZeroMemory(@Login[0], 10); Move(ALogin[1], Login[0], Min(10, Length(ALogin))); Randomize;
परिणामस्वरूप, यहां "Rouse_" लॉगिन के लिए सीरियल नंबर की एक छोटी सूची है:- 5E2BB2006AF04EEE6DB5
- C5929D84D1F0F9996DB5
- BC894434C8F0BA5A6DB5
- 14E19B3320F0B2526DB5
कीजेन्मे हल।14. निष्कर्ष
चलो फिर से चरणों के माध्यम से जाते हैं और याद करते हैं कि सीरियल नंबर सत्यापन एल्गोरिथ्म को छिपाने के लिए किन तरीकों का उपयोग किया गया था:1. प्रवेश बिंदु को शून्य पर रीसेट करना एक बहस योग्य तरीका है, इसके अलावा, एंटीवायरस निष्पादन योग्य फ़ाइलों पर संदेह के साथ दिखेगा जो इस चाल का उपयोग करते हैं, क्योंकि हमेशा की तरह; संकलक कभी भी ऐसी निष्पादन योग्य फ़ाइल उत्पन्न नहीं करेगा, इसलिए किसी ने इसे संशोधित किया है, जो एंटीवायरस के लिए एक संकेत है।2. निष्पादन योग्य फ़ाइल निकाय का एन्क्रिप्शन सिद्धांत रूप में दंडनीय नहीं है, लेकिन जैसा कि ऊपर दिखाया गया है, इस तरह के एन्क्रिप्शन को काफी सरल रूप से हटा दिया जाता है और एक गंभीर बाधा नहीं बनती है।3. डिक्रिप्टर कोड कचरा के साथ बहुतायत से पतला होता है - एक विवादास्पद समाधान, यह इस तथ्य के कारण काफी सरलता से हटा दिया जाता है कि कचरा ब्लॉकों का उपयोग कचरा के रूप में नहीं किया गया था। उदाहरण के लिए, ADD EAX, 2 निर्देश के बजाय, आप इस तरह का कचरा ब्लॉक (पहली बात जो मन में आई) लिख सकते हैं: asm inc eax
इस तरह के कचरा ब्लॉक को मशीन पर स्क्रिप्ट के साथ हटाया नहीं जा सकता है, जो रजिस्टरों में परिवर्तन की अनुपस्थिति का पता लगाता है, क्योंकि वास्तविकता में प्रत्येक निर्देश उनके अर्थ को बदल देगा।यदि इस तरह के ब्लॉक के ऊपर पफ करना अच्छा है और बड़े आकार के लिए फुलाया जाता है, तो कचरे की शुरुआत और अंत का निर्धारण करना काफी समस्याग्रस्त होगा। इसके अलावा, वहाँ हमेशा एक स्टैक उपलब्ध होता है, इस पर रजिस्टरों के मूल्य को रखते हुए, कचरा के रूप में हो सकता है, हालांकि फ़र्मेट के प्रमेय को अभी तक परेशान नहीं किया जा सकता है, जिसके बाद बस रजिस्टरों के मूल्यों को पुनर्स्थापित करें और कार्यक्रम जारी रखें :)4. वर्चुअल मशीन - असुरक्षित हैंडलर (वीएम इंस्ट्रक्शन हैंडलर) के कारण, इसके एनालॉग को लिखने में बहुत समय नहीं लगा। एक अच्छे युद्धक अनुप्रयोग के लिए, वीएम हैंडलर को वीएम के तर्क को समझने में मुश्किल बनाने के लिए ठीक से बाधित होना चाहिए। यह स्पष्ट है कि कीगेनमे के लिए यह कदम बहुत अच्छा है, लेकिन इस तरह की सूक्ष्मता के बारे में मत भूलना।5. पी-कोड का तर्क - स्पष्ट रूप से कचरा निर्देशों की उपस्थिति के बावजूद, लॉगिन / एसएन के साथ बफर से डेटा प्राप्त करने के लिए मुख्य एल्गोरिथ्म सभी चरणों में समान था, जिसने टेम्पलेट के आधार पर सभी तर्क को जल्दी से पार्स करना संभव बना दिया। यदि डेटा प्राप्त करने के कई विकल्प पेश किए गए (या, बेहतर, प्रत्येक चरित्र प्राप्त करने के लिए एक अनूठा विकल्प), तो यह विश्लेषण को बहुत जटिल करेगा।6. यदि वी-कोड (उदाहरण के लिए, उसी ब्रेनफक के आधार पर) के अंदर एक और आभासी मशीन थी, तो वीएम विश्लेषण बहुत अधिक जटिल होगा, जो अपने स्वयं के पी-कोड की व्याख्या करेगा।परिणाम क्या है:आभासी मशीन के साथ पूरी तरह से काम दिखा रहा है, बहुत ही उच्च गुणवत्ता वाले keygenme। केवल एक ही बारीकियाँ जिसमें वह जवाब नहीं देता है कि वर्चुअल मशीन के लिए पी-कोड कैसे प्राप्त किया गया है जिसे वह निष्पादित करता है :)यह चरण प्रत्येक डेवलपर के लिए अलग-अलग है, प्रत्येक अपने स्वयं के तरीकों का उपयोग करता है, हालांकि, मेरे पास पिकनिक पीढ़ी एल्गोरिदम के कार्यान्वयन पर एक लेख है। , VM के थोड़े अलग प्रकार के लिए सच है।अपने हिस्से के लिए, मैंने एक टूलकिट के रूप में वीएम निष्पादन ग्राफ का उपयोग करके, एएसएम लिस्टिंग को हटाने के चरण के बिना, ऐसे वीएम को हैक करने के विकल्पों में से एक दिखाया।मुझे लगता है कि यह आपके वीएम कार्यान्वयन का विश्लेषण करने में मदद करेगा, एक समान हैकिंग विकल्प के लिए उनके प्रतिरोध के लिए।खैर, अगर आप वीएम के वास्तविक कार्यान्वयन के लिए नहीं आए हैं, तो कम से कम अब आपके पास न्यूनतम विचार है कि यह कैसे काम कर सकता है।लेख के लिए डेमो उदाहरणों का स्रोत कोड इस लिंक से लिया जा सकता है: http://rouse.drkb.ru/blog/vm_analize.zipनिष्कर्ष और शुभकामनाएं ड्रा करें।