تقييم الموضوع :
  • 0 أصوات - بمعدل 0
  • 1
  • 2
  • 3
  • 4
  • 5
برنامج بدون استيرادات
#1
أثناء بحثي عن موضوع ما وجدت برنامجا يُدّعى أنه يعمل بدون استيرات أي مكتبة ولا حتى kernel32.dll، ولكنه يستدعي الدالة MessageBox والتي توجد في المكتبة USER32.dll. وفعلا في cff explorer لا وجود لجدول imports.
البرنامج للأسف لا يعمل عندي على windows 8. من كان عنده windows xp إلى 7 يمكنه التحقق لنا Smile .

فقمت بتنقيح البرنامج ومحاولة فهم الأوامر، في ما يلي سأوضح نظريا كيف يعمل البرنامج على الرغم من عدم امتلاكي لنسخة تعمل منه حاليا، كما سأوضح سبب فشل البرنامج (البرنامج في المرفقات).
تنبيه: هذا التحليل لا يعني أن البرنامج لن يعمل على الأنظمة السابقة ل 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_array
0B 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 بعد التحميل من هنا.


الملفات المرفقة
.zip   NoImports.zip (الحجم : 2.32 KB / التحميلات : 16)
I am homesick for a place I have not even visited
مَا ابْيَضَّ وجهٌ باكتساب كريمةٍ ... حتى يسوِّدهُ شُحوب المَطلبِ
أعضاء أعجبوا بهذه المشاركة : rce3033 , the9am3 , M!X0R , 0b3l1sk , samoray , [email protected] , MountLegacy , Black Beard , Mahmoud Gz , xdvb_dz , Newhak , EarthMan123 , DarkDeath
#2
ملحق: تذكير سريع بال export directory في ال PE Header.

[صورة مرفقة: vuqk6uG.png]


الملفات المرفقة
.zip   Export Directory.zip (الحجم : 502.72 KB / التحميلات : 22)
I am homesick for a place I have not even visited
مَا ابْيَضَّ وجهٌ باكتساب كريمةٍ ... حتى يسوِّدهُ شُحوب المَطلبِ
أعضاء أعجبوا بهذه المشاركة : the9am3 , samoray , M!X0R , xdvb_dz , Newhak
#3
مجهود ممتاز بارك الله فيك جاري النشر علي الفيس بوك
وَقُل رَّبِّ زِدْنِي عِلْمًا (114) -طه
أعضاء أعجبوا بهذه المشاركة : siddigss , rce3033 , samoray , vosiyons
#4
تحديث: كتبت نسخة من البرنامج تعمل على windows 8، وأتخيل أنها تعمل على جميع الأنظمة من xp إلى 10. أرجو من أصحاب النظم المختلفة تجربة البرنامج.

كما ذكرنا آنفا، البرنامج كان به مشكلتان، الأولى أن طريقته في الوصول إلى عنوان الأساس ل kernel32.dll لم تعد تنفع، والثانية استعماله لقائمة الأسماء بدل قائمة عناوين الأسماء.
المشكلة الثانية حلها بسيط. لحل الأولى علينا استعمال أسلوب مختلف. يمكننا الحصول على معلومات المكتبات المحملة من خانة InMemoryOrderModuleList في جدول LDR الذي بدوره يوجد في جدول PEB وهذا الأخير في TIB. يمكننا الوصول لجدول TIB باستعمال المسجل fs، فمثلا العنوان fs:[0x30] يحوي مؤشرا إلى جدول PEB. بهذه الطريقة يمكننا الوصول إلى عناوين وأسماء المكتبات المحملة، ويجدر بالذكر أن الأسماء في InMemoryOrderModuleList  هي من النوع wchar_t* وليس char*.

في المرفات: تجدون البرنامج مع المصدر بلغة C موضحا بتعليقات وملف نصي فيه أمر التجميع (compilation).

ملاحظات: افترضت أن جميع أسماء المكتبات في InMemoryOrderModuleList  تنتهي بصفر على الرغم من أن صفحة مايكروسوف لا تؤكد ذلك (انظر UNICODE_STRING في المراجع).

المراجع:
https://docs.microsoft.com/en-us/windows...b_ldr_data
https://docs.microsoft.com/en-us/windows...nternl-peb
https://en.wikipedia.org/wiki/Win32_Thre...tion_Block
https://docs.microsoft.com/en-us/windows...ode_string


الملفات المرفقة
.zip   NoImports.zip (الحجم : 3.77 KB / التحميلات : 18)
I am homesick for a place I have not even visited
مَا ابْيَضَّ وجهٌ باكتساب كريمةٍ ... حتى يسوِّدهُ شُحوب المَطلبِ
أعضاء أعجبوا بهذه المشاركة : samoray , the9am3 , rce3033 , M!X0R , xdvb_dz , mire100 , Newhak , EarthMan123
#5
موضوع جميل جدا بارك الله فيك اخي الحبيب

مشكلة استيراد واخفاء الدوال تواجه الكثير منا بالذات في البرامج المضغوط حيث يقوم الضاغط باخفاء بعض الدوال المهمة لمنع العبث بالبرنامج ونحتاج في كثير من الاحيان لاضافة الدوال يدويا

اتمنى ان يكون هناك شرح مصور لمثال عملي

على العموم الله يجزيك كل خير على طرح الموضوع وهذه المعلومات القيمة
بعد سنوات من ممارسة هذا الفن قررت الاعتزال نهائيا وبدون رجعة
ارجو من الله ان يغفر لي اذا اخطاءت في حق اي شخص او اصحاب البرامج التى قدمت عليها دروسي وان لا اكتب من المفسدين في الارض فكان كل هدفي التعليم
لاتنسو اخيكم من دعوة صالحة فقد ظلم نفسه اسال الله ان يغفر لي ولكم ماتقدم وماتاخر
أعضاء أعجبوا بهذه المشاركة : rce3033 , DarkDeath


التنقل السريع :


يقوم بقرائة الموضوع: بالاضافة الى ( 1 ) ضيف كريم