ساختار فایل PE قسمت ۳ – صادرات (Exports)
در سیستم عامل ویندوز ، فایل های PE میتوانند توابعی را صادر (export) کنند تا فایل های PE دیگر از طریق وارد (import) کردن ، از آن ها استفاده کنند . این رفتار را اغلب در فایل های DLL مشاهده میکنیم ولی در واقعیت هر فایل PE حتی فایل های اجرایی میتوانند export هایی داشته باشند .
فایل های دیگر به دو طریق میتوانند به توابع صادراتی یک فایل PE دسترسی داشته باشند :
۱ – از طریق اسامی توابع صادراتی
۲ – از طریق شماره منحصر به فرد آنها که به آن شماره Ordinal میگویند .
در کارکرد روزمره ما به جهت سهولت دسترسی ، اکثرا از روش اول برای دسترسی به توابع فایل های دیگر استفاده میکنیم زیرا اسم ها به راحتی قابل به یاد سپردن هستند اما خب موقعیت هایی هم هستند که توابع صادراتی را از طریق یک عدد که Ordinal آن ها است صدا میزنیم و نه اسم آن ها .
بخش edata.
در قسمت قبلی در مورد Section ها توضیح داده شد . همانطور که اشاره شد فایل های PE اگر قرار است توابعی را صادر کنند ، یک section به نام edata. مخفف export data خواهند داشت که اطلاعات مربوط به توابع صادراتی در آن section ذخیره میشود .
edata. از قسمت های مختلف تشکیل شده است که در این پست به بررسی آن ها میپردازیم . ساختار کلی بخش edata. به شکل زیر است :
در ادامه به ترتیب آن ها را بررسی میکنیم .
Export Directory Table
این ساختار ، اولین قسمت از بخش edata. است و حاوی اطلاعات کلی در مورد صادرات فایل PE مثلا تعداد توابع صادراتی و اطلاعات کلی دیگر است . این ساختار به شکل زیر در فایل winnt.h تعریف شده است :
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
DWORD Name;
DWORD Base;
DWORD NumberOfFunctions;
DWORD NumberOfNames;
DWORD AddressOfFunctions;
DWORD AddressOfNames;
DWORD AddressOfNameOrdinals;
} IMAGE_EXPORT_DIRECTORY,*PIMAGE_EXPORT_DIRECTORY;
Characteristics : رزرو شده است و همیشه برابر صفر خواهد بود .
TimeDateStamp : تاریخ و زمانی است که صادرات این فایل ساخته شده اند . در این فیلد تعداد ثانیه هایی که از تاریخ یکم ژانویه ۱۹۷۰ به وقت گرینویج (UTC) گذشته است .
MajorVersion و MinorVersion : شماره نسخه های مربوط به این صادرات .
Name : حاوی RVA یک رشته در فایل است که مشخص کننده اسم خود فایل است . این فایل معمولا یک فایل DLL است . قبلا هم ذکر شده که فایل های exe هم حتی میتوانند export داشته باشند ولی در سیستم عامل ویندوز چنین چیزی معمول نیست . معمولا DLL ها هستند که توابعی را export میکنند .
Base : همانطور که در اول کار اشاره شد ، توابع صادراتی را میتوان به وسیله ی ordinal ها نیز به آن ها دسترسی پیدا کرد . این فیلد مشخص میکند ordinal ها از چه عددی شروع شوند . این فیلد معمولا برابر ۱ خواهد بود . یعنی اعداد ordinal از ۱ شروع میشوند و افزایش پیدا میکنند.
NumberOfFunctions : مشخص کننده تعداد توابع صادراتی توسط فایل است
NumberOfNames : مشخص کننده تعداد توابعی است که از طریق اسم (و نه ordinal) در این فایل صادر شده اند .
AddressOfFunctions : حاوی RVA مربوط به ساختار Export Address Table در edata. است . Export Address Table یک آرایه از RVA های توابعی است که این فایل صادر میکند ( در ادامه توضیح میدهیم )
AddressOfNames : حاوی RVA مربوط به ساختار Name Pointer Table در edata. است ( در ادامه توضیح میدهیم )
AddressOfNameOrdinals : حاوی RVA مربوط به ساختار Ordinal Table در edata. است (در ادامه توضیح میدهیم )
Export Address Table
این قسمت یک آرایه ای از RVA های توابعی است که قرار است صادر شوند . loader ویندوز از طریق خواندن اطلاعات این آرایه میتواند آدرس توابع صادراتی را پیدا کند . عضو NumberOfFunctions در بخش قبلی که معرفی کردیم یعنی Export Directory Table مشخص کننده تعداد اعضای این آرایه است .
لازم به ذکر است که یک حالت خاصی برای بعضی از اعضای این آرایه میتواند وجود داشته باشد که در آن ، عضو آرایه RVA تابع صادراتی نخواهد بود بلکه RVA مربوط به یک رشته ای است که اسم یک تابع صادراتی از یک DLL دیگر را ذکر میکند !!
شاید در نگاه اول عجیب به نظر برسد ولی فایل های PE این قابلیت را دارند که توابع صادراتی فایل های دیگر را از جانب خود صادر کنند به این صورت که در عضو مورد نظر از آرایه Export Address Table به جای آن که RVA تابع صادراتی نوشته شود ، یک RVA که آدرس یک رشته در فایل است مشخص میشود . داخل این رشته نوشته شده است چه تابعی و از کدام فایل DLL دیگر صادر شود . برای مثال رشته MYDLL.function1 میگوید تابع function1 از فایل MYDLL از جانب فایل فعلی صادر شود .
دقت کنید همین دو بخشی که بالا توضیح داده شد یعنی Export Directory Table و Export Address Table برای صادر کردن توابع از طریق ordinal کافی است . در واقع ordinal ها یک جور اندیس در آرایه Export Address Table هستند . فرض کنید عضو Base در Export Directory Table که مشخص کننده شروع عدد ordinal ها بود برابر ۱ است . اگر بخواهیم تابع با ordinal عدد ۳ از یک فایل DLL را صدا بزنیم ، کافی است عدد ۱ (که شروع ordinal ها است) را از ۳ کم کنیم تا اندیس واقعی آن تابع در آرایه export address table بدست بیاید که در این مورد ۲ میشود . حالا اگر اندیس ۲ از آرایه Export Address Table را ببینیم آدرس تابع مورد نظرمان نوشته شده است .
بنابراین اگر بخوایم از طریق ordinal به توابع صادراتی یک فایل دسترسی داشته باشیم فقط دو بخش Export Directory Table و Export Address Table کافی است اما اگر بخواهیم از طریق اسامی ، توابع صادراتی را صدا بزنیم نیاز به بخش های بعدی edata. که در تصویر ساختار edata. آمده بود داریم . در ادامه باقی بخش ها را بررسی میکنیم .
Name Pointer Table و Ordinal Table و Export Name Table
این سه قسمت را همزمان باهم بررسی میکنیم زیرا هر سه آن ها کاملا مربوط به هم هستند . اگر قرار باشد برخی توابع صادراتی به وسیله ی اسامی آن ها مورد دسترسی قرار بگیرند ، نیاز است تا حتما این سه قسمت در فایل PE وجود داشته باشند.
Export Name Table به طور ساده حاوی دنباله ای از رشته هایی از نوع ASCII هست که هر کدام به وسیله ی یک کاراکتر null خاتمه یافته اند . این رشته ها همان اسم توابعی هستند که توسط این فایل صادر میشوند . این اسامی به صورت مرتب شده بر اساس حروف الفبایی پشت سرهم چیده شده اند که دلیل این مرتب سازی را جلوتر اشاره میکنیم .
و اما دو قسمت Name Pointer Table و Ordinal Table هر دو آرایه هایی با تعداد اعضای دقیقا یکسان هستند . در واقع دو آرایه کاملا موازی هستند . Name Pointer Table آرایه ای از RVA های رشته های داخل Export Name Table است . به عبارتی این آرایه RVA های مربوط به اسامی توابع صادراتی را در خود نگه میدارد . حال Ordinal Table نیز آرایه ای موازی با Name Pointer Table است که در هر اندیس آن Ordinal مربوط به تابعی که اسم آن در همان اندیس Name Pointer Table است ذخیره شده است . به عبارتی اعضای دو آرایه Name Pointer Table و Ordinal Table که اندیس های یکسانی دارند متناظر باهم هستند و در نتیجه اعضای این دو آرایه یک به یک باهم متناظر اند . هر اندیسی را که در نظر بگیریم ، در آرایه Name Pointer Table اسم آن تابع صادراتی مشخص میشود و آرایه Ordinal Table عدد ordinal آن تابع مشخص میشود . تعداد اعضای دو آرایه Name Pointer Table و Ordinal Table توسط عضو NumberOfNames در قسمت Export Directory Table مشخص میشود .
به موضوعی خیلی مهم دقت کنید . Ordinal هایی که داخل آرایه Ordinal Table ذخیره میشوند همیشه از صفر شروع خواهند شد. بنابراین این ها دقیقا همان اندیس آدرس توابع مورد نظر در آرایه Export Address Table هستند و نیازی به کم کردم عدد شروع Ordinal ها از آن ها نیست .
حال میتوانیم درک کنیم ویندوز چگونه توابع صادراتی را پیدا میکند . Loader ویندوز برای پیدا کردن توابع صادراتی از طریق اسم آنها ، ابتدا اسامی داخل Name Pointer Table را جستجو میکند تا اسم تابع مورد نظر پیدا شود . سپس میبینید این اسم در کدام اندیس از Name Pointer Table وجود داشت . همان اندیس در آرایه Ordinal Table را میبینید تا اندیس آدرس تابع مورد نظر در آرایه Export Address Table را بدست بیاورد . حال آن اندیس را در Export Address Table بررسی میکند تا در نهایت آدرس تابع صادراتی مورد نظر را بدست بیاورد .
دلیل اینکه اسامی داخل Export Name Table در نتیجه Name Pointer Table به صورت الفبایی مرتب شده اند این است که Loader میتواند از الگوریتم جستوجوی دودوئی برای پیدا کردن اندیس تابع مورد نظر در این آرایه ها استفاده کند .
مشاهده Export های یک فایل PE در PE-BEAR
فایل kernel32.dll را در PE-BEAR باز میکنیم و به تب Exports مراجعه میکنیم :
همانطور که در تصویر مشخص است در قسمت بالایی تب Exports قسمت Export Directory Table و مقادیر آن مشخص شده و در قسمت پایینی نیز لیست تمام توابع صادر شده به همراه اطلاعات آن ها مثل اسم آن ها ، ordinal آن ها و غیره آمده است . دقت کنید ستون Name RVA برای هر تابع صادراتی مشخص کننده RVA مربوط به اسم تابع در Export Name Table است .
همچنین دقت کنید توابعی که در ستون Forwarder آن ها چیزی نوشته شده از یک فایل DLL دیگر صادر شده اند . برای مثال در تصویر بالا ، تابع AddVectoredExceptionHandler همان NTDLL.RtlAddVectoredExceptionHandler است که با نامی متفاوت و از جانب فایل kernel32.dll صادر شده است . به این حالت خاص در بخش Export Address Table اشاره کردیم .
منابع
این آموزش متعلق به بخش مهندسی معکوس است
برای مشاهده تمام آموزش های مهندسی معکوس وبسایت مسترپایتون به بخش مهندسی معکوس مراجعه کنید
علاقه مند به دنیای کامپیوتر ها ...