यह आलेख
Dota 2 गेम के संबंध में x64 के लिए DirectX 9 के उदाहरण का उपयोग करके चित्रमय एपीआई फ़ंक्शन के अवरोधन पर चर्चा करता है।
यह विस्तार से वर्णित किया जाएगा कि खेल प्रक्रिया में घुसपैठ कैसे करें, निष्पादन के प्रवाह को कैसे बदलें, कार्यान्वित तर्क का एक संक्षिप्त विवरण दिया गया है। अंत में, हम इंजन प्रदान करने वाली अन्य सुविधाओं के बारे में बात करेंगे।

अस्वीकरण: लेखक इस लेख में प्राप्त ज्ञान के आपके उपयोग या उनके उपयोग के परिणामस्वरूप होने वाले नुकसान के लिए ज़िम्मेदार नहीं है। यहां प्रस्तुत सभी जानकारी केवल शैक्षिक उद्देश्यों के लिए है। खासतौर पर MOBA विकसित करने वाली कंपनियों के लिए, ताकि वे सिनेमाघरों से निपटने में मदद कर सकें। और, ज़ाहिर है, लेख के लेखक एक बॉट स्विचर, एक चीटर है, और वह हमेशा से रहा है।
अंतिम वाक्य समझाने लायक है - मैं निष्पक्ष प्रतियोगिता के लिए हूं। मैं केवल एक खेल हित के रूप में धोखा देती हूं, रिवर्स स्किल में सुधार करती हूं, एंटी-चीट के काम का अध्ययन करती हूं और केवल रेटिंग प्रतियोगिताओं के बाहर।
1. परिचय
इस लेख को एक श्रृंखला के पहले के रूप में योजनाबद्ध किया गया है और यह विचार देता है कि आप अपने स्वयं के प्रयोजनों के लिए ग्राफिकल एपीआई का उपयोग कैसे कर सकते हैं, अगले भाग को समझने के लिए आवश्यक कार्यक्षमता का वर्णन करता है। मैं स्रोत 2 में संस्थाओं की सूची के लिए एक संकेतक की खोज करने के लिए दूसरा लेख समर्पित करने की योजना बना रहा हूं (उदाहरण के रूप में Dota 2 का उपयोग करके) और
Source2Gen के साथ संयोजन के रूप में इसका उपयोग "अतिरिक्त" तर्क लिखने के लिए (कुछ
इस तरह से सबसे अधिक संभावना है कि "नक्शा हैक" की जाँच करें) उद्धरण पर ध्यान दें, जो कुछ भी दांव पर लगा है वह वीडियो में देखा जा सकता है), या पहले लेख का स्वचालन)। तीसरे लेख की योजना एक ड्राइवर लिखने के रूप में है, इसके साथ संवाद (IOCTL) है, इसका उपयोग VAC संरक्षण (इसके समान कुछ) को बायपास करने के लिए
किया जाता है ।
2. मुझे इसकी आवश्यकता क्यों पड़ी
मुझे अपने बॉट को नेत्रहीन रूप से डीबग करने के लिए ग्राफिकल एपीआई के उपयोग की आवश्यकता थी, जो मैंने डोटा 2 के लिए लिखा था (वास्तविक समय की दृश्य जानकारी बहुत सुविधाजनक है)। मैं एक स्नातक छात्र हूं और 3 डी सिर के पुनर्निर्माण में लगा हूं और छवियों और एक गहन कैमरे का उपयोग करके मॉर्फ कर रहा हूं - विषय काफी दिलचस्प है, लेकिन मेरा पसंदीदा नहीं है। चूंकि मैं पांचवें वर्ष (मास्टर कार्यक्रम के साथ शुरू) कर रहा हूं, मुझे एक बात समझ में आई- हां, मैंने इस क्षेत्र का अच्छी तरह से अध्ययन किया है, मैं आसानी से तरीकों और तरीकों के साथ लेखों का अध्ययन करता हूं और उन्हें लागू करता हूं। लेकिन यह सब, मैं स्वयं केवल अगले सीखे हुए एल्गोरिथ्म का अनुकूलन कर सकता हूं, पहले से अध्ययन किए गए और कार्यान्वित किए गए लोगों के साथ तुलना कर सकता हूं और यह तय कर सकता हूं कि किसी विशेष कार्य में इसका उपयोग करना है या नहीं। यह अनुकूलन का अंत है, यह संभव नहीं है कि कुछ नया खुद के साथ आए, जो स्नातक विद्यालय (अध्ययन की नवीनता) के लिए बहुत महत्वपूर्ण है। मैंने सोचना शुरू किया - जबकि समय है, आप एक नया विषय पा सकते हैं। आपको पहले से ही विषय को अच्छी तरह से समझने की आवश्यकता है (वर्तमान स्तर पर) या आप इसे जल्दी से खींच सकते हैं।
उसी समय, मैंने खेल देव में काम किया, और यह शायद सबसे दिलचस्प है कि एक प्रोग्रामर (व्यक्तिगत राय) क्या कर सकता है और एआई, बॉट्स के विषय में बहुत रुचि रखता था। उस समय, दो विषय थे जिन्हें मैं अच्छी तरह से जानता था - तब मैं एक गतिशील नेविगेशन जाल (क्लाइंट-सर्वर) का निर्माण कर रहा था और एक गतिशील शूटर के नेटवर्क भाग का अध्ययन कर रहा था। डायनेमिक नेविगेटर के साथ एक विषय एकदम से फिट नहीं है - मैंने काम के घंटों के दौरान ऐसा किया था, मुझे प्रबंधन से अपने डिप्लोमा में इसका उपयोग करने की अनुमति के लिए पूछना था, इसके अलावा, नवीनता विषय खुला था - मैंने लेख के साथ मौजूदा तरीकों का अच्छी तरह से अध्ययन किया और लागू किया, लेकिन यह कोई नई बात नहीं थी। डायनेमिक शूटर के नेटवर्क भाग के साथ विषय (मैंने इसे आभासी वास्तविकता में बातचीत के लिए उपयोग करने की योजना बनाई) फिर से इस तथ्य के बारे में दोनों को तोड़ दिया कि मैं इसे काम के घंटों के दौरान कर रहा था, और नवीनता के बारे में, आप Pixonic से
लेखों की एक
श्रृंखला पढ़ सकते हैं, जहां लेखक खुद कहता है कि विषय यह दिलचस्प है, केवल 30 साल पहले दृष्टिकोण का आविष्कार किया गया था और बहुत कुछ बदल नहीं गया था।
इस समय के दौरान, OpenAI ने अपने बॉट को जारी किया। यह निश्चित रूप से
5 बाई 5 नहीं है , लेकिन यह बहुत बढ़िया था! मैं एक बॉट बनाने की कोशिश करने के लिए विचारों को बाहर नहीं फेंक सका और सबसे पहले मैंने यह सोचना शुरू कर दिया कि इसे एक शोध प्रबंध के रूप में कैसे उपयोग किया जाए, नवीनता के बारे में और इसे एक नेता को कैसे प्रस्तुत किया जाए। इस संबंध में नवीनता के साथ, सबकुछ बहुत बेहतर था - निश्चित रूप से दो पिछले विषयों के लिए कुछ के साथ आना संभव था, लेकिन जाहिर तौर पर बॉट ने मुझे विचारों को जकड़ना, विकसित करना और विचारों को अधिक मजबूत बनाना बताया। इसलिए, मैंने 1-ऑन -1 बॉट बनाने का फैसला किया (मिडए पर एक लड़ाई, जैसे ओपनएआई), इसे नेता को प्रस्तुत करें, बताएं कि यह कितना शांत है, कितने अलग-अलग दृष्टिकोण, गणित और सबसे महत्वपूर्ण बात, नया।
पहले चरण में बॉट की सबसे ज़रूरी चीज़ यह है कि यह उस वातावरण का ज्ञान है - जिसमें मैं खेल की याददाश्त से दुनिया की स्थिति लेना चाहता हूं और पहला चरण एंटिटी लिस्ट को इंगित करने और प्रार्थना डॉग 2 के दिमाग के साथ एकीकरण के लिए एक खोजकर्ता के लिए बिताया है। Source2Gen - यह बात Source2 इंजन की संरचना को उत्पन्न करती है, जिसे वह सर्किट से लेता है। योजनाओं के उद्भव के लिए मुख्य विचार और पूर्वापेक्षा ग्राहक और सर्वर के बीच राज्य की प्रतिकृति है, लेकिन जाहिर तौर पर डेवलपर्स को यह विचार पसंद आया और उन्होंने इसे अधिक व्यापक रूप से वितरित किया, मैं आपको
यहां पढ़ने की सलाह देता हूं।
मुझे रिवर्स इंजीनियरिंग का अनुभव था: मैंने साइलेंट स्टॉर्म के लिए
चीट बनाया, मुख्य जनरेटर बनाया (सबसे दिलचस्प ब्लैक एंड व्हाइट के लिए था) -
केजेन को
DrMefistO से यहां क्या पढ़ा जा सकता
है ,
काबल ऑनलाइन में
कॉम्बो निष्पादन (सब कुछ इस तथ्य से जटिल था कि यह गेम गेम गार्ड द्वारा संरक्षित था। (यह कर्नेल मोड में ड्राइवर के तहत) ring0 से पहरा दिया, इस प्रक्रिया को छिपाते हुए (जो कम से कम इसे घुसपैठ करना आसान नहीं बनाता है) - अधिक विवरण
यहां पाया जा सकता
है )।
तदनुसार, इस क्षेत्र में मेरा विकास हुआ, बॉट को नियोजित समय के लिए पर्यावरण तक पहुंच मिली। यह आश्चर्यजनक है कि बंकर सर्वर ग्राहक को डेल्टा के माध्यम से कितनी जानकारी देता है, उदाहरण के लिए, क्लाइंट के पास किसी भी टेलीपोर्टर्स, स्वास्थ्य और एजेंटों के बीच इसके परिवर्तनों की जानकारी है (रोशन को छोड़कर, वह दोहराता नहीं है) - यह सब युद्ध के कोहरे में है। हालाँकि मुझे कुछ कठिनाइयों का सामना करना पड़ा, यह मैं अगले लेख में बात करने जा रहा हूँ।
यदि आपके पास एक प्रश्न है कि मैंने
डोटा बॉट स्क्रिप्टिंग का उपयोग क्यों नहीं किया है, तो मैं प्रलेखन से एक अंश के साथ उत्तर दूंगा:
API प्रतिबंधित है जैसे स्क्रिप्ट धोखा नहीं दे सकती - FoW में यूनिट्स को क्वेर नहीं किया जा सकता, स्क्रिप्ट्स को नियंत्रित नहीं करने वाली इकाइयों को कमांड्स जारी नहीं किए जा सकते, आदि।
लेखों की यह श्रृंखला उन शुरुआती लोगों के लिए लक्षित है जो रिवर्स इंजीनियरिंग के विषय में रुचि रखते हैं।
3. मैं इस बारे में क्यों लिख रहा हूं
नतीजतन, मुझे मिलीलीटर से बॉट के कार्यान्वयन में बहुत सारी समस्याओं का सामना करना पड़ा, जो कि मुझे यह समझने में पर्याप्त समय लगा कि प्रशिक्षण के अंत से दो साल पहले मैं वर्तमान विषय में अपने ज्ञान और अनुभव को पार नहीं कर सका। Dota 2 में, मैं Dota Auto Chess रिवाज के रिलीज होने से नहीं खेलता, मैं अब अपना खाली समय एपेक्स लेजेंड के डिप्लोमा और रिवर्स पर बिताता हूं (जिसकी संरचना Dota 2 के समान है, जैसा कि मुझे लगता है)। तदनुसार, किए गए कार्य से एकमात्र लाभ इस विषय पर एक तकनीकी लेख का प्रकाशन है।
4. डोटा 2
मैं इन सिद्धांतों को एक वास्तविक गेम - डोटा 2 पर दिखाने की योजना बना रहा हूं। खेल
एंटी- चीट
वाल्व एंटी चीट का उपयोग करता है। मुझे वास्तव में एक कंपनी के रूप में वाल्व पसंद है: बहुत ही शांत उत्पाद, निर्देशक, खिलाड़ियों के लिए रवैया, स्टीम, स्रोत इंजन 2, ... वीएसी। वीएसी उपयोगकर्ता-मोड (रिंग 3) से काम करता है, यह सब कुछ स्कैन नहीं करता है और अन्य एंटी-चीट (जो कि एसिया करता है (विशेषकर उनके एंटी-चीट से) की तुलना में हानिरहित है, इस प्लेटफॉर्म का उपयोग करने की सभी इच्छाएं गायब हो जाती हैं)। मुझे यकीन है कि वीएसी अपने कार्य को इस तरह से करता है - यह कर्नेल मोड से मॉनिटर नहीं करता है, यह हार्डवेयर (केवल एक खाता) पर प्रतिबंध नहीं लगाता है, यह स्क्रीनशॉट में वॉटरमार्क सम्मिलित नहीं करता है - खिलाड़ियों के लिए वाल्व के रवैये के लिए, वे आपके लिए एक पूर्ण एंटीवायरस स्थापित नहीं करते हैं, जैसा कि वे करते हैं। गेम गार्ड, बैटलएई, वार्डन और अन्य, क्योंकि यह सभी हैक हो गया है और प्रोसेसर संसाधनों को खर्च करता है जो गेम ले सकता है (भले ही यह समय-समय पर किया जाता है), झूठी सकारात्मक हैं (विशेषकर लैपटॉप पर खिलाड़ियों के लिए)। वहाँ एक दीवार हैक, aimbot, गति हैक, PUBG, सर्वोच्च, Fortnite में ESP नहीं है?
वास्तव में Dota 2. गेम
40Hz (25 ms) की आवृत्ति पर चलता है, क्लाइंट गेम स्थिति को प्रक्षेपित करता है, इनपुट भविष्यवाणी का उपयोग नहीं किया जाता है - यदि आपके पास एक अंतराल है, तो एक गेम - यह महत्वपूर्ण नहीं है कि गेम भी नहीं है, नियंत्रित इकाइयां - पूरी तरह से फ्रीज हो गई हैं। गेम मैकेनिक्स सर्वर RUDP (विश्वसनीय यूडीपी) के माध्यम से क्लाइंट के साथ एन्क्रिप्टेड संदेशों का आदान-प्रदान करता है, क्लाइंट मूल रूप से इनपुट भेजता है (यदि आप लॉबी होस्ट करते हैं, कमांड भेजे जा सकते हैं), सर्वर गेम की दुनिया और टीमों की प्रतिकृति भेजता है। नेविगेशन को 3D ग्रिड पर किया जाता है, प्रत्येक सेल का अपना प्रकार का धैर्य होता है। नेविगेशन और फिजिक्स का उपयोग करके आंदोलन किया जाता है (एक प्रकार के बरतन, कोगी क्लोवेर्का, आदि की फिसलन से गुजरने की असंभवता)।
सभी संस्थाओं के साथ दुनिया की स्थिति एन्क्रिप्शन के बिना अपने शुद्धतम रूप में स्मृति में है - आप चीट इंजन का उपयोग करके खेल की मेमोरी का अध्ययन कर सकते हैं। स्ट्रिंग और कोड पर लागू नहीं होता है।
DirectX9, DirectX11, Vulkan, OpenGL ग्राफिकल एपीआई से उपलब्ध हैं। 5. समस्या का विवरण
डोटा 2 गेम में एक तटस्थ "प्राचीन" है, जिसमें से हत्या एक अच्छा इनाम देती है: अनुभव, सोना, कौशल और वस्तुओं के कोल्डॉब्स को रोल करने की क्षमता, एजिस (दूसरा जीवन), उसका नाम रोशन है। एजिस मूल रूप से खेल को चारों ओर मोड़ सकता है या मजबूत पक्ष को और भी अधिक लाभ दे सकता है, क्रमशः, खिलाड़ी एक साथ पाने और उस पर हमला करने, या उसकी सुरक्षा के लिए पास होने की योजना बनाने के लिए उसकी मृत्यु के समय को याद करने / रिकॉर्ड करने का प्रयास करते हैं। सभी दस खिलाड़ियों को रोशन की मौत के बारे में सूचित किया जाता है, भले ही वह युद्ध के कोहरे में छिपा हो। रिस्पॉन्स टाइम में आठ मिनट अनिवार्य है, जिसके बाद रोशन तीन मिनट के अंतराल में बेतरतीब ढंग से दिखाई दे सकता है।
कार्य इस प्रकार है : रोशन की वर्तमान स्थिति (जीवित-जीवंत, ressurect_base-revives आधार समय, ressurect_extra-revives अतिरिक्त समय) के बारे में जानकारी प्रदान करने के लिए खिलाड़ी को प्रदान करना।
चित्रा 1 - संक्रमण के दौरान राज्यों और कार्यों के बीच संक्रमण के लिए शर्तेंजिन स्थितियों में रोशन की मृत्यु हुई है, उन स्थितियों के लिए इस राज्य में रहने का अंतिम समय प्रदर्शित करें। जीवित अवस्था से ressurect_base में परिवर्तन खिलाड़ी को मैनुअल मोड में बटन द्वारा किया जाना चाहिए। Ressurect_extra राज्य में रोशन का पता लगाने / मृत्यु के मामले में (उदाहरण के लिए, एक दुश्मन टीम चुपके से मांद में घुस गई और उसे मार डाला), जीवित / ressurect_base राज्य में संक्रमण को मैन्युअल रूप से बटन का उपयोग करके भी किया जाता है। रोशन की स्थिति (और पुनरुत्थान की स्थिति में होने का अंतिम समय) को पाठ रूप में दिखाया जाना चाहिए, आवश्यक इनपुट (ressurect_extra के राज्य की हत्या और रुकावट) एक बटन के साथ प्रदान किया जाना चाहिए।
चित्रा 2 - इंटरफ़ेस तत्व - लेबल, बटन और कैनवासयह एकमात्र ऐसा कार्य है जो मैं कर सकता था ताकि मुझे खेल की स्मृति के साथ काम करने की आवश्यकता न हो और खिलाड़ी के लिए कम से कम कुछ मूल्य हो - यहां तक कि किसी भी प्राथमिक विशेषताओं, जैसे स्वास्थ्य, मन और संस्थाओं के पदों को प्राप्त करने के लिए, आपको उन्हें पहले से खोजने की जरूरत है। चीट इंजन खेल की मेमोरी में मदद करता है, जिसे अतिरिक्त रूप से और लंबे समय तक या सोर्स 2 गेन की मदद से समझाया जाना चाहिए, जिसकी चर्चा अगले लेख में की जाएगी। समस्या का बयान खिलाड़ी को रोशन का अनुसरण करने के लिए मजबूर करता है, जिससे उसके लिए बहुत सारी कार्रवाइयां होती हैं, जो कि असुविधाजनक है - लेकिन दूसरे भाग पर भरोसा करने के लिए कुछ करना होगा।
हम अपने इंजेक्शन इंजेक्ट लिखेंगे। जिसमें MVC- आधारित व्यावसायिक तर्क होगा और इसे Dota 2 प्रक्रिया में लागू किया जाएगा। Dll हमारी रेशम_वे.लिब लाइब्रेरी का उपयोग करेगा, जिसमें निष्पादन प्रवाह, लकड़हारा, मेमोरी स्कैनर और डेटा संरचनाओं को बदलने के लिए ट्रैप लॉजिक होगा। ।
6. इंजेक्टर
रिक्त C ++ प्रोजेक्ट बनाएँ, NativeInjector को कॉल करें। मुख्य कोड इंजेक्ट फ़ंक्शन में है।
void Inject(string & dllPath, string & processName) { DWORD processId = GetProcessIdentificator(processName); if (processId == NULL) throw invalid_argument("Process dont existed"); HANDLE hProcess = OpenProcess(PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE, FALSE, processId); HMODULE hModule = GetModuleHandle("kernel32.dll"); FARPROC address = GetProcAddress(hModule, "LoadLibraryA"); int payloadSize = sizeof(char) * dllPath.length() + 1; LPVOID allocAddress = VirtualAllocEx( hProcess, NULL, payloadSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); SIZE_T written; bool writeResult = WriteProcessMemory(hProcess, allocAddress, dllPath.c_str(), payloadSize, & written); DWORD treadId; CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE) address, allocAddress, 0, & treadId); CloseHandle(hProcess); }
फ़ंक्शन को पथ और प्रक्रिया का नाम मिलता है, GetProcessIdentificator का उपयोग करके प्रक्रिया के नाम से इसकी आईडी की खोज करता है।
फ़ंक्शन GetProcessIdentificator DWORD GetProcessIdentificator(string & processName) { PROCESSENTRY32 processEntry; processEntry.dwSize = sizeof(PROCESSENTRY32); HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL); DWORD processId = NULL; if (Process32First(snapshot, & processEntry)) { while (Process32Next(snapshot, & processEntry)) { if (!_stricmp(processEntry.szExeFile, processName.c_str())) { processId = processEntry.th32ProcessID; break; } } } CloseHandle(snapshot); return processId; }
संक्षेप में, GetProcessIdentificator सभी चल रही प्रक्रियाओं से गुजरता है और उचित नाम के साथ एक प्रक्रिया की तलाश करता है।
चित्रा 3 - प्रक्रिया की प्रारंभिक स्थितिअगला, एक दूरस्थ स्ट्रीम बनाकर पुस्तकालय का प्रत्यक्ष कार्यान्वयन।
इंजेक्शन समारोह की विस्तृत व्याख्यामिली आईडी के आधार पर, थ्रेड बनाने के लिए अधिकारों के साथ ओपनप्रोसेस फ़ंक्शन का उपयोग करके प्रक्रिया को खोला जाता है, प्रक्रिया की जानकारी प्राप्त करता है, लिखने और क्षमताओं को पढ़ता है। GetModuleHandle फ़ंक्शन कर्नेल 32 लाइब्रेरी मॉड्यूल को पुनः प्राप्त करता है, यह GetProcAddress फ़ंक्शन द्वारा इसमें लोड किए गए LoadLibraryA फ़ंक्शन का पता प्राप्त करने के लिए किया जाता है। LoadLibrary का उद्देश्य हमारे इंजेक्टेड .ll को निर्दिष्ट प्रक्रिया में लोड करना है। यही है, हमें हमें (“Dota2.exe”) को ब्याज की प्रक्रिया से लोडल्वेट्स कॉल करने की आवश्यकता है, इसके लिए हम CreateRemoteThread का उपयोग करके एक नया थ्रेड दूरस्थ रूप से बनाते हैं। उस फ़ंक्शन के लिए एक पॉइंटर के रूप में जिसमें से नया धागा शुरू होता है, हम LoadLibraryA फ़ंक्शन के पते को पास करते हैं। यदि आप LoadLibraryA फ़ंक्शन के हस्ताक्षर को देखते हैं, तो उसे तर्क के रूप में लोड की गई लाइब्रेरी के लिए पथ की आवश्यकता होती है - HMODULE LoadLibraryA (LPCSTR lpLibFileName)। हम इस तर्क को निम्नानुसार देते हैं: CreateRemoteThread पैरामीटर में प्रारंभ फ़ंक्शन का पता इसके पैरामीटर को इंगित करने के बाद, हम WriteProcessMemory फ़ंक्शन (VirtualAllocExEx का उपयोग करके मेमोरी आवंटित करने के बाद) प्रक्रिया मेमोरी का मान लिखकर lpLibFileName के लिए एक पॉइंटर बनाते हैं।
चित्रा 4 - एक दूरस्थ धारा बनानाCloseHandle फ़ंक्शन के साथ अंत में प्रक्रिया हैंडलर को बंद करना सुनिश्चित करें, आप आवंटित मेमोरी को भी मुक्त कर सकते हैं। हमारा इंजेक्टर तैयार है और हमारे लिए इंतज़ार कर रहा है कि हम सिल्क लॉवे.लिब लाइब्रेरी के साथ इंजेक्टेड हैं।
चित्र 5 - पुस्तकालय के कार्यान्वयन को पूरा करनासिद्धांत की बेहतर समझ के लिए, आप
वीडियो देख सकते हैं। अंत में, मैं कहूंगा कि यह प्रक्रिया के
मुख्य सूत्र में कोड के प्रत्यक्ष कार्यान्वयन के साथ सुरक्षित दृष्टिकोण है।
7. रेशम मार्ग
चलो रेशम_वे.लिब को लागू करना शुरू करते हैं, एक स्थिर पुस्तकालय जिसमें डेटा संरचनाएं, एक लकड़हारा, एक मेमोरी स्कैनर और जाल होते हैं। वास्तव में, मैंने अपने काम का एक छोटा सा हिस्सा लिया, कुछ ऐसा जिसे सबसे सरल तरीके से समझाया जा सकता है, जो कि बाकी के लिए बहुत अधिक नहीं है, लेकिन एक ही समय में समस्या को हल करता है।
7.1। डेटा संरचनाएं।
डेटा संरचनाओं के बारे में संक्षेप में: वेक्टर - क्लासिक सूची, प्रविष्टि और विलोपन समय ओ (एन), खोज ओ (एन), मेमोरी ओ (एन); कतार - एक परिपत्र कतार, ओ (1) के सम्मिलन और विलोपन का समय, कोई खोज नहीं, मेमोरी ओ (एन); RBTree - लाल-काला पेड़, सम्मिलन और विलोपन समय O (logN), खोज O (logN), मेमोरी O (N)। मैं C # और पायथन में शब्दकोशों को लागू करने के लिए उपयोग किए जाने वाले हैश को पसंद करता हूं, मानक C ++ लाइब्रेरी का उपयोग करने वाले लाल-काले पेड़। इसका कारण यह है कि एक हैश एक पेड़ की तुलना में अधिक सही तरीके से लागू करना अधिक कठिन है (लगभग हर आधे साल में मुझे लगता है और हैश की किस्मों की कोशिश करें), और आमतौर पर एक हैश अधिक मेमोरी लेता है (हालांकि यह तेजी से काम करता है)। इन संरचनाओं का उपयोग व्यावसायिक तर्क और जाल में संग्रह बनाने के लिए किया जाता है।
मैं मानक पुस्तकालय से संरचनाओं का उपयोग न करने और उन्हें स्वयं लागू करने का प्रयास करता हूं, विशेष रूप से यह हमारे मामले में महत्वपूर्ण नहीं है, लेकिन यह महत्वपूर्ण है कि यदि आपका डीएल डीबग किया जाता है या असेंबली स्पष्ट है (यह वाणिज्यिक धोखा देती है, जिसकी निंदा अधिक है) )। मैं आपको सभी संरचनाओं को स्वयं लिखने की सलाह देता हूं, इससे आपको अधिक अवसर मिलते हैं।
एक उदाहरण के रूप में, यदि आप एक गेम बनाते हैं और यह नहीं चाहते हैं कि "स्कूली बच्चे" इसे चीट इंजन का उपयोग करके स्कैन करें, तो आप प्राइमरी प्रकार के लिए रैपर बना सकते हैं और मेमोरी में
एन्क्रिप्टेड वैल्यू स्टोर कर सकते हैं। वास्तव में, यह एक मोक्ष नहीं है, लेकिन यह उन लोगों में से कुछ को मात दे सकता है जो खेल की स्मृति को पढ़ने और बदलने की कोशिश कर रहे हैं।
7.2। लकड़हारा
कंसोल के लिए आउटपुट कार्यान्वित किया गया और फ़ाइल में लिखें। इंटरफेस:
class ILogger { protected: ILogger(const char * _path) { path = path; } public: virtual ~ILogger() {} virtual void Log(const char * format, ...) = 0; protected: const char * path; };
एक फ़ाइल के लिए उत्पादन के लिए कार्यान्वयन:
class MemoryLogger: public ILogger { public: MemoryLogger(const char * _path): ILogger(_path) { fopen_s( & fptr, _path, "w+"); } ~MemoryLogger() { fclose(fptr); } void Log(const char * format, ...) { char log[MAX_LOG_SIZE]; log[MAX_LOG_SIZE - 1] = 0; va_list args; va_start(args, format); vsprintf_s(log, MAX_LOG_SIZE, format, args); va_end(args); fprintf(fptr, log); } protected: FILE * fptr; };
कंसोल के आउटपुट के लिए कार्यान्वयन समान है। यदि हम लॉगिंग का उपयोग करना चाहते हैं, तो हमें ILogger * इंटरफ़ेस को परिभाषित करने की आवश्यकता है, आवश्यक लकड़हारा घोषित करें, उदाहरण के लिए, आवश्यक फ़ंक्शन के साथ लॉग फ़ंक्शन को कॉल करें:
ILogger* logger = new MemoryLogger(filename); logger->Log("(%llu)%s: %d\n", GetCurrentThreadId(), "EnumerateThread result", result);
7.3। स्कैनर
स्कैनर इस तथ्य में लगा हुआ है कि यह हस्तांतरित पॉइंटर द्वारा इंगित मेमोरी मूल्य को प्रदर्शित करता है और इसकी तुलना मेमोरी में नमूने से करता है। पैटर्न के साथ कार्यात्मक तुलना पर बाद में विचार किया जाएगा।
इंटरफेस:
class IScanner { protected: IScanner() {} public: virtual ~IScanner() {} virtual void PrintMemory(const char * title, unsigned char * memPointer, int size) = 0; };
हैडर फ़ाइल कार्यान्वयन:
class FileScanner : public IScanner { public: FileScanner(const char* _path) : IScanner() { fopen_s(&fptr, _path, "w+"); } ~FileScanner() { fclose(fptr); } void PrintMemory(const char* title, unsigned char* memPointer, int size); protected: FILE* fptr; };
स्रोत फ़ाइल कार्यान्वयन:
void FileScanner::PrintMemory(const char* title, unsigned char* memPointer, int size) { fprintf(fptr, "%s:\n", title); for (int i = 0; i < size; i++) fprintf(fptr, "%x ", (int)(*(memPointer + i))); fprintf(fptr, "\n", title); }
इसका उपयोग करने के लिए, आपको IScanner * इंटरफ़ेस को परिभाषित करने की आवश्यकता है, वांछित स्कैनर की घोषणा करें और PrintMemory फ़ंक्शन को कॉल करें, जहां आप उदाहरण के लिए शीर्षक, सूचक और लंबाई सेट कर सकते हैं:
IScanner* scan = new ConsoleScanner(); scan->PrintMemory("source orig", (unsigned char*)source, 30);
7.4। जाल
रेशम का सबसे दिलचस्प हिस्सा_वे.लिब लाइब्रेरी। कार्यक्रम निष्पादन के प्रवाह को बदलने के लिए हुक का उपयोग किया जाता है। सैंडबॉक्स नामक एक निष्पादन योग्य परियोजना बनाएं।
जाल के संचालन की जांच के लिए डिवाइस क्लास हमारी डमी होगी। class Unknown { protected: Unknown() {} public: ~Unknown() {} virtual HRESULT QueryInterface() = 0; virtual ULONG AddRef(void) = 0; virtual ULONG Release(void) = 0; }; class Device : public Unknown { public: Device() : Unknown() {} ~Device() {} virtual HRESULT QueryInterface() { return 0; } virtual ULONG AddRef(void) { return 0; } virtual ULONG Release(void) { return 0; } virtual int Present() { cout << "Present()" << " " << i << endl; return i; } virtual void EndScene(int j) { cout << "EndScene()" << " " << i << " " << j << endl; } void Dispose() { cout << "Dispose()" << " " << i << endl; } public: int i; };
डिवाइस वर्ग IUnogn इंटरफ़ेस से विरासत में मिला है, हमारा कार्य डिवाइस के किसी भी उदाहरण के वर्तमान और एंडस्कीन कार्यों की कॉल को इंटरसेप्ट करना है, और रिसीवर में मूल कार्यों को कॉल करना है। हमें पता नहीं है कि कोड में ये फ़ंक्शन कहाँ और क्यों कहा जाता है, किस थ्रेड में है।
वर्तमान और एंडस्किन कार्यों को देखते हुए, हम देखते हैं कि वे आभासी हैं। अभिभावक वर्ग के व्यवहार को ओवरराइड करने के लिए आभासी कार्यों की आवश्यकता होती है। वर्चुअल फ़ंक्शंस, साथ ही गैर-आभासी वाले, एक मेमोरी के लिए एक पॉइंटर हैं जिसमें ओपकोड और तर्क मान लिखे गए हैं। चूंकि वर्चुअल फ़ंक्शंस वारिस और माता-पिता के बीच भिन्न होते हैं, उनके पास अलग-अलग पॉइंटर्स होते हैं (ये पूरी तरह से अलग-अलग फ़ंक्शन होते हैं) और वर्चुअल मेथड टेबल (वीएमटी) में संग्रहीत होते हैं। यह तालिका मेमोरी में संग्रहीत है और क्लास पॉइंटर के लिए एक पॉइंटर है, हम इसे डिवाइस के लिए पाते हैं:
Device* device = new Device(); unsigned long long vmt = **(unsigned long long**)&device;
VMT वर्चुअल फ़ंक्शंस की ओर संकेत करता है, अगर हम डिवाइस से इनहेरिट करना चाहते हैं, तो वारिस में अपना VMT होगा। VMT स्टोर पॉइंटर्स को क्रमिक रूप से पॉइंटर के आकार के बराबर एक कदम के साथ स्टोर करता है (x86 के लिए यह 4 बाइट्स है, x64 के लिए यह 8 है), उस क्रम के अनुरूप जिसमें फ़ंक्शन को क्लास में परिभाषित किया गया है। वर्तमान और EndScene फ़ंक्शन के लिए पॉइंटर्स खोजें, जो तीसरे और चौथे स्थान पर स्थित हैं:
typedef int (*pPresent)(Device*); typedef void (*pEndScene)(Device*, int j); pPresent ptrPresent = nullptr; pEndScene ptrEndScene = nullptr; int main() {
यह भी महत्वपूर्ण है कि कक्षा विधि के लिए सूचक को वर्ग उदाहरण के संदर्भ के रूप में पहला तर्क होना चाहिए। C ++, C # में, यह हमसे छिपा हुआ है, और कंपाइलर इसके बारे में जानता है - पायथन में स्वयं को कक्षा पद्धति में पहले पैरामीटर द्वारा स्पष्ट रूप से इंगित किया गया है।
यहां बुलाए जाने वाले सम्मेलन के बारे में, आपको इस कॉल को देखने की जरूरत है।
निर्देश पर विचार करें e9 ff 3a fd ff - यहाँ e9 एक ओपकोड (JMP mnemonics के साथ) है जो प्रोसेसर को निर्देश को निर्देश को बदलने के लिए कहता है (EIP for x86, RIP for x64), वर्तमान पते से FFFD3AFF (4294785791) पर जाएँ। यह भी ध्यान देने योग्य है कि मेमोरी संख्या में "इसके विपरीत" संग्रहीत हैं। फ़ंक्शंस में एक प्रस्तावना और उपसंहार होता है और इन्हें .code अनुभाग में संग्रहीत किया जाता है। आइए देखें कि स्कैनर के उपयोग से सूचक को वर्तमान फ़ंक्शन में क्या संग्रहीत किया जाता है:
IScanner* scan = new ConsoleScanner(); scan->PrintMemory("Present", (unsigned char*)ptrPresent, 30);
कंसोल में हम देखते हैं:
Present: 48 89 4c 24 8 48 83 ec 28 48 8d 15 40 4a 0 0 48 8b d 71 47 0 0 e8 64 10 0 0 48 8d
इन कोडों के सेट को समझने के लिए, आप
तालिका को देख सकते हैं, या उपलब्ध डिस्सेम्बलर्स का उपयोग कर सकते हैं। हम एक रेडीमेड
डिस्सेम्बलर -
hde (हैकर
डिस्सेम्बलर इंजन) लेंगे। आप तुलना के लिए
डिस्टॉर्म और
कैपस्टोन को भी देख सकते हैं। किसी भी असहमति के लिए एक फ़ंक्शन के लिए एक सूचक को पास करें और यह कहेंगे कि यह क्या opcodes का उपयोग करता है, तर्कों के मान, और इसी तरह।
7.4.1 ओपकोड हुक
अब हम सीधे जाल में जाने के लिए तैयार हैं। हम ओपकोड हुक और हार्डवेयर ब्रेकपॉइंट को देखेंगे। सबसे
आम जाल जो मैं लागू करने और खोज करने की सलाह देता हूं।
संभवतः सबसे अधिक इस्तेमाल किया जाने वाला और सरल जाल ओपकोड हुक है (लेख लिस्टिंग ट्रैप्स में इसे बाइट पैचिंग कहा जाता है) - ध्यान दें कि इसका आसानी से एंटी-चीट द्वारा पहचाना जाता है जब इसका दुरुपयोग किया जाता है (बिना यह समझे कि एंटी-चीट कैसे काम करता है, बिना किस क्षेत्र और मेमोरी के अनुभाग को स्कैन किए बिना। वर्तमान क्षण और अन्य चीजें प्रतिबंध स्वयं को धीमा नहीं करेंगी)। जब कुशलता से उपयोग किया जाता है, तो यह एक महान जाल है, त्वरित और समझने में आसान है।
यदि एक लेख को पढ़ते समय आप एक साथ कोड खेल रहे हैं और डिबग मोड में हैं, तो रिलीज़ पर स्विच करें - यह महत्वपूर्ण है।
तो, मुझे आपको याद दिलाना है, हमें वर्तमान और एंडस्कीन कार्यों के निष्पादन को रोकना है।
हम इंटरसेप्टर लागू करते हैं - वे कार्य जहाँ हम नियंत्रण स्थानांतरित करना चाहते हैं:
int PresentHook(Device* device) { cout << "PresentHook" << endl; return 1; } void EndSceneHook(Device* device, int j) { cout << "EndSceneHook" << " " << j << endl; }
आइए उन सार तत्वों के बारे में सोचें जिनकी हमें आवश्यकता है। हमें एक इंटरफ़ेस की आवश्यकता है जो हमें एक जाल स्थापित करने, इसे निकालने और इसके बारे में जानकारी प्रदान करने में सक्षम करेगा। जाल के बारे में जानकारी में इंटरसेप्टेड फ़ंक्शन, रिसीवर और स्प्रिंगबोर्ड फ़ंक्शंस का सूचक होना चाहिए (इस तथ्य को कि हमने किसी फ़ंक्शन को इंटरसेप्ट किया है, इसका मतलब यह नहीं है कि अब इसकी आवश्यकता नहीं है, हम भी इसका उपयोग करने में सक्षम होना चाहते हैं - एक स्प्रिंगबोर्ड मूल इंटरसेप्टेड फ़ंक्शन को कॉल करने में मदद करेगा)।
#pragma pack(push, 1) struct HookRecord { HookRecord() { reservationLen = 0; sourceReservation = new void*[RESERV_SIZE](); } ~HookRecord() { reservationLen = 0; delete[] sourceReservation; } void* source; void* destination; void* pTrampoline; int reservationLen; void* sourceReservation; }; #pragma pack(pop) class IHook { protected: IHook() {} public: virtual ~IHook() {} virtual void SetExceptionHandler( PVECTORED_EXCEPTION_HANDLER pVecExcHandler) = 0; virtual int SetHook(void* source, void* destination) = 0; virtual int UnsetHook(void* source) = 0; virtual silk_data::Vector<HookRecord*>* GetInfo() = 0; virtual HookRecord* GetRecordBySource(void* source) = 0; };
IHook इंटरफ़ेस हमें ऐसी क्षमताएं प्रदान करता है। हम चाहते हैं कि जब डिवाइस वर्ग का कोई भी उदाहरण Present and EndScene फ़ंक्शन को कॉल करे (यानी RIP पॉइंटर इन पतों पर जाता है), हमारे PresentHook और EndSceneHook फ़ंक्शन को तदनुसार निष्पादित किया जाता है।
कल्पना करें कि इंटरसेप्टेड फ़ंक्शन, रिसीवर और स्प्रिंगबोर्ड किस समय मेमोरी (.code सेक्शन) में स्थित हैं, जब नियंत्रण इंटरसेप्टेड फ़ंक्शन में प्रवेश करता है:
चित्रा 6 - स्मृति की प्रारंभिक स्थिति, निष्पादन अवरोधन समारोह में जाता हैअब हम चाहते हैं कि RIP (लाल तीर) स्रोत से गंतव्य की शुरुआत तक जाए। यह कैसे करना है? जैसा कि पहले ही ऊपर कहा गया है, स्रोत मेमोरी में एक ओपकोड होता है जो प्रोसेसर तब निष्पादित होता है जब निष्पादन स्रोत तक पहुंचता है। संक्षेप में, हमें आरआईपी पॉइंटर को पुनर्निर्देशित करने के लिए एक भाग से दूसरे भाग में कूदना होगा। जैसा कि आप अनुमान लगा सकते हैं, एक ओपकोड है जो आपको वर्तमान पते से वांछित एक को नियंत्रण स्थानांतरित करने की अनुमति देता है, यह जेएमपी mnemonics कहलाता है।
आप सीधे इच्छित पते पर कूद सकते हैं, या वर्तमान पते के सापेक्ष, ये छलांगें क्रमशः प्लेट - एफएफ और ई 9 में पाई जा सकती हैं। इन निर्देशों के लिए संरचनाएँ बनाएँ:
#pragma pack(push, 1)
रिश्तेदार कूदने का निर्देश छोटा है, लेकिन एक सीमा है - अहस्ताक्षरित int का कहना है कि आप 4,294,967,295 के भीतर कूद सकते हैं, जो x64 के लिए पर्याप्त नहीं है।
तदनुसार, गंतव्य रिसीवर का गंतव्य फ़ंक्शन पता आसानी से इस मान को पार कर सकता है और अहस्ताक्षरित इंट के बाहर हो सकता है, जो कि x64 प्रक्रिया के लिए काफी संभव है (x86 के लिए सब कुछ बहुत सरल है और आप ओपिन हुक को लागू करने के लिए अपने आप को इस बहुत ही सापेक्ष कूद तक सीमित कर सकते हैं)। एक सीधी छलांग में 14 बाइट्स लगते हैं, तुलना के लिए, एक सापेक्ष कूद केवल 5 है (हमने संरचनाओं को पैक किया है, #pragma पैक पर ध्यान दें (धक्का, 1))।
हमें इन हॉप निर्देशों में से किसी एक के लिए स्रोत पर मान को फिर से लिखना होगा।
किसी फ़ंक्शन को पकड़ने से पहले, आपको इसका अध्ययन करना चाहिए - ऐसा करने का सबसे आसान तरीका एक डिबगर की मदद से है (मैं आपको दिखाता हूं कि इसे x64dbg के साथ बाद में कैसे करना चाहिए), या एक डिस्सेम्बलर। वर्तमान के लिए, हम पहले से ही इसकी शुरुआत से 30 बाइट्स का उत्पादन करते हैं, निर्देश 48 89 4c 24 8 में 5 बाइट्स होते हैं।
चलो एक रिश्तेदार कूद लागू करें। निर्देश की लंबाई के कारण मुझे यह विकल्प अधिक पसंद है। विचार यह है: हम मूल फ़ंक्शन के पहले 5 बाइट्स को बदल देते हैं, बदले हुए बाइट्स को संरक्षित करते हैं, उन्हें निर्देश पते के सापेक्ष कूद के साथ प्रतिस्थापित करते हैं, जो अहस्ताक्षरित इंट के भीतर स्थित है।
चित्र 7 - स्रोत फ़ंक्शन के स्रोत 5 बाइट्स को सापेक्ष कूद द्वारा प्रतिस्थापित किया जाता हैजो हमें आवंटित मेमोरी (बैंगनी क्षेत्र) के लिए कूदता है, हम इस कार्रवाई के साथ गंतव्य पर नियंत्रण स्थानांतरित करने के लिए खुद को कैसे करीब लाए? हमारे द्वारा आवंटित मेमोरी में, एक सीधी छलांग है, जो आरआईपी को गंतव्य तक ले जाएगी।
चित्र 8 - आरआईपी को रिसीवर फ़ंक्शन पर स्विच करनायह पता लगाना रहता है कि पकड़े गए फ़ंक्शन को कैसे कॉल किया जाए। हमें जाम किए गए निर्देशों को निष्पादित करने और स्रोत के अछूते हिस्से से निष्पादन शुरू करने की आवश्यकता है। हम निम्नानुसार आगे बढ़ते हैं - क्षतिग्रस्त निर्देशों को ट्रैम्पोलिन की शुरुआत में सहेजें, याद रखें कि कितने बाइट्स क्षतिग्रस्त हो गए थे और "स्वस्थ" निर्देशों के लिए सीधे स्रोत + भ्रष्ट में कूद गए।
एक रिश्तेदार छलांग द्वारा मिटाए गए सहेजे गए निर्देशों का निष्पादन:
चित्र 9 - इंटरसेप्टेड फ़ंक्शन को कॉल करने के लिए स्प्रिंगबोर्ड का उपयोग करनानिर्देशों का आगे निष्पादन जो मैशिंग को प्रभावित नहीं करता है:
चित्रा 10 - इंटरसेप्टेड फ़ंक्शन के निर्देशों का निरंतर निष्पादनऊपर वर्णित विचार को लागू करने वाला कोड int OpcodeHook::SetHook(void* source, void* destination) { auto record = new HookRecord(); record->source = source; record->destination = destination; info->PushBack(record); JMP_ABS pattern = {0xFF, 0x25, 0x00000000,
समारोह की व्याख्या SetHookएक रिकॉर्ड बनाया जाता है जो जाल के बारे में जानकारी संग्रहीत करता है, जिसके बाद रिकॉर्ड संग्रह में जोड़ा जाता है। स्रोत पते की शुरुआत से निर्देश क्रॉल किए जाते हैं जब तक कि सापेक्ष कूद अनुदेश पूरी तरह से प्रवेश नहीं किया जा सकता (5 बाइट्स), जाम किए गए निर्देशों को आरक्षण में कॉपी किया जाता है, उनकी लंबाई याद की जाती है।
एक बहुत महत्वपूर्ण बिंदु यह है कि हमें स्प्रिंगबोर्ड और रिले के लिए मेमोरी आवंटित करने की आवश्यकता होती है, जिसमें हम स्रोत से गंतव्य तक धारा को पुनर्निर्देशित करने के लिए निर्देश संग्रहीत करेंगे और इस मेमोरी के लिए पता सीमा के भीतर होना चाहिए कि एक रिश्तेदार कूदने के लिए कूद सकता है (अहस्ताक्षरित) पूर्णांक)।
यह कार्यक्षमता AllocateMemory फ़ंक्शन को लागू करती है।
void* OpcodeHook::AllocateMemory(void* origin, int size) { const unsigned int MEMORY_RANGE = 0x40000000; SYSTEM_INFO sysInfo; GetSystemInfo(&sysInfo); ULONG_PTR minAddr = (ULONG_PTR)sysInfo.lpMinimumApplicationAddress; ULONG_PTR maxAddr = (ULONG_PTR)sysInfo.lpMaximumApplicationAddress; ULONG_PTR castedOrigin = (ULONG_PTR)origin; ULONG_PTR minDesired = castedOrigin - MEMORY_RANGE; if (minDesired > minAddr && minDesired < castedOrigin) minAddr = minDesired; int test = sizeof(ULONG_PTR); ULONG_PTR maxDesired = castedOrigin + MEMORY_RANGE - size; if (maxDesired < maxAddr && maxDesired > castedOrigin) maxAddr = maxDesired; DWORD granularity = sysInfo.dwAllocationGranularity; ULONG_PTR freeMemory = 0; ULONG_PTR ptr = castedOrigin; while (ptr >= minAddr) { ptr = FindPrev(ptr, minAddr, granularity, size); if (ptr == 0) break; LPVOID pAlloc = VirtualAlloc((LPVOID)ptr, size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); if (pAlloc != 0) return pAlloc; } while (ptr < maxAddr) { ptr = FindNext(ptr, maxAddr, granularity, size); if (ptr == 0) break; LPVOID pAlloc = VirtualAlloc((LPVOID)ptr, size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); if (pAlloc != 0) return pAlloc; } return NULL; }
विचार सरल है - हम स्मृति से जाएंगे, एक निश्चित पते से शुरू (हमारे मामले में, एक स्रोत के लिए सूचक) ऊपर और नीचे तब तक जब तक कि हम स्वतंत्र आकार का एक उपयुक्त टुकड़ा न मिल जाए।
वापस SetHook फ़ंक्शन के लिए। आवंटित बाइट्स को स्रोत से आवंटित मेमोरी में कॉपी करें और बिना किसी निर्देश के निष्पादन को जारी रखने के लिए तुरंत स्रोत + भ्रष्ट में सीधे कूद डालें।
अगला रिले पॉइंटर की स्थापना है, जो सीधे रिसीवर के पते पर कूदकर गंतव्य तक निष्पादन थ्रेड को पुनर्निर्देशित करने के लिए जिम्मेदार है। अंत में, हम स्रोत बदलते हैं - हम मेमोरी के स्थान पर लिखने की अनुमति देते हैं, जहां फ़ंक्शन स्थित है और रिले पते पर जाने वाले रिश्तेदार कूद के साथ पहले 5 बाइट्स को बदलें।
हम एक जाल सेट करते हैं, लेकिन इसे भी साफ करने में सक्षम होना चाहिए। ब्रेकिंग - निर्माण नहीं, विचार सरल है - हम स्रोत के जर्जर बाइट्स को वापस कर देंगे, संग्रह से जाल के बारे में रिकॉर्ड को हटा दें, और आवंटित मेमोरी को मुक्त करें:
int OpcodeHook::UnsetHook(void* source) { auto record = GetRecordBySource(source); DWORD oldProtect = 0; VirtualProtect(source, sizeof(JMP_REL), PAGE_EXECUTE_READWRITE, &oldProtect); memcpy(source, record->sourceReservation, record->reservationLen); VirtualProtect(source, sizeof(JMP_REL), oldProtect, &oldProtect); info->Erase(record); FreeMemory(record); return SUCCESS_CODE; }
कार्य का परीक्षण। तुरंत हमारे रिसीवर को बदल दें ताकि वे स्प्रिंगबोर्ड का उपयोग करके इंटरसेप्ट किए गए कार्यों को कॉल कर सकें:
int PresentHook(Device* device) { auto record = hook->GetRecordBySource(ptrPresent); pPresent pTrampoline = (pPresent)record->pTrampoline; auto result = pTrampoline(device); cout << "PresentHook" << endl; return result; } void EndSceneHook(Device* device, int j) { auto record = hook->GetRecordBySource(ptrEndScene); pEndScene pTrampoline = (pEndScene)record->pTrampoline; pTrampoline(device, 2); cout << "EndSceneHook" << " " << j << endl; }
हम परीक्षण करते हैं कि क्या हमने सब कुछ सही ढंग से किया है, क्या स्मृति बह रही है, क्या सब कुछ सही तरीके से निष्पादित किया गया है। int main() { while (true) { Device* device = new Device(); device->i = 3; unsigned long long vmt = **(unsigned long long**)&device; ptrPresent = (pPresent)(*(unsigned long long*)(vmt + 8 * 3)); ptrEndScene = (pEndScene)(*(unsigned long long*)(vmt + 8 * 4)); IScanner* scan = new ConsoleScanner(); scan->PrintMemory("Present", (unsigned char*)ptrPresent, 30); hook = new OpcodeHook(); hook->SetHook(ptrPresent, &PresentHook); hook->SetHook(ptrEndScene, &EndSceneHook); device->Present(); device->EndScene(7); device->Present(); device->EndScene(7); device->i = 5; ptrPresent(device); ptrEndScene(device, 9); hook->UnsetHook(ptrPresent); hook->UnsetHook(ptrEndScene); ptrPresent(device); ptrEndScene(device, 7); delete hook; delete device; } }
यह काम करता है।
आप अतिरिक्त रूप से x64dgb में देख सकते हैं।याद रखें, सबसे पहले मैंने आपको रिलीज़ बिल्ड में काम करने के लिए कहा था? अब डिबग पर जाएं और कार्यक्रम चलाएं। प्रोग्राम क्रैश हो जाता है ... जाल ट्रिगर हो जाता है, लेकिन स्प्रिंगबोर्ड को कॉल करने का प्रयास एक अपवाद उठाता है, जो कहता है कि जिस पते पर हम स्प्रिंगबोर्ड कहते हैं वह निष्पादन के लिए बिल्कुल नहीं है। हमें क्या याद आया? डिबग बिल्ड की समस्या क्या है? हम वर्तमान फ़ंक्शन के ओपकोड को शुरू करते हैं और देखते हैं: Present: e9 f4 36 0 0 e9 df 8d 0 0 e9 aa b0 0 0 e9 75 3e 0 0 e9 80 38 0 0 e9 da 81 0 0
जब x64dbg में चल रहा हो, तो आप निम्नलिखित देख सकते हैं। चित्र 11 - डीबग निर्माण निर्देश डीबग में, ओपकोड बदल गया है, अब संकलक सापेक्ष कूद ई 9 एफ 4 36 जोड़ता है। सभी कार्यों को मुख्य और एंट्री पॉइंट टू मेनट्रेटर्टअप सहित कूद में लपेटा जाता है। एक और ओपकोड, ठीक है, ठीक है, इसे स्प्रिंगबोर्ड में कॉपी किया जाना था, जब स्प्रिंगबोर्ड को कॉल किया गया था, तो इस रिश्तेदार कूद को बुलाया जाना चाहिए, फिर स्रोत के undamaged हिस्से के लिए एक सीधा कूद। यहाँ यह स्पष्ट हो जाता है कि हमने जो भी लागू किया है वह सब कुछ है, केवल रिश्तेदार कूदता है और रिश्तेदार एक, कि अलग-अलग पते, स्रोत और ट्रैम्पोलिन से इसका निष्पादन, आरआईपी को पूरी तरह से अलग-अलग मूल्यों से उजागर करता है।
मेरे विनम्र अनुभव में, रिश्तेदार कूद मामले के कार्यान्वयन में 99% उपयोग शामिल हैं। कई और अधिक opcodes हैं जिन्हें अलग से संभाला जाना चाहिए। याद रखें कि किसी फ़ंक्शन पर एक जाल स्थापित करने से पहले, आपको बहुत आलसी नहीं होना चाहिए और इसका अध्ययन करना चाहिए। मैं आपको परेशान नहीं करूंगा और 100 प्रतिशत संस्करण (फिर से, मेरे विनम्र अनुभव में) में कार्यक्षमता जोड़ूंगा, अगर आपको इसकी आवश्यकता है या रुचि है, तो आप देख सकते हैं कि इस तरह के पुस्तकालयों की व्यवस्था कैसे की जाती है और विशेष रूप से वे अन्य मामलों की जांच करते हैं - यह करना आसान होगा अगर आपको पता चल गया कि यह क्या है।एक रिश्तेदार कूद वास्तव में काफी सामान्य है, इसलिए मैं इसे लागू करने का प्रस्ताव करता हूं। एक रिश्तेदार कूद में ई 9 ओपकोड और मूल्य होता है जिसे आपको वर्तमान पते के सापेक्ष कूदने की आवश्यकता होती है। तदनुसार, आप बस यह जान सकते हैं कि कहां कूदना है, और सीधे कूद के साथ स्प्रिंगबोर्ड से वहीं कूदना है। यहां तक कि अगर हम एक नए रिश्तेदार से मिलते हैं, तो यह पहले से ही सही पते पर होगा।सापेक्ष कूद को ध्यान में रखते हुए जाल की स्थापना का कार्यान्वयन int OpcodeHook::SetHook(void* source, void* destination) { auto record = new HookRecord(); record->source = source; record->destination = destination; info->PushBack(record); JMP_ABS pattern = {0xFF, 0x25, 0x00000000,
यदि डिस्सेम्बलर यह जानकारी देता है कि इस कमांड का opcode e9 है, तो हम (ULONG_PTR ripPtr = (ULONG_PTR) pSource + संदर्भ.len + (INT32) संदर्भ: .imm.imm32) पर जाने के लिए पते की गणना करते हैं और स्प्रिंगबोर्ड में पता लिखते हैं प्रत्यक्ष कूद तर्क के मूल्य के रूप में।मैं यह भी ध्यान देता हूं कि एक बहु-थ्रेडेड वातावरण में, एक हुक लगाने / हटाने के समय एक स्थिति उत्पन्न हो सकती है, थ्रेड्स में से एक उस फ़ंक्शन को निष्पादित करना शुरू कर सकता है जिसे हम पकड़ते हैं - परिणामस्वरूप, प्रक्रिया गिर जाएगी। इससे निपटने का एक हिस्सा हार्डवेयर ब्रेकपॉइंट में वर्णित किया जाएगा।यदि आपको एक सिद्ध उपकरण की आवश्यकता है, तो आप यह सुनिश्चित करना चाहते हैं कि आपका जाल काम करेगा, आपके पास अपने विचार नहीं हैं और आप फ़ंक्शन प्रस्ताव का अध्ययन नहीं करना चाहते हैं - तैयार समाधानों का उपयोग करें, उदाहरण के लिए, Microsoft अपनी स्वयं की डेटर लाइब्रेरी प्रदान करता है। मैं इस तरह के पुस्तकालयों का उपयोग नहीं करता हूं और कई कारणों से एक मालिकाना समाधान का उपयोग करता हूं, इसलिए मैं कुछ सलाह नहीं दे सकता, मैं केवल कुछ नया खोजने और इसका उपयोग करने के लिए अपने द्वारा अध्ययन किए गए पुस्तकालयों का नाम दे सकता हूं: पॉलहूक , मिनहूक , इजीबूक (विशेष रूप से) अगर आपको C # में हुक की जरूरत है)।7.4.2। हार्डवेयर ब्रेकपॉइंट
ओपकोड हुक एक सरल और त्वरित जाल है, लेकिन सबसे कुशल नहीं है। एक एंटी-चीट आसानी से मेमोरी के एक टुकड़े में बदलाव को ट्रैक कर सकता है, लेकिन ओपोड हुक को एंटी-चीट के संबंध में या इंटरसेप्टिंग सिस्टम कॉल (उदाहरण के लिए, NtSetInformationThread) का उपयोग किया जा सकता है। हार्डवेयर ब्रेकपॉइंट एक जाल है जो प्रक्रिया मेमोरी को नहीं बदलता है। मैंने मंचों पर धागे से पूछा कि क्या VAC इस जाल का अनुसरण कर रहा है - जवाब आमतौर पर मिश्रित होते हैं। व्यक्तिगत रूप से, वीएसी ने मुझे उनके उपयोग के लिए प्रतिबंध नहीं लगाया और रजिस्टरों को रीसेट नहीं किया (यह छह महीने पहले थोड़ा कम था, शायद कुछ बदल गया है)।, , VAC DR /, - , . HWBP , - , , , DR0-DR7 .
थ्रेड निष्पादन को बाधित करने के लिए HWBP विशेष प्रोसेसर रजिस्टर का उपयोग करता है। यदि प्रवाह संदर्भ में DR0-DR7 रजिस्टर एक निश्चित तरीके से सेट होता है और RIP DR0-DR3 में संग्रहीत चार पते में से एक पर जाता है, तो एक अपवाद फेंका जाता है जिसे अपवाद के प्रकार और संदर्भ की स्थिति से पकड़ा जा सकता है, यह निर्धारित करें कि किस पते पर नियंत्रण ने अपवाद को फेंक दिया और निष्कर्ष निकाला। - एक जाल या नहीं। इस दृष्टिकोण की एक महत्वपूर्ण सीमा यह है कि आप एक समय में केवल चार फ़ंक्शन का उपयोग कर सकते हैं और उन्हें प्रत्येक थ्रेड के लिए अलग-अलग सेट कर सकते हैं, जिससे जाल सेट होने पर असुविधा होती है और एक नया बनाया जाता है / पुराने धागे को फिर से बनाया जाता है, जो जाल का कारण बनता है। यह एक विशेष बाधा नहीं है और यह बेसटह्रेडइनटिटहंक फ़ंक्शन के अवरोधन द्वारा शासित है, 4 जाल के उपयोग पर प्रतिबंध वास्तव में मुझे व्यक्तिगत रूप से परेशान नहीं करता है।यदि हुक की संख्या आपके लिए महत्वपूर्ण है, तो PageGuard दृष्टिकोण देखें।तो, कार्य समान है - हम सैंडबॉक्स (सैंडबॉक्स प्रोजेक्ट) में हैं, डिवाइस प्रेजेंट और एंडस्कीन क्लास के तरीकों को इंटरसेप्ट करना आवश्यक है जिसमें मूल तरीकों को कॉल करना है। हमारे पास पहले से ही जाल के लिए तैयार इंटरफ़ेस है - IHook, चलो "लोहा" ब्रेकप्वाइंट के काम से निपटते हैं।सिद्धांत यह है: चार "काम कर रहे" DR0-DR3 रजिस्टर हैं, जिसमें पता लिखा जा सकता है, DR7 नियंत्रण रजिस्टर की सेटिंग्स के आधार पर जब निर्दिष्ट पते पर लिखने, पढ़ने या निष्पादित करने की कोशिश हो रही है, तो EXCECEION_SINGLE_STEP प्रकार के साथ एक अपवाद होगा, जिसे पहले पंजीकृत हैंडलर में संसाधित किया जाना चाहिए। । आप एसईएच हैंडलर और वीईएच दोनों का उपयोग कर सकते हैं - हम बाद का उपयोग करेंगे, क्योंकि इसकी उच्च प्राथमिकता है।हमें इस विचार का एहसास है: int HardwareBPHook::SetHook(void* source, void* destination, HANDLE* hThread, int* reg) { CONTEXT context; ZeroMemory(&context, sizeof(context)); context.ContextFlags = CONTEXT_DEBUG_REGISTERS; if (!GetThreadContext(*hThread, &context)) return ERROR_GET_CONTEXT; *(&context.Dr0 + *reg) = (unsigned long long)source; context.Dr7 |= 1ULL << (2 * (*reg)); context.Dr7 |= HW_EXECUTE << ((*reg) * 4 + 16); context.Dr7 |= HW_LENGTH << ((*reg) * 4 + 18); if (!SetThreadContext(*hThread, &context)) return ERROR_SET_CONTEXT; return SUCCESS_CODE; }
कोड में क्या होता हैयह कोड एक विशिष्ट थ्रेड का संदर्भ प्राप्त करता है, सफल रसीद पर, फ़ंक्शन के पते को एक मुफ्त रजिस्टर में डाला जाता है, DR7 नियंत्रण रजिस्टर सेट करता है। अंत में, परिवर्तित संदर्भ सेट है।
DR6 और DR7 क्या हैं, साथ ही पेजगार्ड दृष्टिकोण के बारे में अधिक विस्तार से, मैं ग्रे हैट पायथन को सलाह दे सकता हूं: हैकर्स और रिवर्स इंजीनियर्स के लिए पायथन प्रोग्रामिंग। संक्षेप में, DR7 एक "वर्किंग" रजिस्टर के उपयोग को सक्षम / अक्षम करता है - भले ही DR0-DR3 रजिस्टरों में से कोई एक पता होता है, लेकिन DR7 में संबंधित रजिस्टर का ध्वज अक्षम है, ब्रेकप्वाइंट काम नहीं करेगा। DR7 उस पते के साथ काम के प्रकार को भी निर्धारित करता है जिस पर अपवाद को फेंकना आवश्यक है - चाहे पता पढ़ा गया हो, चाहे रिकॉर्ड बनाया गया था या पते का उपयोग निर्देश को निष्पादित करने के लिए किया जाता है (हम अंतिम विकल्प में रुचि रखते हैं)।एक जाल को निकालना भी काफी सरल है और DR7 नियंत्रण रजिस्टर के माध्यम से किया जाता है। int HardwareBPHook::UnsetHook(void* source, HANDLE* hThread) { CONTEXT context; ZeroMemory(&context, sizeof(context)); context.ContextFlags = CONTEXT_DEBUG_REGISTERS; if (!GetThreadContext(*hThread, &context)) return ERROR_GET_CONTEXT; for (int i = 0; i < DEBUG_REG_COUNT; i++) { if ((unsigned long long)source == *(&context.Dr0 + i)) { info->GetItem(i)->source = 0; *(&context.Dr0 + i) = 0; context.Dr7 &= ~(1ULL << (2 * i)); context.Dr7 &= ~(3 << (i * 4 + 16)); context.Dr7 &= ~(3 << (i * 4 + 18)); break; } } if (!SetThreadContext(*hThread, &context)) return ERROR_SET_CONTEXT; return SUCCESS_CODE; }
यह थ्रेड्स से निपटने के लिए रहता है - जाल को उन थ्रेड्स के लिए सेट किया जाना चाहिए जो इंटरसेप्टेड फ़ंक्शन को कॉल करते हैं। हम इस बारे में परेशान नहीं होंगे।हम प्रक्रिया के सभी थ्रेड्स के लिए एक जाल सेट करते हैं। int HardwareBPHook::SetHook(void* source, void* destination) { THREADENTRY32 te32; HANDLE hThread = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); if (hThread == INVALID_HANDLE_VALUE) return ERROR_ENUM_THREAD_START; te32.dwSize = sizeof(THREADENTRY32); if (!Thread32First(hThread, &te32)) { CloseHandle(hThread); return ERROR_ENUM_THREAD_START; } DWORD dwOwnerPID = GetCurrentProcessId(); bool isRegDefined = false; int freeReg = -1; Freeze(); do { if (te32.th32OwnerProcessID == dwOwnerPID) { HANDLE openThread = OpenThread(THREAD_ALL_ACCESS, FALSE, te32.th32ThreadID); if (!isRegDefined) { CONTEXT context; ZeroMemory(&context, sizeof(context)); context.ContextFlags = CONTEXT_DEBUG_REGISTERS; if (!GetThreadContext(openThread, &context)) return ERROR_GET_CONTEXT; freeReg = GetFreeReg(&context.Dr7); if (freeReg == -1) return ERROR_GET_FREE_REG; isRegDefined = true; } SetHook(source, destination, &openThread, &freeReg); CloseHandle(openThread); } } while (Thread32Next(hThread, &te32)); CloseHandle(hThread); Unfreeze(); auto record = info->GetItem(freeReg); record->source = source; record->destination = destination; record->pTrampoline = source; return SUCCESS_CODE; }
उपरोक्त कोड सभी दृश्य प्रक्रियाओं को दरकिनार करता है और वर्तमान प्रक्रिया को खोजता है। अगले धागे के लिए मिली प्रक्रिया में, हम स्ट्रीम हैंडलर प्राप्त करते हैं, चार मुक्त रजिस्टरों में से एक को ढूंढते हैं और एक जाल सेट करते हैं। यह फ्रीज और अनफ्रीज कार्यों पर ध्यान देने योग्य है - यह वही है जो ओपोड हुक ने मल्टीथ्रेडिंग के बारे में बात की थी - वे इस प्रक्रिया के थ्रेड्स (वर्तमान को छोड़कर) के निष्पादन को पूरी तरह से रोक देते हैं ताकि कोई भी स्थिति न हो जब थ्रेड्स में से कोई एक अवरोधन फ़ंक्शन को जोड़ता है।हुक फ़ंक्शन को कॉल करने से धागे की रक्षा करना int IHook::Freeze() { THREADENTRY32 te32; HANDLE hThread = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); if (hThread == INVALID_HANDLE_VALUE) return ERROR_ENUM_THREAD_START; te32.dwSize = sizeof(THREADENTRY32); if (!Thread32First(hThread, &te32)) { CloseHandle(hThread); return ERROR_ENUM_THREAD_START; } DWORD dwOwnerPID = GetCurrentProcessId(); do { if (te32.th32OwnerProcessID == dwOwnerPID && te32.th32ThreadID != GetCurrentThreadId()) { HANDLE openThread = OpenThread(THREAD_ALL_ACCESS, FALSE, te32.th32ThreadID); if (openThread != NULL) { SuspendThread(openThread); CloseHandle(openThread); } } } while (Thread32Next(hThread, &te32)); CloseHandle(hThread); return SUCCESS_CODE; } int IHook::Unfreeze() {
जाल को हटाने के कार्य में इसी तरह की आवश्यकता है।यह वीईएच अपवाद हैंडलर जोड़ने के लिए बना हुआ है। जोड़ना और हटाना AddVectoredExceptionHandler और RemoveVectoredExceptionHandler फ़ंक्शन किसी भी स्ट्रीम से किया जाता है। void HardwareBPHook::SetExceptionHandler(PVECTORED_EXCEPTION_HANDLER pVecExcHandler) { pException = AddVectoredExceptionHandler(1, pVecExcHandler); } ~HardwareBPHook() { info->Clear(); delete info; RemoveVectoredExceptionHandler(pException); }
हैंडलर को अपवाद के प्रकार की जांच करनी चाहिए (EXCEPTION_SINGLE_STEP की आवश्यकता है), उस पते के पत्राचार की जांच करें जिस पर अपवाद क्या रजिस्टरों में है और अगर ऐसा कोई पता चलता है, तो रिसीवर के पते पर RIP पॉइंटर को फिर से व्यवस्थित करता है। स्टैक की स्थिति संरक्षित है, ताकि रिसीवर के आगे निष्पादन पर, स्टैक पर सभी पैरामीटर बरकरार रहेंगे।हम वर्णित हैंडलर को सैंडबॉक्स में लागू करते हैं: LONG OnExceptionHandler( EXCEPTION_POINTERS* exceptionPointers) { if (exceptionPointers->ExceptionRecord->ExceptionCode != EXCEPTION_SINGLE_STEP) return EXCEPTION_CONTINUE_EXECUTION; for (int i = 0; i < DEBUG_REG_COUNT; i++) { if (exceptionPointers->ContextRecord->Rip == (unsigned long long)hook->GetInfo()->GetItem(i)->source) { exceptionPointers->ContextRecord->Rip = (unsigned long long)hook->GetInfo()->GetItem(i)->destination; break; } } return EXCEPTION_CONTINUE_EXECUTION; }
सिद्धांत रूप में, सब कुछ तैयार है, हम प्रोग्राम चलाते हैं, बिल्कुल OpcodeHook के समान कार्य की प्रतीक्षा में।ऐसा नहीं होता है, हमारा कार्यक्रम जम जाता है - अधिक सटीक रूप से, यह लगातार PresentHook में चला जाता है और जिस समय स्प्रिंगबोर्ड को बुलाया जाना चाहिए, उस समय फ़ंक्शन को फिर से कहा जाता है। तथ्य यह है कि "आयरन" ब्रेकप्वाइंट गायब नहीं हुआ है, क्योंकि जब आप स्प्रिंगबोर्ड कहते हैं (जो, "आयरन" ब्रेकप्वाइंट के मामले में, मूल फ़ंक्शन को इंगित करता है), हम फिर से उसी पते को अलार्म करते हैं और एक अपवाद उठाते हैं। समाधान निम्नलिखित है: जब हम हैंडलर में एक विशिष्ट थ्रेड के लिए पाया जाता है, तो हम विराम बिंदु को हटा देंगे, और सही समय पर हम इसे फिर से सेट करेंगे। अद्यतन करने का स्थान उस पल को चुनेगा जब रिसीवर फ़ंक्शन समाप्त होता है।यह निम्नानुसार लागू किया जाता है - हैंडलर में, ब्रेकपॉइंट को हटाने के साथ, एक लंबित कमांड जोड़ा जाता है, जिसका अर्थ निर्दिष्ट स्ट्रीम में ब्रेकपॉइंट को अपडेट करना है। रिसीवर फ़ंक्शन के अंत में कमांड चलता है। IDeferredCommands* hookCommands; int PresentHook(Device* device) { auto record = hook->GetRecordBySource(ptrPresent); pPresent pTrampoline = (pPresent)record->pTrampoline; auto result = pTrampoline(device); cout << "PresentHook" << endl; hookCommands->Run(); return result; } void EndSceneHook(Device* device, int j) { auto record = hook->GetRecordBySource(ptrEndScene); pEndScene pTrampoline = (pEndScene)record->pTrampoline; pTrampoline(device, 2); cout << "EndSceneHook" << " " << j << endl; hookCommands->Run(); } LONG OnExceptionHandler(EXCEPTION_POINTERS* exceptionPointers) { if (exceptionPointers->ExceptionRecord->ExceptionCode != EXCEPTION_SINGLE_STEP) return EXCEPTION_CONTINUE_EXECUTION; for (int i = 0; i < DEBUG_REG_COUNT; i++) { if (exceptionPointers->ContextRecord->Rip == (unsigned long long)hook->GetInfo()->GetItem(i)->source) { exceptionPointers->ContextRecord->Dr7 &= ~(1ULL << (2 * i)); exceptionPointers->ContextRecord->Rip = (unsigned long long)hook->GetInfo()->GetItem(i)->destination; IDeferredCommand* cmd = new SetD7Command(hook, GetCurrentThreadId(), i); hookCommands->Enqueue(cmd); break; } } return EXCEPTION_CONTINUE_EXECUTION; }
लंबित कमांड कार्यान्वयन namespace silk_way { class IDeferredCommand { protected: IDeferredCommand(silk_way::IHook* _hook) { hook = _hook; } public: virtual ~IDeferredCommand() { hook = nullptr; } virtual void Run() = 0; protected: silk_way::IHook* hook; }; class SetD7Command : public IDeferredCommand { public: SetD7Command(silk_way::IHook* _hook, unsigned long long _threadId, int _reg) : IDeferredCommand(_hook) { threadId = _threadId; reg = _reg; } void Run() { HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, threadId); if (hThread != NULL) { bool res = SetD7(&hThread); CloseHandle(hThread); } } private: bool SetD7(HANDLE* hThread) { CONTEXT context; ZeroMemory(&context, sizeof(context)); context.ContextFlags = CONTEXT_DEBUG_REGISTERS; if (!GetThreadContext(*hThread, &context)) return false; *(&context.Dr0 + reg) = (unsigned long long)hook->GetInfo()->GetItem(reg)->source; context.Dr7 |= 1ULL << (2 * reg); if (!SetThreadContext(*hThread, &context)) return false; return true; } private: unsigned long long threadId; int reg; }; class IDeferredCommands : public silk_data::Queue<IDeferredCommand*>, public IDeferredCommand { protected: IDeferredCommands() : Queue(), IDeferredCommand(nullptr) {} public: virtual ~IDeferredCommands() {} }; }
नेत्रहीन "लोहा" ब्रेकप्वाइंट के काम की कल्पना करें।
चित्र 12 - प्रारंभिक स्थितिहम एक जाल सेट करते हैं, एक वीईएच हैंडलर जोड़ते हैं, स्रोत फ़ंक्शन तक पहुंचने के लिए नियंत्रण की प्रतीक्षा करें:
चित्रा 13 - अवरोधन तैयार करने के लिए स्टेजएक अपवाद को फेंक दिया जाता है, एक हैंडलर को कहा जाता है कि रिसीवर को आरआईपी पुनर्निर्देशित करता है और ब्रेकपॉइंट को रीसेट करता है:
चित्रा 14 - निष्पादन थ्रेड को पुनर्निर्देशित करता है। फ़ंक्शन रिसीवरपर इस विषय पर जाल समाप्त हो सकता है, स्थैतिक पुस्तकालय Silk_way.lib तैयार है। अपने स्वयं के अनुभव से मैं कह सकता हूं कि मैं अक्सर OpcodeHook, VMT Hook, Forced Exception हुक (शायद सबसे "नकसीर" जाल), HardwareBreakpoint और PageGuard का उपयोग करता हूं (जब निष्पादन समय महत्वपूर्ण नहीं होता है, एक-बार इंटरसेप्ट)।8. तर्क का स्थापत्य
तर्क का आधार एमवीसी (मॉडल-व्यू-कंट्रोलर) के रूप में प्रस्तुत किया गया है। सभी मुख्य इकाइयां ISilkObject इंटरफ़ेस से विरासत में मिली हैं।8.1। आदर्श
लाइब्रेरी में बॉट विकसित करते समय, मैंने पहली बार ईसीएस लागू किया (आप इस दृष्टिकोण के बारे में यहां और यहां पढ़ सकते हैं )। जब मुझे एहसास हुआ कि वास्तविक खिलाड़ियों के साथ एक बॉट लॉन्च करना एक लंबा काम था, तो मैंने एक सिमुलेशन लिखा जहां हमने मिलीलीटर पुस्तकालयों का परीक्षण किया (नेविगेशन के लिए तीन आयामी ग्रिड के साथ (डोटा 2 सिर्फ नेविगेशन के लिए एक 3 डी ग्रिड का उपयोग करता है ) और बॉडी ब्लॉक के लिए 2 डी भौतिकी को सरल बनाया। जब सिमुलेशन की आवश्यकता गायब हो गई और मुझे पता चला कि कैसे और क्या लॉग इन करना है, तो लड़ाई के दौरान क्या जानकारी इकट्ठा करना है, ईसीएस की अब कोई जरूरत नहीं है और मॉडल में बस घटकों का एक शब्दकोश (स्काइफॉर्ग से लोगों की तरह कुछ का प्रतिनिधित्व करने के लिए शुरू होता है), Avatars और भीड़ "), जिसमें निहित है, वास्तव में, Source2Gen से संरचनाओं पर आवरण। इस लेख के लिए, मैंने सामग्री को सरल बनाने के लिए इस कार्यान्वयन को स्थानांतरित नहीं किया। मॉडल में स्कीमा शामिल है, जिसमें इसका विवरण संग्रहीत किया गया है (इस बिंदु को सरल बनाया गया है और इस कार्यान्वयन में योजना के अनुसार मॉडल नहीं बनाया गया है, योजना केवल इसका वर्णन करती है (पूर्वनिर्धारित मूल्यों को संग्रहीत करता है जो हार्डकोड किया जा सकता है) - इसकी तुलना xml / json में खेल सामग्री के भंडारण से की जा सकती है )।योजना के अनुसार, मॉडल डिवाइस को निम्नानुसार दर्शाया जा सकता है: चित्र 15 - कोड में मॉडल कार्यान्वयन का योजनाबद्ध प्रतिनिधित्व :
template <class S> SILK_OBJ(IModel) { ACCESSOR(IIdentity, Id) ACCESSOR(S, Schema) public: IModel(IIdentity * id, ISchema * schema) { Id = id; Schema = dynamic_cast<S*>(schema); components = new silk_data::RBTree<SILK_STRING*, IComponent>( new StringCompareStrategy()); } ~IModel() { delete Id; Schema = nullptr; components->Clear(); delete components; } template <class T> T* Get(SILK_STRING * key) { return (T*)components->Find(key); } private: silk_data::RBTree<SILK_STRING*, IComponent>* components; };
योजना में एक विशिष्ट मॉडल का विवरण शामिल होता है और इसमें वह संदर्भ होता है जिसका मॉडल उपयोग कर सकता है। class IModelSchema : public BaseSchema { ACCESSOR(ModelContext, Context) public: IModelSchema(const char* type, const char* name, IContext* context) : BaseSchema(type, name) { Context = dynamic_cast<ModelContext*>(context); } ~IModelSchema() { Context = nullptr; } }; class ModelContext : public SilkContext { ACCESSOR(ILogger, Logger) ACCESSOR(IChrono, Clock) ACCESSOR(GigaFactory, Factory) ACCESSOR(IGameModel*, Model) public: ModelContext(SILK_GUID* guid, ILogger* logger, IChrono* clock, GigaFactory* factory, IGameModel** model) : SilkContext(guid) { Logger = logger; Clock = clock; Factory = factory; Model = model; } ~ModelContext() { Logger = nullptr; Clock = nullptr; Factory = nullptr; Model = nullptr; } };
मॉडल का संग्रह और योजनाओं का संग्रह template <class T, class S> class IModelCollection : public silk_data::Vector<T*>, public IModel<S> { protected: IModelCollection(IIdentity* id, ISchema* schema) : Vector(), IModel(id, schema) { auto factory = Schema->GetContext()->GetFactory(); auto guid = Schema->GetContext()->GetGuid(); foreach (Schema->Length()) { auto itemSchema = Schema->GetItem(i); auto item = factory->Build<T>(itemSchema->GetType()->GetValue(), guid->Get(), itemSchema); PushBack(item); } } public: ~IModelCollection() { Clear(); } T* GetByName(const char* name) { foreach (Length()) if (GetItem(i)->GetSchema()->CheckName(name)) return GetItem(i); return nullptr; } };
उदाहरण के लिए, रोशन स्थिति को स्टोर करने वाले मॉडल का इंटरफ़ेस और कार्यान्वयन कैसा दिखता है DEFINE_IMODEL(IRoshanStatusModel, IRoshanStatusSchema) { VIRTUAL_COMPONENT(IStatesModel, States) public: virtual void Resolve() = 0; protected: IRoshanStatusModel(IIdentity * id, ISchema * schema) : IModel(id, schema) {} }; DEFINE_MODEL(RoshanStatusModel, IRoshanStatusModel) { COMPONENT(IStatesModel, States) public : RoshanStatusModel(IIdentity * id, ISchema* schema) : IRoshanStatusModel( id, schema) { auto factory = Schema->GetContext()->GetFactory(); auto guid = Schema -> GetContext() -> GetGuid(); auto statesSchema = Schema -> GetStates(); States = factory->Build<IStatesModel>( statesSchema->GetType()->GetValue(), guid->Get(), statesSchema); } ~RoshanStatusModel() { delete States; } void Resolve() { auto currentStateSchema = States->GetCurrent()->GetSchema(); Schema->GetContext()->GetLogger()->Log("RESOLVE\n"); foreach (currentStateSchema->GetTransitions()->Length()) { auto transition = currentStateSchema->GetTransitions()->GetItem(i); if (transition->GetRequirement()->Check()) { transition->GetAction()->Make(); States->SetCurrent(States->GetByName( transition->GetTo()->GetValue())); break; } } } };
8.2। देखें, स्थिति और नियंत्रक देखें
प्रेजेंटेशन, प्रेजेंटेशन स्टेट और कंट्रोलर के बारे में कहने के लिए बहुत कुछ नहीं है, कार्यान्वयन मॉडल के समान है। इनमें स्कीमा और संदर्भ भी होते हैं। View, Canvas, ViewCollection, Label और Button के लिए समस्या को हल करने के लिए, पिछले दो राज्यों के लिए, रोशन स्थित राज्यों के लिए भी लागू किया जाता है।योजनाबद्ध दृश्य
चित्र 16 - दृश्य का योजनाबद्ध प्रतिनिधित्व व्यू स्टेट का योजनाबद्ध प्रतिनिधित्व
चित्र 17 - दृश्य राज्य का योजनाबद्ध प्रतिनिधित्व 8.3। कारखाना है
फैक्ट्री का उपयोग करके ऑब्जेक्ट बनाए जाते हैं। इकाइयां एक कुंजी के रूप में एक अंतरफलक प्रकार का उपयोग करती हैं, इसे टाइपिड (टी) .raw_name () का उपयोग करके स्ट्रिंग में अनुवाद करती हैं। सामान्य तौर पर, ऐसा करना बुरा है, आंद्रेई अलेक्जेंड्रेस्कु, मॉडर्न सी ++ डिज़ाइन: जेनेरिक प्रोग्रामिंग में क्यों और कैसे सही ढंग से पढ़ा जाए। फैक्टरी कार्यान्वयन: class SilkFactory { public: SilkFactory() { items = new silk_data::RBTree<SILK_STRING*, IImplementator>( new StringCompareStrategy()); } ~SilkFactory() { items->Clear(); delete items; } template <class... Args> ISILK_WAY_OBJECT* Build(const char* type, Args... args) { auto key = new SILK_STRING(type); auto impl = items->Find(key)->payload; return impl->Build(args...); } void Register(const char* type, IImplementator* impl) { auto key = new SILK_STRING(type); items->Insert(*items->MakeNode(key, impl)); } protected: silk_data::RBTree<SILK_STRING*, IImplementator>* items; }; class GigaFactory { public: GigaFactory() { items = new silk_data::RBTree<SILK_STRING*, SilkFactory>( new StringCompareStrategy()); } ~GigaFactory() { items->Clear(); delete items; } template <class T, class... Args> T* Build(const char* concreteType, Args... args) { auto key = new SILK_STRING(typeid(T).raw_name()); auto factory = items->Find(key)->payload; return (T*)factory->Build(concreteType, args...); } template <class T> void Register(SilkFactory* factory) { auto key = new SILK_STRING(typeid(T).raw_name()); items->Insert(*items->MakeNode(key, factory)); } protected: silk_data::RBTree<SILK_STRING*, SilkFactory>* items; };
वस्तुओं का निर्माण करने के लिए कारखाने का उपयोग करने से पहले, आपको पंजीकरण करने की आवश्यकता है।मॉडल पंजीकरण उदाहरण void ModelRegistrator::Register( GigaFactory* factory) { auto requirement = new SilkFactory(); requirement->Register("true", new SchemaImplementator<TrueRequirement>); requirement->Register("false", new SchemaImplementator<FalseRequirement>); requirement->Register("roshan_killed", new SchemaImplementator<RoshanKilledRequirement>); requirement->Register("roshan_alive_manual", new SchemaImplementator<RoshanAliveManualRequirement>); requirement->Register("time", new SchemaImplementator<TimeRequirement>); requirement->Register("roshan_state", new SchemaImplementator<RoshanStateRequirement>); factory->Register<IRequirement>(requirement); auto action = new SilkFactory(); action->Register("action", new SchemaImplementator<EmptyAction>); action->Register("set_current_time", new SchemaImplementator<SetCurrentTimeAction>); factory->Register<IAction>(action); auto transition = new SilkFactory(); transition->Register("transition", new SchemaImplementator<TransitionSchema>); factory->Register<ITransitionSchema>(transition); auto transitions = new SilkFactory(); transitions->Register("transitions", new SchemaImplementator<TransitionsSchema>); factory->Register<ITransitionsSchema>(transitions); auto stateSchema = new SilkFactory(); stateSchema->Register("state", new SchemaImplementator<StateSchema>); factory->Register<IStateSchema>(stateSchema); auto statesSchema = new SilkFactory(); statesSchema->Register("states", new SchemaImplementator<StatesSchema>); factory->Register<IStatesSchema>(statesSchema); auto roshanStatusSchema = new SilkFactory(); roshanStatusSchema->Register("roshan_status", new SchemaImplementator<RoshanStatusSchema>); factory->Register<IRoshanStatusSchema>(roshanStatusSchema); auto triggerSchema = new SilkFactory(); triggerSchema->Register("trigger", new SchemaImplementator<TriggerSchema>); factory->Register<ITriggerSchema>(triggerSchema); auto triggersSchema = new SilkFactory(); triggersSchema->Register("triggers", new SchemaImplementator<TriggersSchema>); factory->Register<ITriggersSchema>(triggersSchema); auto resourceSchema = new SilkFactory(); resourceSchema->Register("resource", new SchemaImplementator<ResourceSchema>); factory->Register<IResourceSchema>(resourceSchema); auto resourcesSchema = new SilkFactory(); resourcesSchema->Register("resources", new SchemaImplementator<ResourcesSchema>); factory->Register<IResourcesSchema>(resourcesSchema); auto gameSchema = new SilkFactory(); gameSchema->Register("game", new SchemaImplementator<GameSchema>); factory->Register<IGameSchema>(gameSchema); auto gameModel = new SilkFactory(); gameModel->Register("game", new ConcreteImplementator<GameModel>); factory->Register<IGameModel>(gameModel); auto resources = new SilkFactory(); resources->Register("resources", new ConcreteImplementator<ResourceCollection>); factory->Register<IResourceCollection>(resources); auto resource = new SilkFactory(); resource->Register("resource", new ConcreteImplementator<Resource>); factory->Register<IResource>(resource); auto triggers = new SilkFactory(); triggers->Register("triggers", new ConcreteImplementator<TriggerCollection>); factory->Register<ITriggerCollection>(triggers); auto trigger = new SilkFactory(); trigger->Register("trigger", new ConcreteImplementator<Trigger>); factory->Register<ITrigger>(trigger); auto roshanStatus = new SilkFactory(); roshanStatus->Register("roshan_status", new ConcreteImplementator<RoshanStatusModel>); factory->Register<IRoshanStatusModel>(roshanStatus); auto states = new SilkFactory(); states->Register("states", new ConcreteImplementator<StatesModel>); factory->Register<IStatesModel>(states); auto state = new SilkFactory(); state->Register("state", new ConcreteImplementator<StateModel>); factory->Register<IStateModel>(state); }
योजना को किसी भी तरह से भरा जा सकता है - आप जसन का उपयोग कर सकते हैं, आप सीधे कोड में कर सकते हैं।JSON में मॉडल के लिए स्कीमा को पॉप्युलेट करने का विकल्प { "game": { "roshan_status": { "states": [ { "name": "alive", "transitions": [ { "from": "alive", "to": "ressurect_base", "requirement": { "typename": "roshan_killed", "action": { "typename": "set_current_time", "resource": "roshan_killed_ts" } } } ] }, { "name": "ressurect_base", "transitions": [ { "from": "ressurect_base", "to": "ressurect_extra", "requirement": { "typename": "time", "resource": "roshan_killed_ts", "offset": 480 }, "action": { "typename": "action" } } ] }, { "name": "ressurect_extra", "transitions": [ { "from": "ressurect_extra", "to": "alive", "requirement": { "typename": "time", "resource": "roshan_killed_ts", "offset": 660 }, "action": { "typename": "action" } }, { "from": "ressurect_extra", "to": "alive", "requirement": { "typename": "roshan_alive_manual" }, "action": { "typename": "action" } } ] } ] }, "triggers": { "roshan_killed": {}, "roshan_alive_manual": {} }, "resources": { "roshan_killed_ts": {} } } }
कोड सबमिशन के लिए स्कीमा को पॉप्युलेट करने का विकल्प void GameController::InitViewSchema(ICanvasSchema** schema) { *schema = factory->Build<ICanvasSchema>("canvas_d9", "canvas_d9", "canvas_d9", viewContext); IViewCollectionSchema* elements = factory->Build<IViewCollectionSchema>( "elements", "elements", "elements", viewContext); (*schema)->SetElements(elements); ILabelSchema* labelSchema = factory->Build<ILabelSchema>( "label_d9", "label_d9", "roshan_status_label", viewContext); labelSchema->SetRecLeft(new SILK_INT(30)); labelSchema->SetRecTop(new SILK_INT(100)); labelSchema->SetRecRight(new SILK_INT(230)); labelSchema->SetRecDown(new SILK_INT(250)); labelSchema->SetColorR(new SILK_FLOAT(1.0f)); labelSchema->SetColorG(new SILK_FLOAT(1.0f)); labelSchema->SetColorB(new SILK_FLOAT(1.0f)); labelSchema->SetColorA(new SILK_FLOAT(1.0f)); labelSchema->SetText(new SILK_STRING("Roshan status: alive\0")); elements->PushBack((IViewSchema*&)labelSchema); IButtonSchema* buttonSchema = factory->Build<IButtonSchema>( "button_d9", "button_d9", "roshan_kill_button", viewContext); ILabelSchema* buttonLabelSchema = factory->Build<ILabelSchema>( "label_d9", "label_d9", "button_text", viewContext); buttonLabelSchema->SetRecLeft(new SILK_INT(30)); buttonLabelSchema->SetRecTop(new SILK_INT(115)); buttonLabelSchema->SetRecRight(new SILK_INT(110)); buttonLabelSchema->SetRecDown(new SILK_INT(130)); buttonLabelSchema->SetColorR(new SILK_FLOAT(1.0f)); buttonLabelSchema->SetColorG(new SILK_FLOAT(0.0f)); buttonLabelSchema->SetColorB(new SILK_FLOAT(0.0f)); buttonLabelSchema->SetColorA(new SILK_FLOAT(1.0f)); buttonLabelSchema->SetText(new SILK_STRING("Kill Roshan\0")); buttonSchema->SetLabel(buttonLabelSchema); buttonSchema->SetBorderColorR(new SILK_INT(0)); buttonSchema->SetBorderColorG(new SILK_INT(0)); buttonSchema->SetBorderColorB(new SILK_INT(0)); buttonSchema->SetBorderColorA(new SILK_INT(70)); buttonSchema->SetFillColorR(new SILK_INT(255)); buttonSchema->SetFillColorG(new SILK_INT(119)); buttonSchema->SetFillColorB(new SILK_INT(0)); buttonSchema->SetFillColorA(new SILK_INT(150)); buttonSchema->SetPushColorR(new SILK_INT(0)); buttonSchema->SetPushColorG(new SILK_INT(0)); buttonSchema->SetPushColorB(new SILK_INT(0)); buttonSchema->SetPushColorA(new SILK_INT(70)); buttonSchema->SetBorder(new SILK_FLOAT(5)); elements->PushBack((IViewSchema*&)buttonSchema); }
8.4। घटनाओं
दृश्य घटनाओं के माध्यम से मॉडल में परिवर्तन के बारे में सीखता है। आप कक्षा के तरीकों और सामान्य कार्यों में प्रतिक्रिया प्राप्त कर सकते हैं। #define VIRTUAL_EVENT(e) public: virtual IEvent* Get##e() = 0; #define EVENT(e) private: IEvent* e; public: IEvent* Get##e() { return e; } const int MAX_EVENT_CALLBACKS = 1024; class IEventArgs {}; class ICallback { public: virtual void Invoke(IEventArgs* args) = 0; }; template <class A> class Callback : public ICallback { typedef void (*f)(A*); public: Callback(f _pFunc) { ptr = _pFunc; } ~Callback() { delete ptr; } void Invoke(IEventArgs* args) { ptr((A*)args); } private: f ptr = nullptr; }; template <typename T, class A> class MemberCallback : public ICallback { typedef void (T::*f)(A*); public: MemberCallback(f _pFunc, T* _obj) { ptr = _pFunc; obj = _obj; } ~MemberCallback() { delete ptr; obj = nullptr; } void Invoke(IEventArgs* args) { (obj->*(ptr))((A*)args); } private: f ptr = nullptr; T* obj; }; class IEvent { public: virtual void Invoke(IEventArgs* args) = 0; virtual void Add(ICallback* callback) = 0; virtual bool Remove(ICallback* callback) = 0; virtual ~IEvent() {} };
यदि कोई वस्तु अपने अंदर होने वाली घटनाओं की सूचना देना चाहती है, तो आपको प्रत्येक घटना के लिए IEvent * जोड़ना होगा। एक अन्य ऑब्जेक्ट जो इस ऑब्जेक्ट के अंदर होने वाली घटनाओं में रुचि रखता है, उसे ICallback * बनाना चाहिए और इसे IEvent * (ईवेंट की सदस्यता) के अंदर पास करना चाहिए।उदाहरण नियंत्रक में होने वाली सदस्यताएँ void Attach() { statesChangedCallback = new MemberCallback<GameController, IEventArgs>( &GameController::OnStatesChanged, this); Model->GetRoshanStatus()->GetStates()->GetCurrentChanged()->Add( statesChangedCallback); buttonClickedCallback = new MemberCallback<GameController, IEventArgs>( &GameController::OnKillRoshanClicked, this); killButton->GetClickedEvent()->Add(buttonClickedCallback); }
एक कक्षा के अंदर एक घटना घोषित करने का एक उदाहरण - घड़ी के प्रत्येक हिट (टिक विधि को कॉल करके) के साथ, एक स्ट्राइक इवेंट उठाया जाता है class IChrono { VIRTUAL_EVENT(Struck) public: virtual void Tick() = 0; virtual long long GetStamp() = 0; virtual long long GetDiffS(long long ts) = 0; }; class Chrono : public IChrono { EVENT(Struck) public: Chrono() { start = time(0); Struck = new Event(); } ~Chrono() { delete Struck; } void Tick() { auto cur = clock(); worked += cur - savepoint; bool isStriking = savepoint < cur; savepoint = cur; if (isStriking) Struck->Invoke(nullptr); } long long GetStamp() { return start * CLOCKS_PER_SEC + worked; } long long GetDiffS(long long ts) { return (GetStamp() - ts) / CLOCKS_PER_SEC; } private: long long worked = 0; time_t start; time_t savepoint; };
मूल आदिम प्रकार (SILK_INT, SILT_FLOAT, SILK_STRING, ...) Core..9. DirectX 9
DirectX 9 Dota 2 द्वारा समर्थित ग्राफिक एपीआई में से एक है। डिवाइस एक वर्ग है जिसे IUnogn से विरासत में मिला है और इसमें वर्चुअल फ़ंक्शन हैं। तदनुसार, एक आभासी विधि तालिका में एक संकेतक प्राप्त करने के बाद, हम उन फ़ंक्शन को इंगित कर सकते हैं जिनकी हमें ज़रूरत है। गैर-वर्चुअल क्लास फ़ंक्शंस तालिका में शामिल नहीं हैं और वे .code खंड में हैं, क्योंकि वे केवल वही हैं जिन्हें ओवरराइड नहीं किया जा सकता है। वैसे, OpenGL और Vulkan में, डिवाइस फ़ंक्शन को इंटरसेप्ट करना बहुत आसान है, क्योंकि वे वर्चुअल नहीं हैं और आप GetProcAddress () का उपयोग करके पॉइंटर प्राप्त कर सकते हैं। DirectX 11 वास्तुकला 9 से अधिक जटिल है, लेकिन बहुत अधिक नहीं।वर्चुअल क्लास मेथड (साथ ही नॉन-वर्चुअल वन) को इंटरसेप्ट करने के लिए हमें इस क्लास के किसी भी इंस्टेंस की जरूरत है। उदाहरण का उपयोग करते हुए, हम आभासी तरीकों की तालिका प्राप्त करते हैं और कार्यों के लिए आवश्यक संकेत प्राप्त करते हैं। कक्षा का एक उदाहरण खोजने का सबसे आसान तरीका यह है कि आप इसे स्वयं बनाएं।ऐसा करने के लिए, हमें Direct3DCreate9 फ़ंक्शन का उपयोग करके IDirect3D9 इंटरफ़ेस के साथ एक ऑब्जेक्ट बनाने की आवश्यकता है, और हम CreateDevice विधि को कॉल करके इस ऑब्जेक्ट का उपयोग करके डिवाइस बनाएंगे। हम इन कार्यों को डायरेक्टएक्स लाइब्रेरी से सीधे कॉल कर सकते हैं, लेकिन सामग्री को मजबूत करने के लिए, हम उन्हें पॉइंटर्स के माध्यम से कॉल करेंगे। जैसा कि d3d9.h से देखा जा सकता है, Direct3DCreate9 एक नियमित कार्य है और इसे करने के लिए एक संकेतक GetProcAddress के माध्यम से प्राप्त किया जा सकता है (ठीक उसी तरह जैसे हमने NativeInjector में एक सूचक को लोडरवर्क को प्राप्त करने के लिए किया था)।
चित्र 18 - d3d9.h में CreateDevice का वर्णन IDirect3D9 काएक उदाहरण बनाएँ: typedef IDirect3D9* (WINAPI *SILK_Direct3DCreate9) (UINT SDKVersion);
IDirect3D9 का उपयोग करके, हम pD3D-> CreateDevice (...) को कॉल करके एक डिवाइस बना सकते हैं। वीएमटी से आवश्यक कार्यों के लिए एक संकेतक प्राप्त करने के लिए, हमें इन विधियों को निर्धारित करने के लिए प्रक्रिया का पता लगाने की आवश्यकता है। चित्र 19 - IDirect3D9 इंटरफ़ेस की CreateDevice विधि के लिए अनुक्रमणिका खोज 16 वां सूचकांक प्राप्त करें । CreateDevice के अलावा, हमें रिलीज़ और GetAdapterDisplayMode विधियों की भी आवश्यकता है।
हम कोड में डिवाइस के निर्माण को लागू करते हैं typedef HRESULT(WINAPI *SILK_GetAdapterDisplayMode)(IDirect3D9* direct3D9, UINT Adapter, D3DDISPLAYMODE* pMode); typedef HRESULT(WINAPI *SILK_CreateDevice)(IDirect3D9* direct3D9, UINT Adapter, D3DDEVTYPE DeviceType, HWND hFocusWindow, DWORD BehaviorFlags, D3DPRESENT_PARAMETERS* pPresentationParameters, IDirect3DDevice9** ppReturnedDeviceInterface); typedef ULONG(WINAPI *SILK_Release)(IDirect3D9* direct3D9); const int RELEASE_INDEX = 2; const int GET_ADAPTER_DISPLAY_MODE_INDEX = 8; const int CREATE_DEVICE_INDEX = 16; BOOL CreateSearchDevice(IDirect3D9** d3d, IDirect3DDevice9** device) { if (!d3d || !device) return FALSE; *d3d = NULL; *device = NULL;
ठीक है, हमने डायरेक्टएक्स 9 डिवाइस बनाया, अब हमें यह समझने की आवश्यकता है कि दृश्य को प्रस्तुत करने के लिए किन कार्यों का उपयोग किया जाता है, हमें क्या करने की आवश्यकता है। हमें इस प्रश्न का उत्तर देने की आवश्यकता है: "डायरेक्ट 9 हमें दृश्य कैसे दिखाता है?" वर्तमान फ़ंक्शन का उपयोग दृश्य को प्रदर्शित करने के लिए किया जाता है । यह फ्रंट बफ़र के रूप में ऐसी अवधारणाओं को पेश करने के लायक भी है (एक बफर जो स्क्रीन पर प्रदर्शित की गई लंबी अवधि की कार्रवाई को संग्रहीत करता है), बैक बफर - इसमें वह शामिल है जो प्रदर्शन के लिए तैयार है और सामने बफर, स्वैप चेन बनने की तैयारी कर रहा है - वास्तव में बफ़र्स का एक सेट आगे से पीछे की ओर फ्लिपिंग (DirectX 9 में केवल 1 स्वैप चेन है)। प्रेजेंट को कॉल करने से पहले, BeginScene और EndScene फ़ंक्शन के एक जोड़े को कहा जाता है , जहां आप बैक बफर को संशोधित कर सकते हैं।चलो दो कार्यों को रोकते हैं (वास्तव में, व्यापार तर्क को निष्पादित करने के लिए, एक हमारे लिए पर्याप्त है): एंडस्कैन और प्रेजेंट। ऐसा करने के लिए, इन फ़ंक्शन के स्थान को IDirect3DDevice9 वर्ग चित्र 20 में देखें - निम्नलिखित फ़ंक्शन हस्ताक्षरों के साथ IDirect3DDiceice9 इंटरफ़ेस की घोषणा की घोषणा :
typedef HRESULT(*VirtualOverloadPresent)(IDirect3DDevice9* pd3dDevice, CONST RECT* pSourceRect, CONST RECT* pDestRect, HWND hDestWindowOverride, CONST RGNDATA* pDirtyRegion); VirtualOverloadPresent oOverload = NULL; typedef HRESULT(*VirtualOverloadEndScene)(IDirect3DDevice9* pd3dDevice); VirtualOverloadEndScene oOverloadEndScene = NULL; const int PRESENT_INDEX = 17; const int END_SCENE_INDEX = 42;
हम हार्डवेयर हैंडलर के साथ तुरंत एक जाल की घोषणा करते हैं, क्योंकि HardwareBreakpoint वास्तव में हमारा एकमात्र कार्यान्वित सुरक्षित अवरोधन विकल्प है जो VAC को ट्रैक नहीं करता है (आप Opcode Hook के साथ भी परीक्षण कर सकते हैं, लेकिन आपका खाता संभवतः प्रतिबंध में उड़ जाएगा) silk_way::IDeferredCommands* deferredCommands; silk_way::IHook* hook; LONG OnExceptionHandler(EXCEPTION_POINTERS* exceptionPointers) { if (exceptionPointers->ExceptionRecord->ExceptionCode != EXCEPTION_SINGLE_STEP) return EXCEPTION_EXIT_UNWIND; for (int i = 0; i < silk_way::DEBUG_REG_COUNT; i++) { if (exceptionPointers->ContextRecord->Rip == (unsigned long long) hook->GetInfo()->GetItem(i)->source) { exceptionPointers->ContextRecord->Dr7 &= ~(1ULL << (2 * i)); exceptionPointers->ContextRecord->Rip = (unsigned long long) hook->GetInfo()->GetItem(i)->destination; silk_way::IDeferredCommand* cmd = new silk_way::SetD7Command(hook, GetCurrentThreadId(), i); deferredCommands->Enqueue(cmd); break; } } return EXCEPTION_CONTINUE_EXECUTION; }
हमारे दो जालों में से किसी एक के निर्दिष्ट कार्यों को ध्वनि दें: BOOL HookDevice(IDirect3DDevice9* pDevice) { unsigned long long vmt = **(unsigned long long **)&pDevice; int pointerSize = sizeof(unsigned long long); VirtualOverloadPresent pointerPresent= (VirtualOverloadPresent) ((*(unsigned long long *)(vmt + pointerSize * PRESENT_INDEX))); VirtualOverloadEndScene pointerEndScene = (VirtualOverloadEndScene) ((*(unsigned long long *)(vmt + pointerSize * END_SCENE_INDEX))); oOverload = pointerPresent; oOverloadEndScene = pointerEndScene; deferredCommands = new silk_way::DeferredCommands();
समारोह रिसीवर: HRESULT WINAPI PresentHook(IDirect3DDevice9* pd3dDevice, CONST RECT* pSourceRect, CONST RECT* pDestRect, HWND hDestWindowOverride, CONST RGNDATA* pDirtyRegion) { Capture(pd3dDevice); auto record = hook->GetRecordBySource(oOverload); VirtualOverloadPresent pTrampoline = (VirtualOverloadPresent) record->pTrampoline; auto result = pTrampoline(pd3dDevice, pSourceRect, pDestRect, hDestWindowOverride, pDirtyRegion); deferredCommands->Run(); return result; } HRESULT WINAPI EndSceneHook(IDirect3DDevice9* pd3dDevice) { if (controller == nullptr) { controller = new GameController(); controller->SetDevice(pd3dDevice); } controller->Update(); auto record = hook->GetRecordBySource(oOverloadEndScene); VirtualOverloadEndScene pTrampoline = (VirtualOverloadEndScene) record->pTrampoline; auto result = pTrampoline(pd3dDevice); deferredCommands->Run(); return result; }
वर्तमान में, प्रत्येक कॉल कैप्चर फ़ंक्शन का उपयोग करके वीडियो कार्ड बफर (सत्यापन के लिए) से एक स्क्रीनशॉट लेता है VOID WINAPI Capture(IDirect3DDevice9* pd3dDevice) { IDirect3DSurface9 *renderTarget = NULL; IDirect3DSurface9 *destTarget = NULL; HRESULT res1 = pd3dDevice->GetRenderTarget(0, &renderTarget); D3DSURFACE_DESC descr; HRESULT res2 = renderTarget->GetDesc(&descr); HRESULT res3 = pd3dDevice->CreateOffscreenPlainSurface( descr.Width, descr.Height, descr.Format, D3DPOOL_SYSTEMMEM, &destTarget, NULL); HRESULT res4 = pd3dDevice->GetRenderTargetData(renderTarget, destTarget); D3DLOCKED_RECT lockedRect; ZeroMemory(&lockedRect, sizeof(lockedRect)); if (destTarget == NULL) return; HRESULT res5 = destTarget->LockRect(&lockedRect, NULL, D3DLOCK_READONLY); HRESULT res7 = destTarget->UnlockRect(); HRESULT res6 = D3DXSaveSurfaceToFile(screenshootPath, D3DXIFF_BMP, destTarget, NULL, NULL); renderTarget->Release(); destTarget->Release(); }
EndScene एक व्यावसायिक तर्क नियंत्रक बनाता है। निर्माण के बाद, नियंत्रक अपडेट कहा जाता है, जहां सभी तर्क अपडेट किए जाते हैं।मैं ध्यान देता हूं कि अब हमने DirectX 9 के साथ काम को लागू कर दिया है। यदि हम किसी प्रकार का मॉड, धोखा, आदि बनाना चाहते हैं, तो सभी चार एपीआई का समर्थन करना चाहिए। यह उचित है यदि शस्त्रागार में पहले से ही आपके पसंदीदा पुस्तकालय हैं, UI के लिए रिक्त है, अन्यथा आप एक और तरीका - कार्यक्षमता का उपयोग कर सकते हैं जो गेम को रेंडर करने के लिए इंजन का उपयोग करता है।यह भी कहने योग्य है कि EndScene () से लॉजिक अपडेट को कॉल करना सबसे अच्छा विकल्प नहीं है - आप समय-समय पर कॉल करने के लिए इंजन फ़ंक्शन या अपनी स्ट्रीम में लॉजिक कॉल कर सकते हैं। यदि, हालांकि, आप एंडस्कीन के कॉल से संतुष्ट हैं, तो लॉकस्टेप के साथ ऐसा करना बेहतर है।अब हमने वह सब कुछ लागू कर दिया है जिसकी हमने योजना बनाई थी।परीक्षण सिफारिशेंDirectX SDK , , DirectX 9 DirectX 11. DirectX 11, - SDK, ( , ) , , DXUT, , — , FPS .
21 — DirectX SDK StateManager.exe अब आप स्टीम में एक नकली खाता बना सकते हैं और इंजेक्शन लगा सकते हैं। Dota 2 प्रक्रिया में इंजेक्ट कर सकते हैं। मैं तुरंत कहूंगा, मुझे नहीं पता कि "आयरन" ब्रेकप्वाइंट के साथ वर्तमान स्थिति कैसी है - ओपकोड हुक का उपयोग करना (जिस तरह से हम इसे वर्तमान में करते हैं। फार्म) आपको निश्चित रूप से प्रतिबंध मिलेगा। मैंने लगभग छह महीने पहले ऐसा किया था - हार्डवेयर ब्रेकप्वाइंट के लिए कोई प्रतिबंध नहीं था, मैं यह नहीं कह सकता कि इस समय स्थिति क्या है। लेख तैयार करने से पहले, मैंने दो खातों को लिया और उन पर ओपकोड हुक और एचडब्ल्यूबीपी की कोशिश की, पहला प्रतिबंध में उड़ गया (लगभग 2 सप्ताह बीत गए), दूसरा नंबर (3 सप्ताह बीत गए)। लेकिन फिर भी इस बात की कोई गारंटी नहीं है कि प्रतिबंध भविष्य में नहीं होगा। तब नाराज मत होइए यदि आप गलती से अपने मुख्य खाते से एक परिचय बनाते हैं या नकली खाते में लॉग इन करना भूल जाते हैं - तो पहले से ही अपना ख्याल रखें और सावधान रहें।( )
22 —
23 — 1x1 मोड में कार्यान्वयन। चित्र 24 - एक मैच में इंजेक्शन यह भी ध्यान देने योग्य है कि उपयुक्त आकार के साथ एक दूसरी विंडो बनाकर रेंडरिंग का एक और तरीका है - सतह रेंडरिंग। दुर्भाग्य से, मैं फ़ुल-स्क्रीन मोड के मामले के लिए एक सतह दृष्टिकोण का उपयोग करने की संभावना का एहसास नहीं कर सका, लेकिन लेख में वर्णित दृष्टिकोण आपको पूर्ण स्क्रीन और विंडो मोड दोनों में रेंडरिंग को बिना किसी समस्या के लागू करने की अनुमति देता है। हमारे एम्बेडेड यूआई में केवल एक पाठ लेबल और शुद्ध DirectX 9 पर लागू एक बटन शामिल है - यह वह सब है जो कार्य को हल करने के लिए आवश्यक है। आप जटिल तालिकाओं, सुंदर मेनू और आरेखों को लागू कर सकते हैं - सामान्य रूप से, किसी भी जटिलता का एक यूआई, शुद्ध एपीआई पर और तैयार पुस्तकालयों का उपयोग करके। बेशक, न केवल 2 डी।
10. इंजन कार्यों का उपयोग करना
प्रत्येक एपीआई के लिए समान कार्यक्षमता को लागू करना बल्कि नीरस है, डेवलपर्स ड्राइंग, यूआई और इतने पर कार्य प्रदान करके सुविधाजनक आवरण बनाते हैं, जो खेल सीधे उपयोग करता है। वाल्व जावास्क्रिप्ट और लुआ के लिए डोटा 2 एपीआई भी प्रदान करता है । यह मध्यस्थों और गेम डिजाइनरों के लिए जीवन को आसान बनाने के लिए किया जाता है, जिनके लिए सी ++ जटिल है (यहां तक कि सी ++ ही नहीं, लेकिन इंजन के संदर्भ में उचित उपयोग)। यहाँ प्रतिपादन के लिए कार्य हैं, और खेल के तर्क के लिए - आप इकाई के व्यवहार को निर्धारित कर सकते हैं, उदाहरण के लिए, आइटम का चयन करना, कौशल का उपयोग करना, और बहुत कुछ। दरअसल, इसकी मदद से कस्टम अक्षर लिखे जाते हैं।हमें DoIncludeScript फ़ंक्शन में रुचि होगी, जो आपको अपनी स्क्रिप्ट को Lua पर चलाने और वहां स्क्रिप्टिंग API का उपयोग करने की अनुमति देता है। मैंने अपने प्रोजेक्ट में इसका उपयोग नहीं किया, क्योंकि मैंने इसमें मूल्य नहीं देखा था, सी ++ से सीधे फ़ंक्शन का उपयोग करते हुए, मैंने इसे or_75 के साथ उपयोग करने का विचार देखा और इसे लेख में शामिल करने का निर्णय लिया। यह आपको दूसरे हिस्से में क्या होगा और इसमें जगह बचाने के लिए आपको पेश करेगा, आपको डिबगर के कुछ पहलुओं की व्याख्या नहीं करनी होगी।चलिए शुरू करते हैं।
कार्य निम्नानुसार है: आपको DoIncludeScript फ़ंक्शन के लिए एक संकेतक खोजने की आवश्यकता है, जो इसे पढ़ने के लिए स्क्रिप्ट और हैंडलर का नाम लेता है। हम अपने सिल्क_वे.लिब लाइब्रेरी से स्कैनर का उपयोग करके फ़ंक्शन की खोज करेंगे। फ़ंक्शंस, जैसा कि हम पहले ही पता लगा चुके हैं, ओपकोड तालिका का उपयोग करके मेमोरी में एन्कोडेड हैं - आइए इस फ़ंक्शन की जांच करें और मेमोरी में इसके भंडारण पैटर्न की पहचान करने का प्रयास करें। अब स्कैनर में आवश्यक कार्यक्षमता नहीं है, हमें प्रक्रिया मेमोरी में टेम्पलेट की खोज करने की क्षमता की आवश्यकता है।खोज को गति देने के लिए, हम पूरी प्रक्रिया मेमोरी में एक पैटर्न की खोज नहीं करेंगे, लेकिन एक विशिष्ट मॉड्यूल में (हमारा फ़ंक्शन क्लाइंटडॉल में निहित है, यह डिबगर में देखा जाएगा और नीचे चर्चा की जाएगी)। हम प्रक्रिया के सभी मॉड्यूलों की गणना करके tlHelp32 का उपयोग करके मॉड्यूल की खोज करेंगे, जिसके लिए हम वर्तमान GetModuleInfo प्रक्रिया में मॉड्यूल को खोजने के लिए एक फ़ंक्शन बनाएंगे।GetModuleInfo फ़ंक्शन कोड int IScanner::GetModuleInfo(const char* name, MODULEENTRY32* entry) { HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE32 | TH32CS_SNAPMODULE, GetCurrentProcessId()); if (snapshot == INVALID_HANDLE_VALUE) return 1; entry->dwSize = sizeof(MODULEENTRY32); if (!Module32First(snapshot, entry)) { CloseHandle(snapshot); return 1; } do { if (!_stricmp(entry->szModule, name)) break; } while (Module32Next(snapshot, entry)); CloseHandle(snapshot); return 0; }
पैटर्न बाइट्स के मूल्य के साथ एक स्ट्रिंग है, बाइट को छोड़ना प्रतीक द्वारा इंगित किया गया है "??" - उदाहरण के लिए, "j9 ?? ?? ?? ??
४ ०३ ० 48 ?? f1 ff ”।स्ट्रिंग को पार्स करना, सुविधा के लिए हम स्ट्रिंग प्रतिनिधित्व से पैटर्न को अहस्ताक्षरित चार मूल्यों की सूची में स्थानांतरित करेंगे, बाइट्स के झंडे को छोड़ दें। unsigned char* IScanner::Parse(int& len, const char* strPattern, unsigned char* skipByteMask) { int strPatternLen = strlen(strPattern); unsigned char* pattern = new unsigned char[strPatternLen]; for (int i = 0; i < strPatternLen; i++) pattern[i] = 0; len = 0; for (int i = 0; i < strPatternLen; i += 2) { unsigned char code = 0; if (strPattern[i] == SKIP_SYMBOL) skipByteMask[len] = 1; else code = Parse(strPattern[i]) * 16 + Parse(strPattern[i + 1]); i++; pattern[len++] = code; } return pattern; } unsigned char IScanner::Parse(char byte) {
खोज कोर FindPattern फ़ंक्शन में कार्यान्वित किया जाता है, जहां, मॉड्यूल के बारे में प्राप्त जानकारी के आधार पर, खोज के प्रारंभ और अंत पते सेट होते हैं। खोज की गई मेमोरी के बारे में जानकारी VirtualQuery फ़ंक्शन द्वारा अनुरोध की गई है, मेमोरी के लिए कई आवश्यकताएँ हैं - यह व्यस्त होना चाहिए (यह मुफ्त मेमोरी में खोज करने के लिए एक त्रुटि होगी), मेमोरी को पढ़ने योग्य, निष्पादन योग्य होना चाहिए और इसमें पेजबोर्ड ध्वज नहीं होना चाहिए: void* pStart = moduleEntry.modBaseAddr; void* pFinish = moduleEntry.modBaseAddr + moduleEntry.modBaseSize; unsigned char* current = (unsigned char*)pStart; for (; current < pFinish && j < patternLen; current++) { if (!VirtualQuery((LPCVOID)current, &info, sizeof(info))) continue; unsigned long long protectMask = PAGE_READONLY | PAGE_READWRITE | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE | PAGE_EXECUTE_READ; if (info.State == MEM_COMMIT && info.Protect & protectMask && !(info.Protect & PAGE_GUARD)) { unsigned long long finish = (unsigned long long)pFinish < (unsigned long long)info.BaseAddress + info.RegionSize ? (unsigned long long)pFinish : (unsigned long long) info.BaseAddress + info.RegionSize; current = (unsigned char*)info.BaseAddress; unsigned char* rip = 0; for (unsigned long long k = (unsigned long long)info.BaseAddress; k < finish && j < patternLen; k++, current++) { if (skipByteMask[j] || pattern[j] == *current) { if (j == 0) rip = current; j++; } else { j = 0; if (pattern[0] == *current) { rip = current; j = 1; } } } if (j == patternLen) { current = rip; break; } } else current += sysInfo.dwPageSize; }
अब हम प्रक्रिया मेमोरी में वांछित टेम्पलेट की खोज करने में सक्षम हैं, लेकिन अभी तक नहीं पता है कि क्या देखना है। फ़ेक अकाउंट के तहत स्टीम चलाएं और अपना पसंदीदा डीबगर खोलें (आइए सहमत हैं कि लेख पढ़ने के समय के लिए x64dbg आपके लिए भी है - मेरे पास आईडीए प्रो के लिए भुगतान लाइसेंस नहीं है), इसमें से dota2.exe चलाएं ... \ स्टीम \ स्टीमैप्स \ डायरेक्टरी आम \ dota 2 बीटा \ गेम \ बिन \ विन ६४। सिद्धांत रूप में, मैंने यह नहीं देखा कि वीएसी धोखा इंजन और x64dbg के प्रति उदासीन नहीं था, मुझे याद नहीं है कि इन उपकरणों का उपयोग करते समय खाते पर प्रतिबंध लगा दिया गया था। वैसे, डिबगर में एक ScyllaHide प्लगइन होता है जो सिस्टम कार्यों को स्वीकार करता है जैसे NtCreateThreadEx, NtSetInformationThread, आदि, इसके काम के तथ्य को छिपाते हुए, आप इस प्लगइन को स्थापित कर सकते हैं।प्रत्येक स्टॉप पर (10-15 होगा), हम रन (F9) का उपयोग करना जारी रखते हैं। जब खेल शुरू होता है, तो हम मेनू देखेंगे और शोध शुरू कर सकते हैं। खेल शुरू करने के बाद, लाइनों पर एक खोज करें (खोजें-> सभी मॉड्यूल-> स्ट्रिंग संदर्भ), "DoIncludeScript" फ़िल्टर सेट करें। चित्र 25 - खेल प्रक्रिया की स्मृति में लाइनों की खोज करना पहले परिणाम पर डबल-क्लिक करके डिस्सेम्बलर (सीपीयू टैब) पर जाएं। यह हमारा शुरुआती पता होगा, क्योंकि यह client.dll में है, बाकी के परिणाम server.dll और animationsystem.dll में हैं। हम प्राप्त पते से कॉल का एक ग्राफ बनाते हैं। चित्र 26 - कॉल ग्राफ़ , विघटन के बाद, हम प्रवेश बिंदु पाते हैं जहाँ DoIncludeScript का उपयोग किया जाता है - ग्राफ़ का चौथा नोड। दरअसल, फंक्शन ही।

चित्र 27 - DoIncludeScriptग्राफ़ फ़ंक्शन । चित्र 28 - DoIncludeScript से कॉल ग्राफ फ़ंक्शन के उपयोग को कम करना निम्नलिखित कोड और इसकी कॉल की जगह को दर्शाता है ( डिकम्पोज़िलेशन ग्राफ़ से किया जाता है, डिस्सेम्बलर से नहीं)। चित्र 29 - DoIncludeScript फ़ंक्शन के लिए कॉल को घटाकर DoIncludeScript फ़ंक्शन को कॉल के चित्र 27 में दिए गए निर्देशों से एक टेम्पलेट लिखें। तर्क बदल सकते हैं, क्रमशः, हम खोज करते समय टेम्पलेट में तर्कों को छोड़ना चाहते हैं, हम उन्हें "??" द्वारा निरूपित करते हैं। मुझे निम्नलिखित मिला: 40 57 48 81 ईसी ??

?? ?? ??
48 83 3 डी ?? ?? ?? ?? ??
48 8B F9 0F 84. टेम्प्लेट को संकलित करने के लिए, हमने चित्र 28 से ग्राफ के पहले नोड का उपयोग किया, जिसके निर्देश चित्र 27 में पाए जा सकते हैं।Lua Silk_way.lua पर एक स्क्रिप्ट बनाएं, इसे "... स्टीम / स्टीम \ स्टीम्प्स \ आम 2 डॉटा 2 बीटा" में डालें। \ game \ dota \ स्क्रिप्ट \ vscripts "। print("SILK_WAY START") local first = Entities:First() while (first ~= nil) do local position = first:GetAbsOrigin() local strInfo = "[" .. "pos:" .. tostring(position.x) .. "," .. tostring(position.y) .. "," .. tostring(position.z) .. "]" DebugDrawText(position, strInfo, true, 300.0) first = Entities:Next(first) end print("SILK_WAY FINISH")
यह स्क्रिप्ट सभी संस्थाओं को दरकिनार करती है और अपनी स्थिति के अनुसार निर्देशांक प्रदर्शित करती है।ऊपर के प्रलेखन और चित्र 29 से विघटित कोड का उपयोग करके फ़ंक्शन को घोषित करें। typedef bool(*fDoIncludeScript)(const char*, unsigned long long);
फंक्शन कॉल। HRESULT WINAPI EndSceneHook(IDirect3DDevice9* pd3dDevice) { if (controller == nullptr) { controller = new GameController(); controller->SetDevice(pd3dDevice); fDoIncludeScript DoIncludeScript = (fDoIncludeScript) scanner->FindPattern("client.dll", "40 57 48 81 EC ?? ?? ?? ?? 48 83 3D ?? ?? ?? ?? ?? 48 8B F9 0F 84"); DoIncludeScript("silk_way", 0); }
कार्यान्वयन के बाद, हम खेल की संस्थाओं की स्थिति के बारे में जानकारी देखेंगे। चित्र 30 - कार्यान्वयन परिणाम अब हम अपनी स्क्रिप्ट चलाने में सक्षम हैं। लेकिन वे लुआ में निष्पादित किए जाते हैं, और आइए हम बताते हैं कि रोशन की मृत्यु C ++ कोड में हमारे लिए आवश्यक है (क्योंकि हमारे पास उस पर लिखा मुख्य तर्क है), हमें क्या करना चाहिए? हमें स्रोत SDK और Source2Gen का उपयोग करके उसी तरह (जैसे हमने DoIncludeScript के लिए किया था), इंजन फ़ंक्शंस, और ब्याज की अन्य फ़ंक्शंस के लिए आवश्यक कार्यों के लिए पॉइंटर्स ढूंढना होगा। लेकिन इसके बारे में अगले भाग में, जहां हम एक संकेतक को संस्थाओं की सूची में पाएंगे और खेल के यांत्रिकी के करीब तर्क लिखेंगे। आप एक बार में सब कुछ चाहते हैं, आप के एक साधन के रूप में आप के लिए संलग्न कोशिश कर सकते हैं इस , यह , यह और इस
लिंक।11. निष्कर्ष
अंत में, मैं हर किसी को धन्यवाद देना चाहता हूं जो अपनी सर्वश्रेष्ठ प्रथाओं और ज्ञान को रिवर्स के क्षेत्र में साझा करता है, दूसरों के साथ अपने अनुभव को साझा करता है। प्रेटॉग के बिना केवल डोटा 2 के बारे में बोलते हुए , मैंने चीट इंजन का उपयोग करके गेम की डेटा संरचना प्राप्त करने के लिए बहुत समय मारा होगा, और किए गए उपलब्धियों को किसी भी वाल्व अपडेट के साथ तोड़ सकते हैं। अपडेट्स ने स्थिर स्टेटर्स को पाया और कभी-कभी संस्थाओं की संरचना को बदल दिया। में or75 मैं उपयोग DoIncludeScript समारोह देखा था और इसका इस्तेमाल खेल इंजन के पाठ उत्पादन के माध्यम से एक उदाहरण स्थापित करने के लिए।प्रस्तुति की सादगी की खोज में, मुझे कुछ याद आ सकता है, विभिन्न मामलों को छोड़ दें जिन्हें मैंने ध्यान देने योग्य माना, या इसके विपरीत, स्पष्टीकरण को फुलाया - यदि एक चौकस पाठक को ऐसी त्रुटियां मिलती हैं, तो मुझे उन्हें सुधारने और टिप्पणियों को सुनने में खुशी होगी। स्रोत कोड लिंक पर पाया जा सकता है ।लेख पढ़ने के लिए समय निकालने वाले सभी का धन्यवाद।