ساختار فایل PE قسمت ۱ – header ها

ساختار فایل های اجرایی در طول تاریخ ، تغییرات زیادی داشته است . اگر برگردیم به دوران سیستم عامل MS-DOS و احتمالا کمی قبل تر ، فایل های اجرایی با فرمت COM را داشتیم . ساختار این فایل های اجرایی به شدت ساده بود به طوری که یک فایل COM به سادگی فقط حاوی کد های باینری قابل اجرا برای CPU بود . سیستم عامل ها حین اجرای این فایل ها به سادگی از اولین بایت فایل شروع به اجرای دستورات باینری میکردند . فایل های COM برای اجرا همیشه در آدرس ثابتی از حافظه توسط سیستم عامل بارگذاری و اجرا میشد . این آدرس ثابت 0x100 معادل ۲۵۶ ده دهی بود . 

زمان گذشت ، کامپیوتر ها پیشرفت کردند ، سیستم عامل های جدیدتر به همراه حافظه های بزرگتر روی کار آمدند . دیگر نیاز به ساختار های پیچیده تری برای فایل های اجرایی بود . برای مثال برخی از فایل های اجرایی ممکن است نیاز به یک سری از کتابخانه های دیگر برای اجرا داشته باشند . باید برای سیستم عامل مشخص بشود این فایل اجرایی به چه فایل های کتابخانه ای نیاز دارد . اینجا بود که ساختار های پیچیده تری برای فایل های اجرایی پدید آمد که فقط حاوی کد اجرایی نبود بلکه شامل یک سری اطلاعات اضافی (Header) نیز بود که مواردی را برای سیستم عامل مشخص میکرد که چگونه و به چه صورت این فایل را اجرا کند ، در کدام قسمت حافظه و چگونه بارگذاری شود ، به چه کتابخانه هایی نیاز دارد و ….. 

 

در زمان سیستم عامل MS-DOS ، ساختار فایل اجرایی جدیدی به بوجود آمد که پسوند این فایل ها غالبا EXE. مخفف EXECUTABLE بود . این فایل ها ، فایل های اجرایی مختص سیستم عامل MS-DOS بودند و کمی از فایل های ساده COM پیچیده تر بودند . فایل های اجرایی DOS  علاوه بر کد های اجرایی ، شامل یک Header نیز بود که اطلاعات اضافی مثل مقدار اولیه بعضی از رجیستر ها ، حجم فایل و … داخل آن نوشته شده بود و سیستم عامل به وسیله همین اطلاعات میتوانست فایل را اجرا کند .

ساختار یک فایل اجرایی MS-DOS :

اولین ۲ بایت فایل های اجرایی DOS در بخش header عبارت “MZ” است که signature این نوع فایل ها است . بخش DOS HEADER اطلاعات کلی در مورد فایل اجرایی را مشخص میکند که سیستم عامل به کمک آن میتواند فایل اجرایی را اجرا کند . مثل اینکه مقدار اولیه رجیستر ها چه باشد و … بخش DOS STUB نیز کد های اجرایی فایل هستند که در حافظه بارگذاری و اجرا میشوند .

بالاخره بریم سراغ فایل های اجرایی ویندوز های امروزی . در سیستم عامل ویندوز ، ما با اجرایی قابل حمل (Portable Executable) طرف هستیم . این ساختار فایل های اجرایی ویندوز است . دقت کنید ساختار اجرایی قابل حمل (PE) فقط مختص به فایل های اجرایی با پسوند exe. نیست بلکه پسوند هایی مثل dll , sys , ocx , … نیز از همین فرمت فایل استفاده میکنند بنابراین دانستن آن بسیار مهم است .فرمت فایل PE برای فایل های ۳۲ بیتی را PE32 و برای فایل های ۶۴ بیتی +PE32 میگویند 

به طور کلی فرمت فایل PE از یک سری header و section تشکیل شده است . header ها حاوی اطلاعات کلی در مورد بخش های مختلف فایل هستند که جلوتر به بررسی آنها خواهیم پرداخت . section ها نیز برای نگهداری کدهای اجرایی ، داده ها و ساختار های اطلاعاتی خاص مثل import table , export table استفاده میشوند که به بررسی این ها نیز میپردازیم .

قبل از شروع لازم است تا نکته ای در مورد آدرس های حافظه ذکر شود . ما میدانیم هر پروسه داخل ویندوز یک حافظه مجازی مخصوص به خود را دارد . به طور کلی به آدرس های داخل حافظه مجازی یک پروسه ، آدرس مجازی (VirtualAddress) یا به اختصار VA میگوییم . پس در این پست هر جا VA یا VirtualAddress یا آدرس مجازی گفتیم منظور این است .

همچنین فرض کنید یک فایل PE در آدرس X از حافظه بارگذاری شده است . میتوانیم آدرس یک بخش از آن فایل PE را نسبت به شروع آن (آدرس X) ذکر کنیم . به این آدرس RVA مخفف Relative Virtual Address میگوییم . در تصویر زیر مفهوم VA و RVA را نشان داده ایم :

RVA and VA

همچنین مفهوم دیگری به نام offset داریم . این مفهوم نشان دهنده آدرس یک بخش از فایل PE روی دیسک است به این صورت که فرض میکنیم آدرس اولین بایت فایل PE روی دیسک برابر صفر است . سپس هر بخش از فایل PE روی دیسک را که در نظر بگیریم ، آدرس آن میشود فاصله اولین بایت فایل PE تا اولین بایت آن بخش . به این آدرس offset میگوییم که به عبارتی تفاوت مکان بین یک نقطه نسبت به ابتدای فایل مورد نظر روی دیسک است . 

ساختار کلی یک فایل PE به صورت زییر است :

به ترتیب به بررسی سه بخشی که در تصویر مشخص و شماره گذاری شده اند میپردازیم 

DOS HEADER & DOS STUB

DOS Header and DOS STUB

همانطور که میبینید این بخش یک برنامه کامل MS-DOS است . فایل های اجرایی PE روی MS-DOS قابل اجرا نیستند . با توجه به این موضوع همیشه در ابتدای یک فایل PE یک برنامه ساده MS-DOS وجود دارد که به محض اجرا شدن پیغامی مثل پیغام زیر روی صفحه چاپ میکند و خارج میشود :

This Program Cannot be Run in DOS Mode!

در این صورت اگر یک فایل اجرایی PE را بخواهیم روی ویندوز اجرا کنیم که مشکلی نیست . ویندوز به سادگی بخش اول فایل PE که یک برنامه DOS است را نادیده میگیرد اما اگر بخواهیم همین فایل اجرایی را روی DOS اجرا کنیم ، سیستم عامل MS-DOS فقط به اول فایل نگاه میکند و متوجه میشود یک برنامه معتبر DOS اینجا نشسته است 🙂 . پس آن را اجرا میکند و پیغامی که بالاتر گفتیم روی صفحه نمایش داده میشود و برنامه بسته میشود .

DOS HEADER یک ساختار ۶۴ بایتی است که به صورت زیر در فایل winnt.h تعریف شده است :

typedef struct _IMAGE_DOS_HEADER {      // DOS .EXE header
    WORD   e_magic;                     // Magic number
    WORD   e_cblp;                      // Bytes on last page of file
    WORD   e_cp;                        // Pages in file
    WORD   e_crlc;                      // Relocations
    WORD   e_cparhdr;                   // Size of header in paragraphs
    WORD   e_minalloc;                  // Minimum extra paragraphs needed
    WORD   e_maxalloc;                  // Maximum extra paragraphs needed
    WORD   e_ss;                        // Initial (relative) SS value
    WORD   e_sp;                        // Initial SP value
    WORD   e_csum;                      // Checksum
    WORD   e_ip;                        // Initial IP value
    WORD   e_cs;                        // Initial (relative) CS value
    WORD   e_lfarlc;                    // File address of relocation table
    WORD   e_ovno;                      // Overlay number
    WORD   e_res[4];                    // Reserved words
    WORD   e_oemid;                     // OEM identifier (for e_oeminfo)
    WORD   e_oeminfo;                   // OEM information; e_oemid specific
    WORD   e_res2[10];                  // Reserved words
    LONG   e_lfanew;                    // File address of new exe header
  } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

اکثر فیلد های این ساختار برای ما مهم نیستند در این سطح . تنها فیلد مهم برای ما آخرین عضو ساختار یعنی e_lfanew است . این عضو یک نوع داده long به طول ۴ بایت است که آدرس (offset) شروع بخش PE Header که در ساختار PE دیدیم را در فایل اجرایی مورد نظر مشخص میکند .

ویندوز حین اجرای فایل ،‌از طریق خواندن مقدار این فیلد میتواند بخش PE Header را پیدا کند .

بخش DOS STUB نیز یک تکه کد باینری قابل اجرا است که میتوان آن را Disassemble کرد . 

برای تست ، از فایل calc.exe (ماشین حساب ویندوز) در ویندوز xp نسخه ۳۲ بیتی استفاده میکنیم . 

برای بررسی محتویات باینری فایل نیز از ویرایشگر HxD استفاده میکنیم :

همانطور که در تصویر میبینید بخش DOS HEADER مشخص شده است . همچنین طبق توضیحی که دادیم ، ۴ بایت آخر بخش DOS HEADER قرار است به آدرس شروع PE HEADER اشاره کند . مقدار ۴ بایت آخر DOS HEADER برابر ۰۰ ۰۰ ۰۰ F0 است . دقت کنید مقادیر. عددی در فایل PE به صورت Little Endian ذخیره میشوند . یعنی بایت های پر ارزش آن ها زودتر از بایت های کم ارزششان میاید . بنابراین اگر بخواهیم آدرس صحیح شروع PE HEADER برابر :

00 00 00 F0

است .

اگر به آدرس F0 در فایل نگاهی بیاندازیم متوجه میشویم شروع PE HEADER است زیرا PE HEADER همیشه با امضای مربوط به آن یعنی عبارت “PE” به همراه ۲ بایت صفر جلوی آن شروع میشود که در تصویر زیر مشهود است :

حال به فضای بین DOS HEADER و PE HEADER دقت کنید . یعنی قبل از شروع PE HEADER و بعد از DOS HEADER :

طبق تعریف شاید فکر کنید این بخش فقط حاوی DOS STUB است اما همیشه اینگونه نیست . در بعضی از فایل های اجرایی PE  ، مابین فضای DOS HEADER و PE HEADER علاوه بر DOS STUB یک header دیگری به نام Rich Header وجود دارد . اگر به خود داده های تصویر بالا هم دقت کنید متوجه وجود عبارت Rich در آن میشوید . Rich Header کمی مرموز است . به صورت رسمی جایی در مورد آن توضیح داده نشده است ولی از یک زمانی متخصصان متوجه وجود آن در فایل های اجرایی PE شدند . خوشبختانه افراد اهل فن و متخصص از طریق بررسی و مهندسی معکوس Compiler های مایکروسافت (که فایل های PE شامل Rich Header تولید میکنند) ،توانستند به راز Rich Header پی ببرند و آن را کدگشایی کنند . به طور کلی Rich Header شامل اطلاعاتی در مورد محیطی است که فایل اجرایی فعلی توسط آن تولید شده مثل اسم و نسخه نرم افزار های مورد استفاده (Compiler , Linker ,…) .

ما خیلی کاری به این ساختار نداریم. Rich Header میتواند باشد میتواند هم نباشد . بود و نبودش هیچ تاثیری در کارکرد فایل ندارد . بنابراین در فضای مابین DOS HEADER  و PE HEADER میتواند DOS STUB خالی باشد یا اینکه به همراه Rich Header باشد . احتمالا در یک مطلب جداگانه در مورد Rich Header و کدگشایی آن صحبت خواهیم کرد . 

بیایید بخش DOS STUB یعنی قسمت قبل Rich Header و بعد از DOS HEADER را جدا کنیم و توسط ghidra آن را disassemble کنیم :

اگر با زبان اسمبلی آشنایی ندارید از این بخش رد شوید . اما اگر آشنا هستیم متوجه میشوید که یک برنامه ساده است که توسط فراخوانی وقفه مربوط به چاپ رشته روی صفحه ، عبارت زیر را روی صفحه چاپ میکند و سپس خارج میشود :

This Program cannot be Run in Dos Mode.

توسط نرم افزار های متعددی میتوانید ساختار فایل های PE مثل Header های آن را پیمایش و بررسی کنید . ما در این مطالب از ابزار PE-Bear استفاده میکنیم که در آخر پست لینک صفحه این نرم افزار قرار میگیرد . 

اگر همان فایل calc.exe را در PE-Bear باز کنیم و به سربرگ DOS Hdr مراجعه کنیم میبینیم که به صورت کامل header مربوط به Dos را به ما نمایش میدهد :

همچنین با مراجعه به سربرگ های دیگر مثل RichHeader , FileHeader میتوانیم header های دیگر فایل را ببینیم . دقت کنید pe-bear قابلیت تغییر مقادیر این header ها را نیز به ما میدهد .

NT HEADERS

پس از DOS STUB ساختار NT HEADERS آمده است :

این ساختار دارای دو نسخه ۳۲ بیتی و ۶۴ بیتی است که در اکثر بخش ها مشابه یکدیگر هستند بجز در برخی قسمت ها که به آن ها اشاره خواهیم کرد .

ساختار NT HEADERS در فایل winnt.h به صورت زیر تعریف شده است : 

نسخه ۳۲ بیتی :

typedef struct _IMAGE_NT_HEADERS {
  DWORD                   Signature;
  IMAGE_FILE_HEADER       FileHeader;
  IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

نسخه ۶۴ بیتی :

typedef struct _IMAGE_NT_HEADERS64 {
  DWORD                   Signature;
  IMAGE_FILE_HEADER       FileHeader;
  IMAGE_OPTIONAL_HEADER64 OptionalHeader;
} IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;

NT HEADERS شامل ۳ بخش کلی است :

1 – Signature 

2 – FileHeader

3 – OptionalHeader

بخش اول و دوم در نسخه ۳۲ و ۶۴ بیتی کاملا یکسان است و تفاوت آن ها در بخش OptionalHeader مشخص میشود . به ترتیب به توضیح این ۳ بخش میپردازیم . 

بخش Signature همیشه برابر ۴ بایت زیر است :

PE\0\0

این بایت ها امضای مربوط به شروع بخش NT Headers هستند که تشکیل شده از دو کاراکتر PE و سپس ۲ کاراکتر صفر پشت سرهم است .  دقت کنید گاهی اوقات بخش NT Headers را PE Header نیز میگویند . 

FileHeader

ساختار  بخش FileHeader در winnt.h به صورت زیر تعریف شده است :

typedef struct _IMAGE_FILE_HEADER {
  WORD  Machine;
  WORD  NumberOfSections;
  DWORD TimeDateStamp;
  DWORD PointerToSymbolTable;
  DWORD NumberOfSymbols;
  WORD  SizeOfOptionalHeader;
  WORD  Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

دقت کنید هر WORD برابر ۲ بایت و هر DWORD برابر ۴ بایت است .

Machine : مشخص کننده معماری سخت افزاری سیستمی است که این فایل روی آن اجرا میشود . مقادیر مختلف این فیلد :

Value Meaning

IMAGE_FILE_MACHINE_I386

0x014c

x86

IMAGE_FILE_MACHINE_IA64

0x0200

Intel Itanium

IMAGE_FILE_MACHINE_AMD64

0x8664

x64

NumberOfSections : تعداد Section های موجود در فایل PE . طبق گفته منابع رسمی ، حداکثر ۹۶ Section میتواند در یک فایل PE باشد .

TimeDateStamp : تاریخ و زمانی که این فایل توسط Linker تولید شده است . این فیلد یک مقدار ۲ بایتی است که تعداد ثانیه هایی که از ساعت ۰۰:۰۰:۰۰ یکم ژانویه سال ۱۹۷۰ تا آن زمان مدنظر گذشته است را در خود ذخیره میکند . 

PointerToSymbolTable و NumberOfSymbols : این دو فیلد به ترتیب offset شروع جدول symbol ها در فایل و تعداد symbol ها در آن جدول را نشان میدهد . از آنجایی که این symbol ها دیگر منسوخ شده اند این دو فیلد همیشه در فایل های PE برابر مقدار صفر هستند .

SizeOfOptionalHeader : اندازه بخش OptionalHeader که بعد از FileHeader است به واحد بایت .

Characteristics : مشخص کننده برخی ویژگی های فایل PE برای مثال اینکه آیا فایل اجرایی است یا نه ؟ DLL است یا نه ؟ و …

برای دیدن مقادیر مختلف این فیلد به

https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#characteristics

مراجعه کنید

OptionalHeader

بخش OptionalHeader مهمترین بخش فایل برای Loader ویندوز است . Loader آن بخشی از سیستم عامل است که فایل های اجرایی را در حافظه بارگذاری (Load) و اجرا میکند . بخش OptionalHeader شامل اطلاعاتی جهت Load شدن فایل در حافظه است . بنابراین Loader برای اجرای فایل به اطلاعات این بخش احتیاج دارد . همانطور که قبل تر ذکر شد ، ساختار OptionalHeader شامل دو نسخه ۳۲ بیتی و ۶۴ بیتی است . تفاوت نسخه ۳۲ و ۶۴ بیتی در دو مورد است :

۱ – تعداد فیلد های آن ها متفاوت است . OptionalHeader نسخه ۳۲ بیتی دارای ۳۱ عضو ،‌ولی نسخه ۶۴ بیتی دارای ۳۰ عضو است . آن یک عضو اضافه در نسخه ۳۲ بیتی BaseOfData است که جلوتر آن را بررسی میکنیم .

۲ – اندازه برخی فیلد های آن ها باهم متفاوت است . پنج فیلد زیر در نسخه ۳۲ بیتی به صورت DWORD ولی در نسخه ۶۴ بیتی به صورت ULONGLONG که ۸ بایتی است تعریف شده اند :

  •  ImageBase
  •  SizeOfStackReserve
  •  SizeOfStackCommit
  •  SizeOfHeapReserve
  •  SizeOfHeapCommit

ساختار OptionalHeader نسخه ۳۲ بیتی تعریف شده از winnt.h :

typedef struct _IMAGE_OPTIONAL_HEADER {
  WORD                 Magic;
  BYTE                 MajorLinkerVersion;
  BYTE                 MinorLinkerVersion;
  DWORD                SizeOfCode;
  DWORD                SizeOfInitializedData;
  DWORD                SizeOfUninitializedData;
  DWORD                AddressOfEntryPoint;
  DWORD                BaseOfCode;
  DWORD                BaseOfData;
  DWORD                ImageBase;
  DWORD                SectionAlignment;
  DWORD                FileAlignment;
  WORD                 MajorOperatingSystemVersion;
  WORD                 MinorOperatingSystemVersion;
  WORD                 MajorImageVersion;
  WORD                 MinorImageVersion;
  WORD                 MajorSubsystemVersion;
  WORD                 MinorSubsystemVersion;
  DWORD                Win32VersionValue;
  DWORD                SizeOfImage;
  DWORD                SizeOfHeaders;
  DWORD                CheckSum;
  WORD                 Subsystem;
  WORD                 DllCharacteristics;
  DWORD                SizeOfStackReserve;
  DWORD                SizeOfStackCommit;
  DWORD                SizeOfHeapReserve;
  DWORD                SizeOfHeapCommit;
  DWORD                LoaderFlags;
  DWORD                NumberOfRvaAndSizes;
  IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

نسخه ۶۴ بیتی :

typedef struct _IMAGE_OPTIONAL_HEADER64 {
  WORD                 Magic;
  BYTE                 MajorLinkerVersion;
  BYTE                 MinorLinkerVersion;
  DWORD                SizeOfCode;
  DWORD                SizeOfInitializedData;
  DWORD                SizeOfUninitializedData;
  DWORD                AddressOfEntryPoint;
  DWORD                BaseOfCode;
  ULONGLONG            ImageBase;
  DWORD                SectionAlignment;
  DWORD                FileAlignment;
  WORD                 MajorOperatingSystemVersion;
  WORD                 MinorOperatingSystemVersion;
  WORD                 MajorImageVersion;
  WORD                 MinorImageVersion;
  WORD                 MajorSubsystemVersion;
  WORD                 MinorSubsystemVersion;
  DWORD                Win32VersionValue;
  DWORD                SizeOfImage;
  DWORD                SizeOfHeaders;
  DWORD                CheckSum;
  WORD                 Subsystem;
  WORD                 DllCharacteristics;
  ULONGLONG            SizeOfStackReserve;
  ULONGLONG            SizeOfStackCommit;
  ULONGLONG            SizeOfHeapReserve;
  ULONGLONG            SizeOfHeapCommit;
  DWORD                LoaderFlags;
  DWORD                NumberOfRvaAndSizes;
  IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER64, *PIMAGE_OPTIONAL_HEADER64;

به بررسی فیلد های OptionalHeader میپردازیم :

Magic : این فیلد مشخص کننده این است که آیا این فایل ۳۲ بیتی است یا ۶۴ بیتی یا اینکه فایل ROM است . مقادیر مختلف این فیلد عبارتند از :

 

Value Meaning

IMAGE_NT_OPTIONAL_HDR32_MAGIC

0x10b

this is a 32-bit executable image

IMAGE_NT_OPTIONAL_HDR64_MAGIC

0x20b

this is a 64-bit executable

IMAGE_ROM_OPTIONAL_HDR_MAGIC

0x107

this is a ROM image

به نظر میرسد فیلد Machine در FileHeader نیز به شکلی همین موضوع را مشخص میکرد پس دلیل وجود این فیلد چیست ؟ دقت کنید که Loader ویندوز از این فیلد برای شناسایی معماری فایل استفاده میکند و فیلد Machine در FileHeader را اصلا در نظر نمیگیرد .

MajorLinkerVersion و MinorLinkerVersion : نسخه نرم افزار Linker که این فایل را تولید کرده است .

SizeOfCode : در فایل های PE همانطور که گفتیم تعدادی Section وجود دارد که در این section ها اطلاعاتی مثل کد های اجرایی و داده های مورد نیاز کد ها ذخیره میشود . برخی از این Section ها حاوی کد های اجرایی فایل هستند . این فیلد برابر مجموع اندازه همه Section های حاوی کد اجرایی در واحد بایت است . لازم به ذکر است هر فایل PE یک section به نام text. دارد که حاوی کد های اجرایی آن است .

SizeOfInitializedData : اندازه داده های مقدار دهی شده در فایل به واحد بایت .

SizeOfUninitializedData : اندازه داده هایی که به صورت اولیه مقدار دهی نشده اند به واحد بایت . دقت کنید در فایل های PE یک Section مختص داده های مقدار دهی شده و یک Section هم مخصوص داده های مقدار دهی نشده وجود دارد که در بخشی که Section هارا توضیح میدهیم به معرفی این ها خواهیم پرداخت

AddressOfEntryPoint : حاوی RVA نقطه ای که اجرای کد از آن شروع میشود 

BaseOfCode : حاوی RVA مربوط به شروع Section کد وقتی فایل در حافظه بارگذاری میشود . 

BaseOfData (فقط در ۳۲ بیتی) : حاوی RVA مربوط به شروع Section داده وقتی فایل در حافظه بارگذاری میشود .                                               

ImageBase : حاوی آدرس پیشنهادی برای قرار گرفتن فایل PE در حافظه به طوری که اولین بایت فایل PE در این آدرس از حافظه قرار بگیرد یعنی شروع فایل PE از این آدرس باشد . دقت کنید لزوما فایل PE در این آدرس بارگذاری نمیشود این صرفا یک پیشنهاد است . با توجه به مکانیزم های امنیتی مثل ASLR هر فایل PE به طور شانسی در یک آدرس دلخواه در حافظه بارگذاری میشود و از این رو در سیستم عامل های ویندوز جدید فایل های PE معمولا هیچوقت در آدرسی که این فیلد ذکر کرده بارگذاری نمیشوند !

SectionAlignment :  مشخص کننده Alignment مربوط به قرار گیری Section ها در حافظه است . به عبارتی آدرس شروع هر Section در حافظه باید مضرب صحیحی از این عدد باشد . مقدار این فیلد نمیتواند کوچک تر از مقدار فیلد بعدی یعنی FileAlignment باشد . معمولا مقدار این فیلد برابر اندازه یک page در حافظه سیستم عامل است .

FileAlignment : مشخص کننده Alignment مربوط به قرارگیری Section ها در خود فایل روی دیسک است . به عبارتی آدرس شروع هر Section در فایل (offset) باید مضرب صحیحی از این مقدار باشد . طبق منابع رسمی ، مقدار این فیلد باید توانی از ۲ باشد و بین ۵۱۲ بایت تا ۶۴  کیلوبایت باشد . دقت کنید با توجه به این موضوع اگر فضای خالی بین Section ها در فایل بوجود آمد ، آن فضا با صفر پر میشود . 

به تفاوت بین SectionAlignment و FileAlignment دقت کنید . SectionAlignment مربوط به آدرس شروع Section ها در حافظه است ولی FileAlignment مربوط به آدرس شروع آن ها در خود فایل روی دیسک است .

MajorOperatingSystemVersion و MinorOperatingSystemVersion : این دو فیلد مجموعا نسخه سیستم عامل مورد نیاز برای اجرای این فایل را ذخیره میکنند . 

MajorImageVersion و MinorImageVersion : این دو فیلد مجموعه نسخه (Version) خود فایل PE را مشخص میکنند .

MajorSubsystemVersion و MinorSubSystemVersion : این دو فیلد مجموعا نسخه مربوط به subsystem مورد نیاز برای اجرای فایل را مشخص میکنند .

Win32VersionValue : یک فیلد رزرو شده است و مقدار آن همیشه برابر صفر است .

SizeOfImage : اندازه کل فایل PE شامل header ها و section ها در واحد بایت . این اندازه هرچه باشد به سمت یک مضرب صحیح از مقدار SectionAlignment گرد میشود . مثلا اگر SectionAlignment برابر 4 کیلوبایت باشد و اندازه فایل 7000 بایت باشد ، از آنجایی که مضرب بعدی SectionAlignment عدد ۸ کیلوبایت است پس مقدار SizeOfImage برابر ۸ کیلوبایت خواهد شد . 

SizeOfHeaders : مجموع اندازه بخش DOS STUB ، NT HEADERS و Section Table . این مقدار هرچه باشد به سمت مضرب صحیحی از FileAlignment گرد میشود .

CheckSum : یک checksum از کل فایل PE . برای بررسی یکپارچگی فایل حین بارگذاری توسط Loader استفاده میشود . 

Subsystem : مشخص کننده subsystem مورد نیاز برای اجرای فایل است . مقادیر مختلف این فیلد را در لینک زیر ببینید :

https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#windows-subsystem

DllCharacteristics : این فیلد نیز مشخص کننده یک سری دیگر از ویژگی ها و قابلیت هایی است که فایل پشتیبانی میکند مثلا اینکه آیا Relocatable است یا نه . دقت کنید این فیلد مربوط به همه ی نوع فایل های اجرایی PE است و صرفا مخصوص DLL ها نیست . مقادیر مخلتف این فیلد را در لینک زیر ببینید :‌

https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#dll-characteristics

SizeOfStackReserve و SizeOfStackCommit و SizeOfHeapReserve و SizeOfHeapCommit : مشخص کننده اندازه حافظه رزرو شده برای stack ، اندازه حافظه commit شده برای stack , اندازه حافظه رزرو شده برای heap و اندازه حافظه commit شده برای heap . 

دقت کنید رزرو کردن مقداری از حافظه برای یک پروسه به معنای این است که فقط بخشی از آدرس های حافظه مجازی پروسه برای استفاده های آینده رزرو شده اند و به جایی از حافظه فیزیکی map نشده اند هنوز . حال اگر بخشی از آن حافظه رزرو شده commit شود ، آن بخش به بخش هایی از حافظه فیزیکی map میشود . 

LoaderFlags : این فیلد رزرو شده است و مقدار آن صفر خواهد بود

NumberOfRvaAndSizes : تعداد عناصر آرایه DataDirectory (فیلد بعدی)

DataDirectory : آرایه ای از ساختار IMAGE_DATA_DIRECTORY است که به صورت زیر تعریف شده است :

typedef struct _IMAGE_DATA_DIRECTORY {
  DWORD VirtualAddress;
  DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

هر عنصر آرایه DataDirectory یک نمونه از ساختار بالا است که شامل یک آدرس مجازی (VA) و یک اندازه (Size) است . هر کدام از این عناصر به یک جدول خاص و معنادار در فایل PE اشاره میکنند که حاوی اطلاعات مهم است . VirtualAddress حاوی آدرس مجازی آن جدول در حافظه است و Size هم اندازه آن است.

به هر کدام از این جداول یک DataDirectory میگویند . 

در قسمت های بعدی این مجموعه به بررسی DataDirectory ها خواهیم پرداخت .

میتوانیم بخش های FileHeader و OptionalHeader همان فایل calc.exe را در نرم افزار PE-BEAR مشاهده کنیم :

این آموزش متعلق به بخش مهندسی معکوس است

برای مشاهده تمام آموزش های مهندسی معکوس وبسایت مسترپایتون به بخش مهندسی معکوس مراجعه کنید

پست های مرتبط

مطالعه این پست ها رو از دست ندین!

ساختار فایل PE قسمت ۵ – Relocation ها

مشکل از آنجایی شروع میشود که ویندوز ، همیشه فایل های PE را در آن آدرسی از حافظه که انتظار میرود بارگذاری نمیکند و در این صورت خیلی از چیز ها بهم میریزد ! در این بخش از ساختار فایل PE به بررسی Relocation ها در فایل PE میپردازیم . اینکه اصلا Relocation به چه معناست و در فایل PE چگونه پیاده سازی میشود .

بیشتر بخوانید

ساختار فایل PE قسمت ۴ – واردات (Imports)

در قسمت قبلی صادرات فایل های PE را بررسی کردیم . قطعا وقتی بحث صادرات را داریم ، از طرفی واردات را هم خواهیم داشت . فایل های PE در ویندوز میتوانند توابع صادراتی فایل های دیگر را وارد (Import) کرده و از آن ها استفاده کنند. این رفتار مستلزم این است که خود فایل های PE دیگر که از صادرات آن ها استفاده میکنیم نیز در حافظه بارگذاری شوند که این کار وظیفه ی Loader ویندوز است .

بیشتر بخوانید

ساختار فایل PE قسمت ۳ – صادرات (Exports)

در سیستم عامل ویندوز ، فایل های PE میتوانند توابعی را صادر (export) کنند تا فایل های PE دیگر از طریق وارد (import) کردن ، از آن ها استفاده کنند . این رفتار را اغلب در فایل های DLL مشاهده میکنیم ولی در واقعیت هر فایل PE حتی فایل های اجرایی میتوانند export هایی داشته باشند .

بیشتر بخوانید

نظرات

سوالات و نظراتتون رو با ما به اشتراک بذارید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

آواتار کاربر کاربر مهمان ایمان ۲۳ مرداد ۱۴۰۲

با سلام، بسیار عالی و زیبا و روان توضیح داده شده، اگه امکانش هست هر چه زودتر در مورد بخش دوم و یا بخش های اضافه تر، علی الخصوص در مورد توابع وارداتی (Import Functions) و توابع صادراتی (Export Functions) فایهای dll، مخصوصن از نحوه و چگونگی چیدمان و قرارگیری تمامی پارامترهای این توابع (نظیر Original First Thunk، First Thunk، Hint, Forward chain و …) و آدرس های اونها چه در سکشن های خود فایل و هارد دیسک و چه در حافظه ی مجازی و همینطور در حافظه فیزیکی به هنگام اجرا در سیستم عامل ویندوز هم توضیحات کامل بدین، همینطور چگونه ابزارهای تحلیل این آدرسها رو هم در خود فایل و هم در حافظه شناسایی و استخراج و تفسیر و تعریف می کنند رو توضیح بدید، علاوه براین، مطالب در مورد سکشن های reloc و overlay و عناوینی نظیر tls و … به زبان فارسی بسیار کمه که اگه در مورد این عناوین هم توضیحات کاملی ارائه کنید، بسیار ممنون میشیم. البته در مورد ساختارهای Resource Directory، Exception Directory، Relocation Directory، Debug Directory، TLS Directory و هر آنچه که در مورد ساختار فایل اجرایی که نرم افزارهایی نظیر CFF Explorer و یا PE bear و سایر انواع نرم افزارهای آنالیز فایلهای اجرایی به ما نشون میدن رو اگه در سایتتون به طور کامل توضیح بدید بسیار عالی میشه. مطالب به این کاملی در مورد ساختار فایل های اجرایی به خصوص به زبان فارسی به شدت کمه. ممنون از شروع بسیار خوب و قوی در مورد آموزش بحث مهندسی معکوس. با تشکر. همیشه پایدار و موفق و پاینده باشید.

حسین احمدی ۲۴ مرداد ۱۴۰۲

سلام و درود .
مچکرم بابت نظرتون . حتما تلاش میکنیم که این موضوع فایل های PE رو در پست های آینده ادامه بدیم … 🙂

آواتار کاربر کاربر مهمان محمدصالح سوزنچی ۲۵ مهر ۱۴۰۲

با خوندن این مطلب، مخصوصا بخش comساختار! شدیدا یاد آنزمانی افتادم که دنبال این بودم که فایل‌های تحت داس رو آلوده کنم🤣
خیلی وقت بود که اطلاعاتم مربوط به این حوزه رو بروزرسانی نکرده بودم. از خوندن این مطلب و ادامه آن بسیار لذت بردم.
توضیحات خوبی داده بودید🥰

حسین احمدی ۲۵ مهر ۱۴۰۲

همین الان هم patch کردن DOS باحاله برا من 🙂
خوشحالم که بازدید کردید 🙂