ساختار فایل PE قسمت ۵ – Relocation ها
مشکل از آنجایی شروع میشود که ویندوز ، همیشه فایل های PE را در آن آدرسی از حافظه که انتظار میرود بارگذاری نمیکند و در این صورت خیلی از چیز ها بهم میریزد ! در این بخش از ساختار فایل PE به بررسی Relocation ها در فایل PE میپردازیم . اینکه اصلا Relocation به چه معناست و در فایل PE چگونه پیاده سازی میشود .
Relocation چیست ؟
همانطور که در بخش اول ساختار فایل PE (قسمت header ها) شرح داده شد ، در بخش OptionalHeader فایل های PE فیلدی به نام ImageBase موجود میباشد . این فیلد حاوی آدرسی از حافظه است که ترجیح داده شده تا فایل PE در آن بارگذاری شود . اگر در مورد ویندوز های قدیمی مثل XP صحبت کنیم ، فایل های PE معمولا در همین آدرسی که فیلد ImageBase میگوید بارگذاری میشوند مگر زمانی که آن آدرس اشغال باشد . برای مثال اگر یک فایل exe میخواهد در آدرس 0x00400000 بارگذاری شود و از بخت بد قبلا در این آدرس یک فایل PE دیگر بارگذاری شده باشد ، loader ویندوز مجبور است این فایل را در آدرسی غیر از آن چیزی که ImageBase میگوید بارگذاری کند .
این در مورد ویندوز های قدیمی است . در ویندوز های جدید (ویندوز ۷ به بعد) ، تقریبا هیچوقت یک فایل PE در آدرسی که ImageBase آن فایل میگوید بارگذاری نمیشود ! . دلیل این اتفاق وجود مکانیزم امنیتی به نام ASLR مخفف Address Space Layout Randomization است .
این مکانیزم امنیتی اطمینان حاصل میکند که در هر دور boot ویندوز ، آدرس بارگذاری فایل های PE به طور شانسی عوض شود و در آدرس های از پیش تعیین شده به هیچ وجه بارگذاری نشود . این رفتار باعث میشود تا انجام برخی حملاتی که توسط هکر ها یا بدافزار ها انجام میشوند سخت تر و یا حتی غیر ممکن شوند زیرا هکر برخلاف قبل نمیتواند آدرس بارگذاری برخی فایل های PE در حافظه را پیشبینی کند !
وقتی یک سورس کد compile میشود یا به شکلی یک فایل PE حاوی کد اجرایی تولید میشود ، کد اجرایی آن فایل فرض میکند که فایل PE در همان آدرسی که در فیلد ImageBase نوشته شده ، بارگذاری شده است در نتیجه آدرس متغییر ها و مقادیری که در حافظه میخواهد به آن ها دسترسی پیدا کند را بر مبنای ImageBase بدست می آورد . تکه کد زیر را فرض کنید :
int variable = 1000;
variable++ ;
فرض کنید آدرس نسبی متغییر variable از ابتدای فایل (RVA)در حافظه برابر 0x100 است . یعنی متغییر variable به فاصله 0x100 بایت بعد از شروع فایل PE در حافظه قرار دارد . حال اگر ImageBase برابر 0x1000 باشد ، کد اجرایی این مقدار را به 0x100 اضافه میکند تا آدرس واقعی و مطلق variable در حافظه بدست بیاید که میشود 0x1100 . تا اینجا همه چیز درست پیش میرود و طبق کدی که نوشته ایم مقدار variable در آدرس 0x1100 حافظه ذخیره شده است برنامه هر کاری بخواهد میتواند با مقدار آن بکند .
حال فرض کنید به دلیل وجود ASLR ، فایل PE در آدرسی که ImageBase گفته بود بارگذاری نشود و مثلا در آدرس 0x2000 بارگذاری شود . اینجاست که مشکل پیش می آید . کد اجرایی فرض میکند آدرس متغییر variable در حافظه برابر 0x1000 + 0x100 = 0x1100 است اما در واقع به دلیل اینکه فایل PE در آدرس 0x2000 بارگذاری شده ، آدرس جدید variable برابر با 0x2000 + 0x100 = 0x2100 میشود نه 0x1100 !
پس کد اجرایی اینجا دچار اشتباه میشود و به درستی حافظه را نمیشناسد .
برای حل این مشکل ، کاری که loader ویندوز انجام میدهد این است که ابتدا اختلاف ImageBase خود فایل PE و آدرس فعلی که فایل PE در آن بارگذاری شده را حساب میکند . اسم آن را delta میگذاریم :
ImageBase = 0x1000
ActualBase = 0x2000
delta = ActualBase - ImageBase = 0x1000
حال این مقدار delta را به آدرس هایی که کد اجرایی با آن ها کار میکند اضافه میکند . برای مثال اگر کد اجرایی از آدرس 0x1100 برای دسترسی به variable استفاده میکند ، loader مقدار delta که 0x1000 است را به این آدرس در کد اجرایی اضافه میکند و کد اجرایی تغییر یافته از این به بعد از آدرس 0x2100 که آدرس صحیح variable است استفاده میکند !
به این کار یعنی اصلاح کد اجرایی و تغییر آدرس ها قبل از اجرای کد توسط loader ویندوز Relocate کردن یا Relocation میگوییم . عمل Relocation تنها زمانی نیاز است انجام شود که فایل PE به هر دلیلی در آدرس پیشفرض ImageBase بارگذاری نشود .
بخش reloc.
بخش reloc. در فایل PE مخفف Relocations حاوی اطلاعات مربوط به Relocation ها است . loader ویندوز باید دقیقا بداند کدام قسمت کد اجرایی فایل نیاز به اصلاح دارد . باید بداند کدام آدرس ها را باید تغییر دهد و این تغییر دادن چگونه باید باشد . همه ی این اطلاعات در بخش reloc. فایل مشخص شده است .
به طور کلی بخش reloc. تشکیل شده از تعدادی بلوک است . هر بلوک ، شامل داده های مربوط به Relocate کردن ۴ کیلوبایت از حافظه فایل است و آدرس شروع هر بلوک در حافظه باید مضرب صحیحی از ۴ باشد (بلوک باید در مرز های ۳۲ بیتی شروع شود) . هر بلوک با ساختار زیر شروع میشود :
typedef struct _IMAGE_BASE_RELOCATION {
DWORD VirtualAddress;
DWORD SizeOfBlock;
} IMAGE_BASE_RELOCATION;
VirtualAddress : حاوی RVA شروع بخش ۴ کیلوبایتی است که این بلوک قرار است اطلاعات relocation آن را ذخیره کند .
SizeOfBlock : اندازه کل بلوک فعلی در واحد بایت شامل ساختار فعلی (VirtualAddress و SizeOfBlock) و داده هایی که در ادامه ، تحت بلوک فعلی می آید .
در هر بلوک ، در ادامه ی ساختار بالا به تعداد دلخواهی مدخل های Type / Offset می آید . هر مدخل Type / Offset یک عدد ۲ بایتی است و وظیفه ی Relocate کردن یک آدرس از کد ، در محدوده ۴ کیلوبایتی را دارد .
۴ بیت پر ارزش این عدد Type نام دارد که مشخص کننده نوع Relocation است . باقی بیت های عدد Offset نام دارد که فاصله آن نقطه از کد است که باید Relocation در آن انجام شود . این فاصله نسبت به ابتدای محدوده ۴ کیلوبایتی است که بلوک فعلی مربوط به relocate کردن آن است . آدرس این محدوده ۴ کیلوبایتی در عضو VirtualAddress ساختار بالا ذخیره شده بود بنابراین مقدار VirtualAddress ساختار بالا به اضافه مقدار Offset میشود تا RVA نقطه ای که نیاز است در آن relocation انجام شود مشخص شود.
انواع مختلف Relocation وجود دارد که فیلد Type که در بالا معرفی شد وظیفه ی مشخص کردن آن را داشت . در مرجع رسمی Microsoft این انواع به طور کامل لیست شده اند اما اگر بخواهیم نمونه هایی از آن هارا بگوییم دو نمونه پرکاربرد آن یکی IMAGE_REL_BASED_HIGHLOW است و یکی IMAGE_REL_BASED_DIR64 .
اولی مشخص میکند نقطه ای که آدرس آن توسط Offset مشخص شده ، یک آدرس ۳۲ بیتی است که باید delta به آن اضافه شود .
دومی مشخص میکند نقطه مورد نظر یک آدرس ۶۴ بیتی است که باید مقدار delta به آن اضافه شود .
این آموزش متعلق به بخش مهندسی معکوس است
برای مشاهده تمام آموزش های مهندسی معکوس وبسایت مسترپایتون به بخش مهندسی معکوس مراجعه کنید
علاقه مند به دنیای کامپیوتر ها ...