ما هو هجوم تجاوز سعة المخزن المؤقت وكيف يمكن منعه؟
ما المقصود بهجوم تجاوز سعة المخزن المؤقت؟
يحدث Buffer Overflow عندما تتجاوز كمية البيانات المكتوبة في موقع من الذاكرة الحجم المخصص له مسبقاً. وعند وقوع ذلك، قد تتعرض البيانات للتلف، أو يتعطل البرنامج، أو في الحالات الأخطر قد يتم تنفيذ تعليمات برمجية خبيثة يستغلها المهاجم للسيطرة على التطبيق أو النظام.
تظهر هذه الثغرة غالباً في اللغات التي تتعامل مباشرة مع الذاكرة مثل C وC++ وObjective-C. ورغم أن بعض اللغات الحديثة مثل Python تُعد أكثر أماناً من حيث إدارة الذاكرة، فإنها قد تبقى معرضة للخطر إذا اعتمدت على مكتبات مكتوبة بهذه اللغات منخفضة المستوى.

كيف تُخصَّص الذاكرة داخل البرامج؟
لفهم ثغرات Buffer Overflow بشكل صحيح، يجب أولاً التعرف إلى الطريقة التي تُدار بها الذاكرة داخل البرامج. في برامج C مثلاً، يمكن حجز الذاكرة بطريقتين شائعتين:
- على
Stackأثناء الترجمة أو ضمن نطاق الدوال المحلية. - على
Heapأثناء التشغيل بشكل ديناميكي.
فيما يلي مثالان مبسطان:
int numberPoints = 10;
int* ptr = malloc(10 * sizeof(int));
قد يحدث تجاوز السعة في Stack أو في Heap. لكن الاستغلال العملي يكون في كثير من الأحيان أسهل على Stack، لأن هذا الجزء من الذاكرة يحتوي على عناوين العودة الخاصة بالدوال، وهي هدف شائع للمهاجمين.
الفرق بين تجاوز السعة في Stack وHeap
تجاوز السعة في Stack
يُعد الأكثر شهرة وخطورة، لأنه قد يتيح للمهاجم الكتابة فوق return address الخاص بالدالة، ثم توجيه التنفيذ إلى تعليمات خبيثة.
تجاوز السعة في Heap
يحدث في الذاكرة الديناميكية المخصصة أثناء التشغيل. ورغم أنه قابل للاستغلال، فإن تنفيذه غالباً أصعب من تجاوز Stack، لأنه يتطلب العبث بمؤشرات أو هياكل بيانات أكثر تعقيداً مثل function pointers.
كيف تعمل ذاكرة Stack أثناء تنفيذ البرنامج؟
عند تشغيل أي ملف تنفيذي، ينشئ النظام عملية Process، ولكل عملية مكدس استدعاء خاص بها يسمى Call Stack. وعندما يبدأ البرنامج بتنفيذ الدالة main ثم يستدعي دوال أخرى، تُنشأ لكل دالة بنية خاصة تُعرف باسم Stack Frame.
هذه البنية تحتوي عادةً على:
- المتغيرات المحلية.
- عنوان العودة
Return Address. - بيانات تنظيم التنفيذ بين الدوال.

أهم المؤشرات المستخدمة في إدارة المكدس
Stack Pointer: يشير إلى أعلى عنصر موجود حالياً في المكدس.Instruction Pointer: يحدد عنوان التعليمة التالية التي سينفذها المعالج.Base PointerأوBP: يحدد قاعدة إطار المكدس الحالي، ويُستخدم لتتبع متغيرات الدالة الحالية.
يسمح هذا التنظيم للحاسوب بمعرفة ترتيب تنفيذ التعليمات، ومتى يعود من دالة إلى أخرى بعد انتهاء العمل.
مثال مبسط على استدعاء الدوال داخل المكدس
int main() {
int j = firstFunction(5);
return 0;
}
int firstFunction(int z) {
int x = 1 + z;
return x;
}
عند استدعاء الدالة firstFunction من داخل main، تُدفع بيانات الدالة الجديدة إلى أعلى Call Stack. ويشمل ذلك المتغيرات المحلية وعنوان العودة إلى الدالة المستدعية.

تكمن الخطورة هنا في أن أي كتابة غير منضبطة داخل مصفوفة محلية أو مخزن نصي قد تمتد إلى مواقع أخرى في المكدس، بما فيها عنوان العودة نفسه.
مثال عملي على ثغرة Buffer Overflow بلغة C
int main() {
bufferOverflow();
}
bufferOverflow() {
char textLine[10];
printf("Enter your line of text: ");
gets(textLine);
printf("You entered: ", textLine);
return 0;
}
في هذا المثال، تُستخدم الدالة gets() لقراءة مدخلات المستخدم دون التحقق من حجمها الفعلي. المشكلة أن المصفوفة textLine تتسع فقط لـ 10 محارف، بينما يمكن للمستخدم إدخال نص أطول بكثير.
عندما يحدث ذلك، تبدأ البيانات الإضافية بالكتابة فوق مواقع مجاورة في الذاكرة. وإذا كان الإدخال طويلاً بما يكفي، فقد يطال return address الخاص بالدالة. وهنا يصبح بالإمكان توجيه تنفيذ البرنامج إلى مسار خبيث بدلاً من العودة الطبيعية.
لماذا تحدث ثغرات تجاوز سعة المخزن المؤقت؟
السبب الجوهري هو أن بعض الدوال القديمة في C وC++ لا تنفذ فحصاً صارماً للحدود Bounds Checking. أي أنها تقبل الكتابة في الذاكرة دون التأكد من أن حجم البيانات يطابق المساحة المخصصة.
وتزداد احتمالية ظهور الثغرة في الحالات التالية:
- عند الاعتماد على مدخلات خارجية من المستخدم أو من الشبكة.
- عندما يكون الكود معقداً ويصعب تتبع سلوك الذاكرة فيه.
- في الأنظمة التي تعتمد على مكتبات خارجية أو مكونات قديمة.
- داخل خوادم الويب، وخوادم التطبيقات، وبيئات تشغيل التطبيقات منخفضة المستوى.
وقد استُغلت هذه الفئة من الثغرات في هجمات شهيرة، ما يثبت أنها ليست مشكلة نظرية، بل خطر عملي مستمر في الأمن السيبراني.
كيف يمكن الحد من هجمات Buffer Overflow؟
1) استخدام لغات أكثر أماناً
اختيار لغة توفر حماية أفضل للذاكرة، مثل اللغات المفسرة أو المدارة، يقلل مساحة الخطر. ومع ذلك، يجب الانتباه دائماً إلى المكتبات الأصلية Native Libraries التي قد تكون مكتوبة بـ C أو C++.
2) تجنب الدوال غير الآمنة
من المهم الابتعاد عن دوال مثل gets()، واستبدالها ببدائل أكثر أماناً مثل fgets()، لأنها تسمح بتحديد الحد الأقصى لحجم الإدخال.
3) الاعتماد على المترجمات ووسائل التحليل
تستطيع بعض المترجمات Compilers وأدوات التحليل الثابت كشف الاستدعاءات غير الآمنة أو الأنماط البرمجية المعرضة للمخاطر قبل وصولها إلى بيئة الإنتاج.
4) استخدام Canaries
Canaries هي قيم حراسة تُوضع قبل عنوان العودة في المكدس. إذا حاولت البيانات المتدفقة تجاوز حدودها، تتغير هذه القيمة، فيكتشف النظام التلاعب ويوقف العملية قبل نجاح الاستغلال.
5) إعادة ترتيب المتغيرات المحلية
يمكن تقليل الأثر عبر وضع المتغيرات البسيطة Scalar Variables في مواضع أقل عرضة للتأثر، ووضع المصفوفات أو الكتل الأكبر بطريقة تحد من وصول الفيض إليها.
6) جعل المكدس غير قابل للتنفيذ
يمكن تفعيل البت NX أو No-eXecute لمنع تنفيذ التعليمات البرمجية المزروعة مباشرة داخل Stack. هذه الحماية مفيدة، لكنها ليست كافية وحدها، لأن بعض الهجمات تتجاوزها بطرق أخرى.
7) تفعيل ASLR
تقنية Address Space Layout Randomization أو ASLR تعيد توزيع عناوين الذاكرة عشوائياً عند تشغيل العملية. وبهذا يصبح من الصعب على المهاجم معرفة العنوان الصحيح للدوال أو المكتبات التي يريد استغلالها.
هل تكفي هذه الوسائل وحدها؟
لا توجد وسيلة واحدة تمنع جميع أشكال الاستغلال. لذلك تعتمد الحماية الفعلية على الجمع بين أكثر من طبقة دفاعية، مثل:
- كتابة كود آمن ومراجعته دورياً.
- تحديث المكتبات والاعتماديات باستمرار.
- تفعيل وسائل الحماية على مستوى النظام والمترجم.
- اختبار التطبيق بأدوات
Fuzzingوتحليل الثغرات.
كلما تعددت طبقات الحماية، انخفضت فرص نجاح المهاجم في تحويل الخطأ البرمجي إلى اختراق فعلي.
ملاحظة مهمة: ما هو Buffer Underflow؟
إلى جانب تجاوز السعة، توجد مشكلة أخرى تُعرف باسم Buffer Underflow. تحدث هذه الحالة عندما تتعامل أجزاء مختلفة من البرنامج مع نفس مساحة الذاكرة بطرق غير متوافقة.
على سبيل المثال، إذا حجزت مصفوفة بحجم X ثم ملأتها فعلياً ببيانات أقل من ذلك، مثل x حيث x < X، وبعدها حاولت قراءة كل X بايت، فقد تحصل على بيانات عشوائية أو قديمة من استخدام سابق للذاكرة.
في أفضل الأحوال تكون هذه البيانات عديمة الفائدة، وفي أسوأ الأحوال قد تحتوي على معلومات حساسة يمكن أن تُستغل أو تُسرّب.
أفضل الممارسات للمطورين وفرق الأمن
- استخدم واجهات آمنة للتحقق من طول المدخلات قبل معالجتها.
- راجع أي استدعاء يتعامل مباشرة مع الذاكرة أو المؤشرات.
- فعّل خصائص الحماية في المترجم ونظام التشغيل.
- أجرِ اختبارات اختراق دورية على التطبيقات الحساسة.
- اعتمد مراجعات كود جماعية خاصة في المكونات المكتوبة بـ
CوC++.
الخلاصة التقنية
ثغرة Buffer Overflow من أقدم ثغرات الذاكرة وأكثرها تأثيراً، وما زالت حتى اليوم سبباً مباشراً في كثير من الحوادث الأمنية. خطورتها لا تكمن فقط في تعطل البرنامج، بل في إمكانية تحويلها إلى تنفيذ أوامر خبيثة والسيطرة على النظام. تقنياً، أفضل نهج للوقاية هو الجمع بين كتابة كود منضبط، واستخدام دوال آمنة، وتفعيل وسائل مثل Canaries وNX وASLR، مع اختبار مستمر للتطبيقات قبل نشرها.