قسمت سوم بررسی استراکچرها در زبان C - فهمایش در سطح دیزاسمبلی
بررسی دیزاسمبلی x86 در لینوکس – کامپایلر gcc
به منظور بررسی دیزاسمبلی کد منبع C در لینوکس که محتوای آن در تصویر 2 نمایش داده شد، ابتدا خروجی تولید شده توسط کامپایلر gcc برای معماری x86 را توسط دیباگر gdb مورد بررسی قرار خواهیم داد. همانطور که در تصویر 12 قابل مشاهده است، وقتی کامپایلر باینری را با فلگ -ggdb کامپایل میکند، به دلیل وجود اطلاعات دیباگ درون باینری، دیباگر gdb میتواند اطلاعات تکمیلی و کامل درباره ساختار درونی باینری ELF ما نمایش دهد. برنامه ای که در تصویر 12 برای دیباگ باینری مورد استفاده قرار گرفته است، GDB Dashboard است که علاوه بر کد منبع، کد دیزاسمبلی، اطلاعات مرتبط با پشته و رجیسترها و خروجی که توسط کدها در حال تولید است، نمایش میدهد.
تصویر 12: خروجی دیزاسمبلی برنامه در محیط لینوکس
در ادامه به منظور درک بهتر کدی که توسط gcc برای معماری x86 تولید شده است، خط به خط مورد بررسی و تحلیل قرار خواهیم داد تا با رفتار کامپایلر gcc در تولید کدهای اسمبلی آشنا شویم. همانطور که در تصویر 13 قابل نمایش است، وقتی دستورالعمل call فراخوانی میشود، مجدد آدرس دستورالعمل بعدی را درون پشته قرار خواهد داد. آدرسی که توسط call بر روی پشته قرار گرفته است، در تصویر 13 با رنگ قرمز قابل مشاهده است.
تصویر 13: خروجی اجرای دستورالعمل call
وقتی وارد تابع StructureAnalysis میشویم، در گام اول با Prologue تابع روبهرو خواهیم شد. در Prologue تابع StructureAnalysis بعد از اینکه فریم جدید تابع ایجاد میشود، مقدار 0x424 (1060 دسیمال) از مقدار جاری رجیستر ESP کم خواهد شد. این مقدار نشان میدهد که 1060 بایت برای ذخیرهسازی متغییرهای درون این تابع رزرو خواهد شد.
تصویر 14: دیزاسمبی مقداردهی اعضای Structure توسط GCC
خروجی که توسط کامپایلر GCC تولید شده است، مشابه خروجی MSVC است. متغیرهای درون Struct با استفاده از آدرس پایهای که درون رجیستر EBP قرار دارد، و همچنین آفستهایی که مشخص کننده اندازه متغیرها است، مقداردهی میشوند. برای کار با دادههای اعشاری هم از دستورات fld و fstp استفاده شده است. در نهایت وقتی استراکچر مقداردهی اولیه شد، برای نمایش مقادیر آنها به تابع printf عبور داده میشوند.
تصویر 15: خروجی دیزاسمبلی تابع StructureAnalysis
شایان ذکر است، در ابتدای هر تابع درون برنامه همانطور که در تصویر 15 و تصویر 16 قابل مشاهده است، یک تابع با نام __x86.get_pc_thunk.bx فراخوانی میشود. در تجزیه و تحلیل یک باینری لینوکس، تابع __x86.get_pc_thunk.bx معمولاً همراه با ثبت ebx برای محاسبه آدرس پایه یک بخش داده یا کد مستقل از موقعیت (PIC) استفاده میشود. هدف __x86.get_pc_thunk.bx این است که آدرس دستور بعدی را در رجیستر ebx بارگذاری کند. این امکان را فراهم میکند تا دستورات بعدی بتوانند از ebx به عنوان یک رجیستر پایه استفاده کنند تا به دادهها یا کدهای نسبت به موقعیت فعلی در حافظه دسترسی پیدا کنند. با استفاده از __x86.get_pc_thunk.bx و ebx، کدهای مستقل از موقعیت میتوانند به گونهای نوشته شوند که به آدرس مطلق کد یا داده وابسته نباشند. شایان ذکر است، پیادهسازی __x86.get_pc_thunk.bx ممکن است بسته به کامپایلر و توزیع لینوکس متفاوت باشد. این تابع معمولاً توسط کتابخانههای اجرایی کامپایلر یا کتابخانه C سیستم ارائه میشود. در تصویر 16، این موضوع قابل نمایش است.
تصویر 16: نمایش فراخوانی __x86.get_pc_thunk.ax در ابتدای تابع main
بررسی دیزاسمبلی x86 در لینوکس – کامپایلر clang
خروجی که کامپایلر clang تولید میکند به مراتب از خروجی که توسط GCC و MSVC در مراحل قبلی تولید شده است، متفاوتر است. در ادامه خروجی نسخه 14 کامپایلر clang را برای برنامهای که به زبان C نوشته شده بود، مورد بررسی قرار خواهیم داد. در تصویر 17، ساختار دیزاسمبلی تابع main قابل مشاهده است که توسط کامپایلر clang تولید شده است. یکی از مهم ترین تفاوت هایی که در خروجی تولید شده توسط کامپایلر clang نسبت به gcc و msvc وجود دارد، در شروع توابع است. خروجی که clang تولید میکند، بعد از ایجاد فریم برای تابع، ما یک دستور call خواهیم داشت. هنگامیکه این دستور call اجرا میشود، تازه بدنه اصلی تابع شروع به اجرا خواهد شد.
تصویر 17: دیزاسمبلی کد تولید شده توسط clang
همانطور که در تصویر 17 قابل مشاهده است، در ابتدای تابع یک call به یک لیبل با عنوان LAB_000112EA داریم که از ابتدای آن اجرای بدنه اصلی تابع شروع میشود. تفاوت بعدی که وجود دارد، Calling Convention مورد استفاده توسط کامپایلر clang است که نسبت به msvc و gcc تفاوت دارد. به عنوان مثال، در تصویر 18 اصول عبور پارامترها به تابع printf قابل مشاهده است. در خروجی دیزاسمبلی clang به جای PUSH شدن پارامترها از راست به سمت چپ درون پشته، در خروجی clang از رجیسترها برای عبور پارامترها به توابع C استفاده میشود.
تصویر 18: نحوه عبور پارامترها به تابع
با توجه بررسی خروجی کامپایلرهای GCC و MSVC و Clang در پردازش Structها در زبان سی تفاوت برجسته ای وجود نداشت. هر سه کامپایلر برای مقداردهی اعضای استراکچر از آدرس پایه ای استفاده کرده بودند که توسط رجیستر EBP مورد ارجاع قرار میگرفت. دو کامپایلر GCC و MSVC از یک اصول تقریبا مشابهای دنباله روی می کردند، ولی clang به جای استفاده از Stack از رجیسترهای عمومی x86 برای عبور پارامترها به توابع استفاده میکرد.
پایان.
0 دیدگاه
نظرهای پیشنهاد شده
هیچ دیدگاهی برای نمایش وجود دارد.