بسم الله الرحمن الرحيم
في هذا المقال سنتعرف على كيفية تعريف الدوال.
1. أطر المكدس (Stack Frames)
في أعلى مستويات التجريد، يُنظَّم المكدس إلى أطر مكدس (Stack Frames). كل استدعاء دالة يُنشئ إطار مكدس جديدًا يحتوي على جميع البيانات المتعلقة بتلك النسخة من الدالة.

الشكل 4.1: المكدس منظّم في سلسلة من أطر المكدس
عندما تستدعي دالةٌ دالةً أخرى، تُنشئ الدالة المستدعاة إطار مكدسها الخاص فوق إطار مكدس الدالة المستدعية. وعندما ترجع الدالة المستدعاة، تنظّف إطار مكدسها. وبناءً عليه، فإن عمر إطار المكدس يطابق مدة تنفيذ دالته.

الشكل 4.2: كل إطار مكدس يتكون من وسائط الدالة وعنوان الإرجاع وEBP المحفوظ والمتغيرات المحلية
يقرّب الشكل 4.2 لتوضيح البنية الداخلية لإطار المكدس. تذكّر أن ESP (مؤشر المكدس) يشير دائمًا إلى قمة المكدس. وبينما تنفّذ الدالة نفسها، تدفع البيانات وتسحبها، مما يجعل ESP يتحرك باستمرار. وهذا يجعل ESP نقطة إسناد غير ملائمة للوصول إلى عناصر المكدس، لأن إزاحاتها بالنسبة إلى ESP تتغير باستمرار.
لحل هذه المشكلة، تستخدم الدوال سجل EBP (نقطة الأساس — Base Pointer) كنقطة ارتساء مستقرة. تُنشئ مقدمة الدالة (Prologue) إطار المكدس وتضبط EBP على موقع ثابت داخل الإطار، مما يتيح الوصول إلى الوسائط والمتغيرات عبر إزاحات ثابتة من EBP.
يحتوي إطار المكدس القياسي على العناصر التالية (مرتبة من العنوان الأعلى إلى الأدنى):
- وسائط الدالة: تدفعها الدالة المستدعية قبل الاستدعاء. يُوصَل إليها بإزاحات موجبة نسبةً إلى
EBP(مثل[ebp+8]). - عنوان الإرجاع: تدفعه تعليمة
CALLتلقائيًا. يقع عند[ebp+4]. - EBP المحفوظ (Saved EBP): تدفع الدالة المستدعاة قيمة
EBPالخاصة بالمستدعي للحفاظ عليها. ثم يُضبطEBPالحالي ليشير إلى هذا الموقع، مشكِّلًا الأساس للإطار الجديد. - المتغيرات المحلية: مساحة تخصّصها الدالة لاستخدامها الخاص. يُوصَل إليها بإزاحات سالبة نسبةً إلى
EBP(مثل[ebp-4]).
2. وسائط الدالة والمتغيرات المحلية
push 20
push 10
call calculate
...
calculate proc near
push ebp
mov ebp, esp
sub esp, 8
القائمة 4.1: إنشاء إطار مكدس جديد
توضح القائمة 4.1 كيف تُنشئ مقدمة الدالة النموذجية إطار مكدس:
- وسائط الدالة: تدفع الدالة المستدعية وسيطين،
10و20، إلى المكدس. - عنوان الإرجاع: تدفع تعليمة
CALLعنوان الإرجاع إلى المكدس تلقائيًا. - EBP المحفوظ: تعليمة
calculateالأولى (push ebp) تحفظEBPالخاص بالمستدعي بحيث يمكن استعادته. التعليمة التالية (mov ebp, esp) تنسخ مؤشر المكدس الحالي إلىEBPلتأسيس مؤشر الأساس لإطار مكدسcalculate. - المتغيرات المحلية: التعليمة الأخيرة (
sub esp, 8) تعدّلESPلحجز مساحة للمتغيرات المحلية. ويختلف مقدار التعديل حسب حجم المتغيرات المحلية وعددها.

الشكل 4.3: إطار المكدس المُنشأ بواسطة التسلسل في القائمة 4.1
يوضح الشكل 4.3 إطار المكدس الجديد بعد تسلسل القائمة 4.1. هذا التسلسل PUSH-MOV-SUB هو مقدمة الدالة القياسية (Standard Function Prologue). بعد إنشاء إطار مكدسها، تصل الدالة إلى متغيراتها المحلية عند إزاحات سالبة نسبةً إلى EBP، وإلى وسائطها عند إزاحات موجبة.
3. اصطلاحات الاستدعاء (Calling Conventions: STDCALL و CDECL)
تحدد اصطلاحات الاستدعاء كيفية تفاعل الدوال مع بعضها، فهي تفرض:
- كيفية تمرير الوسائط إلى الدالة المستدعاة.
- مكان تخزين قيمة الإرجاع.
- كيفية تنظيف المكدس بعد الاستدعاء.
يجب أن تتفق الدالتان المستدعية والمستدعاة على اصطلاح الاستدعاء نفسه لمنع السلوك غير المتوقع وتلف المكدس.
أكثر اصطلاحي استدعاء شيوعًا في برامج ويندوز ذات 32-بت x86 هما:
stdcall: اصطلاح الاستدعاء القياسي لواجهة برمجة ويندوز (Windows API).cdecl: اصطلاح الاستدعاء الافتراضي لمترجمات لغة C.
| stdcall | cdecl |
|---|---|
| |
القائمة 4.2: مقارنة بين اصطلاحي الاستدعاء stdcall و cdecl
مقارنة بين اصطلاحي الاستدعاء:
- الوسائط: كلا الاصطلاحين يدفع الوسائط إلى المكدس من اليمين إلى اليسار. ففي استدعاء الدالة
calculate(10, 20)، تُدفع القيمة20أولًا تليها10. - قيم الإرجاع: كلا الاصطلاحين يرجع الناتج في سجل
EAX. - تنظيف المكدس:
stdcall(تنظيف المستدعى): تنظّف الدالة المستدعاة الوسائط قبل الإرجاع. في العمود الأيمن من القائمة 4.2، تعليمةretn 8تسحب عنوان الإرجاع وتزيدESPبمقدار 8 بايتات.cdecl(تنظيف المستدعي): ينظّف المستدعي الوسائط بعد رجوع الدالة المستدعاة. في العمود الأيسر من القائمة 4.2، يعدّل المستدعيESPبالتعليمةadd esp, 8فور رجوعcalculate.
4. المتغيرات المحلية (Local Variables)
| C | Assembly |
|---|---|
| |
القائمة 4.3: برنامج C صغير بدالة تستخدم متغيرات محلية، وشيفرة الملف المُصرَّف المقابلة له
تحتوي الدالة main في القائمة 4.3 على متغيرين محليين، a و b:
- تُهيّئ الدالة
aإلى 0 وbإلى 1. في التجميع، تُجري تعليمتاMOVالأوليان هذه التهيئة، فتضبطان موقعَي المكدسvar_4إلى 0 وvar_8إلى 1. تُنشئ IDA تلقائيًا تسميات مثلvar_4وvar_8لتمثيل الإزاحات الرقمية الخام (مثل[ebp-4]و[ebp-8]). - بعد ذلك، يجمع البرنامج
aوbويخزن الناتج فيa. ولأن x86 لا تستطيع جمع موقعين في الذاكرة مباشرةً، يُولّد المترجم تسلسلًا من ثلاث خطوات: يحمّلvar_4إلى سجل، ثم يجمعه مع قيمةvar_8، ثم يخزن الناتج مجددًا فيvar_4. - أخيرًا، يستدعي البرنامج
printfمع سلسلة تنسيق والوسيطa. يدفع البرنامج الوسائط من اليمين إلى اليسار: يدفعvar_4أولًا ثم عنوان سلسلة التنسيق.
5. المتغيرات العامة (Global Variables)
| C | Assembly |
|---|---|
| |
القائمة 4.4: برنامج C صغير بدالة تستخدم متغيرات عامة، وشيفرة الملف المُصرَّف المقابلة له
تعرض القائمة 4.4 البرنامج نفسه الوارد في القائمة 4.3 تقريبًا، لكنها تستبدل المتغيرات المحلية بمتغيرات عامة.
المتغيرات المحلية في القائمة 4.3 مخصصة ضمن إطار مكدس الدالة؛ ولهذا فهي لا توجد بعد انتهاء عمر الدالة: فعندما ترجع الدالة، يؤدي تدمير إطار مكدسها إلى تحريرها تلقائيًا.
أما المتغيرات العامة فتوجد طوال مدة تشغيل البرنامج بأكمله. وتُخزَّن المتغيرات العامة في مقطع بيانات ثابت من الملف الثنائي (عادةً .data).
يوضح التفكيك في القائمة 4.4 الفرق: التسميتان dword_40CF60 و dword_40C000 تمثلان عناوين ذاكرة مطلقة مُدمجة في الشيفرة (Hard-Coded). تُنشئ IDA هذه التسميات لتكون أسماء بديلة لمراجع الذاكرة مثل [40CF60] و [40C000]، مما يجعل التجميع أكثر قابلية للقراءة. وبادئة التسمية تدل على حجم البيانات المخزنة هناك، بناءً على تحليل IDA.
6. اختبار سريع: argumentative
في الأسئلة التالية، افترض وجود إطار مكدس قياسي مبني على
EBP.السؤال الأول: هل تصل التعليمة التالية إلى متغير محلي أم إلى وسيط (Argument)؟
mov eax, [ebp+8]
السؤال الثاني: هل تصل التعليمة التالية إلى متغير محلي أم إلى وسيط؟
mov dword ptr [ebp-4], 42
الحل (اضغط للعرض)
- السؤال الأول: إزاحة موجبة عن
EBP([ebp+8]) تعني الوصول إلى وسيط (الوسيط الأول؛ إذ يقع[ebp+4]على عنوان الإرجاع). - السؤال الثاني: إزاحة سالبة عن
EBP([ebp-4]) تعني الوصول إلى متغير محلي.
7. تمرين اختراق: funky_print
اكتب ونفّذ تسلسل تعليمات من أجل:
- عند العنوان
401020h، أنشئ دالة تقوم بما يلي:
- طباعة سلسلة نصية إلى الشاشة مُمرَّرة إلى الدالة على المكدس كوسيط. تتبع دالة
printfاصطلاح الاستدعاءcdecl— تذكّر ضبطESPبعد الاستدعاء بإزاحة تساوي أربعة أضعاف عدد الوسائط الممررة إلىprintf.- عند العنوان
401000h، أنشئ شيفرة تقوم بما يلي:
- استدعاء الدالة عند
401020hمع سلسلة وسيطة أنشأتها أو سلسلة موجودة في الذاكرة.- استدعاء
ExitProcessبرمز إرجاع 0.درجة إضافية (Bonus):
- أنشئ الدالة عند العنوان
401020hمع مقدمة (Prologue) وخاتمة (Epilogue) وتنظيف مكدس صحيحين.
الحل (اضغط للعرض)
; عند 401000h:
push 402118h ; عنوان السلسلة النصية
call 401020h
push 0
call dword ptr [_ExitProcess]
; عند 401020h:
push ebp ; مقدمة
mov ebp, esp
push dword ptr [ebp+8] ; الوسيط الأول (عنوان السلسلة)
call dword ptr [_printf]
add esp, 4 ; تنظيف وسيط واحد (cdecl)
mov esp, ebp ; خاتمة
pop ebp
ret
خلاصة المقال
| المفهوم | النقاط الرئيسية |
|---|---|
| أطر المكدس | كل استدعاء دالة ينشئ إطاراً؛ عمره = مدة تنفيذ الدالة |
EBP | نقطة ارتساء ثابتة للإطار؛ ESP يتحرك باستمرار |
| بنية الإطار | وسائط ([ebp+8] فأعلى) ← عنوان الإرجاع ([ebp+4]) ← EBP محفوظ ← متغيرات محلية ([ebp-4] فأدنى) |
| مقدمة الدالة | التسلسل القياسي: push ebp ← mov ebp, esp ← sub esp, N |
stdcall | تنظيف المستدعى عبر retn n؛ اصطلاح Windows API |
cdecl | تنظيف المستدعي عبر add esp, n؛ اصطلاح مترجمات C |
| الوسائط والقيم المرجعة | الوسائط تُدفع من اليمين إلى اليسار؛ الناتج يرجع في EAX |
| المتغيرات المحلية | في إطار المكدس؛ تُحرَّر عند رجوع الدالة |
| المتغيرات العامة | في مقطع .data؛ عناوين مطلقة ثابتة طوال عمر البرنامج |
المصدر الأصلي: Malware Analysis Crash Course — FLARE Learning Hub