بسم الله الرحمن الرحيم
المعمل الأول: حل معمل apple.exe (Lab Solution - apple.exe)
معلومات العينة
- اسم الملف: apple.exe
- MD5:
30e92463ee572d8dbfa95ff3285bfa82
الإجابات المختصرة (Short Answers)
1. ما القيمة المدفوعة عند العنوان 0x401099 كوسيط للدالة sub_401000؟
الوسيط هو المحرف الأول من السلسلة النصية التي أدخلها المستخدم من سطر الأوامر.
2. ادخل إلى الدالة sub_401000. ما الذي تقابله الثوابت 0x61 و 0x62 و 0x63؟
تقابل الثوابت قيم ASCII: 0x61 = a و 0x62 = b و 0x63 = c.
3. أي بنية برمجية تُعرِّف الدالة sub_401000؟
سلسلة من عبارات if/else أو عبارة switch.
4. لخّص كيف يعمل البرنامج.
يطالب البرنامج المستخدم بإدخال سلسلة نصية، ثم يطبع apple إذا كان المحرف الأول من الإدخال a، أو banana إذا كان b، أو cantalope إذا كان c، أو no fruit في أي حالة أخرى.
التحليل التفصيلي (Detailed Analysis)
السؤال 1: ما القيمة المدفوعة عند العنوان 0x401099 كوسيط للدالة sub_401000؟
كما هو موضح في القائمة 1، يوجد عند العنوان 0x401099 استدعاء للدالة sub_401000. لتحديد عدد الوسائط التي تتطلبها هذه الدالة وقيمها، يمكننا البحث عن تعليمات PUSH السابقة لـ CALL التي تدفع الوسائط إلى المكدس. في هذا الاستدعاء لـ sub_401000 يوجد PUSH واحد يدفع قيمة EDX إلى المكدس.
40108C mov cl, [ebp+var_84]
401092 mov [ebp+var_4], cl
401095 mov dl, [ebp+var_4]
401098 push edx
401099 call sub_401000
القائمة 1: دفع القيمة في EDX كوسيط للدالة sub_401000
لفهم ما تحتويه EDX، يمكننا العمل عكسيًا من تعليمة PUSH بتتبع مصدر كل قيمة تُعدَّل:
- عند العنوان
0x401095، نلاحظ أن التعليمة السابقة لـPUSHهيmov dl, [ebp+var_4]. تقرأ هذه التعليمة بايتًا واحدًا من المتغير المحليvar_4وتكتبه في سجلDL، وهو البايت الأقل أهمية فيEDX. - المهمة التالية هي معرفة القيمة المخزنة في
var_4قبل كتابتها في سجلDL. التعليمة السابقة التي تعدّلvar_4هيmov [ebp+var_4], clعند العنوان0x401092. - سجل
CLبدوره تعدّله التعليمة السابقةmov cl, [ebp+var_84]عند العنوان0x40108C.
40106B push offset aEnterAString ; "Enter a string:"
401070 call printf
401075 add esp, 4
401078 lea eax, [ebp+var_84]
40107E push eax
40107F push offset a15s ; "%15s"
401084 call scanf
401089 add esp, 8
القائمة 2: قراءة سلسلة نصية من المستخدم باستخدام scanf
كما هو موضح في القائمة 2، المتغير المحلي [ebp+var_84] لا يظهر كمعامل الوجهة (الأيسر) لأي تعليمات سابقة في الدالة، لكنه مُشار إليه في التعليمة lea eax, [ebp+var_84] عند العنوان 0x401078. تُحمّل تعليمة LEA عنوان المتغير المحلي var_84 في سجل EAX. وتدفع تعليمة PUSH التالية العنوان في EAX إلى المكدس كوسيط ثانٍ لاستدعاء scanf عند العنوان 0x401084.
تقرأ الدالة scanf بيانات منسقة من مجرى الإدخال القياسي وفقًا لسلسلة التنسيق في الوسيط الأول، وتكتب تلك البيانات في المواقع التي توفرها الوسائط اللاحقة. ووفقًا لمواصفات سلسلة التنسيق، وسيط سلسلة التنسيق (%15s) المدفوع عند العنوان 0x40107F يوجّه scanf لقراءة سلسلة نصية واحدة من 15 محرفًا كحد أقصى من سطر الأوامر. والوسيط الثاني يوجّه scanf لتخزين تلك السلسلة في موقع المكدس الخاص بالمتغير المحلي var_84.
يوفر استدعاء printf السابق عند العنوان 0x401070 ووسيط سلسلته النصية المدفوع عند العنوان 0x40106B السياق الذي يوضح أن الإدخال الذي تقرؤه scanf هو استجابة المستخدم للمطالبة (“Enter a string:”) التي يعرضها apple.exe عند تنفيذه. وبناءً عليه، يمكننا إعادة تسمية المتغير/السلسلة المحلية var_84 إلى user_input.
40108C mov cl, [ebp+user_input]
401092 mov [ebp+first_byte_of_user_input], cl
401095 mov dl, [ebp+first_byte_of_user_input]
401098 push edx
401099 call sub_401000
القائمة 3: وسيط الدالة sub_401000 هو البايت الأول من إدخال المستخدم
بهذا السياق، يمكننا الآن تفسير تعليمة MOV عند العنوان 0x40108C. سجل CL هو البايت الأقل أهمية في ECX، لذلك تقرأ تعليمة MOV بايتًا واحدًا من user_input، وهو البايت الأول من سلسلة إدخال المستخدم. ويمكننا كذلك إعادة تسمية المتغير الوسيط var_4 إلى first_byte_of_user_input.
السؤال 2: ما الذي تقابله الثوابت 0x61 و 0x62 و 0x63؟
قبل تحليل الدالة sub_401000، ادخل إلى الدالة أولًا وأعد تسمية وسيطها من التسمية الافتراضية arg_0 إلى first_byte_of_user_input كما في القائمة 4، ليعكس السياق من الدالة المستدعية.
401000 sub_401000 proc near
401000
401000 var_4= byte ptr -4
401000 first_byte_of_user_input= byte ptr 8
القائمة 4: ترويسة الدالة sub_401000 تحدد وسيطاً واحداً اسمه arg_0
كما هو موضح في القائمة 5، بعد إنشاء إطار المكدس، تنسخ الدالة الوسيط إلى المتغير المحلي var_4، الذي يمكننا إعادة تسميته إلى first_byte_of_user_input_copy. ثم تقارن الدالة هذه القيمة مع الثابت 0x61 استعدادًا لتعليمة القفز المشروط JZ. وبما أن الوسيط يمثل محرفًا من سلسلة إدخال المستخدم، يمكننا تفسير الثابت 0x61 بدقة أكبر بوصفه المحرف a بترميز ASCII.
401000 push ebp
401001 mov ebp, esp
401003 push ecx
401004 mov al, [ebp+first_byte_of_user_input]
401007 mov [ebp+var_4], al
40100A cmp [ebp+var_4], 61h ; 'a'
40100E jz short loc_40101E
القائمة 5: مقارنة الوسيط مع الثابت 0x61 الممثل للمحرف a
تعليمة JZ تعادل تعليمة JE (“اقفز إذا كان مساويًا”). ضع معامل = بين معاملي تعليمة CMP: تقفز تعليمة JZ إلى loc_40101E إذا كان المحرف (البايت) الأول من إدخال المستخدم مساويًا للمحرف a. كما هو موضح في القائمة 6، إذا أُخذت القفزة، تطبع الدالة السلسلة apple باستخدام printf.
40101E loc_40101E:
40101E push offset Format ; "apple\n"
401023 call printf
401028 add esp, 4
40102B jmp short loc_401058
القائمة 6: طباعة السلسلة apple إذا كان المحرف الأول من إدخال المستخدم هو a
إذا لم تُؤخذ القفزة، كما هو موضح في القائمة 7، تُجري الدالة مقارنة مماثلة مع الثابت 0x62، وهو المحرف b بترميز ASCII.
401010 cmp [ebp+first_byte_of_user_input_copy], 62h ; 'b'
401014 jz short loc_40102D
القائمة 7: مقارنة الوسيط مع الثابت 0x62 الممثل للمحرف b
على غرار القفزة السابقة، إذا أُخذت تعليمة القفز المشروط JZ عند 0x401014، تطبع الدالة السلسلة banana، وإذا لم تُؤخذ القفزة، كما هو موضح في الشكل 1، تُجري الدالة مقارنة ثالثة وأخيرة مع الثابت 0x63 الممثل للمحرف c، مما يقود الدالة إلى طباعة السلسلة cantalope إذا تطابق محرف الإدخال.

الشكل 1: المقارنة النهائية مع المحرف c والحالة الافتراضية
إذا لم تأخذ الدالة القفزة المشروطة الأخيرة عند 0x40101A، تقفز الدالة إلى العنوان 0x40104B لطباعة السلسلة no fruit.
السؤال 3: أي بنية برمجية تُعرِّف الدالة sub_401000؟
مخطط الدالة sub_401000 كما يُرى عبر التبويب الفرعي Graph Overview، الموضح في الشكل 2، يعرض سلسلة متتالية من التفريعات المشروطة تشكل نمطًا يشبه السلّم.

الشكل 2: نظرة عامة على مخطط الدالة sub_401000
هذا النمط يشير عادةً إلى سلسلة من عبارات if-else أو عبارة switch في لغة C. في حالة سلسلة عبارات if-else، يمكن أن تشبه شيفرة C ما يلي:
if (first_byte_of_user_input == 'a') {
printf("apple\n");
} else if (first_byte_of_user_input == 'b') {
printf("banana\n");
} else if (first_byte_of_user_input == 'c') {
printf("cantalope\n");
} else {
printf("no fruit\n");
}
وفي حالة عبارة switch، يمكن أن تشبه شيفرة C ما يلي:
switch(first_byte_of_user_input) {
case 'a':
printf("apple\n"); break;
case 'b':
printf("banana\n"); break;
case 'c':
printf("cantalope\n"); break;
default:
printf("no fruit\n"); break;
}
المعمل الثاني: حل معمل bomb.exe (Lab Solution - bomb.exe)
معلومات العينة
- اسم الملف: bomb.exe
- MD5:
ec2e81d6623299d881a2910c2827a14d
الإجابات المختصرة (Short Answers)
1. ما الوسيطان لاستدعاء الدالة عند 0x4010A8؟
الوسيطان الأول والثاني هما العددان الأول والثاني، على التوالي، اللذان أدخلهما المستخدم في سطر الأوامر.
2. ما القيمة التي يجب أن ترجعها الدالة sub_401000 حتى يطبع البرنامج “You are safe!"؟
يجب أن ترجع الدالة sub_401000 قيمة غير صفرية حتى يطبع البرنامج You are safe!.
3. حلّل الدالة sub_401000. في أي الظروف ترجع الدالة قيمة غير صفرية؟
ترجع الدالة sub_401000 قيمة غير صفرية إذا تحقق أحد الشرطين التاليين:
- الوسيط الأول يساوي
12والوسيط الثاني يساوي1. - الوسيط الأول أكبر من
2والوسيط الثاني أقل من5.
4. اكتب الشيفرة الوهمية (Pseudocode) للدالة sub_401000.
لا توجد إجابة واحدة صحيحة. يمكن أن تكون الإجابات المختلفة صحيحة ما دامت متكافئة منطقيًا. إليك إجابة ممكنة:
int sub_401000 (int first_input_number, int second_input_number) {
if (first_input_number == 12 && second_input_number == 1) {
return 1;
} else if (first_input_number > 2 && second_input_number < 5) {
return 1;
}
return 0;
}
التحليل التفصيلي (Detailed Analysis)
السؤال 1: ما الوسيطان لاستدعاء الدالة عند 0x4010A8؟
انتقل إلى العنوان 0x401048 ولاحظ وجود تعليمتي PUSH سابقتين. كما هو موضح في القائمة 1، تدفع تعليمة PUSH عند العنوان 0x4010A7 قيمة المتغير المحلي var_8 كوسيط أول، وتدفع تعليمة PUSH الأخرى عند 0x4010A3 قيمة المتغير المحلي var_4 كوسيط ثانٍ.
4010A0 mov edx, [ebp+var_4]
4010A3 push edx
4010A4 mov eax, [ebp+var_8]
4010A7 push eax
4010A8 call sub_401000
القائمة 1: الدالة sub_401000 تأخذ قيمة متغيرين محليين، var_8 وvar_4، كوسيطين
المهمة التالية هي إيجاد قيم المتغيرين المحليين var_4 و var_8. بالبحث عكسيًا في الدالة، نجد أن var_4 مُشار إليه بواسطة تعليمة LEA عند العنوان 0x40108F، الموضحة في القائمة 2. تُحمّل تعليمة LEA عنوان المتغير المحلي var_4 في سجل ECX لتدفع تعليمة PUSH التالية ذلك العنوان إلى المكدس كوسيط ثانٍ لدالة scanf.
401082 push offset aEnter ; "Enter the second Number:"
401087 call printf
40108C add esp, 4
40108F lea ecx, [ebp+var_4]
401092 push ecx
401093 push offset aD_0 ; "%d"
401098 call scanf
40109D add esp, 8
القائمة 2: قراءة عدد صحيح عشري من المستخدم باستخدام scanf
تقرأ الدالة scanf بيانات منسقة من مجرى الإدخال القياسي وفقًا لسلسلة التنسيق في الوسيط الأول، وتكتب تلك البيانات في المواقع التي توفرها الوسائط اللاحقة. ووفقًا لمواصفات سلسلة التنسيق، وسيط سلسلة التنسيق (%d) المدفوع عند العنوان 0x401093 يوجّه scanf لقراءة عدد صحيح عشري واحد من سطر الأوامر. والوسيط الثاني يوجّه scanf لتخزين تلك القيمة في المتغير المحلي var_4.
يوفر استدعاء printf السابق عند 0x401087 ووسيط سلسلته النصية المدفوع عند العنوان 0x401082 السياق الذي يوضح أن الإدخال الذي تقرؤه scanf هو استجابة المستخدم لمطالبة bomb.exe بإدخال الرقم الثاني من التركيبة اللازمة لإبطال مفعول القنبلة. وبناءً عليه، يمكننا إعادة تسمية var_4 إلى second_input_number.
وبالمثل، يستدعي printf عند 0x401069 المستخدم لإدخال الرقم الأول، ويخزّن استدعاء scanf عند 0x40107A بسلسلة التنسيق %d العدد المُدخل في var_8. وبناءً عليه، يمكننا إعادة تسمية var_8 إلى first_input_number.
السؤال 2: ما القيمة التي يجب أن ترجعها الدالة sub_401000 حتى يطبع البرنامج “You are safe!"؟

الشكل 1: تفريع مشروط مبني على القيمة المرجعة من sub_401000
حتى يطبع البرنامج You are safe!، يجب ألا يأخذ البرنامج القفزة المشروطة JZ عند العنوان 0x4010AF. وهذا يعادل الشرط القائل بأن علم الصفر (ZF) يجب أن يكون مصفَّرًا. المقارنة السابقة test eax, eax تضبط علم الصفر إذا كان EAX صفرًا وتصفّره إذا كان غير صفري. وبما أن تعليمة TEST هذه تأتي مباشرةً بعد CALL إلى sub_401000، فإن سجل EAX يحمل القيمة المرجعة من sub_401000. هذا يعني أن sub_401000 يجب أن ترجع قيمة غير صفرية حتى يطبع البرنامج You are safe! ويبطل مفعول القنبلة.
السؤال 3: في أي الظروف ترجع الدالة sub_401000 قيمة غير صفرية؟
لتحليل sub_401000، أعد تسمية وسائط الدالة أولًا ليعكس ما نعرفه بالفعل: الوسيط الأول arg_0 هو first_input_number من الدالة المستدعية، والوسيط الثاني arg_4 هو second_input_number.
401000 sub_401000 proc near
401000
401000 var_8= dword ptr -8
401000 var_4= dword ptr -4
401000 first_input_number= dword ptr 8
401000 second_input_number= dword ptr 0Ch
القائمة 3: إعادة تسمية وسائط sub_401000 بمعلومات من الدالة المستدعية
بما أن اهتمامنا الرئيسي هو فهم متى ترجع هذه الدالة قيمة غير صفرية، فبدلًا من النهج من الأعلى إلى الأسفل، يمكننا اعتماد نهج من الأسفل إلى الأعلى بالبدء من نهاية الدالة والتركيز على القيمة المرجعة.

الشكل 2: مصدر القيمة المرجعة هو المتغير المحلي var_4
في نهاية الدالة، الموضحة في الشكل 2، آخر تعليمة تعدّل EAX — الذي يخزن القيمة المرجعة — هي تعليمة MOV عند العنوان 0x401043. وبما أن هذه التعليمة تضبط القيمة المرجعة على المتغير المحلي var_4، يمكننا إعادة تسميته إلى return_value. وبالبحث عكسيًا، نحدد وجود تعليمتَي MOV، إحداهما عند 0x401025 والأخرى عند 0x40103C، تضبطان var_4 على قيمة غير صفرية. لذلك، لترجع الدالة قيمة غير صفرية، يجب أن تنفّذ إحدى هاتين التعليمتين.
الآن هدفنا هو معرفة تحت أي ظروف تنفّذ الدالة إحدى تعليمتي MOV هاتين، بالبدء من كل تعليمة والعمل عكسيًا نحو بداية الدالة.
401000 push ebp
401001 mov ebp, esp
401003 sub esp, 8
401006 mov [ebp+return_value], 0
40100D mov [ebp+var_8], 5
401014 / mov eax, [ebp+first_input_number]
401017 1b { cmp eax, dword_403000
40101D \ jnz short loc_40102E
40101F / cmp [ebp+second_input_number], 1
401023 1a { jnz short loc_40102E
401025 \ mov [ebp+return_value], 1
40102C jmp short loc_401043
القائمة 4: المجموعة الأولى من الشروط لإرجاع قيمة غير صفرية
1. الوصول إلى تعليمة MOV عند 0x401025:
- تُظهر القائمة 4 أن أول مجموعة تعليمات تسبق
MOVعند0x401025هي تفريع مشروط يقارنsecond_input_numberبالقيمة1. تعليمة القفز هيJNZ، المعادلة لـJNE(اقفز إذا لم يكن مساويًا). باستخدام المعامل المقابل!=، يكون الشرط “اقفز إذا كانsecond_input_number != 1”. وبما أن تعليمةMOVتلي هذا التفريع المشروط مباشرةً، فنحن مهتمون بالحالة التي لا تُؤخذ فيها هذه القفزة. وهذا يعادل نفي شرط القفزة منطقيًا، مما يترجم إلى الشرطsecond_input_number == 1. - التعليمات الثلاث السابقة للتفريع المشروط عند
0x40101Fتُقيّم تفريعًا مشروطًا يتضمنfirst_input_numberوالمتغير العامdword_403000. انقر نقرًا مزدوجًا على المتغير العام أو مرّر المؤشر فوقه لتجد أنه عدد صحيح رباعي البايتات (dword) قيمته0xC(12 بالعشري). تعليمة القفز هيJNZ، مما يعطينا شرط القفزة “اقفز إذا كانfirst_input_number != 12”. وكما في القفزة السابقة، نهتم بالحالة التي لا تُؤخذ فيها القفزة، فنفي شرط القفزة منطقيًا يترجم إلىfirst_input_number == 12.
يمكن تلخيص الشرط 1 كما يلي: first_input_number == 12 AND second_input_number == 1.
40102E / cmp [ebp+first_input_number], 2
401032 2b \ jle short loc_401043
401034 / mov ecx, [ebp+second_input_number]
401037 2a { cmp ecx, [ebp+var_8]
40103A \ jge short loc_401043
40103C mov [ebp+return_value], 1
القائمة 5: المجموعة الثانية من الشروط لإرجاع قيمة غير صفرية
2. الوصول إلى تعليمة MOV عند 0x40103C:
- تُظهر القائمة 5 أن التعليمات الثلاث البادئة عند
0x401034والسابقة لتعليمةMOVتشكل تفريعًا مشروطًا. معاملات الشرط هيsecond_input_numberوالمتغير المحليvar_8. البحث عن الإشارات إلىvar_8يقودنا إلى بداية الدالة، الموضحة في القائمة 4، التي تُهيئvar_8بالقيمة5. تعليمة القفز المشروط هيJGE(اقفز إذا كان أكبر من أو يساوي)، فشرط القفزة الناتج هو “اقفز إذا كانsecond_input_number >= 5”. وبما أن تعليمةMOVتليJGEمباشرةً، نهتم بالحالة التي لا تُؤخذ فيها القفزة. نفي شرط القفزة ينتج الشرطsecond_input_number < 5. - التعليمان السابقان مباشرةً للتفريع المشروط عند
0x401034يشكلان تفريعًا مشروطًا آخر. المعاملان هماfirst_input_numberوالثابت2، وتعليمة القفز المشروط هيJLE(اقفز إذا كان أقل من أو يساوي). شرط القفزة الناتج هو “اقفز إذا كانfirst_input_number <= 2”. إذن لا تُؤخذ القفزة عندما يكونfirst_input_number > 2. - كما هو موضح في الشكل 3، هناك مساران للوصول إلى التفريع المشروط
2bعند0x40102Eمن بداية الدالة. أحدهما هو أخذ القفزة عند الفرع1bكما نوقش أعلاه، والآخر هو أخذ القفزة عند الفرع1a. يمكن تفسير أخذ القفزة عند أيٍّ من هذين الفرعين بوصفه فشلًا في تحقيق الشرط 1.
يمكن تلخيص الشرط 2 كما يلي: عدم تحقق الشرط 1 AND first_input_number > 2 AND second_input_number < 5.

الشكل 3: تُقيَّم المجموعة الثانية من الشروط إذا لم تتحقق المجموعة الأولى من الشروط
خلاصة الحلول
| المعمل | الدالة | المنطق المستخرج |
|---|---|---|
| apple.exe | sub_401000 | مقارنة المحرف الأول من الإدخال: a ← apple، b ← banana، c ← cantalope، غير ذلك ← no fruit |
| bomb.exe | sub_401000 | ترجع 1 إذا: (الرقم1 == 12 AND الرقم2 == 1) أو (عدم تحقق الشرط الأول AND الرقم1 > 2 AND الرقم2 < 5)؛ وإلا ترجع 0 |
الدروس المستفادة من المعملين:
- تتبع وسائط الدالة عكسيًا من تعليمات
PUSHالسابقة لـCALL. - استخدم سلاسل التنسيق (
%15sو%d) ورسائلprintfلاستنتاج دلالة المتغيرات وإعادة تسميتها. - طبّق “الطريقة السهلة”: وسّع اختصار القفزة إلى معامل، وانفي الشرط عندما تكون مهتمًا بعدم أخذ القفزة.
- المتغير العام مثل
dword_403000قد يخفي ثابتًا مهمًا (0xC= 12) — افحصه دائمًا بالنقر المزدوج. - نمط “السلّم” في مخطط الدالة يشير إلى if-else أو switch.
المصدر الأصلي: Malware Analysis Crash Course — FLARE Learning Hub