सबसे छोटी के लिए रिवर्स इंजीनियरिंग: कीजन को तोड़ना

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

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

मूलभूत बातों से चिपके रहने और कुछ समस्याओं से बचने के लिए, हम "क्रैक" कुछ नहीं, बल्कि ... कीगेन। 90% में, यह अंतरराष्ट्रीय कानून सहित पैक, एन्क्रिप्टेड या अन्यथा संरक्षित नहीं किया जाएगा ...



शुरुआत में यह शब्द था। दोहरा


इसलिए, हमें कीजेन और डिस्सेम्बलर की जरूरत है। दूसरे के लिए, मान लें कि यह इडा प्रो होगा। वेब पर पाया जाने वाला प्रायोगिक नाममात्र कीजन:

छवि








इडा में कीजेन फ़ाइल खोलने पर, हम कार्यों की एक सूची देखते हैं।

छवि



इस सूची का विश्लेषण करने के बाद, हम कई मानक फ़ंक्शन (WinMain, start, DialogFunc) और सहायक-सिस्टम फ़ंक्शन का एक गुच्छा देखते हैं। ये सभी मानक विशेषताएं हैं जो रूपरेखा बनाती हैं।

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

छवि



चलो केगेन चलाते हैं। वह दो-चार अंकों की लाइनें मांगता है। मान लीजिए कि एक बार में आठ वर्णों को प्रमुख गणना समारोह में भेजा जाता है। हम फ़ंक्शन कोड उप_401100 का विश्लेषण करते हैं। परिकल्पना का उत्तर पहली दो पंक्तियों में निहित है:

var_4 = dword ptr -4

arg_0 = dword ptr 8


दूसरी पंक्ति स्पष्ट रूप से ऑफसेट 8 पर फ़ंक्शन तर्क प्राप्त करने में संकेत देती है। हालांकि, तर्क का आकार 4 बाइट्स के बराबर एक दोहरा शब्द है। 8. इसलिए, सबसे अधिक संभावना है, फ़ंक्शन एक पास में चार वर्णों की एक पंक्ति को संसाधित करता है, और इसे दो बार कहा जाता है।

यह सवाल जो निश्चित रूप से उठ सकता है: फ़ंक्शन तर्क प्राप्त करने के लिए 8 बाइट की एक ऑफसेट आरक्षित क्यों है, लेकिन 4 को इंगित करता है, क्योंकि केवल एक ही तर्क है? जैसा कि हम याद करते हैं, स्टैक नीचे बढ़ता है; जब स्टैक में कोई मान जोड़ा जाता है, तो बाइट की संगत संख्या से स्टैक पॉइंटर घट जाता है। इसलिए, स्टैक में एक फ़ंक्शन तर्क जोड़ने के बाद और इससे पहले कि यह काम करना शुरू कर देता है, स्टैक में कुछ और जोड़ा जाता है। यह स्पष्ट रूप से रिटर्न पता है जो सिस्टम कॉल फ़ंक्शन को कॉल करने के बाद स्टैक में जोड़ा जाता है।



प्रोग्राम में उन स्थानों को खोजें जहां सब -01100 फ़ंक्शन कॉल पाए जाते हैं। वहाँ वास्तव में उनमें से दो हैं: DialogFunc + 97 और DialogFunc + 113 पर। हमारे यहां शुरू होने वाले निर्देश:

कोड का अपेक्षाकृत लंबा टुकड़ा
loc_401196: mov esi, [ebp+hDlg] mov edi, ds:SendDlgItemMessageA lea ecx, [ebp+lParam] push ecx ; lParam push 0Ah ; wParam push 0Dh ; Msg push 3E8h ; nIDDlgItem push esi ; hDlg call edi ; SendDlgItemMessageA lea edx, [ebp+var_1C] push edx ; lParam push 0Ah ; wParam push 0Dh ; Msg push 3E9h ; nIDDlgItem push esi ; hDlg call edi ; SendDlgItemMessageA pusha movsx ecx, byte ptr [ebp+lParam+2] movsx edx, byte ptr [ebp+lParam+1] movsx eax, byte ptr [ebp+lParam+3] shl eax, 8 or eax, ecx movsx ecx, byte ptr [ebp+lParam] shl eax, 8 or eax, edx shl eax, 8 or eax, ecx mov [ebp+arg_4], eax popa mov eax, [ebp+arg_4] push eax call sub_401100
      
      







सबसे पहले, दो SendDlgItemMessageA फ़ंक्शन को एक पंक्ति में कहा जाता है। यह फ़ंक्शन तत्व का हैंडल लेता है और इसे Msg सिस्टम संदेश भेजता है। हमारे मामले में, दोनों मामलों में Msg 0Dh है, जो WM_GETTEXT स्थिरांक के हेक्साडेसिमल समतुल्य है। यहां, दो टेक्स्ट फ़ील्ड के मानों को पुनः प्राप्त किया जाता है जिसमें उपयोगकर्ता ने "दो 4-वर्ण स्ट्रिंग्स" में प्रवेश किया। फ़ंक्शन नाम में अक्षर A इंगित करता है कि ASCII प्रारूप का उपयोग किया जाता है - प्रति चरित्र एक बाइट।

पहली पंक्ति laramaram ऑफसेट पर लिखी गई है, दूसरी, जो स्पष्ट है - var_1C ऑफ़सेट पर।

इसलिए, SendDlgItemMessageA फ़ंक्शन निष्पादित होने के बाद, रजिस्टरों की वर्तमान स्थिति को पुषा कमांड का उपयोग करके स्टैक पर सहेजा जाता है, फिर पंक्तियों में से एक का एक बाइट ecx, edx और eax रजिस्टरों को लिखा जाता है। नतीजतन, रजिस्टरों में से प्रत्येक फॉर्म लेता है: 000000 ##। तो:

  1. एसएचएल कमांड 1 बाइट द्वारा ईएक्स रजिस्टर की बिट सामग्री को स्थानांतरित करता है या, दूसरे शब्दों में, अंकगणितीय सामग्री को हेक्साडेसिमल प्रणाली में 100 या दशमलव में 256 से गुणा करता है। नतीजतन, eax फॉर्म 0000 ## 00 (उदाहरण के लिए, 00001200) लेता है।
  2. OR OR ## (इसे 00000034 होने दें) में प्राप्त eax और ecx रजिस्टर के बीच एक OR ऑपरेशन किया जाता है। नतीजतन, eax इस तरह दिखेगा: 00001234।
  3. स्ट्रिंग के अंतिम, चौथे बाइट को "मुक्त" एक्सएक्सएक्स पर लिखा गया है।
  4. पूर्व की सामग्री फिर से बाइट द्वारा स्थानांतरित कर दी जाती है, अगले OR आदेश के लिए कम बाइट में स्थान खाली कर देती है। अब eax इस तरह दिखता है: 00123400
  5. OR निर्देश निष्पादित किया जाता है, इस बार पूर्व और edx के बीच, जिसमें शामिल है, कहते हैं, 00000056। अब पूर्व 00123456 है।
  6. दो चरण SHL eax, 8 और OR को दोहराया जाता है, जिसके परिणामस्वरूप नया ecx सामग्री (00000078) "end" eax में जोड़ा जाता है। परिणामस्वरूप, ईएक्सएक्स का मूल्य 12345678 है।


तब यह मान "वैरिएबल" में संग्रहीत किया जाता है - ऑफ़सेट arg_4 पर मेमोरी क्षेत्र में। रजिस्टरों की स्थिति (उनके पिछले मूल्य), पहले स्टैक पर संग्रहीत होती है, स्टैक से खींचा जाता है और रजिस्टरों को वितरित किया जाता है। तब ऑफसेट arg_4 पर मान फिर से रजिस्टर ईएक्सएक्स में लिखा जाता है और इस मान को स्टैक पर रजिस्टर से धकेल दिया जाता है। उसके बाद सब_401100 फंक्शन कॉल फॉलो करता है।



इन ऑपरेशनों का अर्थ क्या है? सिद्धांत के बिना भी पता लगाने के लिए अभ्यास में बहुत सरल है। हम डिबगर में एक ब्रेकपॉइंट सेट करते हैं, उदाहरण के लिए, पुश ईएक्स अनुदेश पर (बस सबफंक्शन से पहले कहा जाता है) और निष्पादन के लिए कार्यक्रम चलाएं। कीजन शुरू होता है, लाइनों के लिए पूछता है। Qwer और tyui में प्रवेश करने और ब्रेकप्वाइंट पर रुकने के बाद, हम : 72657771 का मान देखते हैं। हम पाठ में डिकोड करते हैं: rewq। यही है, इन ऑपरेशनों का भौतिक अर्थ लाइन उलटा है।



अब हम जानते हैं कि सब_401100 में मूल लाइनों में से एक को प्रेषित किया जाता है, एक डबल शब्द के आकार में उल्टा हो जाता है, जो पूरी तरह से किसी भी मानक रजिस्टर में फिट बैठता है। शायद आप निर्देश उप_401100 पर एक नज़र डाल सकते हैं।

कोड का एक और अपेक्षाकृत लंबा टुकड़ा
 sub_401100 proc near var_4= dword ptr -4 arg_0= dword ptr 8 push ebp mov ebp, esp push ecx push ebx push esi push edi pusha mov ecx, [ebp+arg_0] mov eax, ecx shl eax, 10h not eax add ecx, eax mov eax, ecx shr eax, 5 xor eax, ecx lea ecx, [eax+eax*8] mov edx, ecx shr edx, 0Dh xor ecx, edx mov eax, ecx shl eax, 9 not eax add ecx, eax mov eax, ecx shr eax, 11h xor eax, ecx mov [ebp+var_4], eax popa mov eax, [ebp+var_4] pop edi pop esi pop ebx mov esp, ebp pop ebp retn sub_401100 endp
      
      





बहुत शुरुआत में, यहां कुछ भी दिलचस्प नहीं है - रजिस्टरों की स्थिति को स्टैक पर सावधानीपूर्वक संग्रहीत किया जाता है। और यहाँ पहली टीम है जिसमें हम रूचि रखते हैं - PUSHA निर्देश का पालन करते हुए। यह एक्सएक्स में फ़ंक्शन तर्क को ऑफसेट करता है जो arg_0 पर संग्रहीत होता है। तब इस मान को ईएक्सएक्स में स्थानांतरित किया जाता है। और आधा काट दिया: जैसा कि हम याद करते हैं, हमारे उदाहरण में 72657771 को सब_401100 में पारित किया गया है; बाईं ओर 10h (दशमलव में 16) के लिए एक तार्किक बदलाव रजिस्टर के मूल्य को 77710000 में बदल देता है।

उसके बाद, नॉट इंस्ट्रक्शन द्वारा रजिस्टर वैल्यू को उल्टा किया जाता है। इसका मतलब है कि रजिस्टर के द्विआधारी प्रतिनिधित्व में, सभी शून्य लोगों में बदल जाते हैं, और इकाइयां शून्य में बदल जाती हैं। इस निर्देश को निष्पादित करने के बाद रजिस्टर में 888EFFFFF होता है।

ADD निर्देश तर्क के मूल मूल्य को जोड़ता है (जोड़ता है, प्लसस, आदि) जिसके परिणामस्वरूप अभी भी एक्सएक्स रजिस्टर में निहित है (अब यह स्पष्ट है कि इसे एक्सएक्सएक्स में पहले और फिर एक्सएक्सएक्स में क्यों लिखा गया था?)। परिणाम पूर्व में सहेजा गया है। जाँच करें कि यह ऑपरेशन पूरा होने के बाद क्या दिखता है: FAF47770।

यह परिणाम एक्सएक्सएक्स से एक्साह तक कॉपी किया जाता है, जिसके बाद एसएचआर निर्देश को एक्सएक्सएक्स की सामग्री पर लागू किया जाता है। यह ऑपरेशन SHL के विपरीत है - यदि बाद वाले अंक को बाईं ओर शिफ्ट करते हैं, तो पहले उन्हें दाईं ओर शिफ्ट किया जाता है। जिस तरह एक लॉजिकल लेफ्ट शिफ्ट ऑपरेशन दो की शक्ति से गुणा करने के बराबर है, उसी तरह एक तार्किक राइट शिफ्ट ऑपरेशन भी उसी डिवीजन के बराबर है। आइए देखें कि इस ऑपरेशन का परिणाम क्या होगा: 7D7A3BB।

अब हम पूर्व और पूर्व की सामग्री के खिलाफ एक और हिंसा करते हैं: XOR निर्देश मोडुलो 2 जोड़ या "अनन्य या" है। मोटे तौर पर बोलने वाले इस ऑपरेशन का सार यह है कि इसका परिणाम केवल एकता (सत्य) के बराबर है, यदि इसके ऑपरेशन्स अस्पष्ट हैं। उदाहरण के लिए, 0 xor 1 के मामले में, परिणाम सही है, या एक है। 0 xor 0 या 1 xor 1 के मामले में, परिणाम गलत या शून्य होगा। हमारे मामले में, रजिस्टरों eax (7D7A3BB) और एक्सएक्स (FAF47770) के संबंध में इस निर्देश का पालन करने के परिणामस्वरूप, मूल्य FD23D4CB रजिस्टर eax को लिखा जाता है।



अगले LEA ecx कमांड, [eax + eax * 8] सुरुचिपूर्ण ढंग से और आसानी से 9 से गुणा करते हैं और एक्सएक्सएक्स का परिणाम लिखते हैं। तब यह मान edx पर कॉपी किया जाता है और 13 अंकों के दाईं ओर स्थानांतरित किया जाता है: हमें edx में 73213 और exx में E6427B23 मिलता है। तब - फिर से एक्सोरिम एक्सएक्स और एडएक्स, एक्सएक्स ई 6454930 में लिख रहा है। इसे eax पर कॉपी करें, इसे 9 अंको से बाईं ओर शिफ्ट करें: 8A926000, फिर इसे उल्टा करके, 756D9FFF प्राप्त करें। इस मान को एक्सएक्स रजिस्टर में जोड़ें - हमारे पास 5BB2E92F है। इसे ईएक्सई पर कॉपी करें, पहले से ही 17 अंकों - 2DD9 - और एक्सोरिम के साथ एक्सएक्सएक्स पर शिफ्ट करें। हम अंत में 5BB2C4F6 में मिलते हैं। तब ... फिर ... हमारे पास वहाँ क्या है? क्या, सभी को? ।।

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



आइए विश्लेषण के एक उच्च स्तर पर आगे बढ़ें - डिस्सेम्बलर से डिकम्पॉइलर तक। पूरे DialogFunc फ़ंक्शन की कल्पना करें, जिसमें C-like pseudocode के रूप में सब_401100 कॉल शामिल हैं। दरअसल, यह डिस्सेम्बलर इसे "छद्म कोड" कहता है, वास्तव में, यह व्यावहारिक रूप से सी कोड है, बस बदसूरत। हम देखते हैं:

अधिक कोड की आवश्यकता है। एक झिगराट बनाने की जरूरत है।
  SendDlgItemMessageA(hDlg, 1000, 0xDu, 0xAu, (LPARAM)&lParam); SendDlgItemMessageA(hDlg, 1001, 0xDu, 0xAu, (LPARAM)&v15); v5 = sub_401100((char)lParam | ((SBYTE1(lParam) | ((SBYTE2(lParam) | (SBYTE3(lParam) << 8)) << 8)) << 8)); v6 = 0; do { v21[v6] = v5 % 0x24; v7 = v21[v6]; v5 /= 0x24u; if ( v7 >= 10 ) v8 = v7 + 55; else v8 = v7 + 48; v21[v6++] = v8; } while ( v6 < 4 ); v22 = 0; v9 = sub_401100(v15 | ((v16 | ((v17 | (v18 << 8)) << 8)) << 8)); v10 = 0; do { v19[v10] = v9 % 0x24; v11 = v19[v10]; v9 /= 0x24u; if ( v11 >= 10 ) v12 = v11 + 55; else v12 = v11 + 48; v19[v10++] = v12; } while ( v10 < 4 ); v20 = 0; wsprintfA(&v13, "%s-%s-%s-%s", &lParam, &v15, v21, v19); SendDlgItemMessageA(hDlg, 1002, 0xCu, 0, (LPARAM)&v13);
      
      







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

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

वही, केवल चीनी से हिंदू में अनुवादित।
 SendDlgItemMessageA(hDlg, 1000, 0xDu, 0xAu, (LPARAM)&first_given_string); SendDlgItemMessageA(hDlg, 1001, 0xDu, 0xAu, (LPARAM)&second_given_string); first_given_string_encoded = sub_401100((char)first_given_string | ((SBYTE1(first_given_string) | ((SBYTE2(first_given_string) | (SBYTE3(first_given_string) << 8)) << 8)) << 8)); i = 0; do { first_result_string[i] = first_string_encoded % 0x24; temp_char = first_result_string[i]; first_string_encoded /= 0x24u; if ( temp_char >= 10 ) next_char = temp_char + 55; else next_char = temp_char + 48; first_result_string[i++] = next_char; } while ( i < 4 ); some_kind_of_data = 0; second_string_encoded = sub_401100(byte1 | ((byte2 | ((byte3 | (byte4 << 8)) << 8)) << 8)); j = 0; do { second_result_string[j] = second_string_encoded % 0x24; temp_char2 = second_result_string[j]; second_string_encoded /= 0x24u; if ( temp_char2 >= 10 ) next_char2 = temp_char2 + 55; else next_char2 = temp_char2 + 48; second_result_string[j++] = next_char2; } while ( j < 4 ); yet_another_some_kind_of_data = 0; wsprintfA(&buffer, "%s-%s-%s-%s", &first_given_string, &second_given_string, first_result_string, second_result_string); SendDlgItemMessageA(hDlg, 1002, 0xCu, 0, (LPARAM)&buffer);
      
      







उपसंहार


स्तर पूर्ण । अगला (और अंतिम) लक्ष्य इस एल्गोरिथ्म के अनुसार अपने keygen को लिखना है। आदत से बाहर, मैं लिनक्स शेल स्क्रिप्ट भाषा बैश में लिखूंगा। टेस्ट $ {# reg1} -gt && reg1 = `echo" $ {reg1: -8} "" एक स्ट्रिंग का एक ट्रंक्यूशन है जिसमें एक एमुलेटेड रजिस्टर वैल्यू होती है, जिसमें 8 कम वर्ण होते हैं। ऑपरेशन करते समय, अतिरिक्त वरिष्ठ बिट्स को वहां जोड़ा गया था। बाकी सभी असेंबलर लिस्टिंग का एक श्रमसाध्य अनुकरण है। मैंने उपर्युक्त असामान्य प्रोग्रामिंग हब का संकेत दिया है?



कुख्यात सब_401100 का कार्यान्वयन:

छवि



Keygen कुंजी समारोह:

छवि



परीक्षण और तुलना:

छवि






छवि








निष्कर्ष


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

छवि







All Articles