البافر أوفر فلو : فيض المكدس - before overflow

السلام عليكم ورحمة الله وبركاته



إن شاء الله تكونوا في تمام الصحة والعافية إخواني


مامعنى بافر ؟

البافر هو مكان مؤقت في الذاكرة لتخزين البيانات .

مامعنى بافر أوفر فلو ؟

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

ملاحظة في لغة السي :

الوظيفة main()هي دائما أول وظيفة يتم إستدعائها عندما تبدأ عملية تنفيذ البرنامج .ولا يوجد برنامج لايحتوي على هذه الوظيفة وكل برنامج يحتوي على وظيفة main () واحدة فقط .

عند تعريف وظيفة نفتح المعقف "{ " ثم نكتب التعليمات تبع تلك الوظيفة ثم نغلق المعقف "}"

نأخذ مثال لبرنامج مصاب :


#include <stdio.h>

function()
{
    char buffer[4];
 gets(buffer);
 puts(buffer);
}

main()
{
     function();
  return 0;
}
الوظيفة المصابة في هذا البرنامج هي :





function()
{
    char buffer[4];
 gets(buffer);
 puts(buffer);
}
أولا قمنا في هذه الوظيفة بتعريف الرمز بافر بحجم 4 بايت .
كود:
 char buffer[4]
ثم إستعملنا الوظيفة getsلأخذ مدخلات من المستخدم وملئ البافر (مكان في الذاكرة) بهذه المدخلات.
كود:
  gets (buffer)

ثم إستعملنا الوظيفة putsلعرض هذه المدخلات على
الترمينال

 puts(buffer)

ماهو الكود المصاب هنا بالتحديد ؟

يكمن الخطر هنا في الوظيفة gets لأنها لاتتحقق من حجم مدخلات المستخدم .
فنحن قمنا بتخصيص مكان في الذاكرة حجمه 4 بايت لتخزين هذه المدخلات التي يمثلها المتغير buffer ..

فمذا لو قام المستخدم بإدخال بيانات حجمها أكبر من 4 بايت ؟

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





كما رآينا في الصورة السابقة في الأول أدخلنا بيانات حجمها أصغر من 4 بايت فأرجع لنا البرنامج نفس البيانات المدخلة

لكن المرة الثانية أدخلنا بيانات تجاوز حجمها 4 بايت فتوقف البرنامج وعمل كراش .



طيب لو نرجع الآن إلى صورة المكدس التي تحدثنا عليها سابقا .




كنا قد قلنا أنه أولا عندما يتم إستدعاء وظيفة فوسائط argumentsهذه الوظيفة يتم تخزينها في المكدس مثلما هو واضح في الصورة

و ثم RETأيضا تخزن في المكدس والتي قلنا هي تمثل عنوان التعليمة القادمة التي سيقوم المعالج بتنفيذها .

ثم يتم تخزين العنوان القديم للمسجل EBP داخل المكدس .

ثم يتم تخزين المتغير المحلي الذي عرفناه في الوظيفة الجديدة .


خلينا الآن نشوف شكل المكدس مع برنامجنا المصاب




أولا لو نشوف في كود برنامجنا أن الوظيفة function ليس لها arguments لكي يتم دفعهم داخل المكدس .

لكن عندما تقوم main البرنامج بنقل التنفيذ إلى الوظيفة functionحيتم دفع عنوان التعليمة القادمة ( return 0 داخل المكدس .

والتي مثلناها في الصورة ب RET

ثم عندما يبدأ التنفيذ للوظيفة function يعني حنرجع إلى الفوق هنا :

كود:
function()
{
    char buffer[4];
 gets(buffer);
 puts(buffer);
}
يتم تخزين العنوان القديم لل EBP داخل المكدس كما مبين في الصورة

ثم يتم بعد ذلك دفع المتغير المحلي buffer داخل المكدس

كما مبين في الصورة وفي هذا المثال حجمه 4 بايت .

وبعدما يتم دفع المتغير المحلي داخل المكدس بالطبع يجب على ESP أن تغير مكانها .



تعلو الآن نتأكد من شكل المكدس كما في الصورة السابقة بطريقة عملية


قبل كل شيء إخواني نقوم بوقف الحمايات التالية لتبسيط الموضوع وحنتحدث عليهم لاحقا إن شاء الله .

نوقف حماية التوزيع العشوائي لعناوين مقاطع الذاكرة الإفتراضية أي حماية ( ASLR)

كود:
 echo >> 0 /proc/sys/kernel/randomize_va_space
وعند عملية compilation لو ماطلعت معاك segmentation fault قم بإضافة هذه الخاصية كالتالي : ((إيقاف حماية المكدس ))

كود:
 
gcc -ggdb -mpreferred-stack-boundary=2 -fno-stack-protector -o function function.c
نوقف حماية التوزيع العشوائي لعناوين مقاطع الذاكرة الإفتراضية أي حماية ()
نقوم بفتح البرنامج مع GDB

أولا نقوم بعرض كود البرنامج بالضغط على list كما في الصورة التالية :





طيب الآن نقوم بتحويل الوظيفة main إلى لغة الأسمبلي يتم ذلك بتحويل كل وظيفة على حدى (تذكروا أن أول وظيفة يتم إستدعائها عند تنفيذ كل برنامج بلغة السي هي الوظيفة main() )




الآن نقوم بعمل نقطتان وقوف وحدة عند الوظيفة function والآخرى عند الوظيفة getsكما في الصورة التالية :





نقوم الآن بتشغيل البرنامج




كما تلاحظون في الصورة البرنامج توقف أين وضعنا breakpoint

ثم قمنا بعرض محتوى المكدس إبتداء من عنوان ESP

ماعرضناه هو شكل المكدس قبل إستدعاء الوظيفة function

طيب نواصل التنفيذ كما في الصورة التالية :



طيب مقارنة بمحتوى المكدس سابقا

كما لاحظتم هذه البيانات 0xbffff368 تغير مكانها وتم دفع 12 بايت جديدة داخل المكدس والتي تمثل :

4 bytes لحجم مكان البافر التي قمنا بتعريفها .

و 4 bytes ل لالعنوان القديم ل EBP

و 4 bytes ل RET .

كما توضح الصورة التالية :




طيب نواصل الآن التنفيذ




هنا قمنا بتنفيذ الوظيفة getsوالتي ذكرنا أنها تأخذ مدخلات

من عند المستخدم وقمنا بإدخال بيانات أكبر من 4 بايت وهي AAAAAAAAAA فتم كتابة AAAA في المكان

المخصص للبافر فلم يسعها هذا المكان ففاضت على المكان المجاور في الذاكرة والذي يحتوي على EBP - old فتم إستبدال EBP oldبAAAAوأيضا لم يسعها هذا المكان ففاضت أيضا على المكان المجاور والذي يحتوي على RET فتم إستبدال 2 بايت من RETب AA ثم واصلنا التنفيذ فتوقف البرنامج وكتب
"Segmentation Fault

وذلك لأن المعالج عندما أراد تنفيذ آلتعليمة التالية الذي يشير إليها المسجل EIPوهي RETلم يجد أي تعليمة في البرنامج تحمل هذا العنوان 0x08004141 فتوقف .

يعني ببساطة نحن محتاجين لتعبئة أول 8 بايت لكي نصل إلى قيمة RET ثم نحتاج إلى إستبدال قيمة RETبقيمة جديدة وهذه القيمة الجديدة تمثل عنوان التعليمة التالية التي سيقوم المعالج بتنفيذها .

طيب تعالو الآن نقوم بإضافة وظيفة جديدة للبرنامج المصاب تبعنا .

كود:
#include <stdio.h>

function()
{
    char buffer[4];
 gets(buffer);
 puts(buffer);
}

zero_cool()
{

     printf("salam alikom\n")

     exit(0);

{

main()
{
     function();
  return 0;
}
طيب اٱن نقوم بعملية compilation ونفتح البرنامج مع GDB ثم نقوم بتحويل الوظيفة zero_cool إلى لغة الأسمبلي كما هو مبين في الصورة التالية :





مذا سنفعل بالظبط ؟

قبل قليل قلنا نصل إلى قيمة RET بعد 8 بايت .
ماسنفعله هو حنقوم بإرسال 8 بايت الأولى أي 4 بايت لتعبئة مكان old EBP و 4 بايت لتعبئة مكان المتغير buffer
ثم 4 بايت لنستبدل RETبعنوان الوظيفة zero_cool

ونشوف مذا سيحصل .





كما رأينا في الصورة إخواني قمنا بتغيير مسار البرنامج وجعلنا من EIP تشير إلى عنوان الوظيفة التالية وهي الوظيفة zero_coolوذلك بإستخدام هجمة البافر أوفر فلو .

طيب لو تلاحظون قمنا بقلب عنوان الوظيفة zero_cool في الذاكرة

المكدس مخزن من أعلى الذاكرة إلى أسفل الذاكرة .

لكن البرنامج عندما يصل إلى البيانات المخزنة في المكدس يقوم بقرائته بالعكس أي من أسفل الذاكرة إلى أعلى الذاكرة .

يعني عندما نقوم بتخزين \xe4\x83\x04\x08


فال \ xe4 حتكون في أعلى الذاكرة


و \x08 حتكون في أسفل الذاكرة

المشاركات الشائعة من هذه المدونة

بحث مرتقب بخصوص علم البيانات الضخمة Big Data وتحليلها - تدوينة للإعداد

توضيحات بخصوص اساليب التخفي - The Onion Router

أسئلة شائعة حول إضافة Mailvelope والإجابة عليها