در مهندسی معکوس و تحلیل باینری، دیساسمبلی کد تنها نشان میدهد که یک تابع کدام ثباتها را دستکاری میکند؛ اما فهمیدن نقش دقیق هر ثبات نیازمند شناخت قرارداد فراخوانی (Calling Convention) متناظر است. بدون در نظر گرفتن این قرارداد که توسط هدر فایل تعیین میشود، حتی ابزارهای شناختهشدهای مثل radare2 یا rizin ممکن است تعداد آرگومانها را اشتباه استخراج کنند یا ثباتهای بهکاررفته در آدرسدهی را نادیده بگیرند.
در این درس یاد میگیرید چگونه بر پایهی هدر فایلهای ELF، COFF و Mach-O استاندارد حاکم را تشخیص دهید، تفاوتهای کلیدی میان قراردادهای System V، ویندوز x64 و AAPCS64 را بازشناسی کنید و چیدمان پشته را به شکل بایتبهبایت با دادههای بازگشایی پشته (Unwind Data) خود کامپایلر بسنجید تا تحلیلی مستقل و مطمئن از خروجی ابزارهای خودکار داشته باشید.
دیساسمبلی فقط نشان میدهد که یک تابع کدام ثباتها را میخواند. این تنها ABI است که مشخص میکند آن ثباتها چه معنایی دارند، و نوع ABI نیز با هدر آبجکتفایل تعیین میشود؛ بنابراین در این درس، ابتدا هدر را میخوانیم و سپس هر ثبات را با قاعدهای که آن را در آنجا نشانده تطبیق میدهیم.
اهداف یادگیری
- تشخیص شیوهی فراخوانی (Calling convention) از روی هدر آبجکتفایل: ELF x86-64 همان System V psABI است، COFF AMD64 همان Microsoft x64، ساختار ELF AArch64 همان AAPCS64، و Mach-O arm64 گونهی اختصاصی Apple است.
- رمزگشایی (Decode) بایتبهبایت پرولوگ (Prologue) یک تابع برای هر ABI و قرار دادن تکتک ثباتهای ذخیرهشده، آدرس بازگشت و اشارهگر فریم (Frame pointer) در آفست متناظرشان نسبت به اشارهگر پشتهی ورودی (Entry stack pointer).
- استنتاج ثباتهای پارامتر تابع بر اساس ثباتهایی که قبل از نوشتن خوانده میشوند، و استنتاج اسلاتهای آرگومان پشته بر اساس خواندنهای حافظه در بالای اشارهگر پشتهی ورودی.
- تمایز قائل شدن بین توالیهای مستقل ثباتها در System V و اسلاتهای موضعی در Microsoft x64، و تفکیک هر دوی آنها از شمارندههای NGRN و NSRN در AAPCS64.
- اعتبارسنجی هر استنتاج در برابر امضای کد منبع، قواعد مشخصشده در استانداردها، و دادههای بازگشایی پشته (Unwind data) خود کامپایلر در بخشهای
.pdata،.xdataو.eh_frame. - مقایسهی نحوهی نامگذاری و استخراج این حقایق در ابزارهای Ghidra، IDA، Binary Ninja، radare2، rizin و angr، و ثبت مواردی که خروجی آنها با کد منبع مغایرت دارد.
صورت مسئله
فرض کنید فایل convention.c را برای Linux x86-64 با فلگ -O2 کامپایل کردهاید و از ابزار radare2 نسخهی 6.2.4 آرگومانهای تابع eight_args را میخواهید. این ابزار هفت آرگومان گزارش میدهد: rdi، rsi، rdx، r8، r9، rsp+0x8 و rsp+0x10؛ در حالی که سورسکد C هشت پارامتر دارد. در بایتهای کد ماشین، %rcx دقیقاً یک بار در دستور leaq (%rdx,%rcx), %rax خوانده میشود، اما سازوکار بازیابی متغیر این ابزار، ثباتی را که فقط درون یک آدرسدهی ظاهر شده، به حساب نیاورده است.
سپس نسخهی ویندوزی همان فایل را در rizin نسخهی 0.9.1 باز میکنید. این ابزار شیوهی فراخوانی را amd64 تشخیص داده و ثباتهای r8، rcx، rdx و سه اسلات پشته پیدا میکند. از آنجا که فایل خروجی COFF است، شیوهی فراخوانی در واقع Microsoft x64 است، چهارمین آرگومان ثباتی r9 است و باید چهار اسلات پشته وجود داشته باشد. دو ابزار مختلف، در تحلیلی از یک سورسکد یکسان، ثباتهای متفاوتی را نادیده گرفتهاند.
یک لیستینگ (Listing) صرفاً توالیای از دستورالعملهاست. در مقابل، شیوهی فراخوانی قراردادی است که بیرون از این لیستینگ معنا پیدا میکند: اینکه چه ثباتهایی حامل آرگومانها هستند، تابع فراخواندهشده (Callee) موظف به حفظ کدام ثباتهاست، آدرس بازگشت و آرگومانهای پشته در کجا مینشینند، و تابع فراخوان (Caller) چه فضایی را باید بالای اشارهگر پشته خالی بگذارد. چهار سند مرجع، این قراردادهای مهم را تعریف میکنند: بخشهای 3.2.1 تا 3.2.3 از System V AMD64 psABI (نسخهی 1.0، تاریخ ساخت 12 مارس 2025)؛ مستندات شیوهی فراخوانی، استفاده از پشته، و صفحات پرولوگ و اپیلوگ Microsoft x64 (با تاریخهای 2018-12-17 تا 2025-03-19)؛ بخشهای 6.1 تا 6.9 از مستند Arm AAPCS64 ویرایش 2025Q4 (تاریخ 23 ژانویه 2026)؛ و یادداشتهای پلتفرم arm64 شرکت Apple. در این درس این قواعد را میخوانیم، آنها را روی یک فایل C که به سه روش کامپایل شده پیاده میکنیم، و آنالایزری میسازیم که یک لیستینگ خام را به فهرستی دقیق و ارزیابیشده از محل پارامترها، ثباتهای ذخیرهشده و آفستهای فریم تبدیل کند.
تشخیص کتابچهی قواعد از روی یک هدر
فایل convention.c شامل دوازده تابع است: leaf_add، eight_args، mixed، by_value، big_ret، sum_varargs، keeps و پنج تابع فراخوان برای توابع extern با ساختارهای مشابه. در ساختارها (Structs) از نوع long long استفاده شده است، زیرا long در پلتفرم MSVC برابر 4 بایت و در سایر پلتفرمها 8 بایت است؛ تغییری در اندازهی struct به معنای تغییر در شیوهی فراخوانی خواهد بود. کامپایلر Apple clang نسخهی 17.0.0 این سورس را هفت بار کامپایل کرده است: برای تارگتهای -target x86_64-unknown-linux-gnu، x86_64-pc-windows-msvc و aarch64-unknown-linux-gnu در دو سطح بهینهسازی -O0 و -O2، و برای arm64-apple-macos با فلگ -O2. از آنجا که هیچ هدر سیستمعاملی برای لینوکس یا ویندوز روی سیستم نصب نبوده، فایل تمام نیازهایش را خودش تعریف کرده و صرفاً هدرهای استاندارد خود clang یعنی stdarg.h و stddef.h را میآورد.
تابع describe_object() در فایل code/main.py عدد جادویی (Magic number) چهار بایتی و یک فیلد ماشین را خوانده و نام قرارداد را تعیین میکند:
| هدر | فیلد | مقدار | کتابچهی قواعد |
|---|---|---|---|
7f 45 4c 46 ELF |
e_machine در آفست 0x12 |
62 | System V AMD64 psABI |
64 86 COFF |
Machine در آفست 0 |
0x8664 | Microsoft x64 |
7f 45 4c 46 ELF |
e_machine |
183 | AAPCS64 |
cf fa ed fe Mach-O 64 |
cputype در آفست 4 |
0x0100000c | AAPCS64 با تفاوتهای Apple |
این سه قرارداد تقریباً در تکتک سطرهای جدول زیر با یکدیگر تفاوت دارند. تمام مقادیر عیناً از اسناد ذکرشده در سطر هدر استخراج شده و صحت هر کدام با تستهایی روی فایلهای کامپایلشده بررسی شده است.
| قاعده | System V psABI | Microsoft x64 | AAPCS64 |
|---|---|---|---|
| آرگومانهای عدد صحیح | %rdi %rsi %rdx %rcx %r8 %r9، بر اساس اولین ثبات آزاد |
RCX RDX R8 R9، بر اساس موقعیت |
x0 تا x7، بر اساس NGRN |
| آرگومانهای ممیز شناور | %xmm0 تا %xmm7، بر اساس اولین ثبات آزاد |
XMM0 تا XMM3، بر اساس موقعیت |
v0 تا v7، بر اساس NSRN |
| نتیجهی عدد صحیح | %rax، سپس %rdx |
RAX |
x0، سپس x1 |
| نتیجهی ممیز شناور | %xmm0، سپس %xmm1 |
XMM0 |
v0 |
| نتیجهی بزرگتر از گنجایش ثباتها | آدرس با %rdi ارسال میشود «گویی اولین آرگومان است»، و در %rax برمیگردد |
اشارهگر در RCX، آرگومانها یک شیفت به راست میخورند، بازگشت در RAX |
آدرس در x8، ثباتهای x0 تا x7 دستنخورده میمانند |
| ذخیرهشده توسط فراخواندهشده (Callee-saved) | %rbp %rbx %r12 تا %r15 |
RBX RBP RDI RSI RSP R12 تا R15، و XMM6 تا XMM15 |
r19 تا r29، SP، و 64 بیت پایینی v8 تا v15 |
| اولین آرگومان پشته در بدو ورود | 8(%rsp) |
40(%rsp)، بالای فضای 32 بایتی Shadow store |
0(sp) |
| پایینتر از اشارهگر پشته | ناحیهی سرخ (Red zone) 128 بایتی | «فرار (Volatile) تلقی میشود» | بدون Red zone (در Apple: 128 بایت) |
| تراز پشته هنگام فراخوانی | (%rsp + 8) در بدو ورود مضربی از 16 است |
خارج از پرولوگ و اپیلوگ تراز 16 بایتی دارد | SP mod 16 = 0 |
| نشانگر توابع با تعداد متغیر آرگومان (Variadic) | ثبات %al حد بالای تعداد ثباتهای برداری استفادهشده را نگه میدارد |
ممیزهای شناور در ثبات همارز عدد صحیح تکثیر میشوند | هیچ نشانگری در ثباتها نیست؛ Apple از اسلاتهای پشتهی 8 بایتی استفاده میکند |
استاندارد System V: دو توالی مستقل ثبات و یک ناحیهی سرخ
بخش 3.2.3 از psABI هر قطعهی 8 بایتی (Eightbyte) از هر آرگومان را در دستههای INTEGER، SSE یا MEMORY طبقهبندی کرده و سپس ثباتها را از دو فهرست کاملاً مستقل تخصیص میدهد. دستهی INTEGER «اولین ثبات در دسترس از توالی %rdi، %rsi، %rdx، %rcx، %r8 و %r9» را میگیرد. دستهی SSE ثباتهای %xmm0 تا %xmm7 را مصرف میکند. «اگر برای هر یک از بخشهای هشتبایتی یک آرگومان ثبات آزادی وجود نداشته باشد، کل آن آرگومان روی پشته ارسال میشود»، و آرگومانهای حافظه «با ترتیب معکوس (از راست به چپ) روی پشته push میشوند».
تابع eight_args(a, b, c, d, e, f, g, h) در حالت -O2 تنها 27 بایت است:
10: 48 01 fe addq %rdi, %rsi
13: 48 8d 04 0a leaq (%rdx,%rcx), %rax
17: 48 01 f0 addq %rsi, %rax
1a: 4c 01 c0 addq %r8, %rax
1d: 4c 01 c8 addq %r9, %rax
20: 48 03 44 24 08 addq 0x8(%rsp), %rax
25: 48 03 44 24 10 addq 0x10(%rsp), %rax
2a: c3 retq
شش ثبات، سپس دو بار خواندن حافظه در 8(%rsp) و 16(%rsp). شکل 3.3 از مستندات psABI بخش «memory argument eightbyte 0» را در 16(%rbp) تعریف میکند (زمانی که %rbp پایهی فریم است)، که این مقدار در بدو ورود و قبل از هر گونه push معادل 8(%rsp) است. بنابراین g در آفست 8 و h در آفست 16 قرار دارد.
تابع mixed(int i, double d, long long l, float f) رفتار این دو فهرست مستقل را نشان میدهد. اعداد صحیح در %edi و %rsi (اولین و دومین ثبات INTEGER) مینشینند. مقادیر double و float به %xmm0 و %xmm1 (اولین و دومین ثبات SSE) اختصاص مییابند. اولین دستور cvtsi2sd %edi, %xmm2 است که در آن ثبات %xmm2 ثباتی موقت (Scratch register) به شمار میرود که تابع قبل از خواندن، ابتدا روی آن مینویسد. اگر آنالایزری مقصد دستور cvtsi2sd را به عنوان خواندن در نظر بگیرد، به اشتباه یک آرگومان ممیز شناور سوم ابداع میکند. این دقیقاً همان خطایی است که radare2 مرتکب شد و تابع mixed را به عنوان (double arg1, double arg2, double arg3) در ثباتهای xmm0، xmm1 و xmm2 گزارش کرد.
تابع by_value(struct pair p) یک ساختار 16 بایتی را به شکل دو هشتبایتی INTEGER در ثباتهای %rdi و %rsi ارسال کرده و به همان صورت نیز برمیگرداند: leaq 0x1(%rdi), %rax; leaq -0x1(%rsi), %rdx; retq. تابع big_ret(long long seed) یک دادهی 32 بایتی برمیگرداند که در دستهی MEMORY قرار میگیرد؛ طبق psABI «فراخوان فضایی را برای مقدار بازگشتی اختصاص داده و آدرس این حافظه را در %rdi ارسال میکند، گویی اولین آرگومان تابع است». آرگومان seed به ثبات %rsi منتقل میشود و تابع با دستور movq %rdi, %rax آغاز به کار میکند، زیرا «در زمان بازگشت، %rax حاوی همان آدرسی خواهد بود که از سوی فراخوان در %rdi ارسال شده بود».
ناحیهی سرخ (Red zone) مشخصهی کلیدی دیگر System V است. به نقل از بخش 3.2.2 استاندارد psABI: «فضای 128 بایتی فراتر از مکانی که %rsp به آن اشاره دارد، رزرو تلقی شده و نباید توسط هندلرهای سیگنال یا وقفه (Interrupt) دستکاری شود.» در سطح -O0، تابع eight_args با pushq %rbp; movq %rsp, %rbp شروع شده و سپس شش عملیات ذخیرهسازی روی -0x8(%rbp) تا -0x30(%rbp) بدون هیچ دستور subq انجام میدهد. این ذخیرهسازیها 48 بایت زیر اشارهگر پشته مینشینند. خروجی بهینهسازیشدهی -O2 برای تابع sum_varargs با دستور subq $0x58, %rsp مقدار 88 بایت حافظه میگیرد و سپس ناحیهی ذخیرهسازی ثباتها را از -0x58(%rsp) تا -0x80(%rsp) مینویسد: دقیقاً 128 بایت زیر اشارهگر پشته، یعنی تمام ظرفیت Red zone. اگر همین فایل را با فلگ -mno-red-zone بسازید، تابع leaf_add دستورهای pushq %rax و addq $0x8, %rsp را اضافه میکند تا 8 بایت متغیرهای محلی خود را بالای اشارهگر پشته نگهداری کند.
فراخوانیهای با تعداد متغیر آرگومان یک ثبات دیگر را هم درگیر میکنند. «از %al به عنوان یک آرگومان پنهان برای مشخص کردن تعداد ثباتهای برداری استفادهشده استفاده میشود» که یک حد بالا از 0 تا 8 است. تابع call_varargs در حالت -O2 آن را با xorl %eax, %eax و در حالت -O0 با movb $0x0, %al مقداردهی میکند. تابع sum_varargs ابتدا این ثبات را بررسی میکند: دستور testb %al, %al; je باعث میشود در صورت عدم استفاده از ثباتهای برداری، از ذخیرهسازی هشت مرحلهای movaps %xmmN صرفنظر شود.
مایکروسافت x64: موقعیتها، فضای Shadow Store و نبود Red Zone
قواعد مایکروسافت موضعی هستند. «آرگومانهای عدد صحیح در ثباتهای RCX، RDX، R8 و R9 منتقل میشوند. آرگومانهای ممیز شناور در ثباتهای XMM0L، XMM1L، XMM2L و XMM3L ارسال میگردند.» همچنین «هر آرگومان ممیز شناور و دقت مضاعف (Double-precision) در میان چهار پارامتر نخست، بسته به موقعیت خود، در یکی از ثباتهای XMM0 تا XMM3 قرار میگیرد.» همان تابع mixed در اینجا آرگومانهایش را به ترتیب در %ecx، %xmm1، %r8 و %xmm3 دریافت میکند: اسلات 0 عدد صحیح است، پس RCX؛ اسلات 1 نوع double است، پس XMM1؛ اسلات 3 نوع float است، پس XMM3. در این میان XMM0 و XMM2 دستنخورده باقی میمانند.
علاوه بر این، فراخوان باید فضای لازم را رزرو کند. «فراخوان همواره باید فضای کافی برای ذخیرهی چهار پارامتر ثباتی تخصیص دهد، حتی اگر تابع فراخواندهشده این تعداد پارامتر نداشته باشد.» در مستندات به این بخش Shadow store یا Home addresses گفته میشود و مالکیت این چهار اسلات «عملاً در اختیار تابع فراخواندهشده است». در زمان ورود، آدرس 0(%rsp) محل قرارگیری آدرس بازگشت، 8(%rsp) تا 39(%rsp) مربوط به این چهار اسلات، و اولین آرگومان واقعی پشته در 40(%rsp) است. تابع eight_args در حالت -O2 مقادیر 0x28(%rsp)، 0x30(%rsp)، 0x38(%rsp) و 0x40(%rsp) را میخواند: یعنی آفستهای 40، 48، 56 و 64.
محاسبات مربوط به پشته را میتوان در سمت فراخوان دید. تابع call8 در حالت -O2 ابتدا دستور subq $0x48, %rsp را اجرا کرده و سپس مقادیر 5، 6، 7 و 8 را در بازهی 0x20(%rsp) تا 0x38(%rsp) ذخیره میکند و پس از آن دستورهایی مثل movl $1, %ecx میآیند. این 72 بایت متشکل از 32 بایت برای Shadow store، 32 بایت برای چهار آرگومان پشته، و 8 بایت جهت حفظ تراز 16 بایتی آدرس بازگشت است. تابع call_varargs حتی با اینکه فقط آرگومانهای ثباتی دارد، باز هم دستور subq $0x28, %rsp را اجرا میکند: 32 بایت برای Shadow store و 8 بایت برای تراز. تابع keeps سه ثبات غیرفرار (Nonvolatile) را ذخیره میکند (24 بایت) و سپس subq $0x20, %rsp را اجرا میکند تا تابع ext فضای Shadow store خود به اندازهی 32 بایت را در زیر آدرس بازگشت ببیند.
ساختارها قاعدهی خاص خود را دارند. «هر آرگومانی که در 8 بایت جا نشود، یا اندازهی آن 1، 2، 4 یا 8 بایت نباشد، باید با ارجاع (By reference) ارسال شود» و همچنین «آرگومانهای 16 بایتی با ارجاع ارسال میشوند.» بنابراین تابع by_value اشارهگری به یک کپی از ساختار را در ثبات %rdx دریافت میکند که پس از اشارهگر مخفی نتیجه در %rcx قرار میگیرد: «فراخوان باید برای مقدار بازگشتی حافظه تخصیص دهد و اشارهگری به آن را به عنوان اولین آرگومان ارسال کند. سپس باقی آرگومانها یک واحد به سمت راست شیفت پیدا میکنند.» تابع call_pair این کپی را در 0x20(%rsp) با دستور movaps میسازد، آدرس آن را با leaq 0x20(%rsp), %rdx و بافر نتیجه را با leaq 0x38(%rsp), %rcx میفرستد، و مقدار فیلد .b را از 0x40(%rsp) میخواند.
در این معماری Red zone وجود ندارد. «هر بخش از حافظه که فراتر از آدرس جاری RSP باشد فرار محسوب میشود: سیستمعامل یا یک دیباگر ممکن است در طول نشست اشکالزدایی کاربر یا در زمان اجرای هندلر وقفه، روی این حافظه بازنویسی انجام دهند.» تابع leaf_add در حالت -O0 به 8 بایت متغیر محلی نیاز دارد و این فضا را با pushq %rax در ورودی و popq %rcx پیش از retq به دست میآورد؛ یعنی دو دستور تکبایتی به جای ترکیب subq و addq. مستندات استفاده از پشته توابع برگی مجاز به این کار را اینگونه تعریف میکند: «یک تابع برگ تابعی است که به ورودی در جدول توابع نیاز ندارد. این تابع حق ایجاد تغییر در ثباتهای غیرفرار، از جمله RSP را ندارد».
توابع Variadic ثباتهایشان را در فضای خانگی ذخیره میکنند. «در توابع با آرگومان متغیر یا بدون پیشنمونه (Unprototyped)، هر مقدار ممیز شناور باید در ثبات همارز چندمنظوره نیز تکثیر شود.» تابع sum_varargs در وضعیت -O2 پس از یک دستور pushq %rax، ثباتهای %rdx، %r8 و %r9 را در آفستهای 0x18(%rsp)، 0x20(%rsp) و 0x28(%rsp) ذخیره میکند، که در واقع همان آفستهای ورودی 16، 24 و 32 (فضای ذخیرهی پارامترهای ثباتی دوم، سوم و چهارم) هستند. ساختار va_list در آن با دستور leaq 0x18(%rsp), %rax یعنی موقعیت ذخیرهی %rdx (اولین آرگومان بینام) مقداردهی میشود.
معماری AAPCS64 و تغییرات اعمالشده توسط Apple
استاندارد AAPCS64 در بخش 6.8.2 فهرستی از قواعد را بر پایهی دو شمارنده مدیریت میکند. قاعدهی C.9: آرگومانهای با مقدار صحیح «در کمارزشترین بیتهای x[NGRN] کپی میشوند» و سپس مقدار شمارندهی NGRN یکی افزایش مییابد. قاعدهی C.1 همین فرآیند را برای آرگومانهای ممیز شناور به کمک NSRN انجام میدهد. بر اساس قاعدهی C.12، دادههای مرکب با حداکثر اندازهی 8 - NGRN کلمهی مضاعف (Double-word) «در ثباتهای عمومی متوالی کپی میشوند». طبق C.13، به محض اینکه آرگومانی به پشته سرریز کند، مقدار «NGRN برابر با 8 تنظیم میشود» تا پس از آن هیچ دادهای نتواند به ثباتها برگردد. به همین ترتیب، eight_args تمام ثباتهای x0 تا x7 را مصرف کرده و هرگز به پشته کاری ندارد. تابع mixed از ثباتهای w0، d0، x1 و s1 استفاده میکند. تابع by_value هم در ورودی و هم در خروجی ثباتهای x0 و x1 را به کار میگیرد.
بخش 6.9 تعیین میکند نتیجهای که شرایط انتقال در ثبات را داشته باشد «در همان ثباتها بازگردانده میشود»، در غیر این صورت «آدرس بلوک حافظه باید به عنوان یک آرگومان اضافی در ثبات x8 به تابع داده شود». تابع big_ret دستورهای str x0, [x8]، stur q0, [x8, #0x8] و str x9, [x8, #0x18] را اجرا کرده و اصلاً چیزی در x0 نمینویسد. تابع call_big دستور mov x8, sp را تنظیم میکند و مقدار seed درون x0 باقی میماند، چون در ادامهی بخش 6.9 آمده است که «هیچ الزامی بر عهدهی تابع فراخواندهشده نیست که مقدار موجود در x8 را دستنخورده نگه دارد» و x8 در دستهی ثباتهای اصلی آرگومان قرار نمیگیرد.
بخش 6.1.1: «فراخوانی یک تابع موظف است محتوای ثباتهای r19 تا r29 و همچنین SP را حفظ کند.» طبق 6.4.5.2، در رابطهای عمومی همواره باید SP mod 16 = 0 باشد. پشته از نوع نزولی کامل (Full-descending) است و «هیچ نخی (Thread) مجاز به دسترسی خواندن یا نوشتن در ناحیهی غیرفعال» زیر پشته نیست؛ به همین علت تابع leaf_add در پلتفرم لینوکس و حالت -O0 برای اختصاص 8 بایت متغیر محلی دستور sub sp, sp, #0x10 را اجرا میکند، در حالی که System V در این شرایط از Red zone بهره میبرد.
بخش 6.4.6 رکورد فریم (Frame record) را به این شکل تعریف میکند: «یک رکورد فریم متشکل از دو مقدار 64 بیتی روی پشته... کلمهی مضاعف با آدرس پایینتر به رکورد فریم قبلی اشاره کرده و کلمهی مضاعف با آدرس بالاتر شامل مقداری است که در زمان ورود به تابع جاری، در ثبات LR قرار داشته است.» و در ادامه میآورد: «محل قرارگیری رکورد فریم در داخل یک فریم پشته الزام خاصی ندارد.» کامپایلر clang هر دو حالت قرارگیری را در همین فایل نشان میدهد. تابع keeps با کامپایلر لینوکس و فلگ -O2 این رکورد را در پایینترین بخش قرار میدهد: stp x29, x30, [sp, #-0x30]!; mov x29, sp. همین تابع در سطح -O0 آن را در بالاترین بخش فریم میگذارد: sub sp, sp, #0x20; stp x29, x30, [sp, #0x10]; add x29, sp, #0x10.
یادداشتهای فنی پلتفرم arm64 شرکت Apple، قواعد ثباتها و تراز پشته را حفظ میکند اما ساختار فریم، Red zone و توابع با پارامتر متغیر را تغییر میدهد. «ثبات اشارهگر فریم (x29) باید همواره به یک رکورد فریم معتبر اشاره داشته باشد. برخی توابع مانند توابع برگ یا فراخوانیهای انتهایی (Tail calls) مجازند از ایجاد ورودی در این لیست صرفنظر کنند.» نسخهی Apple تابع keeps ابتدا ثباتهای x22, x21, x20, x19 را ذخیره میکند و سپس رکورد فریم را در بالا میگذارد: stp x29, x30, [sp, #0x20]; add x29, sp, #0x20. همچنین «پلتفرمها ثبات x18 را برای مصارف داخلی رزرو کردهاند؛ از این ثبات استفاده نکنید.» دربارهی حافظه میگوید: «ناحیهی سرخ ARM64 شامل 128 بایت بلافاصله زیر اشارهگر پشته است»، با این حال clang در تابع برگ -O0 همچنان دستور sub sp, sp, #0x10 را صادر کرد؛ بنابراین کامپایلر به طور پیشفرض آن را مصرف نمیکند. پارامترهای متغیر به «تعداد مناسبی از اسلاتهای پشتهی 8 بایتی» اختصاص داده میشوند و «نوع دادهی va_list نام مستعاری برای *char است». تابع call_varargs در سیستمعامل اپل مقادیر 10، 20 و 30 را در [sp]، [sp, #8] و [sp, #0x10] ذخیره کرده و فقط w0 را پاس میدهد؛ اما نسخهی لینوکس مقادیر را در w1، w2 و w3 قرار داده و با یک پرش بهینهی b extva کار را تمام میکند. تابع sum_varargs اپل، آدرس پشتهی فراخوان را به عنوان va_list خود با دستور add x8, sp, #0x10 میخواند؛ در حالی که همین تابع در لینوکس، ثباتهای x1 تا x7 و q0 تا q7 را در یک فریم 224 بایتی ذخیره میکند و فیلدهای ساختار __gr_offs را روی 56- و __vr_offs را روی 128- با یک جفت دستور mov x8, #-0x38; movk x8, #0xff80, lsl #32 مقداردهی اولیه مینماید.
سیستم نمادگذاری نیز متفاوت است. فرمت Mach-O پیشوند _ را به ابتدای تمام نمادهای C اضافه میکند، بنابراین در لیستینگ آن نام _keeps دیده میشود و اولین تابع با برچسب خصوصی ltmp0 ظاهر میگردد. آنالایزر به کمک جدول نمادها این برچسبها را به نام اصلی بازمیگرداند.
بازخوانی و رمزگشایی دستی یک پرولوگ، در سه حالت
تابع keeps(int n) در یک حلقه تابع ext(i) را فراخوانی میکند و مقادیر n، i و acc را در طول فراخوانی زنده نگه میدارد؛ بنابراین طبق تمام ABIها باید ثباتها ذخیره شوند. بایتهای اولیهی خروجی کامپایل با بهینهسازی -O2 را برای هر هدف بررسی میکنیم.
حالت System V در 4 بایت:
- مقدار
55معادل دستورpush r64است که شمارهی ثبات در سه بیت پایینی آن قرار دارد:50 + 5یعنی%rbp. عمق پشته 8 بایت میشود و%rbpدر آفست ورودی 8- ذخیره میگردد. - مقدار
41 56: پیشوند41بایت REX است که بیتBآن روشن است، و56معادل50 + 6است. ترکیب REX.B شمارهی 6 را به 14 گسترش میدهد: یعنیpushq %r14در آفست 16-. - مقدار
53همان50 + 3است: یعنیpushq %rbxدر آفست 24-.
سه دستور push در مجموع 24 بایت به پشته اضافه میکنند. آدرس بازگشت نیز 8 بایت دارد که مجموعاً 32 بایت (مضرب 16) میشود؛ بنابراین فراخوانی callq ext در داخل حلقه بدون نیاز به دستور subq کاملاً تراز است. چون دستوری مثل movq %rsp, %rbp وجود ندارد، این تابع فاقد اشارهگر فریم است. بدنه از %rbp به عنوان یک ثبات Callee-saved جهت نگهداری متغیر acc استفاده میکند (xorl %ebp, %ebp و addl %eax, %ebp). اپیلوگ معکوس دقیق همین است: 5b 41 5e 5d c3.
حالت Microsoft x64 در 7 بایت:
- بایت
56معادلpushq %rsiدر آفست 8- است. بایت57معادلpushq %rdiدر آفست 16- و53معادلpushq %rbxدر آفست 24- است. هر سه ثبات در این ABI غیرفرار هستند، در حالی کهRDIوRSIدر System V ثباتهای آرگومان بودند. - توالی
48 83 ec 20: پیشوند REX.W، آپکد83 /5با عملوند فوری 8 بیتی، و بایت ModRM با مقدارec(معادل11 101 100): یعنیsubq $0x20, %rsp. عمق پشته به 56 میرسد.
مجموع 56 بایت بهعلاوهی 8 بایت آدرس بازگشت برابر 64 میشود که تراز است. این 32 بایت همان Shadow store الزامی برای ext است. مستندات اپیلوگ فقط ساختارهایی مثل «یک دستور add RSP,constant یا lea RSP,constant[FPReg]، به همراه توالی صفر یا چند دستور pop ثباتهای 8 بایتی و یک دستور return یا jmp» را مجاز میداند، و بایتهای کد در اینجا عبارتند از: 48 83 c4 20 5b 5f 5e c3.
معماری AArch64 در 16 بایت به صورت چهار کلمهی Little-endian:
- دستور
a9bd7bfdیکstpبا پیشایندکس (Pre-index) است. بیتهای 31:30 با مقدار10ثباتهای 64 بیتی را انتخاب میکنند، بیتهای 25:23 با مقدار011حالت Pre-index را مشخص میکنند، و بیت 22 با مقدار 0 نشاندهندهی Store است. مقدار imm7 در بیتهای 21:15 برابر1111010(معادل 6-) است که در 8 ضرب شده و 48- را میسازد. فیلد Rt2 در بیتهای 14:10 عدد 30، فیلد Rn در بیتهای 9:5 عدد 31 (همانsp) و فیلد Rt در بیتهای 4:0 عدد 29 است. خروجیstp x29, x30, [sp, #-0x30]!میشود: اشارهگر پشته 48 واحد پایین میرود،x29در آفست 48- وx30در 40- مینشیند. - دستور
f9000bf5یکstrبا آفست بدون علامت 12 بیتی ضربدر 8 است: مقدار imm12 در بیتهای 21:10 برابر 2، فیلد Rn همانsp، و Rt برابر 21 است. خروجیstr x21, [sp, #0x10]با آفست ورودی 32- خواهد بود. - دستور
910003fdیکaddبا مقدار فوری است: مقدار imm12 برابر 0، ثبات Rn همانsp، و Rd برابر 29 است. خروجیmov x29, spمیشود: اشارهگر فریم دقیقاً به رکورد ایجادشده در آفست 48- اشاره میکند. - دستور
a9024ff4یکstpبا آفست علامتدار است: مقدار imm7 برابر 4 است که مقیاس آن به 32 میرسد. ثبات Rt2 برابر 19 و Rt برابر 20 است. حاصلstp x20, x19, [sp, #0x20]است: ثباتx20در آفست 16- وx19در آفست 8- مینشیند.
اندازهی فریم 48 بایت است: x29 در آفست 0، x30 در آفست 8، x21 در 16، 8 بایت پدینگ (حاشیهگذاری) در 24، x20 در 32، و x19 در 40. همانطور که بخش 6.4.6 الزام میکند، کلمهی آدرسپایینتر رکورد به رکورد قبلی اشاره دارد و کلمهی بعدی حاوی ثبات LR است. در این میان، دستور cmp w0, #0x1 بین اولین و دومین عملیات ذخیرهسازی جا گرفته است؛ اسکژولر این دستور را به درون پرولوگ منتقل کرده و آنالایزر توانایی چشمپوشی از مقایسهها یا دستورهای حسابی بین عملیاتهای پشته را دارد.
نگارش دوگانهی اطلاعات توسط کامپایلر
تجزیهی پرولوگ یک رویکرد برای استخراج اطلاعات است. با این حال، کامپایلر دقیقاً همین دادهها را برای سازوکار بازگشایی پشته در هنگام خطا به شکل دیگری نیز ذخیره میکند که هر دو بخش باید کاملاً یکسان باشند.
در ویندوز، این دادههای موازی در سکشنهای .pdata و .xdata ذخیره میشوند. اگر فایل کامپایلشده با -O2 را بدون فلگ -fno-asynchronous-unwind-tables بسازید، خروجی دستور llvm-objdump -h وجود سکشن .xdata با حجم 0x34 بایت و .pdata با حجم 0x48 بایت را نشان میدهد. مقدار 0x48 معادل 72 است که نشاندهندهی شش ورودی RUNTIME_FUNCTION به حجم 12 بایت است که هر کدام سه جابهجایی (Relocation) از نوع IMAGE_REL_AMD64_ADDR32NB دارند. این شش تابع عبارتند از: sum_varargs، keeps، call8، call_varargs، call_pair و call_big. شش تابع دیگر در دستهی توابع برگ قرار میگیرند؛ مستندات مدیریت خطای ویندوز میگوید: «اگر جستجو به ورودی در جدول توابع نرسد، کد به عنوان تابعی برگ در نظر گرفته میشود و RSP مستقیماً آدرس بازگشت را در بر دارد.»
بایتهای سکشن .xdata برای تابع keeps به صورت 01 07 04 00 07 32 03 30 02 70 01 60 ذخیره شده است. ساختار UNWIND_INFO با یک فیلد 3 بیتی Version و 5 بیتی Flags شروع میشود: نسخهی 1، بدون فلگ. فیلد SizeOfProlog برابر 7، فیلد CountOfCodes برابر 4، و FrameRegister به همراه FrameOffset برابر 0 هستند. هر ساختار UNWIND_CODE شامل یک آفست در پرولوگ به همراه یک بایت مشخصکنندهی آپکد و اطلاعات جانبی است. توالی 07 32: در آفست 7، عملیات 2 همان UWOP_ALLOC_SMALL است، و اطلاعات 3 به معنی تخصیص 3 * 8 + 8 = 32 بایت است. توالی 03 30: عملیات 0 همان UWOP_PUSH_NONVOL است و ثبات 3 به rbx اشاره دارد. توالی 02 70: به ثبات rdi، و 01 60: به ثبات rsi اشاره میکند. کدهای عملیات به ترتیب معکوسِ اجرا ذخیره میشوند و دقیقاً همان سه عمل push و همان تخصیص 32 بایتی موجود در توالی بایتهای 56 57 53 48 83 ec 20 را بازتاب میدهند. ورودی تابع sum_varargs به صورت 01 01 01 00 01 02 00 00 است: پرولوگی تکبایتی با یک کد، یعنی UWOP_ALLOC_SMALL به میزان 8 بایت. از دید آنوایندر، دستور pushq %rax یک عمل تخصیص فضا در نظر گرفته میشود؛ زیرا ثبات %rax فرار است و چیزی در آن ذخیره نمیشود.
در لینوکس، این اطلاعات موازی درون بخش .eh_frame ثبت میشود که ساختار آن در psABI 4.2.4 تعریف شده و با فرمت DWARF 5 بخش 6.4 انکود میگردد. اجرای دستور llvm-dwarfdump --eh-frame روی فایل کامپایلشده، ساختار یک ورودی FDE به ازای هر تابع را چاپ کرده و جابهجاییهای R_X86_64_PC32 را بازمیگشاید؛ از این رو بازهها معتبر هستند. خروجی برای keeps:
DW_CFA_def_cfa_offset: +16
DW_CFA_def_cfa_offset: +24
DW_CFA_def_cfa_offset: +32
DW_CFA_offset: RBX -32
DW_CFA_offset: R14 -24
DW_CFA_offset: RBP -16
مقدار CFA در واقع همان مقدار اشارهگر پشته پیش از فراخوانی است (یعنی 8 بایت بالاتر از پشته در لحظهی ورود). بنابراین RBP -16 معادل آفست ورودی 8-، و R14 -24 معادل 16-، و RBX -32 معادل 24- است. عمیقترین آفست CFA که 32 است، نشاندهندهی فریم 24 بایتی بهعلاوهی 8 بایت آدرس بازگشت است. ساختار FDE در AArch64 برای تابع keeps مقدار DW_CFA_def_cfa: W29 +48 را به همراه مقادیر W29 -48، W30 -40، W21 -32، W20 -16 و W19 -8 نشان میدهد. در آنجا خودِ CFA همان اشارهگر پشته در بدو ورود است؛ بنابراین آفستها بدون نیاز به تبدیل، منطبق بر آفستهای فریم هستند، و مقدار CFA تا زمانی که رکورد فریم زنده باشد بر اساس x29 تعریف میشود. تابع eh_frame_cross_check() این مقایسه را روی تمامی 12 ورودی FDE هر دو آبجکتفایل اجرا میکند. یک مورد نیاز به دقت داشت: تابع call8 در استاندارد System V دو آرگومان را در میانهی اجرای خود روی پشته push میکند؛ از این رو عمیقترین آفست CFA آن 32 است در حالی که فریم پرولوگ آن 8 است، و اعتبارسنجیْ عمیقترین نقطهای را که پشته در طول بدنه لمس میکند (24) با مقدار 8 جمع کرده و تطبیق میدهد.
نامگذاری ابزارهای مختلف مهندسی معکوس برای این مفاهیم
تمام ابزارهای این حوزه ساختار دادهای اختصاصی برای نگهداری شیوهی فراخوانی دارند و از یک مرحلهی تحلیلی (Pass) برای تخصیص آن به هر تابع استفاده میکنند. نامها و خطاهای هر ابزار با دیگری تفاوت دارد. ابزارهای تجاری بر اساس مستنداتشان بررسی شدهاند و ابزارهای متنباز روی نمونههای کامپایلشده اجرا شده و خروجیهایشان در کنار فایلها ثبت گردیده است.
نرمافزار Ghidra 12.1.4 (تحت لایسنس Apache-2.0): قواعد در فایلهای مشخصات کامپایلر با پسوند .cspec در کنار تعاریف زبان SLEIGH قرار دارند. فایل x86-64-gcc.cspec یک پیشالگو تحت عنوان <default_proto> به نام __stdcall تعریف میکند که بر اساس مستندات آن «برگرفته از ضمیمهی معماری پردازندهی System V Application Binary Interface AMD64 نگارش آوریل 2016 است». این فایل لیستهای <pentry> را برای ثباتهای XMM0_Qa تا XMM7_Qa و ثباتهای RDI, RSI, RDX, RCX, R8, R9 مشخص کرده، ورودی پشته را در آفست 8 قرار داده، از قاعدهی join_dual_class برای انواع داده تا 16 بایت استفاده میکند، مقادیر بازگشتی را در XMM0_Qa, XMM1_Qa, RAX, RDX مینشاند و برای بقیهی موارد حالت hidden_return را به کار میبرد. فایل x86-64-win.cspec قرارداد __fastcall را با چهار تگ <group> تعریف میکند که XMM0_Qa را با RCX، و XMM1_Qa را با RDX و الی آخر جفت میکند؛ این همان رویکرد موضعی است. این فایل ورودی پشته را در آفست 40 در نظر گرفته، برای ساختارهای 3، 5، 6 یا 7 بایتی از convert_to_ptr با کامنت «تنها ساختارهای 8، 16، 32 یا 64 بیتی امکان قرارگیری در ثبات را دارند» استفاده میکند، ثباتهای RBX RBP RDI RSI RSP R12 تا R15 و XMM6 تا XMM15 را در بخش unaffected میآورد، و بازهی localrange از آفست 8 تا 39 را به عنوان Shadow store علامت میزند. فایل AARCH64.cspec نیز پروتکل __cdecl را مشخص میکند که اولین ورودی آن x8 با مشخصهی storage="hiddenret" است، سپس q0 تا q7 و x0 تا x7 میآیند؛ انواع بزرگتر از 17 بایت به اشارهگر تبدیل میشوند (convert_to_ptr) و ثبات x8 در لیست killedbycall با این توضیح قرار دارد: «ثبات حامل مکان خروجی غیرمستقیم که در لیست pentry منعکس نشده است». فاز پایانی دکامپایلر "Establish Function's Prototype" نام دارد؛ بخشهای آنالیز نیز با عناوین "Call Convention ID" و "Decompiler Parameter ID" شناخته میشوند و گزینهی "Prototype Evaluation" در تنظیمات، مدل پیشفرض را تعیین میکند.
نرمافزارهای IDA Pro و IDA Free (محصول شرکت Hex-Rays، تجاری؛ نگارش کنونی 9.4، بیلد 9.4.260714): امضای تابع در قالب کدهای C و به وسیلهی دستور SetType ثبت میشود و طبق مستندات «IDA از اطلاعات نوع داده استفاده میکند تا در لیستینگ دیساسمبلی، آرگومانهای تابع را کامنتگذاری کند». آرایشهایی که با هیچ قرارداد استانداردی منطبق نباشند با نامگذاری اختصاصی IDA مشخص میشوند: مثلاً تعریف int __usercall func@<ebx>(int x, int y@<esi>) خروجی را در EBX و متغیر y را در ESI تعیین میکند، و ساختار @<^12.4> برای ارجاع به اسلات پشته بر اساس آفست و اندازه به کار میرود. کلمهی کلیدی __userpurge همین کارکرد را با پاکسازی پشته توسط فراخواندهشده ترکیب میکند. در همان بخش، شناسههای __spoils برای لیست ثباتهای تخریبشده، و __hidden، __return_ptr و __struct_ptr برای نشان دادن آرگومانهای ضمنی (که در این درس دستی استخراج میشوند) مطرح شدهاند. قابلیت "Decompile as call" به کاربر اجازه میدهد برای یک دستورالعمل خاص، پروتوتایپ __usercall تعیین کند. نسخهی IDA Free تنها از x86 و x86-64 پشتیبانی میکند.
نرمافزار Binary Ninja 6.0 با نام Krypton (محصول Vector 35، تجاری، به همراه نسخهی Free با امکان دکامپایل x86، x86_64، ARMv7 و AArch64): در این پلتفرم، ساختار Platform «شامل تمامی اطلاعات محیط اجرای باینری، به ویژه شیوههای فراخوانی، سیستمعامل و معماری است» و مواردی چون linux-x86_64، windows-x86_64، linux-aarch64 و mac-aarch64 را در بر میگیرد که هر کدام متغیر default_calling_convention مشخصی دارند. یک شیء CallingConvention حاوی لیستهای int_arg_regs، float_arg_regs، callee_saved_regs، caller_saved_regs، int_return_reg، high_int_return_reg، float_return_reg و دو فلگ است که تفاوتهای مطرحشده در این درس را مستقیماً نشان میدهند: فلگ arg_regs_share_index که «اگر True باشد، آرگومان Nام بدون توجه به نوع داده، اسلات Nام را از هر دو لیست عدد صحیح و ممیز شناور اشغال میکند»، و فلگ stack_reserved_for_arg_regs که مشخصکنندهی «فضای shadow/home مورد استفاده در شیوهی فراخوانی Windows x64» است. شناسههای eligible_for_heuristics، required_arg_regs و required_clobbered_regs موتور تخمین اکتشافی این ابزار را هدایت میکنند. انواع پلتفرمها در قالب فایلهای هدر C در مسیر types/platform/ قرار دارند.
نرمافزار radare2 6.2.4 (تحت لایسنس LGPL-3.0، تستشده در این درس): دستور afcl لیست قراردادهای شناختهشده توسط معماری جاری را ارائه میدهد: مواردی همچون amd64 amd64syscall dlang golang golang_abi0 ms p9 reg swift برای x86-64 و گزینههای arm64 dlang golang p9 reg swift برای معماری AArch64. دستور afc شیوهی فراخوانی تابع فعلی را نمایش میدهد، دستور afcr نگاشتهایی مثل cc.amd64.arg0: rdi تا arg5: r9 و مقدار بازگشتی ret0: rax را چاپ میکند، و پروفایلها در قالب فایلهای SDB با الگوی نامگذاری cc-ARCH-BITS.sdb ذخیره شدهاند. نرمافزار radare2 به درستی گزینهی amd64 را برای باینری ELF و ms را برای فایل COFF انتخاب کرد. دستور afv آرگومانها و متغیرها را برمیشمرد، و afs امضای تابع را میسازد. در فایل بهینهشدهی -O2 لینوکس، دستور afv هفت آرگومان برای تابع eight_args پیدا کرد و ثبات rcx را جا انداخت؛ در فایل ویندوزی نیز فقط rcx, rdx, r8 و چهار اسلات پشته را تشخیص داد و r9 را از قلم انداخت. در هر دو مورد، ثباتهای جاافتاده تنها درون ساختار دستور lea حضور داشتند. در خصوص تابع mixed، در لینوکس مقدار double arg1 @ xmm0, arg2 @ xmm1, arg3 @ xmm2 را نشان داد و در ویندوز هیچ آرگومانی تشخیص نداد. روی AArch64 آرگومانهای x0 تا x7 را برای eight_args شناخت، ولی برای big_ret آرگومانهای x0 و double arg2 @ d1 را خروجی داد در حالی که تابع عملاً x8 را خوانده و ثابتی را در q1 لود میکند. تمام خروجیهای afs با نوع void شروع میشوند زیرا نوع خروجی استنتاج نمیشود. در حالت -O0 که همهی آرگومانها روی فریم ریخته میشوند، afv هر هشت آرگومان eight_args را به درستی همراه با rbp+0x10 و rbp+0x18 برای جفت روی پشته تشخیص داد.
نرمافزار rizin 0.9.1 (تحت لایسنس LGPL-3.0، تستشده در این درس) و Cutter 2.5.0: دستور afcl گزینههای amd64 amd64syscall ms reg swift را برمیگرداند. دستور afc برای فایل COFF مقدار amd64 را نشان داد؛ بنابراین با اینکه پروفایل مایکروسافت در ابزار تعریف شده بود، اما از فرمت فایل استنتاج نشد. دستور afi برای eight_args روی لینوکس هفت آرگومان (باز هم بدون rcx) نشان داد، و روی ویندوز آرگومانهای r8, rcx, rdx به همراه سه اسلات پشته (بدون r9 و بدون اسلات موجود در 0x40) را خروجی داد. دستوری به نام afv وجود ندارد و معادل آن afvl است. نرمافزار Cutter رابط گرافیکی کاربری بر روی rizin است.
نرمافزار angr 10.0.3 (تحت لایسنس BSD-2، تستشده در این درس): اجرای CFGFast و به دنبال آن CompleteCallingConventions(recover_variables=True) ویژگی function.calling_convention را بر مبنای فرمت فایل به کلاسهای SimCCSystemVAMD64، SimCCMicrosoftAMD64 یا SimCCAArch64 نگاشت میکند و مقدار function.prototype را با یک امضا مقداردهی مینماید. این ابزار هر هشت آرگومان تابع eight_args را در هر سه هدف به درستی پیدا کرد، اشارهگر پنهان big_ret را در x86-64 به شکل (BOT*, long long) -> BOT* شناخت، ارسال ارجاعی ساختار در تابع by_value ویندوز را به صورت (BOT*, BOT*) -> BOT* درآورد، و تابع variadic با نام sum_varargs را در System V به شکل (long long, ...) بازیابی کرد. با این حال خطاهای آن با radare2 متفاوت بود: تابع mixed در System V و AArch64 به شکل (long long, long long, double, float) استخراج شد؛ یعنی ثباتهای صحیح با ترتیبی اشتباه ثبت شدند زیرا خروجی لیست اعداد صحیح را پیش از لیست اعشاری چاپ میکند. تابع big_ret در معماری AArch64 به عنوان (long long) -> double تشخیص داده شد که نشان میدهد قاعدهی x8 نادیده گرفته شده و ذخیرهسازیهای برداری به عنوان خروجی فرض شدهاند. در نهایت تابع sum_varargs روی پلتفرم AArch64 با 16 پارامتر استخراج شد که کل فضای ذخیرهسازی ثباتها را پوشش داده بود.
سایر ابزارها: نرمافزار Hopper 6.6.0 یک دیساسمبلر و دکامپایلر تجاری برای macOS و Linux است؛ عملکرد آن روی شیوههای فراخوانی در این درس پیاده یا مستند نشده است. ابزارهای دیباگر مثل lldb 1703 و gdb ساختار پشتهی در حال اجرا را از طریق دستورهای frame info و register read نمایش میدهند که نمای پویای همین آفستها در درس 42 است. وبسایت Compiler Explorer هر سه لیستینگ دیساسمبلی را در کنار سورسکد اصلی نشان میدهد، که سریعترین روش جهت اعتبارسنجی یک قاعده پیش از بررسی بایتهای آن است.
| مفهوم | Ghidra | IDA | Binary Ninja | radare2 / rizin | angr | این درس |
|---|---|---|---|---|---|---|
| شیء شیوهی فراخوانی | prototype در فایل .cspec |
کلمهی کلیدی calling convention در نوع تابع | CallingConvention بر روی پلتفرم |
نام در afc، فایل cc-ARCH-BITS.sdb |
زیرکلاس SimCC |
expected_locations(abi, name) |
| مقدار پیشفرض هر تارگت | <default_proto> در هر cspec |
تنظیمات کامپایلر | Platform.default_calling_convention |
متغیرهای afc= و anal.cc |
بر اساس فرمت باینری | describe_object() |
| ثباتهای آرگومان | لیست <pentry>، تگ <group> برای موضعی |
@<reg> در __usercall |
ثباتهای int_arg_regs، float_arg_regs، arg_regs_share_index |
cc.NAME.argN |
لیستهای ARG_REGS و FP_ARG_REGS |
INT_ARG_REGS, FLOAT_ARG_REGS |
| فضای Shadow store | اسلات پشتهی 8 تا 39 در localrange |
ضمنی | stack_reserved_for_arg_regs |
cc.ms.argn=stack |
STACKARG_SP_DIFF |
STACK_ARG_BASE[WIN64] = 40 |
| اشارهگر مخفی نتیجه | مقادیر hidden_return و storage="hiddenret" |
عبارات __return_ptr و __hidden |
خروجی بزرگتر از گنجایش int_return_reg |
مدل نشده است | اولین آرگومان با نام *BOT |
HIDDEN_RETURN_REG |
| ثباتهای Callee-saved | تگ <unaffected> |
لیست __spoils عکس آن را دارد |
فیلد callee_saved_regs |
پروفایل | آرگومان CALLEE_CLEANUP و معماری |
CALLEE_SAVED |
| تخصیص شیوه به تابع | آنالایزرهای "Call Convention ID" و "Establish Function's Prototype" | کاربر یا کتابخانهی تایپها | تشخیص اکتشافی با required_arg_regs |
دستورهای afc، afva و aaft |
مرحلهی CallingConventionAnalysis |
تحلیل خواندن قبل از نوشتن به همراه کتابچهی قواعد |
پیادهسازی از صفر
اجرای برنامهی دمو:
cd phases/13-tools-and-protocols/38-calling-conventions-and-stack-frames
python3 code/main.py
خروجی برنامه یک گزارش ساختاریافته به فرمت JSON است. این گزارش به ازای تکتک آن هفت آبجکتفایل، فرمت، معماری و ABI را تعیین میکند؛ و به ازای هر یک از دوازده تابع، بایتهای پرولوگ، ثباتهای ذخیرهشده به همراه آفستهای ورودی، اندازهی فریم پشته، اشارهگر فریم، ثباتهای پارامتر، آفستهای آرگومانهای روی پشته، مقادیر Spilled (ذخیرهشده در پشته)، ثبات برگشتی یا اشارهگر مخفی، عمق پشته در زمان فراخوانیها، و ثباتها و مقادیر ذخیرهشدهی خروجی در توابع فراخوان را ارائه میدهد. در پایان هر تابع، چکهای اعتبارسنجی آن آورده شده است: متن قاعده، مقدار مورد انتظار بر اساس امضای سورسکد، مقدار مشاهدهشده از روی بایتهای ماشین، و وضعیت تطابق آنها. انتهای گزارش شامل دو بررسی متقابل بر اساس دادههای Unwind و اطلاعات استخراجشده از سایر ابزارهاست. در جریان تحلیل این 84 تابع، مجموعاً 489 بررسی تطبیقی انجام میشود که تمام 489 بررسی نتیجهی مثبت میدهند.
ساختار مورد انتظار:
{
"objects": {
"convention-x86_64-windows-O2": {
"format": "COFF", "machine": "AMD64", "abi": "Microsoft x64", "checks": "73/73 agree",
"functions": {
"keeps": {
"prologue": ["56 pushq %rsi", "57 pushq %rdi", "53 pushq %rbx", "48 83 ec 20 subq $0x20, %rsp"],
"frameBytes": 56,
"savedRegisters": [{"register": "rsi", "entryOffset": -8}, {"register": "rdi", "entryOffset": -16}, {"register": "rbx", "entryOffset": -24}],
"parameterRegisters": ["rcx"],
"returnRegisters": ["rax"],
"calls": [{"callee": "ext", "depth": 56}],
"checks": [{"rule": "Microsoft x64: the caller must always allocate ...", "expected": "at least 32 bytes ...", "observed": [56], "agree": true}, "..."]
}
}
}
},
"sehCrossCheck": ["..."],
"ehFrameCrossCheck": {"...": "..."}
}
برای درک بهتر، کدهای فایل code/main.py را به این ترتیب بررسی کنید:
- تابع
describe_object()هدر باینریهای ELF، COFF یا Mach-O و جدول نمادها را پردازش کرده و نام ABI و فهرست توابع را استخراج میکند. - تابع
parse_listing()متن حاصل از دستورllvm-objdump -d -rرا بر اساس توابع تفکیک کرده، مقاصد Relocation را برای پرشها و فراخوانیها نگه میدارد، و کامنتها را بر اساس ساختار نحوی معتبر حذف میکند: کاراکتر#در سینتکس AT&T، و کاراکترهای//و;در AArch64 (چرا که در آنجا#نشانگر عملوند فوری است). - توابع
effect_x86()وeffect_arm()هر دستور را به مجموعهای از خواندنها، خواندنهای آدرس، نوشتنها، بارگذاریها (Loads)، ذخیرهسازیها (Stores) و تغییرات اعمالشده بر اشارهگر پشته تبدیل میکنند. در سینتکس AT&T آخرین عملوند مقصد است؛ در AArch64 اولین عملوند مقصد به حساب میآید، مگر در دستورهای ذخیرهسازی، مقایسهها و branchها. دستورleaفقط آدرس را محاسبه و میخواند و هیچ لودی از حافظه ندارد. مقصد دستوراتی نظیرmov،cvtsi2sd،setcc، و دستور صفر کردن یعنیxor r, rصرفاً یک عمل نوشتن است. یک فراخوانی (Call) تمام ثباتهای فرار را بازنویسی میکند. دستور Store با پیشایندکس اشارهگرspرا پیش از دسترسی، و Load با پسایندکس (Post-index) آن را بعد از دسترسی جابهجا میکند. - تابع
analyze_function()بدنهی تابع را یک بار تا رسیدن به اولینretیا پرش انتهایی پیمایش میکند. این تابع عمق نفوذ اشارهگر پشته را نسبت به وضعیت ورود ردیابی میکند؛ به طوری که عملوند حافظهای مثلdisp(%rsp)در آفست ورودیdisp - depthو عبارتdisp(%rbp)در آفستframe_pointer + dispتفسیر میشود. همچنین ثباتهای آرگومان را که پیش از مقداردهی خوانده میشوند، دسترسیهای پشته از آفست مربوط به اولین آرگومان به بعد، ذخیرهسازی ثباتهای ورودی در فریم پشته، دسترسیهای پایینتر از اشارهگر پشته، و عمق پشته در زمان هر فراخوانی را ثبت میکند. - تابع
_return_registers()اگر آخرین دسترسی به یک ثبات قبل از خروج تابع از نوع نوشتن باشد، آن را به عنوان ثبات خروجی در نظر میگیرد. اگر از ثبات به عنوان عملوند دادهای خوانده شود رد صلاحیت میگردد، اما اگر به عنوان پایهی یک آدرس خوانده شود همچنان معتبر است؛ این همان قاعدهای است که ثباتraxرا در تابعbig_retدر حالت Microsoft x64 حفظ میکند، جایی که بعد ازmovq %rcx, %raxعملیاتهای نوشتن متوالی از طریق(%rax)داریم. - تابع
expected_locations()قواعد سه کتابچه را روی امضای C اعمال میکند: دو لیست مستقل System V همراه با کلاسهای هشتبایتی، اسلاتهای موضعی مایکروسافت با ساختارهای ارجاعی، و شمارندههای استاندارد AAPCS64 همراه با ثباتx8برای خروجیهای بزرگتر از 16 بایت. - تابع
run_checks()مقادیر واقعی را با انتظارات تطبیق داده و قواعد ساختار فریم را میسنجد: ثباتهای غیرفراری که دستکاری شدهاند باید حتماً ذخیره شده باشند، شرط(rsp + 8) % 16 == 0یاsp % 16 == 0در زمان هر فراخوانی برقرار باشد، وجود 32 بایت فضای Shadow در هر فراخوانی مایکروسافت رعایت گردد، هیچ دسترسی به پایین پشته در مایکروسافت و AAPCS64 و حداکثر 128 بایت دسترسی در System V ثبت نشود، و ساختار اپیلوگ قرینهی دقیق پرولوگ بوده و پشته در انتها به مکان اولیهاش بازگردد. - توابع
seh_cross_check()وeh_frame_cross_check()دادههای Unwind موجود در فایلهای بدون فلگ-fno-asynchronous-unwind-tablesرا تحلیل کرده و آنها را با نتایج حاصل از پردازش پرولوگ مقایسه میکنند.
سه دستور کمکی دیگر نیز در برنامه تعریف شده است: دستور python3 code/main.py analyze convention-x86_64-windows-O2 keeps جزئیات یک تابع مشخص را چاپ میکند. دستور python3 code/main.py object FILE خروجی تطبیق هدر را نمایش میدهد. دستور python3 code/main.py seh نیز جزئیات ساختار UNWIND_INFO را برای تمام توابع واجد شرایط رمزگشایی میکند.
استفاده از ابزارهای واقعی
روی یک فایل باینری جدید که کامپایل کردهاید، مراحل زیر را با آنالایزر اجرا کنید:
- دستور
python3 code/main.py object FILE.oرا اجرا کرده و نام ABI گزارششده را یادداشت کنید. اگر نوع هدر با فرمان کامپایل شما تناقض دارد کار را متوقف کنید؛ یکی از آنها اشتباه است. - با اجرای
llvm-objdump -d -r FILE.oفایل لیستینگ را کنار آبجکتفایل ذخیره کرده و تحلیل را شروع کنید. پیش از هر چیز بایتهای پرولوگ و ثباتهای ذخیرهشده را بخوانید، و اندازهی فریم را با عملیاتهای push و دستورsubمقایسه کنید. - ثباتهای پارامتر و آفستهای پشته را با امضای تابع در سورسکد بسنجید. اگر ثباتی وجود دارد که امضا آن را توجیه نمیکند، یا با یک اشارهگر مخفی روبهرو هستید، یا با نشانگر پارامتر متغیر، و یا با یک ثبات موقت که تحلیلگر آن را به اشتباه آرگومان فرض کرده است. مورد آخر از نقص جدول اثرات دستورات (Effect table) ناشی میشود که باید آن را با قاعدهای عمومی برای آن دستور رفع کنید.
- مغایرتهای احتمالی در گزارش ارزیابی را بخوانید. از آنجا که هر ارزیابی به قاعدهی مشخصی ارجاع میدهد، گام نخست بررسی درستی قاعده، سپس بازبینی بایتهای کد، و در نهایت تصحیح آنالایزر است.
- اگر فایل باینری دارای اطلاعات Unwind است، تطبیق متقابل را اجرا کنید. انطباق این دو منبع مستقل، محکمترین شواهد (Evidence) درستی را به دست میدهد.
- اگر ابزار دیگری نصب دارید، خروجی آن را با دستور مربوطه استخراج کرده و نتایج را بسنجید. اگر نتیجه یکسان بود، سه منبع تأیید دارید؛ اگر متفاوت بود، نقطه و ثبات خاصی را پیدا کردهاید که نیازمند موشکافی است، و این موضوع پایهی ادامهی تحلیل در درس 35 خواهد بود.
آزمایشگاه تعاملی
بخش اول: یک دستور، سه اثر مختلف
از مسیر دایرکتوری code مفسر Python را باز کنید:
cd phases/13-tools-and-protocols/38-calling-conventions-and-stack-frames/code
python3 -q
دستورهای زیر را اجرا کنید:
from main import *
effect(Instruction(0, "48 8d 04 0a", "leaq", "(%rdx,%rcx), %rax"), SYSV)
effect(Instruction(0, "f2 0f 2a d7", "cvtsi2sd", "%edi, %xmm2"), SYSV)
effect(Instruction(0, "a9bd7bfd", "stp", "x29, x30, [sp, #-0x30]!"), AAPCS64)
پیش از اجرای هر خط، ثباتهای خواندهشده، ثباتهای به کار رفته در آدرسدهی، ثباتهای نوشتهشده و میزان تغییر پشته را پیشبینی و یادداشت کنید. سپس ساختار [sp, #-0x30]! را با دستور ldp به شکل [sp], #0x30 تغییر دهید و مشاهده کنید که چگونه میزان جابهجایی پشته از پارامتر sp_delta به post_sp_delta منتقل میشود.
بخش دوم: ساختار فریم پشته در تابع keeps
info, listing = load_fixture("convention-aarch64-linux-O2")
keeps = analyze_function("keeps", listing["keeps"], info.abi)
keeps.saved
keeps.frame_pointer
[c.to_dict() for c in frame_cells(keeps)]
آفستهای به دست آمده را با نتایج تحلیل دستی در بخشهای قبل تطبیق دهید. سپس باینری convention-arm64-macos-O2 را بارگذاری کرده و همان سه خط را فراخوانی کنید؛ خواهید دید که رکورد فریم به آفست 16- جابهجا شده و چهار ثبات در زیر آن قرار گرفتهاند. در ادامه فایل convention-x86_64-linux-O2-fp را لود کرده و مقدار saved را برای keeps ببینید: اشارهگر فریم بازگشته است، و همزمان یک دستور pushq %rax اضافه شده که تنها وظیفهاش رساندن (rsp + 8) به مضربی از 16 در زمان فراخوانی است.
بخش سوم: شناسایی الگوی خواندن قبل از نوشتن در تابع mixed
info, listing = load_fixture("convention-x86_64-linux-O2")
mixed = analyze_function("mixed", listing["mixed"], info.abi)
mixed.param_registers
mixed.return_registers
expected_locations(SYSV, "mixed")["parameters"]
expected_locations(WIN64, "mixed")["parameters"]
ترتیب ثباتها در System V برابر با rdi, xmm0, rsi, xmm1 و در مایکروسافت rcx, xmm1, r8, xmm3 است. حالا فایل ویندوزی را لود کرده و سه خط اول را مجدداً اجرا کنید. سپس خروجی ثبتشده از radare2 را برای لینوکس باز کرده و بخش [afv mixed] را پیدا کنید: این ابزار سه متغیر از نوع double یافته است. دستوری را که باعث شده تا ثبات سوم به اشتباه آرگومان تلقی شود پیدا کنید.
بخش چهارم: تأیید از طریق دادههای Unwinder کامپایلر
for row in seh_cross_check():
print(row["function"], row["sizeOfProlog"], row["unwindPushes"], row["unwindAllocBytes"], row["agree"])
for row in eh_frame_cross_check("convention-x86_64-linux-O2-eh"):
print(row["function"], row["cfiSaved"], row["cfiMaxOffset"], row["deepestStack"], row["agree"])
دستور pushq %rax از تابع sum_varargs را در جدول SEH پیدا کنید و توضیح دهید چرا کد Unwind آن را به عنوان یک عمل تخصیص فضا (Allocation) ثبت کرده است. سپس تابع call8 را در جدول .eh_frame بررسی کرده و دلیل وجود عدد 32 را شرح دهید.
تمرین عملی
یک تارگت چهارم و یک کتابچهی قواعد چهارم اضافه کنید.
نیازمندیها:
- فایل
convention.cرا با دستور-target x86_64-apple-macos -O2کامپایل کرده و لیستینگ دیساسمبلی آن را استخراج کنید. تابعdescribe_object()را طوری توسعه دهید که مقدارcputypeبا کد 0x01000007 در Mach-O را بخواند و آن را با در نظر گرفتن پیشوند نماد_به استاندارد System V psABI (که اپل در سامانههای x86-64 از آن تبعیت میکند) نگاشت کند. - تطابق تمام توابع را با ارزیابیهای System V بسنجید. یک تفاوت ساختاری در پرولوگها را پیدا و ثبت کنید: سیستمعامل Darwin در تمامی سطوح بهینهسازی (Optimization)، اشارهگر فریم را حفظ میکند.
- نسخهای از باینری لینوکس x86-64 را همراه با فلگ
-fstack-protector-strongکامپایل کنید. اسکن پرولوگ را طوری گسترش دهید که عملیات بارگذاری Canary از%fs:0x28و ذخیرهی آن در زیر متغیرهای محلی را به عنوان یک خانهیcanaryبشناسد، و دستور مقایسه پیش ازretرا به عنوان چک محافظتی اپیلوگ ثبت نماید. - یک تست بنویسید که اگر ذخیرهسازی مقدار Canary به اشتباه به عنوان یک آرگومان Spilled تلقی شد، با خطا مواجه شود.
- طرح فریم کدهای محافظتشده با قناری را پیش از اجرای آنالایزر، به شکل دستی رسم کنید و تصویر یا دیاگرام آن را کنار کدهای تست قرار دهید.
معیار موفقیت این است که اجرای python3 code/main.py آبجکت جدید را گزارش دهد، تمام چکها با موفقیت پاس شوند، و سلولهای فریمِ توابع محافظتشده، مقدار canary را دقیقاً در میان متغیرهای محلی و ثباتهای ذخیرهشده نشان دهند.
خروجی این درس
این درس سند راهنمای outputs/skill-calling-conventions-and-stack-frames.md را به شما تحویل میدهد. با استفاده از آن میتوانید یک فایل کامپایلشده، لیستینگ و امضاهای C آن را دریافت کنید و محل پارامترها، چیدمان فریم، ثبات مقدار بازگشتی، و انطباق هر استنتاج با قواعد مرجع و اطلاعات Unwind را به دست آورید. این ابزار ابتدا نوع ABI را از روی هدر الزامی میداند، تلقی کردن مقصد دستورهایی نظیر cvtsi2sd یا xor r, r به عنوان خواندن را ممنوع میکند، و با مشاهدهی اولین عدم تطابق، گزارش خطا تولید مینماید.
راستیآزمایی
برنامهی دمو و تستهای قطعی را اجرا کنید:
cd phases/13-tools-and-protocols/38-calling-conventions-and-stack-frames
python3 code/main.py
python3 -m unittest discover -s code/tests -v
اعتبارسنجی باید موارد زیر را اثبات کند:
- هدرهای فرمتهای ELF، COFF و Mach-O در هفت فایل، به درستی به System V، Microsoft x64، AAPCS64 و Apple arm64 اشاره دارند.
- جدول نمادها در تمام فایلها دوازده تابع یکسان را فهرست میکند، و پیشوند
_در Mach-O و برچسبltmp0به درستی رفع ابهام شدهاند. - پارسر لیستینگ، نام تابع
extرا به عنوان مقصد فراخوانی حفظ میکند و مقادیر فوری AArch64 مانند#-0x30را دستنخورده باقی میگذارد. - دستور
leaq (%rdx,%rcx), %raxدو ثبات را به عنوان آدرس میخواند و هیچ دادهای لود نمیکند؛ دستورهایxorl %eax, %eaxوcvtsi2sdصرفاً عملیات نوشتن هستند؛ و دستورstpپیشایندکس مقدارspرا پیش از ذخیره وldpپسایندکس آن را پس از خواندن تغییر میدهد. - تابع
keepsدر سطح-O2روی System V بایتهای55 41 56 53را با یک فریم 24 بایتی بدون اشارهگر فریم دارد؛ در Microsoft x64 توالی56 57 53 48 83 ec 20با فریم 56 بایتی؛ و در AArch64 با دستورa9bd7bfdشروع شده و رکورد فریم را در آفست 48- قرار میدهد. - نسخهی اپل، رکورد فریم را در آفست 16- و بالاتر از چهار ثبات ذخیرهشده قرار میدهد.
- تمام توابع در سطح بهینهسازی
-O0روی لینوکس دارای اشارهگر فریم هستند ولی در-O2فاقد آنند، و فلگ-fno-omit-frame-pointerاشارهگر فریم را همراه با یک عمل push برای تراز پشته بازمیگرداند. - تابع برگ در سطح
-O0از 8 بایت Red zone در System V، دستورpushq %raxدر Microsoft x64، وsub sp, sp, #0x10در AAPCS64 استفاده میکند؛ همچنین فلگ-mno-red-zoneاستفاده از Red zone را کاملاً متوقف میسازد. - ساختار تمام اپیلوگها قرینهی دقیق پرولوگهایشان است و اشارهگر پشته به وضعیت اولیهاش بازمیگردد.
- تابع
eight_argsدر هر دو سطح بهینهسازی، شش ثبات به همراه اسلاتهای 8 و 16، یا چهار ثبات به همراه اسلاتهای 40 تا 64، یا ثباتهایx0تاx7را میخواند. - تابع
mixedبا ثباتهایrdi, xmm0, rsi, xmm1در System V و با ثباتهایrcx, xmm1, r8, xmm3در Microsoft x64 نگاشت میشود و خروجی هر دوی آنها درxmm0قرار دارد. - ساختار 16 بایتی در System V و AAPCS64 درون دو ثبات جابهجا میشود و در Microsoft x64 به صورت ارجاعی در پشت یک اشارهگر پنهان قرار میگیرد.
- ساختار 32 بایتی خروجی خود را از طریق ثباتهای
rdi،rcxیاx8برمیگرداند. - تابع با تعداد متغیر آرگومان در System V ثبات
%alرا چک میکند، در مایکروسافت ثباتهایrdx, r8, r9را در آفستهای 16، 24 و 32 مینشاند، در لینوکس ثباتهایx1تاx7وq0تاq7را ذخیره میکند، و در اپل اشارهگر پشتهی فراخوان را مستقیماً میخواند. - توابع فراخوان ثباتهای مورد انتظار را مقداردهی میکنند، مقادیر 7 و 8 را در آفستهای 0 و 8 یا مقادیر 5 تا 8 را در آفستهای 32 تا 56 پشته میریزند، مقدار
%alرا برای فراخوانی توابع متغیر صفر میکنند، و آدرس بافر خروجی را درrdi،rcxیاx8قرار میدهند. - تمام 489 مورد چک با قواعد استاندارد همخوانی کامل دارند.
- شش ورودی ساختار
UNWIND_INFOدقیقاً با نتایج تحلیل پرولوگ تطابق داشته و توابع برگ فاقد ورودی هستند. - 24 ورودی ساختار FDE به طور کامل با آفستهای ثباتهای ذخیرهشده و عمیقترین نقطهی پشته مطابقت دارند.
- دادههای ابزارها ثبت میکنند که radare2 مقادیر
amd64وmsرا دارد، rizin برچسبamd64را برای COFF زده است، ثباتrcxجا افتاده است، سه متغیر double تشخیص داده شده، و پروتوتایپهای ابزار angr ثبت شدهاند. - دادههای نمودار و جدول که درون ماژول وبسایت تعبیه شدهاند، دقیقاً با خروجی آنالایزر یکسان هستند.
حالتهای شکست در محیط واقعی
| حالت شکست | خطایی که ابزار ضعیف نشان میدهد | حقیقتی که آنالایزر باید اثبات کند |
|---|---|---|
| پردازش باینری COFF با قواعد System V | شناسایی rdi و rsi به عنوان آرگومانهایی که تابع اصلاً آنها را نخوانده است |
هدر عدد 0x8664 دارد؛ شیوهی فراخوانی Microsoft x64 است |
خوانده شدن ثبات تنها در ساختار دستور lea |
گزارش هفت آرگومان برای تابعی که هشت آرگومان دارد | ثباتهای Base و Index نقش ثباتهای خواندهشده را دارند |
فرض عملوند مقصد در cvtsi2sd به عنوان خواندن |
ابداع یک آرگومان اعشاری سوم در ثبات xmm2 |
اولین عمل روی xmm2 نوشتن است؛ mixed تنها دو آرگومان SSE دارد |
| موضعی فرض کردن اسلاتهای پشته به جای توالی | معرفی xmm0 به عنوان آرگومان نوع double در ویندوز |
اسلات شماره 1 ثبات XMM1 است و اسلات 3 ثبات XMM3 |
اشارهگر فریم فرض کردن دستور push rbp |
گزارش وجود Frame pointer در تابعی با فلگ -O2 که از rbp به عنوان انباره استفاده کرده |
فقط در صورتی که پس از push دستور mov %rsp, %rbp باشد فریم داریم |
| اندازهگیری Red zone نسبت به پشته در بدو ورود | ادعای وجود 216 بایت «پایینتر از اشارهگر پشته» | فضا 128 بایت زیر موقعیت فعلی پشته است، آن هم بعد از یک sub به اندازهی 88 بایت |
| متغیر محلی در نظر گرفتن فضای Shadow store | فرض کردن متغیرهای پشته در بازهی 8 تا 32 | آرگومانهای پشته در استاندارد مایکروسافت از آفست 40 شروع میشوند |
دستور lea را یک عمل بارگذاری داده دانستن |
ثبت آرگومان روی پشته در آفست 0x60(%rsp) در تابع variadic |
دستور lea صرفاً آدرس overflow_arg_area را محاسبه میکند؛ لودی ندارد |
| آرگومان عادی دانستن اشارهگر مخفی بازگشتی | نمایش امضا به صورت big_ret(void *, long long) |
اشارهگر مبنای نوشتن است و در rax کپی میشود، یا در x8 میماند |
| مقایسهی عمیقترین نقطهی CFA با فریم پرولوگ | عدم تطابق در تابعی که آرگومانهای خروجی را در میانه push میکند | باید آفست را با عمیقترین نقطهای که پشته در طول بدنه لمس میکند مقایسه کرد |
| حدس زدن شیوهی فراخوانی صرفاً بر مبنای معماری پردازنده | برچسب amd64 زدن به یک باینری با فرمت COFF |
ابتدا فرمت فایل کتابچهی قواعد را معین میکند، سپس معماریْ ثباتها را |
| اعتماد قطعی به امضای استخراجشده توسط یک ابزار | ثبت سه آرگومان double یا شانزده پارامتر variadic | امضای سورس اصلی یا تحلیل ثانویهی Unwind باید آن را تصدیق کند |
قاعدهی عملیاتی
همیشه قبل از خواندن اولین دستورالعمل، هدر فایل را بخوانید و کتابچهی قواعد حاکم را مشخص کنید. هر ثبات ذخیرهشده و هر آرگومان را در آفست مشخصی نسبت به اشارهگر پشته در لحظهی ورود قرار دهید، و درستی آن را در برابر قاعدهای که مسبب آن بوده و دادههای Unwind موجود در کامپایلر بسنجید. امضایی را که یک ابزار مهندسی معکوس ادعا میکند صرفاً یک فرضیه بدانید؛ مگر اینکه بایتهای اسمبلی یا سورسکد آن را تأیید کنند.
برای مطالعهی بیشتر
- System V Application Binary Interface, AMD64 Architecture Processor Supplement, Version 1.0 (sections 3.2.1 to 3.2.3, 4.2.4)
- Microsoft x64 calling convention
- Microsoft x64 stack usage
- Microsoft x64 prolog and epilog
- Microsoft x64 exception handling: RUNTIME_FUNCTION, UNWIND_INFO, UNWIND_CODE
- Arm Procedure Call Standard for the Arm 64-bit Architecture (AAPCS64), release 2025Q4
- Apple: Writing ARM64 code for Apple platforms
- DWARF Debugging Information Format Version 5, section 6.4 Call Frame Information
- Clang driver frame-pointer defaults,
useFramePointerForTargetByDefaultinCommonArgs.cpp - Clang command line reference:
-fomit-frame-pointer,-momit-leaf-frame-pointer - Fedora 38 change: frame pointers by default
- Ubuntu 24.04: performance engineering with frame pointers by default
- Ghidra compiler specification:
x86-64-gcc.cspec - Ghidra compiler specification:
x86-64-win.cspec - Ghidra compiler specification:
AARCH64.cspec - Hex-Rays: SetType and the
__usercallconvention - Hex-Rays: Decompile as call
- Binary Ninja API 6.0:
callingconventionmodule - Binary Ninja API 6.0:
platformmodule - Binary Ninja user documentation: platform types
- The Official Radare2 Book: calling conventions
- The Official Radare2 Book: code analysis
- angr documentation: decompiler analysis passes, including calling convention recovery
- radare2 6.2.4 release
- rizin 0.9.1 release
