الدرس 07: آليات الـ Stack واصطلاحات الـ Calling Conventions | أكاديمية سهم
س أكاديمية سهم · فن الاستغلال
الدرس 07 · سلسلة أساسيات الاستغلال

آليات الـ Stack واصطلاحات الـ Calling Conventions

الـ Stack هو أكثر بنية بيانات جرى استغلالها في تاريخ الحوسبة. في هذا الدرس نُشرّح الـ Stack Frame عظمةً عظمة، ونتتبّع كل Calling Convention حتى تقرأ أي Stack Dump وتعيد بناء سلسلة الاستدعاء كاملة.

المعماريات: x86 · x64 · ARM مخططات حية: 4 المستوى: متوسط
00

مقدمة

كل Buffer Overflow كلاسيكي، وكل سلسلة Return-Oriented Programming (ROP)، وكل عملية Stack Pivot — تعتمد جميعها على فهمٍ دقيق لكيفية تنظيم الـ Stack، وكيف تتلاعب به استدعاءات الدوال، وكيف ترتّب الـ Calling Conventions المختلفة الـ Arguments والـ Return Address والـ Registers المحفوظة.

فكّر في الـ Stack على أنه رصّةٌ من الصحون: آخر صحن تضعه هو أوّل صحن تسحبه (LIFO). كل استدعاء دالة يضيف «صحنًا» جديدًا اسمه الـ Stack Frame، وكل عودة تسحبه. مهمّتنا كمستغِلّين أن نفهم بنية هذا الصحن بدقّة تسمح لنا بالتنبّؤ — بل بالتحكّم — بما يحدث حين يفيض.

عند نهاية هذا الدرس ستكون قادرًا على قراءة Stack Dump خام وإعادة بناء سلسلة الاستدعاء بالكامل، وتحديد كل حقلٍ في كل Frame، والتنبّؤ بالضبط كيف سيُفسِد الـ Buffer Overflow الـ Stack ويصل إلى الـ Return Address.

قبل أن نبدأالعناوين المرتفعة (High Addresses) تقع نحو قاعدة الـ Stack، والمنخفضة نحو قمّته. الـ Stack ينمو نحو الأسفل (نحو العناوين الأقل)؛ أي أن push يُنقِص الـ Stack Pointer ESP/RSP.
01

تشريح الـ Stack Frame

الـ Stack Frame هو المنطقة المخصّصة لاستدعاءٍ واحد لدالة. يحتوي على الـ Local Variables، وقيم الـ Registers المحفوظة، والـ Return Address، و(في بعض الـ Conventions) الـ Arguments نفسها.

المخطط التفاعلي أدناه يمثّل Stack Frame نموذجيًّا على x86 وفق cdecl مع Frame Pointer. مرّر المؤشّر أو انقر على أي شريحة لتفهم دورها وعلاقتها بالاستغلال.

مخطط حي #1

مستكشِف الـ Stack Frame (x86 · cdecl)

انقر على أي شريحة لعرض تفصيلها ودلالتها الأمنية. العناوين محسوبة نسبةً إلى EBP.

‏1.1 الحقول الرئيسية

‏Return Address

عنوان التعليمة التالية للـ CALL التي استدعت هذه الدالة. تدفعه CALL تلقائيًّا، وتقرأه RET لتستأنف تنفيذ الـ Caller. هذا هو الهدف الأساسي لأي Stack Buffer Overflow؛ فمن يتحكّم به يتحكّم بمسار التنفيذ.

‏Saved EBP (الـ Frame Pointer)

الـ Frame Pointer الخاص بالـ Caller، تدفعه الـ Prologue القياسية. عند العودة يُستعاد EBP إلى هذه القيمة. إفساد الـ Saved EBP يفتح باب هجوم Frame Pointer Overwrite الذي قد يحرف التنفيذ عند العودة التالية.

‏Local Variables

تُخصَّص بطرح قيمة من ESP في الـ Prologue (sub esp, N)، ويُوصَل إليها عبر إزاحات سالبة من EBP مثل [ebp-4]. الـ Buffer القابل للفيضان يعيش هنا غالبًا.

‏Arguments

يدفعها الـ Caller على الـ Stack قبل CALL، ويُوصَل إليها عبر إزاحات موجبة مثل [ebp+8]. الإزاحة ‎+8 (لا ‎+4) تُراعي وجود الـ Saved EBP والـ Return Address بينهما.

الزاوية الاستغلاليةالمسافة من بداية الـ Buffer حتى الـ Return Address هي «المسافة الحرجة» التي يجب على الـ Payload تجاوزها. حسابها بدقّة (حجم الـ Buffer + الـ Padding + الـ Saved EBP) هو أوّل خطوة في بناء أي Stack Overflow Exploit.
02

cdecl Calling Convention

cdecl (اختصار C declaration) هو الـ Convention الافتراضي لدوال لغة C على أنظمة x86 بنظام 32-بت في لينكس، وشائع أيضًا على ويندوز 32-بت.

‏2.1 القواعد

الجانبالقاعدة
الـ Argumentsتُدفَع على الـ Stack من اليمين إلى اليسار
تنظيف الـ Stackالـ Caller يزيل الـ Arguments بعد العودة
القيمة المُعادةفي EAXEDX للقيم 64-بت)
‏Callee-savedEBX, ESI, EDI, EBP, ESP
‏Caller-savedEAX, ECX, EDX

لنتتبّع الاستدعاء add(1, 2, 3) خطوةً بخطوة. المخطط الحي أدناه هو قلب هذا الدرس: اضغط «التالي» لتشاهد الـ Stack يتغيّر مع كل تعليمة، من دفع الـ Arguments حتى تنفيذ ret.

مخطط حي #2

تتبّع استدعاء دالة خطوةً بخطوة

add(1,2,3) بأسلوب cdecl. راقب الـ Stack Pointer ESP والـ Frame Pointer EBP وهما يتحرّكان.

cdecl · 32-bit; الـ Caller (main):
push  3            ; دفع الـ argument الأيمن أولًا
push  2            ; الـ argument الأوسط
push  1            ; الأيسر أخيرًا = الأقرب لـ ESP
call  add          ; دفع الـ return address والقفز إلى add
add   esp, 12       ; الـ Caller ينظّف 3 arguments (3×4=12 بايت)

; الـ Callee (add):
add:
push  ebp          ; حفظ الـ frame pointer الخاص بالـ Caller
mov   ebp, esp     ; تأسيس الـ frame الجديد
mov   eax, [ebp+8]   ; a
add   eax, [ebp+12]  ; a + b
add   eax, [ebp+16]  ; a + b + c
pop   ebp          ; استعادة الـ frame pointer
ret                ; سحب الـ return address إلى EIP

‏2.2 لماذا يهمّ تنظيف الـ Caller؟

لأن الـ Caller هو من ينظّف الـ Stack، يدعم cdecl الدوال متغيّرة الـ Arguments (مثل printf). فالـ Callee لا يعرف عدد الـ Arguments الممرَّرة، لذا لا يستطيع تنظيفها؛ أمّا الـ Caller — الذي دفعها — فيعرف عدد البايتات المطلوب إزالتها بدقّة.

03

stdcall Calling Convention

stdcall هو الـ Convention القياسي لدوال Win32 API على ويندوز. وهو مطابق لـ cdecl تمامًا باستثناء أن الـ Callee هو من ينظّف الـ Arguments.

الجانبالقاعدة
الـ Argumentsتُدفَع من اليمين إلى اليسار
تنظيف الـ Stackالـ Callee يزيلها عبر RET n
القيمة المُعادةفي EAX

تعليمة RET n

RET n تسحب الـ Return Address إلى EIP، ثم تضيف n إلى ESP. هكذا يزيل الـ Callee الـ Arguments.

الزاوية الاستغلاليةفي دالة stdcall، إن تحكّمت بالـ Return Address فعليك مراعاة تعديل RET n على ESP عند بناء الـ ROP Chain، وإلا انزاح الـ Stack بمقدار n بايت غير متوقّع فتنكسر السلسلة.
04

fastcall Calling Convention

يمرّر fastcall أوّل Argumentين (أو أكثر) في الـ Registers لتحسين الأداء، بدلًا من دفعهما على الـ Stack.

‏Microsoft fastcall

الجانبالقاعدة
أوّل argumentينECX, EDX (يسار إلى يمين)
باقي الـ Argumentsتُدفَع على الـ Stack يمينًا إلى يسار
تنظيف الـ Stackالـ Callee (مثل stdcall)
القيمة المُعادةEAX
__fastcall; compute(1,2,3,4) — الـ Caller:
push  4            ; d (الرابع، على الـ stack)
push  3            ; c (الثالث، على الـ stack)
mov   edx, 2       ; b (الثاني، في EDX)
mov   ecx, 1       ; a (الأول، في ECX)
call  compute
; داخل الدالة: ret 8 لتنظيف c و d
05

‏thiscall واستدعاءات دوال ++C

يُستخدَم thiscall لدوال الأعضاء غير الساكنة في ++C. الفرق الجوهري: يجب تمرير الـ this Pointer إلى الكائن.

على مترجم MSVC

يُمرَّر الـ this Pointer في الـ Register ECX، وتُدفَع الـ Arguments يمينًا إلى يسار، والـ Callee ينظّف (مثل stdcall).

على مترجم GCC (لينكس)

يُمرَّر this كأوّل Argument على الـ Stack بأسلوب cdecl، والـ Caller ينظّف.

‏Virtual Call — هدفٌ استغلالي كلاسيكي

يمرّ الـ Virtual Call بثلاث خطوات: تحميل الـ vtable Pointer من الكائن، ثم الفهرسة داخله للحصول على الـ Function Pointer، ثم الاستدعاء عبره.

virtual call · MSVC 32-bitmov   ecx, [eax]      ; ECX = vtable pointer من الكائن عند EAX
mov   edx, [ecx+8]    ; EDX = مؤشّر الدالة الافتراضية الثالثة
call  edx             ; استدعاء الـ virtual function
الزاوية الاستغلاليةإن تحكّم المهاجم بالكائن عند EAX، تحكّم بالـ vtable Pointer ومن ثمّ بهدف الاستدعاء بالكامل. إفساد الـ vtable Pointer أو الجدول نفسه (عبر Use-After-Free مثلًا) يحرف الـ Virtual Call إلى شيفرة يسيطر عليها المهاجم.
06

System V AMD64 ABI

هو الـ Convention القياسي على لينكس وmacOS وFreeBSD ومعظم أنظمة يونكس بنظام 64-بت. التحوّل الأهم عن x86: أوّل ستة Arguments صحيحة تُمرَّر في Registers، لا على الـ Stack.

الجانبالقاعدة
‏Integer ArgumentsRDI, RSI, RDX, RCX, R8, R9 (أول 6)
‏Float ArgumentsXMM0–XMM7 (أول 8)
باقي الـ Argumentsتُدفَع على الـ Stack يمينًا إلى يسار
تنظيف الـ Stackالـ Caller
القيمة المُعادةRAX (صحيح) / XMM0 (عشري)
‏Red Zone128 بايت أسفل RSP لا تمسّها الـ Signals
‏Stack Alignment16 بايت قبل CALL
لماذا يهمّ ترتيب الـ Registers؟لأن الـ ROP على x64 يعتمد على Gadgets من نوع pop rdi ; ret لتحميل الـ Arguments في الـ Registers قبل استدعاء دالة مثل system("/bin/sh"). حفظ ترتيب RDI→RSI→RDX→RCX→R8→R9 ضروري لبناء أي Chain تستدعي دوالّ بـ Arguments.
07

Microsoft x64 Convention

يختلف الـ Convention الخاص بمايكروسوفت على 64-بت اختلافًا جوهريًّا عن System V، وهذا الاختلاف يجعل الـ Shellcode غير قابل للنقل بين المنصّتين دون تعديل.

الجانبالقاعدة
‏Integer ArgumentsRCX, RDX, R8, R9 (أول 4 فقط!)
‏Shadow Space32 بايت يحجزها الـ Caller فوق الـ Return Address
‏Red Zoneلا توجد (بخلاف System V)
تنظيف الـ Stackالـ Caller

المخطط أدناه يقارن بصريًّا أين تذهب الـ Arguments في كل Convention. بدّل بين الـ Conventions لترى الفرق بين تمرير الـ Registers والـ Stack، وموقع الـ Shadow Space.

مخطط حي #3

مقارنة الـ Calling Conventions

أين يذهب كل argument؟ اختر Convention لرؤية توزيع الـ Arguments بين الـ Registers والـ Stack.

‏Shadow Space

يُلزِم الـ Convention الخاص بمايكروسوفت الـ Caller بحجز 32 بايت من الـ Shadow Space على الـ Stack، حتى لو كان للدالة أقلّ من أربعة Arguments. يستطيع الـ Callee استخدام هذه المساحة لسكب الـ Register Arguments فيها.

الزاوية الاستغلاليةالـ Shadow Space تُعقّد الـ ROP على ويندوز x64: كل دالة تتوقّع 32 بايت فوق الـ Return Address. عند ربط الـ Gadgets يجب حجز هذه المساحة، وإلا أفسدت الدالةُ المُستدعاة سلسلتَك بسكب الـ Registers فوقها. أضف إلى ذلك اختلاف ترتيب الـ Registers (RCX,RDX,R8,R9 مقابل RDI,RSI,RDX,RCX) وغياب الـ Red Zone — ثلاثة فروق تجعل الـ Payload خاصّة بكل منصّة.
08

Prologue وEpilogue

الـ Prologue والـ Epilogue هما الطقسان اللذان يفتحان الـ Stack Frame ويغلقانه. فهمهما هو ما يفسّر لماذا يعمل الـ Buffer Overflow، ولماذا تُعدّ تعليمة leave أداة ROP قوية.

‏Standard x86 Prologue

push  ebp          ; حفظ الـ frame pointer الخاص بالـ Caller
mov   ebp, esp     ; تأسيس الـ frame pointer الحالي
sub   esp, N       ; حجز N بايت للـ local variables

‏Standard x86 Epilogue

leave              ; يكافئ: mov esp, ebp ; pop ebp
ret                ; سحب الـ return address إلى EIP

‏Frame Pointer Omission

مع الخيار -fomit-frame-pointer لا يحفظ المترجم RBP، بل يحجز الـ Locals مباشرةً ويصل إليها عبر [rsp+offset]. هذا أكفأ (Register إضافي متاح وتعليمات أقل) لكنه يصعّب الـ Debugging والـ Stack Unwinding. يعتمده GCC افتراضيًّا على x64 مع الـ Optimization.

الزاوية الاستغلالية — لماذا leave أداة ذهبيةتضبط leave قيمة RSP لتساوي RBP ثم تسحب RBP. هذا يتيح Stack Pivot: وجّه RBP إلى Buffer تتحكّم به، ثم نفّذ leave ; ret لتنقل الـ Stack إلى منطقتك. أمّا مع الـ Frame Pointer Omission، فلا يوجد Saved EBP بين الـ Locals والـ Return Address، ما قد يُقصّر مسافة الـ Overflow المطلوبة.

لنجعل هذا ملموسًا: المخطط أدناه يحاكي Buffer بحجم 16 بايت. حرّك المنزلق لتكتب المزيد من البايتات وتشاهد متى «يفيض» الـ Overflow فوق الـ Saved EBP ثم يصل إلى الـ Return Address — لحظة السيطرة.

مخطط حي #4

محاكاة الـ Buffer Overflow

‏Buffer = 16 بايت، ثم 4 بايت لـ Saved EBP، ثم 4 بايت للـ Return Address.

09

عملي: تتبّع الاستدعاءات في الـ Debugger

النظرية تكتمل عند لوحة المفاتيح. هذه جلسة GDB مختصرة على x86 لفحص Call Chain من ثلاث دوال متداخلة، وقراءة سلسلة الـ Frames يدويًّا.

GDB · x86# بناء وتشغيل
gcc -m32 -fno-omit-frame-pointer -g -o demo stack_demo.c
gdb ./demo
(gdb) break func_c
(gdb) run

# تتبّع الـ call chain بالكامل
(gdb) backtrace
#0  func_c (x=3) at demo.c:5
#1  0x565561bc in func_b (y=2) at demo.c:12
#2  0x565561e3 in func_a (z=1) at demo.c:19
#3  0x56556204 in main () at demo.c:24

# فحص ذاكرة الـ Stack الخام
(gdb) x/32xw $esp

# سلسلة الـ Saved EBP:
(gdb) x/2xw $ebp
# الكلمة الأولى = Saved EBP (يشير لإطار الـ Caller)
# الكلمة الثانية = Return Address

# تتبّع السلسلة يدويًّا خطوة للأعلى
(gdb) x/2xw *(int*)$ebp
على ويندوز (WinDbg)الأمر k يعرض الـ Call Stack، وkv يضيف تفاصيل الـ Frame Pointer، وdps rsp rsp+100 يُفرّغ ذاكرة الـ Stack مع فكّ رموز العناوين. الأمر .frame 1 ثم dv يعرضان الـ Local Variables لإطارٍ محدّد.
تنبيه أخلاقيكل ما سبق أدواتٌ تحليلية مشروعة تُستخدم في أبحاث الأمن واختبار الاختراق ضمن بيئات مصرّح بها فقط. الغاية من فهم الآلية هي بناء الدفاعات وكشف الثغرات مسؤولًا.

★ الخلاصات الأساسية

  • الـ Return Address هو الهدف الأساسي لأي Stack Overflow؛ يقع على إزاحة ثابتة من الـ Buffer في دالة ذات Frame Pointer، وتضعه CALL تلقائيًّا.
  • cdecl (تنظيف الـ Caller) يدعم الدوال متغيّرة الـ Arguments، بينما stdcall (تنظيف الـ Callee عبر RET n) تستخدمه واجهة ويندوز. طريقة التنظيف تؤثّر على كيفية بناء الـ ROP Chain.
  • System V AMD64 يمرّر 6 Arguments في Registers (RDI,RSI,RDX,RCX,R8,R9)، بينما Microsoft x64 يمرّر 4 فقط (RCX,RDX,R8,R9) ويطلب 32 بايت Shadow Space — لذا الـ Shellcode على x64 خاصّ بكل منصّة.
  • تعليمة leave (mov rsp,rbp ; pop rbp) أداة ROP قوية للـ Stack Pivot؛ فهم الـ Prologue والـ Epilogue يوضّح السبب.
  • Frame Pointer Omission يغيّر تخطيط الـ Stack بإزالة الـ Saved EBP/RBP، ما قد يؤثّر على مسافات الـ Overflow ويصعّب التحليل اليدوي.
  • thiscall يمرّر الـ this في ECX (MSVC) أو كأوّل Argument على الـ Stack (GCC)؛ والاستدعاءات عبر الـ vtable Pointers هدفٌ استغلالي شائع.
  • Stack Alignment بـ 16 بايت مطلوب على x64؛ اختلاله قد يُعطِّل تعليمات SSE فيتعطّل الـ Exploit.
س أكاديمية سهم

منهج «فن الاستغلال» · الدرس السابع من سلسلة أساسيات الاستغلال

جميع الأمثلة لأغراض تعليمية بحتة ضمن بيئات مصرّح بها.