The Stack and Windows x86 Calling Conventions Part 2
السلام عليكم ورحمة الله وبركاته
هذه المقالة استكمال للمقالة السابقة
أهدف في هذه المقالة لشرح النقاط الآتية :
- Calling Conventions
- Function’s
PrologueandEpilogue
لنبدأ بسم الله
Calling Conventions
في كتاب Practical Malware Analysis نجد التعريف التالي
calling conventions govern the way the function call occurs. These conventions include the order in which parameters are placed on the stack or in registers, and whether the caller or the function called (the callee) is responsible for cleaning up the stack when the function is complete.
حتى نفهم التعريف لنأخذ المثال التالي
1 #include <stdio.h>
2
3 // define a function named MyFunction that returns the sum value
4 int MyFunction(int a, int b)
5 {
6 int sum;
7 sum = a + b; // add a and b and store the result in sum
8 return sum; // return the result
9 }
10
11 int main()
12 {
13 int result;
14 result = MyFunction(10, 5); // call MyFunction and store the returned value in result
15 printf("The sum is: %d\n", result); // print the result
16 return 0; // end the program
17 }
البرنامج جدًا بسيط لجمع رقمين وطباعتهما
دالة الـ main هي التي قامت بالاستدعاء ، أو بلغة أخرى نسميها الـ Caller
ودالة الـ MyFunction هي الدالة التي تم نداءها ، أو الـ Callee
نلاحظ أن دالة الـ main قامت أيضًا بتمرير الـ arguments لدالة الـ MyFunction
الشرح هذا قد يبدو بسيط، لكن خلفه أشياء أخرى تحدث ، مثلًا :
كيف سيتم تمرير الـ arguments من دالة الـ main الى الـ MyFunction ؟
وأين سيتم حفظ هذه الـ arguments ؟ في الـ Stack ؟ أم في الـ registers ؟
من سيقوم باعادة الـ Stack لوضعه الأولي قبل نداء الدالة MyFunction ؟ هل ستكون دالة MyFunction هي المسؤولة ؟ أم دالة الـ main ؟
جميع هذه الأسئلة التي طرحناها يُجيب عنها الـ Calling Conventions
فالـ Calling Conventions بشكل مختصر أشبه بالقوانين التي تحكم كيف سيتم نداء الدوال وكيف سيتم تمرير المتغيرات للدالة المُستدعاة ( Callee ) ، كذلك أين و كيف سيتم تخزين هذه المتغيرات التي تم تمريرها، وأيضًا من سيتولى عملية تنظيف الـ Stack وإعادته لوضعه الأولي بعد أن تُكمل الدالة المُستدعاة ( Callee ) عملها
Layout of a Function in Assembly
تطرقنا في المقالة السابقة للتعليمة CALL وعرفنا أن هذه التعليمة في لغة أسمبلي تمكّننا من نداء دالة
ولو عدنا للكود الخاص بنفس البرنامج سنجد السطر الآتي الذي يوضح لنا نداء الدالة MyFunction

ولو تعمقنا في كود الأسمبلي الخاص بأي دالة، سنجد أن كود الأسمبلي الخاص بالدالة يحمل الشكل الآتي

نلاحظ أن بداية الدالة يكون الجزء المسمى Prologue ونهايتها يكون الـ Epilogue ، فماذا نقصد بهذين المفهومين ؟
Function’s Prologue
في نفس كتابنا الرائع Practical Malware Analysis نجد التعريف التالي للـ Prologue
prologue—a few lines of code at the start of the function. The prologue prepares the stack and registers for use within the function
فالــ Prologue هو مجموعة من تعليمات الـ Assembly والغرض الأساسي منها هو تهيئة الـ Stack والـ registers قبل أن يتم تنفيذ الـ Body الخاص بالدالة
في الصورة التالية نجد مثال على الـ Prologue الخاص بالدالة MyFunction

نلاحظ أن الـ Prologue عبارة عن هذه التعليمات
1 ; The function prologue
2 push ebp ; Save the old base pointer
3 mov ebp, esp ; Set the new base pointer
4 sub esp, 0CCH ; Allocate 192 bytes for the local variable
لنبدأ بالحديث عن الـ Prologue بالتفصيل الآن :
2: هذه التعليمة تحفظ قيمة الـEBPفي الـ Stack ، وقيمة الـEBPفي هذه الحالة يعود للـStack Frameالخاص بالدالة السابقة، وفي مثالنا الدالة السابقة هي الـmain، والغرض من هذه الخطوة حتى نستطيع العودة للـStack Frameالخاص بالدالةmainبعد أن تُكمل الدالةMyFunctionعملها
لعل الرسم التالي يوضح العملية بشكل أكبر : 
3: بعد أن قمنا بحفظ قيمة الـEBPالخاص بالدالة السابقة (Caller) في الـStack، الآن نستطيع تحديث قيمة الـEBPلبدءStack Frameجديد ( الخاص بالدالةMyFunction) ، و التعليمة في السطر3هي التي تقوم بهذا ، وهذه التعليمة بشكل مختصر تقوم بنسخ الـESPالى الـEBP، أي كأننا قمنا بتحريك الـEBP، الرسم التالي يلخص هذه الخطوة

4: في هذه التعليمة قمنا بطرح0CCHمن قيمة الـESP، والغرض من هذه الخطوة هو اتاحة مساحة للمتغيرات الخاصة بالدالة ( local variables ) ، أو بتعبير آخر قمنا بتحريك الـESP، ولاحظ أننا قمنا بالطرح على الرغم من أننا نريد إضافة المتغيرات الى الـ Stack Frame ، والسبب في الطرح لأن الـ Stack ينمو بشكل عكسي كما عرفنا في المقالة السابقة ، فكلما تمت إضافة عناصر للـ Stack ، العنصر الأخير سيحمل عنوان أصغر من العنصر السابق ، الرسم التالي لعله يلخّص هذه العملية

an epilogue at the end of a function restores the stack and registers to their state before the function was called.
الـ Epilogue هو العملية العكسية للـ Prologue
فالغرض الأساسي من الـ Epilogue هو إعادة الـ Stack والـ registers للوضع الأولي بعد أن تُكمل الدالة المُستدعاة ( Callee ) عملها
ولو أردنا رؤية الـ Epilogue في الكود الخاص بالدالة MyFunction سنجد الآتي :

إذًا فالـ Epilogue مكوّن من العمليات الآتية :
1 ; The function epilogue
2 mov esp, ebp ; Restore the stack pointer
3 pop ebp ; Restore the base pointer
4 ret ; Return to the caller
لنبدأ بالتفصيل الآن :
2: في هذه التعليمة قمنا بنسخ قيمة الـEBPالى الـESP، أي كأننا قمنا بتحريك الـESPللأعلى وأصبح يشير لنفس العنوان الموجود في الـEBP، أو إن صح التعبير كأننا انتهينا من الـ Stack Frame الحالي ، الرسم التالي يلخّص هذه العملية

3: في هذه التعليمة قمنا بعملPOPللقيمة الأخيرة في الـ Stack وتخزينها في الـEBP، والقيمة الأخيرة هي الـEBPالسابق ( انظر للسطر2، الـESPيشير الى هذه القيمة ) ، وللتلخيص الغرض من هذه الخطوة هو إسترجاع الـEBPالخاص بالدالة السابقة (Caller) ، الرسم التالي يوضح هذه الخطوة

4: تطرقنا في المقالة السابقة الى التعليمةRETوعرفنا أنها تقوم بحذف القيمة الأخيرة في الـ Stack وتخزّنها في الـEIP register، وفي هذه المرحلة ، القيمة الحالية في الـ Stack هو العنوان التالي لنداء الدالةMyFunction، بتعبير آخر ، هذه التعليمة تُعيد التحكم للدالةmainحتى يستكمل البرنامج عمله ، الرسم التالي لعله يلخص هذه العملية

نكتفي بهذا القدر في هذه المقالة، وسنكمل الحديث عن بقية المواضيع في مقالات لاحقة بمشيئة الله ، وإن أصبت فمن توفيق الله وحده.
ولمن أراد الاستزادة حول هذه المواضيع أرشّح وبشدة الاطلاع على سلسلة هذه المقالات :
ℹ️ [ملاحظة] هذه المقالة تمّت كتابتها خلال دراسة هذه المواضيع، فكل ما تم ذكره هنا قد يحتمل الخطأ، لكن بالإمكان العودة إلى المراجع التي إستندت عليها هذه المقالة