السَّلاَمُ عَلَيْكُمْ وَرَحْمَةُ اللهِ وَبَرَكَاتُهُ
اللَّهُمَّ صَلِّ عَلَى مُحَمَّدٍ وَعَلَى آلِ مُحَمَّدٍ، كَمَا صَلَّيْتَ عَلَى إِبْرَاهِيمَ وَآلِ إِبْرَاهِيمَ، إِنَّكَ حَمِيدٌ مَجِيدٌ، وَبَارِكْ عَلَى مُحَمَّدٍ وَعَلَى آلِ مُحَمَّدٍ، كَمَا بَارَكْتَ عَلَى إِبْرَاهِيمَ وَآلِ إِبْرَاهِيمَ، إِنَّكَ حَمِيدٌ مَجِيدٌ،
[+] هنتكلم فـ المقالة دي عن الـ PE Format ، وهنعرف شوية حاجات زي:
[+] الـ Portable Executable (PE) Format هو الهيكل الأساسي لأي ملف تنفيذي ع Windows ، زي:
[+] أي برنامج بتشغله ع ويندوز معمول بصيغة PE ، لأنه الشكل القياسي اللـ الويندوز بيستخدمه لتحميل وتشغيل الملفات التنفيذية.
[+] معرفة الـ PE Format طبعا مهمة ومفيدة لكذا حد ، بمعني إن:
[+] عرفنا فوق إنه الهيكل الأساسي لأي برنامج تنفيذي ع الويندوز ، والهيكل دا بيتكون من 3 أجزاء رئيسية:
↑ ودا فية معلومات تخص الملف نفسه .. زي نوعه (EXE / DLL / SYS) .. وكمان نقدر نعرف معلومات تانية زي هل الملف ده 32-bit أو 64-bit وحجم الملف ، وهكذا .. وهنتكلم عنهم بالتفصيل كمان شوية.
↑ وهنا بنعرف منه نقط الوصول اللـ بيقدر يوصل للـ Libraries ، سواء يعمل Import أو Export.
↑ ودا فيه الأكواد بتاعة البرنامج نفسة.
[+] نبدأ بـ أول جزء وهو:
ودا بيتكون من كذا جزء زي:
[+] تعالي نبدأ بيهم واحد واحد ، وبالتفصيل:
[+] كل ملف PE بيبدأ بالـ Dos Header ، ودا بيهدف إن بيخلي الملف من صيغة MS-DOS Executable ، يعني ملف تشغيلي ع أنظمة MS-DOS.
[+] زمان في أنظمة MS-DOS:
[+] حاليا فـ أنظمة الويندوز الحديثة:
[+] الـ DOS Header حجمه 64 بايت ، بيحتوي ع
معلومات ، ودا كان مهم للـ PE Loader ع الـ MS-DOS ، وهنتكلم
عن أهم عنصرين فيه وهما الـ e_magic ، والـ
e_lfanew.
2 بايت اسمهم الـ (Magic Numbers).
0x5A4D بالـ Hex
، أو MZ كـ Ascii (الحروف دي نسبة
إلى Mark Zbikowski اللي
صمم الصيغة دي). 0x3C.[+] هوضحلك الفكرة بـ مثال عملي:
[+] إفتح برنامج الـ PE-Bear .. إختار أي ملف .exe ، فـ حالتي دي أنا فتحت الـ Mspaint.exe .. روح ع الـ DOS Header .. هتلاقي ايه بقي ؟
0x4D5A بالـ hex أو MZ بالـ Ascii ..
وبيكون موجود ع الـ Offset رقم 0.0x3C ، هتلاقي مكتوب إن الـ
Value بتاعته 0xE8 .. ودا قولنا إنه
بيشير للـ offset بتاع بداية الـ NT Header.
[+] طب تعالي نتأكد ونشوف هل الكلام دا فعلا صح ولا لا .. هنقف ع الـ NT Header .. ونشوف أول Value واللـ
هي فـ الحالة بتاعتي دي 0x50 ودي اللـ هي
تقاطع بداية أول صف مع بداية أول عمود .. طب موجودة عن الـ
Offset كام؟ .. لو بصيت هتلاقي ان دا الـ Offset رقم 0xE8 يعني فعليا الكلام اللـ قولناه فوق طلع
صح.
[+] ودا اتكلمنا عنه فوق وقولنا ان كان زمان موجود تبع أنظمة الـ MS-DOS ، ولسه موجود فـ الأنظمة الحديثة عشان يتوافق مع الإصدارات القديمة .. وكل مهمة الجزء هنا عشان يطبع الجملة دي بس ، وملوش لازمه غير كده ، وحجمة 64 bytes زي الـ Dos Header.
This program cannot be run in DOS mode
[+] بعد الـ DOS Stub ، هتلاقي داتا غريبة بينه وبين NT Headers ، والجزء ده اسمه Rich Header.
[+] قبل مـ ندخل فـ الـ NT Headers ، لازم نفهم يعني إيه RVA الأول ، عشان هنحتاجه كتير بعد كده.
[+] الـ RVA (Relative Virtual Address) هو عنوان نسبي جوه الميموري ، بيشاور ع أماكن محددة جوا الـ PE File.
[+] هقولك مثال عملي عشان تفهم الفكرة:
0x400000RVA = 0x1000 ، فده معناه إيه؟
Base Address (0x400000) + RVA (0x1000) = 0x401000
[+] طب إيه فايدة الـ RVA؟
[+] الـ NT Headers هو تاني جزء رئيسي فـ الـ PE File بعد الـ DOS Header ، وبيحتوي ع 3 أجزاء رئيسية:
0x50450000 بالـ Hex، أو
"PE\0\0" بالـ ASCII.
[+] الـ File Header (أو COFF Header) فيه شوية معلومات أساسية عن الملف ، وبيتكون من 7 عناصر رئيسية:
ده رقم بيعبر عن نوع المعالج (CPU Architecture) اللـ البرنامج ده معمول له زي:
وتقدر تقرأ من [هنا] وتشوف كل الأنواع.
.text (للكود) ، .data (للمتغيرات) ،
وهكذا.مجموعة Flags بتحدد بعض الخصائص بتاعة الملف ، زي:
وتقدر تقرأ من [هنا] وتشوف كل الخصائص التانية.
[+] الـ Optional Header جزء مهم جدًا ، لأن الـ PE Loader بيعتمد عليه بشكل أساسي فـ تشغيل البرامج ، ودا شكل الهيكل بالعناصر بتاعته فـ نسخة الـ 32-bit والـ 64-bit:
[+] طب إيه الفرق بين 32-bit و 64-bit؟
[+] أهم العناصر فـ الـ Optional Header:
↑ بيوضح إذا كان الملف 32-bit أو 64-bit:
↑ بيوضح نسخة الـ Linker اللـ تم استخدامه لإنشاء الملف.
فـ المثال ده ، الـ Linker version بيكون E14 ، ودي حاجة تهم الـ PE Loader:
↑ بيوضح حجم الأكواد التنفـذية ، وغالبًا بتكون موجودة فـ الـ [.text] Section.
↑ حجم البيانات اللـ تم تهيئتها ، وعادةً بتكون فـ الـ [.data] Section.
↑ حجم البيانات الغير مهيأة ، وعادةً بتكون فـ الـ [.bss] Section.
↑ ده RVA بيحدد أول Instruction هيتم تنفـذها لما الملف يتحمل فـ الميموري.
مهم جدًا فـ عمليات Reverse Engineering & Malware Analysis ، لأنه بيحدد أول سطر كود هيشتغل.
↑ ده RVA بيشاور على أول Byte فـ الكود التنفـذي.
↑ ده RVA بيشاور على أول Byte فـ الـ Data Section.
↑ عدد العناصر الموجودة فـ DataDirectory Array.
↑ ده Array فيه بيانات بتحدد حاجات زي Imports / Exports / Resources ، وهنتكلم عنها كمان شوية.
[+] تاني جزء وهو:
[+] فـ الجزء اللـ فات ، كنا بنتكلم عن Optional Header ، وشفنا إنه بيحتوي ع DataDirectory ، واللـ هو عبارة عن Array فيها بيانات بتساعد الـ PE Loader أثناء تحميل وتشغيل الملف.
[+] طب يعني إيه Data Directory أصلاً؟
ببساطة الـ Data Directory دي عبارة عن بيانات بتكون موجودة جوا أحد الـ Sections بتاعة ملف الـ PE ، والهدف منها إنها بتوفر معلومات حيوية للـ Loader عشان يعرف يستخدم الـ Imports, Exports, Resources, Debug Info... etc.
[+] ودي شكلها عامل إزاي؟
الـ Data Directory عبارة عن Array مُكوّن من 16 عنصر:
#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16
وكل عنصر فيهم هو هيكل بيانات من نوع
IMAGE_DATA_DIRECTORY
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
، والهيكل ده بيتكوّن من حاجتين بس:
↑ وده عبارة عن RVA (Relative Virtual Address) بيشاور ع بداية الـ Data Directory فـ الميموري.
↑ وده حجم الـ Data Directory نفسه.
بمعنى أبسط ، أي Data Directory هي مجرد مكان جوا الميموري فيه بيانات معينة ، والـ VirtualAddress بيشاور عليه ، والـ Size بيحدد مساحته.
[+] طب إيه هي أنواع الـ Data Directories؟
كل نوع من الـ Data Directories ليه وظيفة مختلفة ، ودول الـ 16 Directory الأساسيين اللـ بيكونوا موجودين فـ الملفات التنفيذية ع الويندوز:
↑ خاص بالدوال اللـ البرنامج بيصدرها عشان برامج تانية تقدر تستخدمها.
↑ خاص بالدوال اللـ البرنامج بيستوردها من مكتبات تانية (DLLs).
↑ خاص بالـ Resources زي الأيقونات ، الصور ، النصوص ، والـ dialogs اللـ جوه الملف.
↑ بيحتوي ع معلومات الـ Exception Handling الخاصة بالبرنامج.
↑ بيحفظ البيانات الخاصة بالتوقيعات الرقمية (Digital Signatures) لو البرنامج موقع رقمياً.
↑ فيه البيانات الخاصة بتعديل عناوين الميموري أثناء تحميل الملف (لو مكانه فـ الميموري اختلف).
↑ بيحفظ معلومات خاصة بالـ Debugging Symbols لو البرنامج متكامل مع أدوات Debugging.
↑ مش بيستخدم عادةً ، بس معمول لأنظمة معينة لو محتاجة بيانات خاصة.
↑ خاص بأنظمة الـ IA-64 وكان بيستخدم للـ Global Pointer Register.
↑ مسؤول عن بيانات الـ Thread Local Storage (TLS) ، وده بيكون مهم للـ Multithreading.
↑ بيحتوي ع معلومات خاصة بإعدادات تحميل الملف.
↑ بيحتوي ع معلومات عن الـ DLLs اللـ البرنامج مربوط بيها وقت الـ Linking.
↑ خاص بجدول العناوين اللـ بيتم استدعاء الدوال الخارجية منه.
↑ مشابه للـ Import Directory لكنه بيأخر تحميل الـ DLLs لحد ما يتم استدعاؤها فعلياً.
↑ خاص بالملفات اللـ بتستخدم .NET CLR والـ COM Interop.
[+] طب هل كل ملفات الـ PE فيها الـ 16 Directory دول؟
لأ ، مش شرط .. أوقات لما تفتح ملف PE ممكن تلاقي بعض الـ Data Directories القيم بتاعتها 0x00000000
سواء فـ الـ VirtualAddress أو الـ Size ، وده معناه إن النوع ده من الـ Data Directories مش
مُستخدم فـ
الملف.
[+] آخر جزء وهو:
[+] الـ Sections هي الأجزاء اللـ بتحتوي ع الأكواد والبيانات الفعلية للبرنامج ، وهي بتبدأ بعد الـ Headers ع طول .. كل Section بيكون ليه اسم بيدل ع وظيفته ، ودي أشهر الـ Sections الموجودة جوه أي ملف PE:
↑ وده أهم جزء ، لأنه بيحتوي ع الكود التنفيذي (Executable Code) بتاع البرنامج. يعني هنا بتلاقي التعليمات البرمجية اللـ الـ CPU بينفذها.
↑ بيحتوي ع البيانات اللـ اتعملها تهيئة مسبقة (Initialized Data) ، يعني المتغيرات اللـ ليها قيم محددة قبل مـ البرنامج يشتغل.
↑ بيحتوي ع البيانات اللـ ملهاش تهيئة مسبقة (Uninitialized Data) ، يعني أي متغيرات اتعرفت بس مفيش قيم اتحطت فيها قبل تشغيل البرنامج.
↑ وده بيحتوي ع بيانات ثابتة للقراءة فقط (Read-Only Data) ، زي الـ Strings اللـ مش هتتغير أثناء تشغيل البرنامج.
↑ الجزء ده فيه جدول التصدير (Export Table) ، اللـ بيحدد أي دوال (Functions) بيتم تصديرها من الملف عشان تستخدمها برامج تانية.
↑ وده العكس ، بيحتوي ع جدول الاستيراد (Import Table) ، اللـ فيه كل الدوال والـ APIs اللـ البرنامج بيحتاج يستدعيها من مكتبات تانية (زي دوال Windows API من kernel32.dll مثلًا).
↑ ده فيه معلومات إعادة التمركز (Relocation Information) ، ودي مهمة لو البرنامج هيتحمل في الميموري عند عنوان مختلف عن اللـ كان متوقع وقت الكومبايل.
↑ ده خاص بالـ Resources ، يعني أي موارد بيستخدمها البرنامج زي الأيكونات ، الصور ، الفورمات ، أو حتى ملفات مدمجة جواه.
↑ وده بيخص الـ Thread Local Storage (TLS) ، ودي آلية بتوفر لكل Thread مجموعة من البيانات الخاصة بيه منفصلة عن باقي الـ Threads.
[+] طب هل كل ملفات الـ PE فيها نفس الـ Sections دي؟
لأ ، مش كل الملفات بتحتوي ع كل الـ Sections دي ، بعض الملفات ممكن يكون فيها Sections بأسماء مختلفة حسب نوع البرنامج والمترجم (Compiler) اللـ اتعمل بيه.
[+] دا بوستر لطيف شامل ولامم كل حاجه عن الـ PE ، وتقدر تشوفه من [هنا].
[+] فـ الجزء ده ، هنكتب كود C بسيط نتعلم بيه إزاي نقرأ الـ Magic Numbers لأي ملف PE:
[+] دا الكود النهائي وهنشرحه دلوقتي:
#include <windows.h>
#include <stdio.h>
int main(int argc, char* argv[]) {
if (argc < 2) {
printf("Usage: %s <PE File>\n", argv[0]);
return -1;
}
HANDLE hFile = CreateFileA(argv[1], GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
printf("Failed to open file.\n");
return -1;
}
IMAGE_DOS_HEADER dosHeader;
DWORD bytesRead;
ReadFile(hFile, &dosHeader, sizeof(IMAGE_DOS_HEADER), &bytesRead, NULL);
if (dosHeader.e_magic != IMAGE_DOS_SIGNATURE) {
printf("Not a valid PE file.\n");
CloseHandle(hFile);
return -1;
}
printf("Valid PE file detected!\n");
printf("DOS e_magic: 0x%X\n", dosHeader.e_magic);
CloseHandle(hFile);
return 0;
}
[+] الكود ده ببساطة برنامج مكتوب بلغة C بيقرأ الـ DOS Headers بتاعة أي ملف PE (Portable Executable) ، وبيتحقق إذا كان فعلاً ملف PE صالح ولا لأ.
[+] تعالي نشرح الكود جزء بجزء:
(argc < 2)، يبقى اليوزر كدا مكتبش اسم
ملف PE بعد تشغيل البرنامج .. فـ الحالة دي ، بيطبع رسالة
توضح
طريقة الاستخدام.
if (argc < 2) {
printf("Usage: %s <PE File>\n", argv[0]);
return -1;
}
CreateFileA ودي Windows API بتفتح
الملف اللـ اسمه argv[1] .. وبصلاحيات
Read-Only
عشان يقدر يقرأه .. ولو فشل فـ فتح الملف بيقولك انه فشل
عادي جدا.
HANDLE hFile = CreateFileA(argv[1], GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
printf("Failed to open file.\n");
return -1;
}
0x5A4D بالـ Hex
، أو MZ كـ Ascii .. يبقي كدا البرنامج
مش PE ويخرج برسالة خطأ بتقول إن دا مش برنامج
PE.
IMAGE_DOS_HEADER dosHeader;
DWORD bytesRead;
ReadFile(hFile, &dosHeader, sizeof(IMAGE_DOS_HEADER), &bytesRead, NULL);
if (dosHeader.e_magic != IMAGE_DOS_SIGNATURE) {
printf("Not a valid PE file.\n");
CloseHandle(hFile);
return -1;
}
printf("Valid PE file detected!\n");
printf("DOS e_magic: 0x%X\n", dosHeader.e_magic);
CloseHandle(hFile);
return 0;
}
[+] هنبدأ نجرب نـ رن الكود ونشوف إي الكلام:
وبكدا . أتمني تكون استفدت حاجه .. ونلتقي فـ مقالة قادمة .. ولو عندك أي تعليق ، استفسار ، أي حاجه .. متترددش وكلمني.
خدلك لفة فـ المصادر والمراجع يا عزيزي: