The Stack and Windows x86 Calling Conventions Part 3
السلام عليكم ورحمة الله وبركاته
هذه المقالة استكمال للسلسلة السابقة ، وستكون الأخيرة ( إلا إن اكتشفنا موضوع مُقارب للمواضيع هذه ورهيب 😁 )
المقالات السابقة كانت تمهيد -ومقدمة لابد منها- قبل الحديث عن مواضيع المقالة الأخيرة، والنقاط التي سنناقشها في مقالتنا هذه هي :
- C Declaration (
__cdecl
) Calling Convention - Standard Call (
__stdcall
) Calling Convention - Fast Call (
__fastcall
) Calling Convention
المراجع في هذه المقالة بشكل أساسي هي الكتاب الرائع Practical Malware Analysis
والمرجع الرسمي من مايكروسوفت كوننا نتحدث هنا عن الـ Calling Convention في أنظمة مايكروسوفت
لنبدأ بسم الله ،
ملاحظة
في كتاب Practical Malware Analysis
قبل الحديث عن تفاصيل الـ Calling Conventions نجد هذه الملاحظة المهمة
NOTE Although the same conventions can be implemented differently between compilers, we’ll focus on the most common ways they are used.
بايجاز، نستطيع الفهم من الملاحظة السابقة أن الـ Calling Conventions قد يختلف تنفيذها ( implementation ) من Compiler لآخر ، لذلك مثل ما ذكر الكتاب ، سنركّز في المقالة هذه على المفاهيم العامة لكل Calling Convention
أيضًا هذا هو الـ Pseudocode الذي سنعتمد عليه في شرح الجزئيات القادمة
1 | int test(int x, int y, int z);
2 | int a, b, c, ret;
3 |
4 | ret = test(a, b, c);
C Declaration (__cdecl
) Calling Convention
نجد في المرجع الخاص بمايكروسوفت أن هذا الـ Calling Convention هو الـ default في لغات C & C++
__cdecl is the default calling convention for C and C++ programs.
طيب ماهي القوانين في هذا الـ Calling Convention ؟
إذا تم استخدام الـ __cdecl
فسيتم الآتي :
١ - سيتم تمرير الـ Parameters
الى الـ Stack
من اليمين إلى اليسار
٢ - الدالة المُنادية ( Caller
) ستكون هي المسؤولة عن عملية إعادة تهيئة الـ Stack لوضعه الأولي بعد أن تُنهي الدالة المُناداة ( Callee
) عملها
لنعود للـ Pseudocode السابق ونربط النقاط التي ذكرناها أعلاه بالكود
النقطة رقم ١
في السطر 4
نجد نداء الدالة test
، ونلاحظ أنه تم تمرير الـ Parameters
التالية : a
, b
, c
في حالة الـ __cdecl
سيتم أولًا تخزين الـ c
في الـ Stack
ومن ثم الـ b
وآخرًا الـ a
النقطة رقم ٢
الدالة التي ستقوم بنداء الدالة test
ستكون هي المسؤولة عن تنظيف الـ Stack
وإعادة الـ registers
لوضعها بعد أن تُنهي الدالة test
عملها
كود الأسمبلي الآتي لعله يوضّح آلية الـ __cdecl
1 | push c
2 | push b
3 | push a
4 | call test
5 | add esp, 12
6 | mov ret, eax
لاحظ في السطر 1
تم عمل push
للـ c
، والـ c
هو آخر parameter
تم تمريره وقت نداء الدالة test
4 | ret = test(a, b, c);
وفي السطر 2
تم تمرير قيمة الـ b
للـ Stack
وبعده الـ a
وفي السطر رقم 4
تم نداء الدالة test
بالتعليمة CALL
والتي ناقشناها في مقالة سابقة
عند هذه الخطوة سينتقل التحكم للدالة test
وسيتم انشاء Stack Frame
جديد خاص بها
وفي حال انتهت الدالة test
من عملها سيتم إعادة التحكم للدالة المُنادية ( Caller
)
وستتكفل الدالة المُنادية ( Caller
) بإعادة الـ Stack
لحالته الأولى ، هذه الخطوات يتم تنفيذها في السطر رقم 5
بتفصيل أكثر : الكود في السطر رقم 5
يقوم باضافة 12
الى الـ ESP
و كما عرفنا في مقالة سابقة، الـ Stack
ينمو بشكل عكسي ، فعملية الإضافة هنا لأنه نريد إعادة الـ Stack
للحالة الأولى
وبالنسبة للقيمة التي قمنا بإضافتها ( 12
) لأنه تم تمرير 3
متغيرات ( Parameters
) وكل متغيّر يأخذ مساحة 4
بايت ( نتحدّث هنا عن الـ 32bit systems )
Standard Call (__stdcall
) Calling Convention
الـ __stdcall
مثل الـ __cdecl
لكن يختلف عنه في أن الدالة المُناداة ( Callee
) هي المسؤولة عن تنظيف وإعادة تهيئة الـ Stack
ولو عدنا لمثالنا السابق ستكون الدالة test
هي المسؤولة عن تنظيف الـ Stack
، وعلى وجه التحديد، يتم تنفيذ هذه العملية في الـ Epilogue
الخاص بالدالة test
كذلك الـ __stdcall
هو الـ Calling Convention الذي يتم استخدامه في دوال الـ Win32 API
، وبلغة أخرى، أي دوال تقوم بنداء دوال الـ Win32 API
لن تكون مسؤولة عن إعادة تهيئة الـ Stack
، لأنها مسؤولية دوال الـ Win32 API
Fast Call (__fastcall
) Calling Convention
في الـ __fastcall
سيتم الآتي :
١ - الـ Parameters الأولى ( عادةً المتغيرين الأوليين ) سيتم تخزينهم في registers ( عادةً في الـ ECX
و EDX
) وبقية الـ Parameters يتم تمريرها للـ Stack
من اليمين إلى اليسار
٢ - عملية إعادة تهيئة الـ Stack
من مسؤولية الدالة المُناداة ( Callee
)
الجدول التالي يقارن بين الأنواع الثلاث
Calling Convention | Stack Cleanup | Parameter Passing |
---|---|---|
__cdecl | الدالة المُنادية ( Caller ) | يتم تمريرها للـ Stack من اليمين إلى اليسار |
__stdcall | الدالة المُناداة (Callee ) | يتم تمريرها للـ Stack من اليمين إلى اليسار |
__fastcall | الدالة المُناداة (Callee ) | يتم تخزين أول متغييرن في registers والبقية يتم تمريرهم للـ Stack |
ℹ️ [ملاحظة] هذه المقالة تمّت كتابتها خلال دراسة هذه المواضيع، فكل ما تم ذكره هنا قد يحتمل الخطأ، لكن بالإمكان العودة إلى المراجع التي إستندت عليها هذه المقالة