بسم الله الرحمن الرحيم,

في المقال السابق, قمنا بدراسة خوارزمية وجدناها في برنامج من برامج الـ CrackMes, تلك الخوارزمية كانت تعتمد على موارد النظام system resources مثل الساعة, التاريخ, سعة الذاكرة, الخ… و لم تكن تعتمد على أي من مدخلات المستخدم. و لكن الهندسة العكسية البرمجية نادرا ما تكون بهذا القِصَر و تلك السهولة.

سنتطرق اليوم بأذن الله تعالى على خوارزمية متقدمة قليلا عن الخوارزميات التي تناولناها في مواضيعنا السابقة, لذا يجب فهم كل نقطة في المقال جيدا, و لو صادفت نقطة غير واضحة, ارجو ترك تعليق و سأقوم بأذن الله بتعديل المقال  و تبسيط تلك النقطة.

 

البرنامج الذي سنتناوله اليوم بأذن الله هو احد برامج الـ CrackMes, و كالعادة سيكون متاحا للتحميل من خلال قسم المراجع في آخر المقال.

يرجى الضغط على الصورة لمشاهدتها بدقة أعلى.

 

في البداية, دعونا نقوم بفتح البرنامج بشكل طبيعي و نحاول تحليل واجهته و مدخلاته و مخرجاته.

 

1

 

كما نرى, قمت بادخال hello و world كمدخلات بسيطة في البداية, و بالضغط على زر Check لا شئ يحدث. قد تكون تلك مشكلة طول نص المدخلات string length, فاحيانا البرامج تعمل فقط اذا قمنا بادخال المدخلات بطول معين. بعد محاولة ادخال بعض النصوص بأطوال مختلفة, لم نلحظ ايضا أي فرق, فلا شئ يحدث عند الضغط على زر Check. الآن ليس لدينا حل سوى أن نبدأ برحلة الهندسة العكسية…

 

ba2

 

كما رأينا في المقال السابق, عندما قمنا بتحليل المتغيرات النصية للبرنامج استطعنا أن نذهب لمكان قريب من مكان خوارزمية توليد الارقام الصحيحة Valid Serial numbers, و لكن هنا الوضع مختلف, فكما نرى في الصورة السابقة, قمنا بتحليل المتغيرات النصية في البرنامج strings و لم نجد أي شئ من خلال تلك الطريقة.
الآن دعونا نتجه لطريقة اخرى و هى تحليل كل الدوال التي يستخدمها البرنامج.

 

ba3

 

كما نرى, معظم تلك الدوال هى من الدوال المكتبية Windows API.

نستطيع هنا أن نلاحظ استخدام دالة GetDlgItemTextA و هى دالة مسؤولة عن تخزين المدخلات النصية التي يدخلها المستخدم من خلال الواجهة الرسومية الى ذاكرة الحاسب. كما أنه من المنطقي أن نجد تلك الدالة مذكورة مرتين في الصورة السابقة, مما يعني أن البرنامج قد استخدمها مرتين, و هذا لأننانقوم بادخال نصين في الواجهة الرسومية للبرنامج, فسيحتاج البرنامج أن يُخزن في كل مرة النص المُدخل في الذاكرة.

لنقم الآن بالضغط مرتين على احد تلك الدوال.

 

ba4

 

الآن نحن في المكان المسؤول عن أخذ مدخلات المستخدم و تخزينها في الذاكرة, نستطيع أن نرى أن أول نداء للدالة GetDlgItemTextA هو لأخذ المُدخل Username و تخزينه في العنوان 0x004016A4, و ثاني نداء هو لأخذ المُدخل Serial و تخزينه في العنوان 0x004018A4. لنتأكد من نظريتنا, سنقوم بوضع نقطة توقف عند العنوان 0x00401392, و نقوم باعادة تشغيل البرنامج من خلال الضغط على Ctrl+F2, بعد ذلك نقوم بادخال أي نص داخل كل من مكان الـ Username و مكان الـ Serial, سنجد أن البرنامج قد توقف, و نستطيع أن نرى النصوص التي أدخلناها في كل من العناوين التي ذكرناها. من الآن فصاعدا بأذن الله سنتقدم في البرنامج باستخدام الخطوات الفردية Single Step Execution لنتمكن من فهم كل سطر ماذا يفعل و كيف يؤثر على مدخلاتنا و كيف يُوَلَّد الرقم الصحيح Valid Serial Number.

دعونا أيضا نسمي العنوان الذي يحوي مُدخل الـ Username باسم user و العنوان الذي يحوي مُدخل الـ Serial Number باسم serial, ليسهل ذلك عليّ بأذن الله الشرح باستخدام الاسماء بدلا من ذكر العناوين الرقمية كل مرة.

في البداية, نلاحظ أن بعد أول نداء للدالة GetDlgItemTextA, يقوم البرنامج بالتأكد من طول المُدخل Username, فاذا كان أطول من أو يساوي 12 (bytes 0xC بالنظام السادس عشري), لن يقوم البرنامج اصلا بأخذ و تخزين المُدخل الثاني, و لن يقوم بتوليد الرقم الصحيح لمقارنته بمُدخل المستخدم Serial Number.
اذا كان المُدخل Username أقل من 12 bytes, يقوم بأخذ مُدخل الـ Serial Number, و يتأكد اذا كان طوله مساوي لـ 12 bytes بالضبط, اذا كان غير ذلك, لن يتم توليد الرقم الصحيح.
بعد ذلك يقوم البرنامج مرة أخرى بمقارنة طول المُدخل Username بـ 8, اذا كان أقل من ذلك, مجددا لن يقوم البرنامج بتوليد الرقم الصحيح.

نفهم من هذا الجزء أن المُدخل Username يجب أن يكون طوله يساوي 8 bytes أو أكثر و أقل من bytes 12, و يجب أن يكون المُدخل Serial Number يساوي لـ 12 bytes بالضبط.

الآن ننتقل للجزء التالي من الكود…

ba5

 

يقوم الكود بالقفز للعنوان 0x004013A6, هذا الجزء من الكود كل ما يقوم به هو أخذ آخر byte من المُدخل user و ضربه في طول المُدخل user و يحفظ ناتج تلك العملية الحسابية في أول byte من المُدخل user.
بمعنى آخر, نستطيع من خلال لغة السي أن نكتب الجزء الماضي بهذا الشكل:

user[0]=user[strlen(user)-1]*strlen(user); \n

في نهاية هذا الجزء من الكود, نجد قفزة للعنوان  0x004013EE, و لكن لحظة, تعليمة تلك القفزة عنوانها 0x004013ED, و الـ Operation Code الخاص بتلك التعليمة هو 0xEB 0xFF, فبالتالي تقوم القفزة بالذهاب للمكان الذي يحوي أوله البايت 0xFF!!!
كيف ذلك؟ تعليمة تقفز الى جزء من الـ Operation Code الخاص بها!

أن الـ Operation Code ذو الرقم 0xFF هو الخاص بالتعليمة CALL, و لكن لنتأكد من ذلك, علينا أن نقفز للعنوان 0x004013EE بشكل يدوي (بدون استخدام single step, من خلال القفز اليدوي  Ctrl+G) , يجب أن يحتوي هذا العنوان على كود منطقي ليُنفذ بعد القفزة.

ba6

كما توقعنا بالفعل, تحولت تعليمة القفز التي كانت موجودة في العنوان 0x004013ED الى تعليمة CALL باستخدام جزء من الـ Operation Code الخاص بتعليمة القفز!!
اذا تلك أول طريقة حماية يستخدمها برنامج الـ CrackMe لخداع المهندس العكسي ليظن أن تلك القفزة غير صحيحة أو معطوبة, فبالتالي يكف عن تتبع الكود أو لا يستطيع أكمال قرائته.

الآن نرى أن القفزة الأصلية هي في الحقيقة دالة من برمجة صاحب البرنامج, و تبدأ تلك الدالة عند عنوان 0x00401251.

ba7

 

لن أقوم بشرح هذا الجزء بالتفصيل, فهذا بأذن الله  سهل للذي يعلم كيف يقرأ لغة المجمع Assembly language الخاصة بمعالج Intel x86. و لكني سأكتب عن ما تقوم به الدالة كلها باستخدام لغة السي. الدالة لا تقوم سوى بالتعديل على بعض الـ bytes في المُدخل user:

C Code

في آخر تلك الدالة, يحدث أمر غريب, بداية من العنوان 0x004012DA و حتى العنوان 0x004012FB, تلك التعليمات تقوم بتغيير بعض الـ bytes بداية من العنوان 0x0040104C, سأترك للقارئ معرفة ماذا يتغير في هذا الجزء.

بعد ذلك نرى تعليمة قفز في عنوان 0x0040130A, و مكان القفز هو 0x0040130B, نفس الخدعة السابقة, اذا نظرنا لمحتوى العنوان 0x0040130B, سنجد هذا الجزء من الكود:

ba8

تعليمة نداء على دالة أخرى من برمجة صاحب البرنامج, تبدأ تلك الدالة عند عنوان 0x00401166, لننتقل للدالة الجديدة.

 

ba9

 

تلك الدالة تقوم ببعض العمليات الجبرية و المنطقية على المُدخل user, تلخيصها بلغة السي كالتالي:

C Code

في نهاية الدالة, نستطيع أن نرى تعليمة قفز تستخدم نفس الخدعة للنداء على دالة ثالثة عند العنوان 0x004010BF.

ba10

 

تلك الدالة مسؤولة عن بعض التعديلات و المقارنات التي ستثبت للبرنامج اذا كان المستخدم قد أدخل الـ Serial صحيح أم لا.
تقوم الدالة بأخذ كل byte  لأول 8 bytes من المُدخل user (بعد كل التعديلات التي حدثت عليه) و تقوم باضافة القيمة 0x41 عليه, ثم تقوم بمقارنته بالـ byte من المُدخل serial بنفس ترتيبه, بمعنى أنها تأخذ أول byte من user, تضيف عليه 0x41 و من ثم تقارنه بأول byte من serial, و هكذا حتى تصل لثامن byte من user.
بعد ذلك يقوم البرنامج بمقارنة آخر 4 bytes من user بآخر 4 bytes من serial.
اذا تمت كل تلك المقارانات بنجاح, يقوم البرنامج بعمل thread جديدة, تقوم بطباعة رسالة نصية (Good Work).

بعد أن  تمكنا بفضل الله من فهم خوارزمية توليد الارقام الصحيحة, بأذن الله نستطيع أن نكتب برنامج Keygen, نُدخل له نص string مكون من 8 الى 11 bytes و يقوم بتوليد رقم صحيح لادخاله في برنامج الـ CrackMe ليقوم البرنامج بتهنئتنا بأن الرقم صحيح!

ba11

 

تستطيع أن تجد برنامج الـ Keygen في قسم المراجع آخر المقال.

 

في نهاية المقال, أترك للقارئ بعض الاسئلة التي تزيد فهمه للبرنامج و لطريقة عمله, و أرجو لمن يعرف اجابتها أن يترك الجواب في تعليق على صفحة هذا المقال.
– ماذا كان يحتوي العنوان  0x0040104C الذي لاحظنا تعديله من قِبَل البرنامج في آخر الدالة الموجودة في العنوان 0x00401251 ؟

– ماذا كانت فائدة الدالة VirtualProtect و VirtualAlloc في البرنامج؟

 

المراجع: