P01NT
0x8 08/10

حلول معملَي apple.exe و bomb.exe (الفصل الخامس)

حل تفصيلي لمعملي apple.exe وbomb.exe: تتبع الوسائط عكسيًا واستخراج المنطق المشروط بالطريقة السهلة.

· 12 دقائق قراءة · 2472 كلمة
المحتويات

بسم الله الرحمن الرحيم


المعمل الأول: حل معمل 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 بتتبع مصدر كل قيمة تُعدَّل:

  1. عند العنوان 0x401095، نلاحظ أن التعليمة السابقة لـ PUSH هي mov dl, [ebp+var_4]. تقرأ هذه التعليمة بايتًا واحدًا من المتغير المحلي var_4 وتكتبه في سجل DL، وهو البايت الأقل أهمية في EDX.
  2. المهمة التالية هي معرفة القيمة المخزنة في var_4 قبل كتابتها في سجل DL. التعليمة السابقة التي تعدّل var_4 هي mov [ebp+var_4], cl عند العنوان 0x401092.
  3. سجل 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 والحالة الافتراضية

الشكل 1: المقارنة النهائية مع المحرف c والحالة الافتراضية

إذا لم تأخذ الدالة القفزة المشروطة الأخيرة عند 0x40101A، تقفز الدالة إلى العنوان 0x40104B لطباعة السلسلة no fruit.

السؤال 3: أي بنية برمجية تُعرِّف الدالة sub_401000؟

مخطط الدالة sub_401000 كما يُرى عبر التبويب الفرعي Graph Overview، الموضح في الشكل 2، يعرض سلسلة متتالية من التفريعات المشروطة تشكل نمطًا يشبه السلّم.

الشكل 2: نظرة عامة على مخطط الدالة sub_401000

الشكل 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

الشكل 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: مصدر القيمة المرجعة هو المتغير المحلي 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:

  1. تُظهر القائمة 4 أن أول مجموعة تعليمات تسبق MOV عند 0x401025 هي تفريع مشروط يقارن second_input_number بالقيمة 1. تعليمة القفز هي JNZ، المعادلة لـ JNE (اقفز إذا لم يكن مساويًا). باستخدام المعامل المقابل !=، يكون الشرط “اقفز إذا كان second_input_number != 1”. وبما أن تعليمة MOV تلي هذا التفريع المشروط مباشرةً، فنحن مهتمون بالحالة التي لا تُؤخذ فيها هذه القفزة. وهذا يعادل نفي شرط القفزة منطقيًا، مما يترجم إلى الشرط second_input_number == 1.
  2. التعليمات الثلاث السابقة للتفريع المشروط عند 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:

  1. تُظهر القائمة 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.
  2. التعليمان السابقان مباشرةً للتفريع المشروط عند 0x401034 يشكلان تفريعًا مشروطًا آخر. المعاملان هما first_input_number والثابت 2، وتعليمة القفز المشروط هي JLE (اقفز إذا كان أقل من أو يساوي). شرط القفزة الناتج هو “اقفز إذا كان first_input_number <= 2”. إذن لا تُؤخذ القفزة عندما يكون first_input_number > 2.
  3. كما هو موضح في الشكل 3، هناك مساران للوصول إلى التفريع المشروط 2b عند 0x40102E من بداية الدالة. أحدهما هو أخذ القفزة عند الفرع 1b كما نوقش أعلاه، والآخر هو أخذ القفزة عند الفرع 1a. يمكن تفسير أخذ القفزة عند أيٍّ من هذين الفرعين بوصفه فشلًا في تحقيق الشرط 1.

يمكن تلخيص الشرط 2 كما يلي: عدم تحقق الشرط 1 AND first_input_number > 2 AND second_input_number < 5.

الشكل 3: تُقيَّم المجموعة الثانية من الشروط إذا لم تتحقق المجموعة الأولى

الشكل 3: تُقيَّم المجموعة الثانية من الشروط إذا لم تتحقق المجموعة الأولى من الشروط


خلاصة الحلول

المعملالدالةالمنطق المستخرج
apple.exesub_401000مقارنة المحرف الأول من الإدخال: a ← apple، b ← banana، c ← cantalope، غير ذلك ← no fruit
bomb.exesub_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