أثناء بحثي عن موضوع ما وجدت برنامجا يُدّعى أنه يعمل بدون استيرات أي مكتبة ولا حتى kernel32.dll، ولكنه يستدعي الدالة MessageBox والتي توجد في المكتبة USER32.dll. وفعلا في cff explorer لا وجود لجدول imports.
البرنامج للأسف لا يعمل عندي على windows 8. من كان عنده windows xp إلى 7 يمكنه التحقق لنا .
فقمت بتنقيح البرنامج ومحاولة فهم الأوامر، في ما يلي سأوضح نظريا كيف يعمل البرنامج على الرغم من عدم امتلاكي لنسخة تعمل منه حاليا، كما سأوضح سبب فشل البرنامج (البرنامج في المرفقات).
تنبيه: هذا التحليل لا يعني أن البرنامج لن يعمل على الأنظمة السابقة ل windows 8! بل على العكس، فكرة البرنامج تبدو صالحة لو لا بعض الافتراضات الضمنية التي لا تتحقق في windows 8. وغالب ظني أنه فعلا يعمل على الأنظمة السابقة.
خطة البرنامج بشكل مختصر:
1- يفترض البرنامج أن kernel32.dll قد تم تحمليها مسبقا بشكل تلقائي حتى ولو لم يطلب البرنامج ذلك (وهو الحال فعلا).
2- انطلاقا من هذا الفرض 1 يحاول البرنامج البحث عن ال base address ل kernel32.dll.
3- يبحث البرنامج في ال export directory في ال PE Header الخاص ب kernel32.dll عن الدالة LoadLibrary.
4- باستعمال الدالة LoadLibrary يستطيع البرنامج تحميل المكتية USER32.dll ومعرفة عنوانها.
5- البحث في ال export directory في ال PE Header الخاص ب USER32.dll عن الدالة MessageBox
6- استدعاء الدالة MessageBox.
التفاصيل:
سنستعمل ال structure التالي لتخزين معلومات الدوال التي نود البحث عنها.
البرنامج يعرف المتغيرات العمومية (Global أهذه الترجمة جيدة؟) التالية:
لنلق نظرة على kernel32_functions_info_array
الأوامر:
سنفصل الخطوتين 2 و 3، لأن البقية واضحة و الخطوة 5 مطابقة ل 3 (فقط باختلاف قيمة DLL_base_address).
الخطوة 2: عمليا يبدأ البرنامج باستدعاء الدالة Fill_kernel32_address_directly_after_running المعرفة كالتالي
1- عنوان الأساس يساوي قيمة PE_Header.Optional_Header.ImageBase
والذي يمكننا الوصول إليه كالتالي
2- أصغر من قيمة ecx أعلاه (هل هذا صحيح دائما؟).
كما أن البرنامج يضيف الشرط التالي
3- أن يكون dx & F800 == 0 ولعل المقصود أن تكون قيمة edx صغيرة فلا يحصل memory access violation exception.
فما تقوم به الدالة Fill_kernel32_address_directly_after_running هو انقاص قيمة ecx باستمرار والتحقق من الشرطين 1 و 3. إذا تحققا فهذا هو عنوان الأساس ل kernel32.dll !!
لكن لا بد أنكم رأيتم التعليق access violation exception. Fix this أعلاه، وهذا عندي لوجود مناطق ذاكرة محجوزة تتخلل منطقة ذاكرة kernel32.dll. يبدو أن هذا لم يكن الحال في الأنظمة السابقة.
الخطوة 3:
بما أننا حصلنا على عنوان kernel32.dll وخزناه في DLL_base_address فإبمكاننا الوصول إلى export directory ببساطة والبحث عن أسماء الدوال التي نريد. لنعرف الدالة التالية
1- استخراج بعض المعلومات من ال PE Header الخاص بال dll المراد البحث فيه وهو في حالتنا الآن kernel32.dll.
2- في ال while loop يقوم بمقارنة اسم الدالة التي نريد أن نبحث عنها (esi) مع قائمة الأسماء المسخرجة في 1 والتي خزنت في edi.
3- بعد إيجاد ترتيب اسم الدالة في قائمة الأسماء (ebx) نستعمله كأنه ال ordinal المحدد للدالة في ال PE Header الخاص ب kernerl32.dll وباستعماله نستطيع إيجاد عنوان الدالة.
هذه الخطوات تطبيق عملي لل PE Header Structure.
هل انتبهت للتعليق False ordinal؟ في الحقيقة حتى هنا توجد مشكلة وهي أن المبرمج افترض أن ترتيب اسم الدالة ebx في مصوفة الأسماء edi سيكون مطابقا ل ordinal الدالة! للأسف هذا غير صحيح عندي. كان من المفترض أن يستخدم address of addresses of names وليس address of first name (انظر التعليقات على الأوامر) لكنه افترض أن ترتيب الأسماء هنا وهناك سيكون متطابقا!
--------------------
في المرفقات: تجدون البرنامج وملف txt فيه أوامر البرنامج معكوسة كلها تقريبا لمن أراد التعقب أثناء التنقيح أو محاولة إعادة كتابة البرنامج لجعله يعمل على الأنظمة الجديدة.
المراجع: البرنامج الأصلي يوجد في مجلد yoda/NoImports.exe بعد التحميل من هنا.
البرنامج للأسف لا يعمل عندي على windows 8. من كان عنده windows xp إلى 7 يمكنه التحقق لنا .
فقمت بتنقيح البرنامج ومحاولة فهم الأوامر، في ما يلي سأوضح نظريا كيف يعمل البرنامج على الرغم من عدم امتلاكي لنسخة تعمل منه حاليا، كما سأوضح سبب فشل البرنامج (البرنامج في المرفقات).
تنبيه: هذا التحليل لا يعني أن البرنامج لن يعمل على الأنظمة السابقة ل windows 8! بل على العكس، فكرة البرنامج تبدو صالحة لو لا بعض الافتراضات الضمنية التي لا تتحقق في windows 8. وغالب ظني أنه فعلا يعمل على الأنظمة السابقة.
خطة البرنامج بشكل مختصر:
1- يفترض البرنامج أن kernel32.dll قد تم تحمليها مسبقا بشكل تلقائي حتى ولو لم يطلب البرنامج ذلك (وهو الحال فعلا).
2- انطلاقا من هذا الفرض 1 يحاول البرنامج البحث عن ال base address ل kernel32.dll.
3- يبحث البرنامج في ال export directory في ال PE Header الخاص ب kernel32.dll عن الدالة LoadLibrary.
4- باستعمال الدالة LoadLibrary يستطيع البرنامج تحميل المكتية USER32.dll ومعرفة عنوانها.
5- البحث في ال export directory في ال PE Header الخاص ب USER32.dll عن الدالة MessageBox
6- استدعاء الدالة MessageBox.
التفاصيل:
سنستعمل ال structure التالي لتخزين معلومات الدوال التي نود البحث عنها.
struct function_info{
BYTE function_name_length; // including the zero byte at the end.
char* function_name; // zero terminated string.
DWORD function_address; // absolute address.
};
function_name_length و function_name ثابتان في الحقيقة بينما function_address هو المتغير الذي سوف نقوم بتعبئته عند الخطوتين 3 و 5.البرنامج يعرف المتغيرات العمومية (Global أهذه الترجمة جيدة؟) التالية:
Global Variables:
DLL_base_address : 0x40205D
kernel32_base_address : 0x402052
kernel32_functions_info_array : 0x402000
user32_functions_info_array : 00402033
user32_string = "USER32" : 0x402056
message = "without imports, funny eh?" : 0x40106F
"little test" : 0x401053
علي اليمين العناوين المطلقة التي ظهرت عندي وعنوان الأساس كان 0x400000، وأود أن أنوه أن جميع العناويين في هذه المشاركة مطلقة كما أن أسماء المتغيرات والدوال من تسميتي.لنلق نظرة على kernel32_functions_info_array
0E 47 65 74 50 72 6F 63 41 64 64 72 65 73 73 00 .GetProcAddress.
00 00 00 0B 4C 6F 61 64 4C 69 62 72 61 72 79 00 ....LoadLibrary.
00 00 00 0B 45 78 69 74 50 72 6F 63 65 73 73 00 ....ExitProcess.
00 00 00 ...
نرى أن kernel32_functions_info_array عبارة عن ثلاث function_info متعاقبة، وبالمثل user32_functions_info_array0B 4D 65 73 73 61 67 65 42 65 65 70 00 00 00 00 .MessageBeep....
0A 4D 65 73 73 61 67 65 42 6F 78 00 00 00 00 .MessageBox....
أسماء الدوال التي تظهر في kernel32_functions_info_array و user32_functions_info_array هي أسماء الدوال التي سيبحث البرنامج عن عناوينها بدون استيراد مكتباتها بشكل صريح.الأوامر:
سنفصل الخطوتين 2 و 3، لأن البقية واضحة و الخطوة 5 مطابقة ل 3 (فقط باختلاف قيمة DLL_base_address).
الخطوة 2: عمليا يبدأ البرنامج باستدعاء الدالة Fill_kernel32_address_directly_after_running المعرفة كالتالي
// This function must be called right at the beginning of the program excution.
// This function assigns the value of base address to kernel32.dll to the global variable DLL_base_address (0x40205D).
// Function address: 0x0040109A
Fill_kernel32_address_directly_after_running:
ecx = [esp+4] // some address in kernel32
edx = 0
while(!(dx & F800 == 0 && ecx == [edx+ecx+0x34])){ // access violation exception. Fix this.
ecx--
dx = word[ecx+0x3C]
}
// ecx = the base address of kernel32.
[0x402052] = ecx // kernel32_base_address = kernel32.dll base address
[0x40205D] = ecx // DLL_base_address = kernel32.dll base address
ret
تبدأ الدالة ب ecx = [esp+4] وهذا لأن أول ما يوجد في المكدس هو عنوان داخل نطاق kernel32.dll. لكننا نريد عنوان الأساس وليس أي عنوان في kernel32.dll. نعلم أن عنوان الأساس يحقق الخصائص التالية1- عنوان الأساس يساوي قيمة PE_Header.Optional_Header.ImageBase
والذي يمكننا الوصول إليه كالتالي
[base_address + [base_address + 03C] + 0x34]
أي أن الشرط الأول على ecx هوecx == [ecx + [ecx + 03C] + 0x34]
2- أصغر من قيمة ecx أعلاه (هل هذا صحيح دائما؟).
كما أن البرنامج يضيف الشرط التالي
3- أن يكون dx & F800 == 0 ولعل المقصود أن تكون قيمة edx صغيرة فلا يحصل memory access violation exception.
فما تقوم به الدالة Fill_kernel32_address_directly_after_running هو انقاص قيمة ecx باستمرار والتحقق من الشرطين 1 و 3. إذا تحققا فهذا هو عنوان الأساس ل kernel32.dll !!
لكن لا بد أنكم رأيتم التعليق access violation exception. Fix this أعلاه، وهذا عندي لوجود مناطق ذاكرة محجوزة تتخلل منطقة ذاكرة kernel32.dll. يبدو أن هذا لم يكن الحال في الأنظمة السابقة.
الخطوة 3:
بما أننا حصلنا على عنوان kernel32.dll وخزناه في DLL_base_address فإبمكاننا الوصول إلى export directory ببساطة والبحث عن أسماء الدوال التي نريد. لنعرف الدالة التالية
// Find_Address_By_Name_From_DLL uses two pieces of information
// 1- [0x40205D]: DLL_base_address = The base address of the DLL.
// 2- esi : The name of the wanted function.
// Function address: 0x004010D9
Find_Address_By_Name_From_DLL:
edx = [0x40205D] // edx = base address of the dll
edx = edx + [edx+0x3C] // edx = NT Header address
edx = [edx + 0x78] // edx = Export Directory RVA
edx = edx + [0x40205D] // edx = export directory address.
edi = [edx+20] // edi = RVA of address of names.
edi = edi + [0x40205D] // edi = address of addresses of names
edi = [edi] // edi = RV address of first name
edi = edi + [0x40205D] // edi = address of first name.
eax = [edx + 18] // eax = number of functions names.
ebx = 1
while(esi equals edi as Strings){ // repe cmpsb. edi at this point is a null terminated string.
ebx++
while(byte[edi]!=0){
edi++
}
edi++
eax--
if(eax == 0){
push 0
call [0x40202F] // ExitProcess address. Address not correct! Crash if not filled!
}
}
ecx = [edx + 24] // RV address of name ordinals
ecx = ecx + [0x40205D] // address of name ordinals
ebx-- // ebx is the order of the name esi in the name array edi.
eax = word [ecx + ebx*2] // zero extended. eax = ordinal of the function. False ordinal!
ebx = [edx + 1C] // RV address of functions in kernel32.dll
ebx = ebx + [0x40205D] // address of functions in kernel32.dll
eax = [ebx + 4*eax] // RV address of the wanted function
eax = eax + [0x40205D] // address of the wanted function
ret
قد تبدو كبيرة لكن نظريا بسيطة وجل ما تقوم به هو التالي:1- استخراج بعض المعلومات من ال PE Header الخاص بال dll المراد البحث فيه وهو في حالتنا الآن kernel32.dll.
2- في ال while loop يقوم بمقارنة اسم الدالة التي نريد أن نبحث عنها (esi) مع قائمة الأسماء المسخرجة في 1 والتي خزنت في edi.
3- بعد إيجاد ترتيب اسم الدالة في قائمة الأسماء (ebx) نستعمله كأنه ال ordinal المحدد للدالة في ال PE Header الخاص ب kernerl32.dll وباستعماله نستطيع إيجاد عنوان الدالة.
هذه الخطوات تطبيق عملي لل PE Header Structure.
هل انتبهت للتعليق False ordinal؟ في الحقيقة حتى هنا توجد مشكلة وهي أن المبرمج افترض أن ترتيب اسم الدالة ebx في مصوفة الأسماء edi سيكون مطابقا ل ordinal الدالة! للأسف هذا غير صحيح عندي. كان من المفترض أن يستخدم address of addresses of names وليس address of first name (انظر التعليقات على الأوامر) لكنه افترض أن ترتيب الأسماء هنا وهناك سيكون متطابقا!
--------------------
في المرفقات: تجدون البرنامج وملف txt فيه أوامر البرنامج معكوسة كلها تقريبا لمن أراد التعقب أثناء التنقيح أو محاولة إعادة كتابة البرنامج لجعله يعمل على الأنظمة الجديدة.
المراجع: البرنامج الأصلي يوجد في مجلد yoda/NoImports.exe بعد التحميل من هنا.
I am homesick for a place I have not even visited
مَا ابْيَضَّ وجهٌ باكتساب كريمةٍ ... حتى يسوِّدهُ شُحوب المَطلبِ
مَا ابْيَضَّ وجهٌ باكتساب كريمةٍ ... حتى يسوِّدهُ شُحوب المَطلبِ