﻿ الهندسة العكسية : كسر حماية برنامج بسيط |
الهندسة العكسية : كسر حماية برنامج بسيط
الهندسة العكسية : كسر حماية برنامج بسيط.
و درس جديد يجمعنا و إياكم إن شاء الله سنقوم اليوم بالتعرف على مفاهيم و مبادئ الهندسة العكسية أو Reverse Engineering الذي يعد من أخطر مجالات أمن المعلومات نظرا لما يسببه من خسائر بمليارات الدولارات لمصممي برامج الحاسوب و التطبيقات و لا أبالغ ها
من منا لم يستعمل نسخة مقرصنة من هذا البرنامج أم ذاك التطبيق ؟ بداية من نظام التشغيل و وصولا إلى برامج معالجة النصوص و الألعاب و و حتى المحتوى المشفر على اجهزة البث و بات المطورون و صناع الملتيميديا في حرب شرسة مع القراصنة للحفاظ على ثمرة عملهم و أبحاثهم
لهذه الحرب عنوان وحيد ألا وهو الهندسة العكسية التي سنحاول شرح أهم مبادئها و تقنياتها
شرح اليوم مخصص لكسر حماية البرامج و لنا شرح قادم إن شاء الله لاستغلال الثغرات و حقن شفرة ضارة
سيكون الشرح مطبقا على برامج بسيطة مكتوبة بلغة السي على بيئة الويندوز فهو حبيب القراصنة
لفهم جيد للشرح سأفترض أن لكم إلماما بمعمارية الحواسيب و هندسة الذاكرة و نظم التشغيل لكن هذا لا يمنع أني سأشرح كل خلفية نظرية و كل نقطة أراها مهمة
نظرا لثراء المجال الذي لن يتسع له موضوع وحيد بطبيعة الحال
سيكون الشرح متسلسلا على شكل حلقات أو أجزاء كي لا أثقل عليكم بكم المعلومات
حسنا ماهي الهندسة العكسية ؟
لنفرض أن لديك فكرة و تريد تحويلها إلى برنامج
دورة حياة أي برنامج محترم تتكون من أربعة مراحل أساسية و هي
دراسة الحاجة : برنامجنا عبارة عن فكرة بسيطة
التصميم : تحولت الفكرة إلى مخططات UML و بدأ المصممون بالتشاجر كالعادة حول مخطط قاعدة البيانات إلخ…
البرمجة : برنامجنا الآن عبارة عن أسطر برمجية بأحد اللغات
الإستعمال : يقوم المترجم compiler بترجمة الأسطر البرمجية إلى شي يفهمه المعالج الذي لا يفهم سوى 0 و 1 و تكون النتيجة ملفا تنفيذيا عبارة عن أصفار و آحاد تحتوي على تعليمات يقوم المعالج بتنفيذها بدوائره الإلكترونية كيف تتم عملية الترجمة compilation ؟
كما نرى في الصورة تكون الشفرة المصدرية عبارة عن أسطر برمجية بصيغة معينة​
مثلا c. للبرامج المكتوبة بلغة السي​
يقوم المترجم بتحويل هذه الملفات إلى ملفات تنفيذية و أثناء ذلك يقوم بإضافة المكتبات الديناميكية و هي مجموعة من الدوال مترجمة سابقا already compiled تحتوي على أكواد و وظائف يحتاجها البرنامج (تسمى هذه العملية ب dynamic linking) نحصل في النهاية على ملف عبارة عن أصفار و آحاد و هو الملف التنفيذي الذي نتعامل معه كمستخدمين​
الترجمة قد قضت على الشفرة المصدرية و صهرتها نهائيا فالميتخدم يحصل فقط على الملف التنفيذي
أي مجموعة من الأوامر إلى المعالج أما الشفرة المصدرية فتبقى في حوزة المطورين
كما أسلفنا المستخدم يحصل على ملف تنفيذي
المستخدم العادي يقتنع بما يحصل عليه فيشغل ألعابه و يستعمل مضاد فيروساته و غيره
أما القرصان فلا يقتنع بهذا بل يريد المزيد
التطبيق له فترة تجريبية محددة أو له خصائص لا يسمح باستعمالها سوى للنسخة التجارية
واجهة التطبيق الرسومية مزعجة
البرنامج شفر ملفاتي و قال بأنه علي شراء نسخة تجارية أو إدخال رقم سلسلة صحيح لفك التشفير
البرنامج يفتقر إلى ميزة أساسية أم فيه عيب برمجي خطير
كيف يمكن أن أصلح هذا و أنا أتوفر فقط على ملف تنفيذي فيه مجموعة من الأصفار و الآحاد ؟
هنا يأتي دور الهندسة العكسية
هل تذكرون دورة حياة البرنامج المحترم ؟ في الهندسة العكسية سنقوم بعكسها بكل بساطة أي بداية من برنامج عبارة عن ملف تنفيذي سنقوم بمحاولة استنتاج تصميم البرنامج و شفرته البرمجية و بالتالي نكون قادرين على التعديل عليه بما يعجبنا مثال بسيط تخيل أن برنامجا ما يطلب منك ادخال كلمة سر حتى يتم تفعيله
هذا الأمر من الناحية البرمجية عبارة عن جملة شرطية بسيطة تأخذ متغيرا و تقارنه بثابت مخزن لديه المتغير بطبيعة الحال هة كلمة السر التي ستدخلها و الثابت هو كلمة السر الصحيحة في حالة التساوي نجد دالة تقوم بالتفعيل أما حالة عدم التساوي تعني استمرار القفل
ماذا نستطيع أن نفعل بالهندسة العكسية في هذه الحالة ؟ يمكننا تغيير الجملة الشرطية الآنفة بما يضمن لنا قبول أي مدخل
يمكننا معرفة قيمة الثابت و بالتالي استنتاج كلمة السر
يمكننا تجاهل الجملة الشرطية و مناداة الدالة فورا فور فتح البرنامج
كما رأيتم المشكل واحد و الحلول كثيرة بهذه الطريقة يتم فعل أشياء متنوعة مثل كسر حماية البرامج و تجاوز فترة التفعيل أو تجاوز الصلاحيات أو حتى تحطيم البرنامج أو تعديل سلوكه
الآن بعد أن توضحت الأمور حول الهندسة العكسية دعونا نمر للتطبيق و سأقوم بشرح كل ما سنتعرض له من مفاهيم إن شاء الله حيث أني أحببت أن أشرح بالتوازي مع تطبيق حي عوض عن بسط الأمور النظرية التطبيق الأول سنكتب شفرة مصدرية بلغة السي ليكن هذا البرنامج البسيط ​
char *mystr = (char*)malloc(sizeof(char)*50), *mypass = (char*)malloc(sizeof(char)*50);
strcpy(mypass, "tunisia-sat.com");
printf("Please enter password to unlock: ");
fgets(mystr, 49, stdin);
if(!strcmp(strcat(mypass, "\n"), mystr))
printf("Good job \n");
printf("Bad boy, try again \n");
الشفرة بسيطة جدا تنتج لنا برنامجا يطلب كلمة سر من المستخدم ثم يقارنها بأخرى لديه في حالة التطابق يطبع رسالة نجاح على الشاشة و غير ذلك تأتي رسالة خطأ عوضا عن رسالة النجاح يمكن أن تتخيلوا صلاحيات معينة كمميزات نسخة تجارية أم صلاحيات مدير
لا تعجبوا فكل البرامج تعمل بنفس المبدأ تقريبا على أن مستوى التعقيد يختلف
الآن نعطي للمستخدم الملف التنفيذي password.exe لهذا البرنامج بعد ان تمت ترجمته
لكن لا نعطيه الشفرة المصدرية و هذا ما يحدث عادة في البرمجيات التجارية غير مفتوحة المصدر
يفتح المستخدم البرنامج فيجد هذه الشاشة
هل يمكن لهذا المستخدم أن يخمن كلمة المرور الصحيحة لتفعيل البرنامج ؟ هذا ما سنفعله الآن عن طريق الهندسة العكسية فاستعدوا جيدا
بهذا يكون الجزأ الأول من الموضوع قد انتهى و ألقاكم بحول الله في الجزأ الموالي حيث سنبدأ تطبيق الهندسة العكسية على هذا البرنامج سنتكلم بالتحديد على كيفية تشغيل البرامج و بنية المعالج و الذاكرة و العمليات التي تحصل أثناء تنفيذ برنامج ما من قبل المعالج المركزي
حسنا توقفنا في الجزأ السابق عند برنامجنا البسيط الذي نريد معرفة كلمة السر التي تمكننا من حمايته كنا قد ذكرنا أن الشفرة المصدرية للبرنامج قد تمت ترجمتها عن طريق الcompiler الى ملف تنفيذي عبارة عن آحاد و أصفار لكن لسائل أن يسأل هل يمكن عكس هذه العملية بكل بساطة ؟
الإجابة نعم و لا و المثال البسيط تخيل أيها القارء الكريم عمارة مشيدة البنيان فهل يمكنك كمقاول بناء
أن تعيد تلك العمارة الشاهقة إلى ما كانت عليه من أكياس اسمنت و آجر و حديد ؟ الأمر ليس مستحيلا عقليا و لكنه مستحيل عمليا
و نفس الشيء لاسترجاع شفرة مصدرية من أصفار و آحاد إلا أن الخبر الجيد أنه يمكننا تحليل سلوك البرنامج
و مراقبة التعليمات التي قام المعالج بتنفيذها سطرا سطرا و بالتالي تحليل سلوك البرنامج و استنتاج وظائفه
ما يصعب الأمر هو أن هذه التعليمات تعد بالملايين في برنامج بسيط فما بالك في مضاد فيروسات
إلا أن القرصان الذكي يعرف اختصار مجال البحث و يركز جهوده في زوايا محددة من الذاكرة
هناك برامج تستعرض لنا التعليمات التي قام المعالج بتنفيذها و توفر لنا أدوات لتحليلها تعليمة تعليمة تسمى هذه البرامج بالمترجمات المضادة أي decompilers و توفر لنا فحصا شاملا للملفات التنفيذية و تحليلها المترجم المضاد الذي سنستعين به هو Immunity debugger و هو مجاني و مفتوح المصدر
تستطيعون تحميله من موقعه الرسمي
لا تنسوا أننا نشتغل على بيئة الويندوز و سنتعرض لهذه النقطة بالذات في سياق تحليلنا المترجم المضاد يوفر لنا أدوات فقط لا غير أما المادة الشخمة فهي التي ستجلب لنا كلمة السر و هي التي تمتعنا كل يوم بالبرامج و الألعاب
نفتح على بركة الله المترجم المضاد ثم نقوم بشحن الملف التنفيذي لبرنامجنا البسيط فيه
لا تنسوا أن تترجموا الشفرة المصدرية التي كتبناها في الجزأ السابق للحصول على الملف التنفيذي و يجب أن تكون الترجمة في بيئة الويندوز يعني استعينوا بvisual studio أو ما شابه هذا ما نحصل عليه عند شحن البرنامج
إضافة إلى ملاحظة أن البرنامج قد اشتغل
الآن بدأ المرح
ما هذا بالظبط ؟
حسنا نعلم أن أي برنامج نأمر المعالج بتنفيذه يقع شحنه من القرص الصلب إلى الذاكرة الحية
تخزن الذاكرة الحية تلك التعليمات المكونة من الأصفار و الآحاد التي يتكون منها الملف التنفيذي و يملك المعالج صلاحية الوصول إلى التعليمات
من الذاكرة بسهولة حسنا البرنامج الآن في الذاكرة لكن على أي شكل ؟
الصورة التالية تشرح لنا ذلك
هذا هو شكل البرنامج في الذاكرة و سنشرح ذلك إن شاء الله قد نحتاج تعريف دوال في برنامجنا الدوال تقبل متغيرات أليس كذلك ؟ أين تخزن الذاكرة هذه المتغيرات ؟ هذه وظيفة المكدس stack memory تخيل أنك قد استدعيت دالة strcmp بالمتغيرين a و b لتقارن بينهما ذاكرة المكدس ستخزن a و b ثم تقوم بتنفيذ تعليمات المقارنة و بعد ذلك تقوم هذه الدالة بإرجاع النتيجة و يقوم المكدس بمسح هذين المتغيرين سياسة التخزين في المكدس تعرف ب LIFO أي الذي يأتي آخرا ينصرف أولا و هذا أمر طبيعي حيث تخيل أنك استدعيت دالة ثانوية وسط دالة أخرى و هذا حقك
الدالة الثانوية لها متغيرات قام المكدس بإضافتها و عندما تعطي نتيجتها تمسح و نعود الى الدالة الأصلية التي بدورها لها متغيراتv المتغيرات الخاصة بالدالة الثانوية أضيفت آخرا إلى المكدس (طبيعي لأن الدالة الثانوية استدعيت بعد الدالة الرئيسية) لذلك تمسح أولا هل فهمتم الآن لماذا Last In First Out LIFO ؟
شيء آخر هو عندما نستدعي دالة و تعطي نتيجتها يجب أن نعود مباشرة إلى أول نقطة بعد الإستدعاء مباشرة مثلا استدعيت دالة تحسب الجذر التربيعي إذن بعد أن تعطي نتيجتها نعود لاستكمال الدالة الرئيسية و بطبيعة الحال أول دالة يراها المترجم هي الدالة main و كل الدوال الأخرى تتفرع منها في الذاكرة
هذا بالنسبة للمكدس و هو أهم شيء بالنسبة لنا Heap memory هذه المساحة من الدالة تختص بالمعطيات التي تم حجزها ديناميكيا مثلا في البرمجة الشيئية new تقوم بحجز مساحة في الHeap للمتغير الذي تم تعريفه و هي لا تهمنا كثيرا في تطبيقنا اليوم
Instructions تقوم بحجز تعليمات البرنامج
و static data تقوم بحجز الثوابت
لعلكم فهمتم الآن أن المعالج عندما يريد تنفيذ برنامج ما فانه يبحث في الذاكرة الخاصة بالبرنامج عن الجزء المخصص للتعليمات و تلك التعليمات تأمره بما يجب عليه فعله
قد تأمره بالقيام بعمليات حسابية أو بمناداة دالة ما و بالتالي حجز مساحة في المكدس و غيره نعود إلى برنامجنا
ما نراه على الشاشة أربع جهات رئيسية
الجهة العليا على اليسار مخصصة لعرض التعليمات التي يقوم المعالج بتنفيذها
مهلا نحن قلنا أن هذه التعليمات أصفار و آحاد و لكنا نرى كلمات مفهومة من قبيل mov add هذه هي لغة الassembly و هي بمثابة نقطة عبور بين لغة الآلة و المبرمج تسمح هذه اللغة بتفسير (لاحظ لم أقل ترجمة) لغة الآلة إلى شيء مفهوم للمبرمج يعني هي مجرد قاموس مثلا add ESP, 1C تفسر للمعالج ب 000101001001010 (مثال) و هي تأمره بأن يزيد للمسجل register ESP القيمة السادس عشرية 1C دون هذه اللغة يجب علينا تحليل مئات الملايين من الأصفار و الآحاد (لهذا السبب أيضا نستعمل نظام العد السادس عشري) كل معالج لديه مجموعة من التعليمات التي يستطيع فهمها و تنفيذها و تسمى بالinstruction set و إجادة مجموعة التعليمات هذه يعني القدرة على التحدث مباشرة مع المعالج و أمره بما نريد في العادة نتحدث معه عن طريق لغة مستوى عالي على غرار الجافا أو السي لكن نحن الآن قمنا بتفكيك البرنامج إلى لغته الأولية هل تذكرون ؟ لذلك يكفي تحديد أي تعليمة حساسة و تبديلها لنجبر المعالج على تنفيذ ما نريد لا ما أراده المبرمج و بالتالي نستطيع تغيير سلوك البرنامج أو كسر حمايته و بطبيعة الحال التعامل مع الassembly أهون بكثير من التعامل مع أصفار و آحاد
هل فهمتم الآن كيف تسير الأمور ؟
لكل شيء عنوان في الذاكرة و هذه هي بالظبط وظيفة الأرقام التي نراها في أقصى اليسار
يعني كل تعليمة لها عنوان في الذاكرة
و لإجبار المعالج مثلا على تخطي خطوة التحقق من كلمة السر مثلا يمكن أن نبحث عن عنوان الذاكرة الذي يحتوي على التعليمة التي تظهر لنا رسالة النجاح و من ثم من خلال تعليمة assembly بسيطة نجبر المعالج على القفز إلى هذا العنوان مهما كان المدخل الذي يدخله المستخدم الجزء السفلي من اليسار يحتوي على محتوى الذاكرة لكن بصفة مظغوطة و هو لا يهمنا كثيرا الجزء العلوي من اليمين يحتوي على محتوى المسجلات registers
ماهي المسجلات ؟
تحدثنا على المكدس و الرجوع إلى التعليمة السابقة إلخ … لكن كيف يمكن حفظ العنوان للرجوع إليه لاحقا
هذه وظيفة المسجلات
المسجلات هي نقاط ذاكرة محدودة موجودة عند المعالج (ليست ذاكرة حية) يحتاجها كثيرا تخيل نجارا يعمل طول النهار أين تراه يضع مطرقته التي يستعملها كل 5 دقائق تقريبا أكيد في جيبه و ليس في ركن مهمل من الورشة لهذا السبب تحديدا تم وضع المسجلات كذاكرة محدودة تتبع المعالج بالأساس لأنه يحتاجها جدا هناك الكثير من المسجلات لكن ما يهمنا هو المسجلات التالية ESP يقوم بخزن عنوان آخر متغير تم وضعه في المكدس أكيد سيكون في أعلى المكدس فقد قلنا أن الذي يأتي أخيرا يخرج أولا لذلك عندما نريد اخراج متغير من المكدس يكفي أن نقوم ب pop ESP بعد استكمال تنفيذ دالة ما
EIP يقوم بخزن عنوان التعليمة الموالية في البرنامج قلنا أن البرنامج عبارة عن مجموعة تعليمات مخزنة في جزء الذاكرة المسمى Instructions ينفذ المعالج تعليمة ثم ينتقل إلى التعليمة الموالية فكيف يفعل ذلك ببساطة يقوم ب add EIP, 1
عندما يقوم بتنفيذ دالة فرعية يجب عليه العودة إلى التعليمة التي تلي هذه الدالة في البرنامج الأصلي بعد الانتهاء من تنفيذها فكيف العمل ؟
ببساطة يقوم بخزن عنوان الرجوع return address قبل الإنتقال إلى التعليمات الخاصة بالدالة التي تتواجد في مساحة أخرى مستقلة من الذاكرة
و لها stack heap instructions etc. الخاص بها و هكذا
عندما نريد اجبار البرنامج على تنفيذ دالة معينة مثلا الدالة التي تعطينا صلاحيات النسخة المدفوعة يكفي أن نبحث على عنوان
هذه الدالة في الذاكرة و نكتب تعليمة assembly بسيطة تجعل EIP يتوجه مباشرة الى هذا العنوان
الجزء السفلي من اليمين يعرض لنا حالة المكدس أثناء تشغيل البرنامج
آمل أن تكون قد تكونت لديكم فكرة أولية على العمليات التي تدور بين الذاكرة و المعالج أثناء تشغيل البرامج
و كذلك بعض الأساليب المحتملة لتغيير سلوك البرنامج الأمر له علاقة معمارية كل معالج و لغة الassembly التي يفهمها
حيث أن حجم المسجلات في معمارية x86 هو 32 بيت عكس 64 بيت لمعمارية x64 و هذا يجعل التعامل مختلفا كليا مع كل معمارية
يسمح لنا المترجم المعاكس بتشغيل البرنامج تعليمة تعليمة على مستوى المعالج أي ما يعرف بالdebugging mode و إليكم بعض الأوامر المفيدة
F2 تحديد نقطة بداية
F7 تشغيل تعليمة واحدة
كما توجد عدة أوامر أخرى في شريط المهام في أعلى الشاشة أدعوكم لاكتشافها على كل نعود إلى مشكلتنا الأساسية نريد اكتشاف كلمة السر التي تسمح بتفعيل البرنامج
لنفكر بطيقة براغماتية
البرنامج يقبل مدخلا وحيدا هو كلمة السر ثم يخرج لنا رسالة تقول بأن الكلمة إما صحيحة أو خاطئة إذن لا بد أنه يقارن بين مدخلنا و بين قيمة مخزنة لديه كي يقرر بناءا على جملة شرطية ما يجب أن يقدم لنا كرسالة ؟
هنا أعود إلى كوننا نشتغل على منصة ويندوز
نعلم أن الدالة التي تقارن بين كلمتين قامت win API بتعريفها تحت اسم strcmp (ليست هي نفسها strcmp في السي) لهذا ينتغي أن نعرف متى تم النداء على هذه الدالة في البرنامج الرئيسي لذلك ننقر يمينا ثم search for ثم all intermodular calls أي سنستعرض كل النداءات على الدوال في البرنامج ملاحظة يجب أن تكون متواجدا في مساحة الذاكرة الخاصة بالبرنامج أي المعنونة بداية من 00401000 و ليس في مساحة الذاكرة المخصصة لأحد ملفات الdll أي المكتبات الديناميكية هل تذكرون link edition التي سبق و تحدثنا عنها ؟ تجدونها معنونة في العادة ب 7… و هكذا لفعل ذلك ننقر يمينا ثم view ثم Module الخاص باسم البرنامج
كان تخميننا صحيحا و وجدنا ضالتنا إذن ننقر عليها و نتجه الى العنوان الذي نوديت فيه هذه الدالة يعني ان البرنامج بداية من هذه النقطة سيقارن بين كلمتين احداهما مدخل المستخدم و الآخر الكلمة الصحيحة
حسنا هذا نداء دالة مع متغيرين فأين سيتم تسجيل المتغيرين ؟ نعم في المكدس
إذن ماذا ينبغي أن نفعل للإطلاع على المتغير الذي يحتوي على كلمة السر الصحيحة ؟
نعم علينا أن نلقي نظرة على المكدس بعد تشغيل هذه التعليمة
هذا ما سنفعله من خلال تركيز نقطة توقف Break point بالظغط على F2 في هذا العنوان ثم تشغيل البرنامج بF9
نلاحظ هنا أن البرنامج كالعادة يطلب منا كلمة السر فهنا نقوم بإدخال ما نشاء فهدفنا في النهاية قراءة محتوى المكدس بعد هذه العملية
و بعد إدخال كلمة السر و الظغط على Enter نقوم الآن بالإطلاع على المكدس و النتيجة أمامكم على الشاشة
تم كسر البرنامج و معرفة كلمة السر التي كانت ضرورية لتفعيله للتحقق نجرب إدخالها
هكذا تمكنا من كسر البرنامج من خلال معرفة كلمة السر
تخيلوا مكان رسالة النجاح دالة تعطينا أمورا مفيدة أو وصول لقاعدة بيانات أو ما شابه ؟
استعملنا هنا طريقة مباشرة لكسر البرنامج من خلال معرفة الكلمة التي تنجح في تفعيله
لكن توجد حلول أخرى حيث كان بالإمكان إجبار البرنامج على اظهار رسالة النجاح مهما كان المدخل و ذلك من خلال التلاعب بالقيمة المسجلة في الEIP كreturn address بعد انتهاء دالة المقارنة و يمكننا اعتماد هذه الطريقة عندما تكون كلمة السر مشفرة مثلا
أرأيتم الحلول موجودة بكثرة أهم شيء هوالتفكير المنطقي و السليم
إلى هنا ينتهي درس اليوم على أمل أنكم استفدتم منه و استمتعتم به
في الدرس القادة سنقوم بتحليل برنامج بسيط يطلب هذه المرة رقما تسلسليا و اسم مستخدم لتفعيله
سنقوم إن شاء الله بكتابة مولد أرقام تسلسلية أو ما يسمى بال key generator كما سنقوم بطريقة أخبث بكثير لكسر البرنامج
عن طريق كتابة Patch
إلى اللقاء في الدرس القادم في موضوع جديد بحول الله أنتظر أسئلتكم و اقتراحاتكم
شرح لهجمة الرجل في المنتصف في شبكة محلية
حل مشكل فشل تفكيك apk مع برنامج Advanced ApkTool
كلمات مفاتيح البحث :
العكسية,الهندسة,الهندسة العكسية,برنامج بسيط,حماية,كسر