بسم الله الرحمن الرحيم
في هذا المقال سنتحدث عن فك التجميع المتوسط.
1. المؤشرات والذاكرة (Pointers and Memory)
في اللغات عالية المستوى مثل C، المؤشر (Pointer) هو متغير يخزّن عنوان ذاكرة. في الشيفرة على الجانب الأيسر من الشكل 7.1، يحصل البرنامج على عنوان المتغيرين الصحيحين num1 و num2 عبر معامل “عنوان” (&) ويسندهما تباعًا إلى متغير المؤشر ptr.

الشكل 7.1: يسار: شيفرة C تستخدم متغير مؤشر. يمين: تجميع البرنامج. أسفل: حالة الذاكرة أثناء التنفيذ
على مستوى التجميع، يمكن أن يشير مصطلح “مؤشر” إلى المتغير نفسه، لكنه يُستخدم غالبًا للإشارة إلى عنوان ذاكرة كقطعة من البيانات:
- المؤشر كمتغير: تستخدم التجميع على يمين الشكل 7.1 التعليمة
LEAلحساب عنوان المتغير المحليnum1وتخزنه في المتغير المحليptr. - المؤشر كقيمة: يعرض عرض الذاكرة في أسفل الشكل 7.1 الحالة مباشرةً بعد تنفيذ أول تسلسل
LEA-MOV. المتغير المحليptrالموجود عند0x12FF48يحمل الآن القيمة0x12FF40.- يمكنك الإشارة إلى المتغير عند
0x12FF48بوصفه مؤشرًا. ويمكنك أيضًا القول إن القيمة0x12FF40هي مؤشر، لأنها عنوان ذاكرة يُعامل كبيانات.
- يمكنك الإشارة إلى المتغير عند
2. تعليمتا MUL و IMUL
تُجري تعليمتا MUL و IMUL عملية الضرب الحسابي. وتُستخدمان كثيرًا لحساب إزاحات الذاكرة للوصول إلى المصفوفات والبنيات.
| الحجم | المصدر 1 | المصدر 2 | الوجهة |
|---|---|---|---|
| بايت | AL | r/m8 | AX |
| كلمة | AX | r/m16 | DX:AX |
| كلمة مزدوجة | EAX | r/m32 | EDX:EAX |
الجدول 7.1: ملخص معاملي المصدر والوجهة لتعليمة MUL
mov eax, 10
mov ecx, 7
mul ecx ; تُضبط edx إلى 0، وتُضبط eax إلى 70
القائمة 7.1: ضرب عددين صحيحين غير موقّعين باستخدام MUL
تُجري تعليمة MUL ضربًا غير موقّع. تأخذ معاملًا صريحًا واحدًا (سجل أو ذاكرة) وتضربه في معامل ضمني. المعامل الضمني هو AL أو AX أو EAX حسب حجم المعامل الصريح.
ولأن ناتج ضرب عددين صحيحين قد يتطلب حتى ضعف عرض المعاملين، تُخزَّن النتيجة عبر سجلين: جزء عالٍ و جزء منخفض، كما يلخصه الجدول 7.1:
- 8-بت: اضرب
ALفي المعامل، وخزّن الناتج فيAX. - 16-بت: اضرب
AXفي المعامل، وخزّن الناتج فيDX:AX. - 32-بت: اضرب
EAXفي المعامل، وخزّن الناتج فيEDX:EAX.
تُجري القائمة 7.1 ضربًا بحجم 32-بت للعددين 7 و 10. الناتج 70 يُخزَّن في EDX:EAX، حيث تُضبط EAX إلى 70 وتُضبط EDX إلى 0.
mov eax, 10
imul edx, eax, 7 ; تُضبط edx إلى 70
القائمة 7.2: ضرب عددين صحيحين موقّعين باستخدام IMUL
تُجري تعليمة IMUL ضربًا موقّعًا. ولها ثلاث صيغ:
- معامل واحد (
IMUL <source>): على غرارMUL، تضرب المعامل فيEAX/AX/ALوتخزن الناتج عبر سجلين (مثلEDX:EAX). - معاملان (
IMUL <dest>, <source>): تضرب المعاملين وتخزن الناتج في<dest>. - ثلاثة معاملات (
IMUL <dest>, <source1>, <source2>): تضرب<source1>في<source2>وتخزن الناتج في<dest>.
لاحظ أنه على عكس صيغة المعامل الواحد، فإن صيغتي المعاملين وثلاثة معاملات تقتطعان النتيجة لتناسب معامل الوجهة. لكن هذا نادرًا ما يكون مصدر قلق عند حساب إزاحات المؤشرات.
3. المصفوفات (Arrays)
المصفوفة (Array) هي مجموعة مرتبة من العناصر تشترك في نوع بيانات واحد. وسواء كان نوع البيانات بدائيًا (مثل عدد صحيح أو مؤشر) أو مركبًا (مثل بنية)، فإن كل عنصر في المصفوفة له حجم موحّد. وهذا يتيح وصولًا سريعًا إلى أي عنصر بضرب الفهرس المطلوب في عرض نوع البيانات.
يمكن أن توجد المصفوفة في أي منطقة ذاكرة: محليًا على المكدس، أو ديناميكيًا في الكومة (Heap)، أو عامًا في مقطع بيانات البرنامج. لتحديد المصفوفات، ابحث عن:
- تعليمات تصل إلى الذاكرة عبر سجل قاعدة بالإضافة إلى إزاحة محسوبة باستخدام فهرس.
- حلقات تصل إلى الذاكرة بشكل تسلسلي.

الشكل 7.2: الوصول إلى عناصر في مصفوفة محلية
يعرض الشكل 7.2 مثالًا على الوصول إلى مصفوفة في التجميع:
- تبدأ الشيفرة على اليسار بتهيئة مصفوفة محلية من نوع
intاسمهاnumsبثلاثة عناصر. - تُهيّئ التجميع على اليمين المصفوفة
numsبالقيم 1 و 2 و 3. لاحظ أن تعليماتMOVتستخدم إزاحات ثابتة (مثل[ebp+nums+4]).
تصل الكتل الثلاث التالية من التعليمات (مميزة بالألوان) إلى المصفوفة لتمرير وسائط إلى printf:
- الكتلة الأولى (الفهرس 2): تحمّل
4(حجمint) فيEAXوتضربها في2(الفهرس) باستخدام إزاحة يسارية (SHL) بمقدار 1. النتيجة إزاحة مصفوفة مقدارها 8. - الكتلة الثانية (الفهرس 1): حساب مشابه يعطي إزاحة مقدارها 4.
- الكتلة الأخيرة (الفهرس 0): تستخدم مزيجًا من
MOVوIMULلتوليد إزاحة مقدارها 0 للوصول إلى العنصر الأول (الشيفرة غير المحسَّنة قد تُجري هذا النوع من الحسابات الزائدة).

الشكل 7.3: الوصول إلى عناصر في مصفوفة عامة
تُعلن الشيفرة في الشكل 7.3 عن nums كمصفوفة عامة بدلًا من مصفوفة محلية. في التجميع، تقع المصفوفة الآن في موقع ثابت في مقطع .data. ولا تزال التجميع تصل إلى المصفوفة بحساب إزاحة قائمة على الفهرس وإضافتها إلى عنوان القاعدة للمصفوفة.
4. الحلقات (Loops)
تُستخدم المصفوفات غالبًا بالاقتران مع الحلقات. تتكون حلقة for القياسية بنمط C، مثل الموجودة في الشكل 7.4، من المكونات التالية:
- التهيئة (
int i = 0): تُنفَّذ مرة واحدة قبل بدء الحلقة؛ وتُهيّئ عادةً عدّاد الحلقة إلى صفر. - الشرط (
i < len): تعبير منطقي يُقيَّم قبل كل دورة من الحلقة. إذا كان صحيحًا، يُنفَّذ جسم الحلقة؛ وإذا كان خاطئًا، تنتهي الحلقة. - الجسم (
arr[i]++): جسم الحلقة الذي يُنفَّذ في كل دورة. - التحديث (
i++): يُنفَّذ في نهاية كل دورة، عادةً بزيادة عدّاد الحلقة.

الشكل 7.4: حلقة for في لغة C ومكوناتها في التجميع
يُعرض تجميع الحلقة كمخطط من الكتل الأساسية:
- التهيئة: تُهيّئ عدّاد الحلقة
iإلى صفر في الأعلى. - الشرط: يُنفَّذ كتعليمة
CMPتقارنiمعlenيليهاJNB(اقفز إذا لم يكن أقل).- غالبًا ما تعكس المترجمات منطق المصدر: بدلًا من فحص ما إذا كان يجب أن تستمر الحلقة (
i < len)، تفحص ما إذا كان يجب أن تتوقف (i >= len). - تأخذ
JNBالقفزة إذا لم يكنiأقل منlen(أيi >= len). إذا أُخذت القفزة (السهم الأخضر)، يخرج التنفيذ من الحلقة. وإلا، إذا لم تُؤخذ القفزة (السهم الأحمر)، يدخل التنفيذ إلى جسم الحلقة.
- غالبًا ما تعكس المترجمات منطق المصدر: بدلًا من فحص ما إذا كان يجب أن تستمر الحلقة (
- الجسم: يفهرس في المصفوفة باستخدام
iويزيد ذلك العنصر. - التحديث: يزيد
iويقفز عائدًا لأعلى لإعادة تقييم الشرط. السهم المتجه لأعلى من كتلة التحديث إلى كتلة الشرط هو المؤشر البصري الرئيسي على وجود حلقة في مخطط التفكيك.
تمرين اختراق: poindexter
ابدأ بالملف
poindexter.i64. يحتوي البرنامج على شيفرة تقوم بما يلي:
- طباعة رقم وسلسلة نصية على الشاشة أربع مرات في حلقة.
أكمل البرنامج بترقيع الشيفرة عند العنوان
401020hمن أجل:
- تمرير سلسلة عنوان من
titles_arrayوسنة منyears_arrayكوسائط إلى استدعاءprintfعند العنوان401043h.- طباعة سنة وسلسلة عنوان مختلفتين في كل دورة من الحلقة.
- كل فيلم في
titles_arrayصدر في السنة المطابقة لفهرسه فيyears_array(أي الفيلم الأول صدر عام 1984، والرابع عام 1994). من الناحية المثالية، يجب أن يطبع البرنامج السنوات والعناوين بدقة.نصيحة: ابدأ كتابة شفرتك عند
401020h— لن تحتاج إلى كل المساحة التي تشغلها تعليماتNOP. لا تحاول ملء المساحة أو التخلص من جميع تعليماتNOP.
الحل (اضغط للعرض)
org 401020h
mov eax, [esp+0Ch] ; عدّاد الحلقة الحالي
mov edx, [eax*4+402xxxh] ; عنوان السنة من years_array
push edx
mov ecx, titles_array ; عنوان مصفوفة العناوين
mov edx, [ecx+eax*4] ; أو حساب المؤشر المناسب للسلسلة
push edx
call dword ptr [_printf]
add esp, 8
(الحل الدقيق يعتمد على عناوين المصفوفات في ملفك — راجع الحل الرسمي في ملحق المستند الأصلي.)
5. تعليمات السلاسل النصية: MOVS و STOS و SCAS
توفر مجموعة تعليمات x86 تعليمات سلاسل نصية تنفّذ بكفاءة عمليات شائعة مثل النسخ والتخزين والمسح الضوئي للسلاسل (تسلسلات من البايتات أو الكلمات أو الكلمات المزدوجة) في الذاكرة. تستخدم هذه التعليمات ضمنيًا السجلات التالية:
ESI(فهرس المصدر): يشير إلى عنوان المصدر (تستخدمهMOVS).EDI(فهرس الوجهة): يشير إلى عنوان الوجهة (تستخدمهMOVSوSTOSوSCAS).EAX: يحمل قيمة البيانات (تستخدمهSTOSوSCAS).

الشكل 7.5: تعليمات السلاسل النصية الشائعة في x86
يحدد لاحق تعليمة السلسلة حجم العملية (b للبايت، w للكلمة، d للكلمة المزدوجة). علاوة على ذلك، تزيد هذه التعليمات تلقائيًا (أو تنقص، إذا كان علم الاتجاه DF مضبوطًا) قيمةَ ESI و/أو EDI بمقدار حجم العملية.

الشكل 7.6: نسخ أربعة بايتات باستخدام MOVSD
تنسخ MOVS البيانات من العنوان في ESI إلى العنوان في EDI.
- يحمّل المثال في الشكل 7.6 عنوان المصدر
0x401020فيESIوعنوان الوجهة401030hفيEDI. - ثم ينفّذ
MOVSD. هذا يقرأ أربعة بايتات (ba 5e ba 11) من0x401020ويكتبها إلى0x401030. - تزيد تعليمة
MOVSDكلًّا منESIوEDIبمقدار 4، محدِّثةً إيهما إلى0x401024و0x401034على التوالي.

الشكل 7.7: تصفير بايتين باستخدام STOSW
تخزّن STOS القيمة في EAX/AX/AL في العنوان عند EDI.
- يحمّل المثال في الشكل 7.7 القيمة
0x401020فيEDIويصفّرEAXباستخدامXOR. - ثم ينفّذ
STOSW. هذا يكتب القيمة فيAXإلى العنوان0x401020، مما يصفّر بايتين فعليًا. - تزيد تعليمة
STOSWقيمةEDIبمقدار 2، محدِّثةً إياها إلى0x401022.
6. بادئات تكرار التعليمات: REP و REPE/REPZ و REPNE/REPNZ
تُعدّل بادئات التكرار تعليماتِ السلاسل لتُنفَّذ في حلقة. هناك ثلاثة أنواع من بادئات التكرار:
REP: يكرر التعليمة طالماECX > 0.REPE/REPZ(كرر طالما متساوٍ/صفر): يكرر طالماZFمضبوط وECX > 0.REPNE/REPNZ(كرر طالما غير متساوٍ/غير صفر): يكرر طالماZFمصفَّر وECX > 0.
تستخدم بادئات التكرار ECX ضمنيًا كعدّاد وتنقصه بعد كل دورة.
وبينما هناك تركيبات محتملة عديدة من بادئات التكرار وتعليمات السلاسل، فإن الأزواج الثلاثة التالية هي الأكثر شيوعًا:
rep movs: ينسخ البيانات منESIإلىEDI. يجب تهيئةECXبعدد العناصر المراد نسخها.rep stos: مع ضبطEAXعلى صفر، يُستخدم غالبًا لتهيئة مخزن مؤقت (Buffer).repne scasb: مع ضبطALعلى صفر، يُستخدم غالبًا لحساب طول سلسلة نصية بإيجاد إزاحة محرف الإنهاء NULL.

الشكل 7.8: نسخ ثمانية بايتات باستخدام REP MOVSD
يوضح المثال في الشكل 7.8 نسخ ثمانية بايتات باستخدام rep movs:
- يُضبط
ECXإلى 2 (كلمتان مزدوجتان = 8 بايتات). عنوان المصدر هو0x401020وعنوان الوجهة هو0x401030. - تنسخ التعليمة 4 بايتات من
0x401020إلى0x401030h، وتنقصECX، وتكرر. - بعد التعليمة، يشير
ESIوEDIإلى نهاية الكتل المنسوخة عند0x401028و0x401038على التوالي.

الشكل 7.9: تصفير ثمانية بايتات باستخدام REP STOSB
يوضح المثال في الشكل 7.9 تصفير مخزن مؤقت بحجم ثمانية بايتات باستخدام rep stosb:
- يشير
EDIإلى عنوان المخزن المؤقت0x401020، ويُصفَّرEAX، ويُضبطECXإلى 8 (حجم المخزن المؤقت). - تتكرر التعليمة ثماني مرات. في كل دورة، تكتب 0 في
EDI، وتزيدEDIبمقدار 1، وتنقصECX.
7. الدوال الجوهرية (Intrinsic Functions)
في Visual C/C++، الدوال الجوهرية هي دوال خاصة توفرها المترجم وتقابل مباشرةً تعليمات آلة محددة. عادةً، تُصرَّف وظائف المكتبة القياسية (مثل memcpy) كاستدعاء لمكتبة خارجية. ومع ذلك، عند تمكين التحسين، يدمج المترجم (Inline) الدوال التي لها مقابلات جوهرية، مستبدلًا استدعاءات الدوال بتسلسلات تعليمات جوهرية.
| دالة C | التجميع الجوهري | الملخص |
|---|---|---|
memcpy() | rep movs* | نسخ البيانات من موقع إلى آخر |
strcpy() | repne movsb | مثل memcpy لكن يتوقف عند بايت NULL |
memset() | rep* stos* | ضبط جميع البايتات في منطقة ذاكرة إلى قيمة ما (غالبًا 0) |
strlen() | repne scasb | تحديد طول سلسلة نصية بالمسح الضوئي بحثًا عن بايت NULL |
الجدول 7.2: وظائف C الشائعة وقت التشغيل ومقابلاتها الجوهرية
يسرد الجدول 7.2 وظائف المكتبة القياسية الشائعة ومقابلاتها الجوهرية المقابلة. ولأن هذه الوظائف تُستخدم بكثرة، من الضروري التعود على تسلسلات التعليمات التي تؤدي الوظيفة نفسها.
تمرين اختراق: stringcheese
ابدأ بالملف
stringcheese.i64. يحتوي البرنامج على شيفرة تقوم بما يلي:
- مطالبة المستخدم بإدخال سلسلة نصية وقراءتها إلى مخزن مؤقت على المكدس.
- تمرير مؤشر إلى سلسلة المستخدم إلى الدالة
sub_401039.- توفر الدالة
sub_401039مقدمة وخاتمة للدالة وتحمّل مؤشر سلسلة المستخدم في سجلESI.- تنتهي
sub_401039باستدعاءprintf()لعرض محتويات المتغير العامstring_length(العنوان402034h) ومحتويات المخزن المؤقت العامcopied_string_buffer(العنوان402038h).أكمل البرنامج بترقيع الشيفرة عند العنوان
401046hلضمان طباعة تعليمةprintf()عند40108Ahلسلسلة المستخدم وطولها:
- اكتب
strcpy()(عبرrepne movsb) لنقل بيانات السلسلة إلى المخزن المؤقت العامcopied_string_buffer(العنوان402038h).- اكتب
strlen()(عبرrepne scasb) لحساب طول سلسلة المستخدم وتخزينه في المتغير العامstring_length(العنوان402034h).
الحل (اضغط للعرض)
; ESI يحمل مؤشر سلسلة المستخدم
mov edi, 402038h ; copied_string_buffer
mov ecx, 0FFFFFFFFh ; عدّاد كبير
xor al, al
cld
repne movsb ; strcpy: انسخ حتى NULL
not ecx
dec ecx ; ecx = طول السلسلة
mov [402034h], ecx ; string_length = الطول
8. البنيات (Structs)
البنية (Struct) — اختصار لـ Structure — هي نوع بيانات معرَّف من المستخدم يجمع البيانات ذات الصلة تحت اسم واحد. وتُسمى العناصر الفردية داخل البنية أعضاءً (Members) أو حقولًا (Fields).
تشكل البنيات عقبة لمهندسي العكس لأن معلومات نوع البنية تُفقد أثناء التصريف. فعلى سبيل المثال، إذا خُصصت بنية على المكدس، فإن التجميع لا يشير صراحةً إلى أي المتغيرات مستقلات وأيها أعضاء في بنية.
يستكشف بقية هذا القسم البنيات وإعادة بنائها في IDA باستخدام العينة التالية:
- اسم الملف: StructExample.exe
- MD5:
7d27410b6f912652c1aa787e5b38ecdf
| C | Assembly |
|---|---|
| |
القائمة 7.3: برنامج بسيط في C يستخدم بنية اسمها msg
افتح StructExample.exe وانتقل إلى main (0x401080). يكون فقدان معلومات البنية واضحًا في القائمة 7.3:
- تُعرِّف الشيفرة على اليسار بنية
msgبثلاثة أعضاء:xor: مفتاح XOR أحادي البايت.len: طول الرسالة.buf: مخزن الرسالة المؤقت.
- تستخدم الدالة
mainالبنيةmsg:- تُعلن عن متغير محلي
mمن نوعstruct msgوتُهيئه بـmemset. - تقرأ سلسلة نصية بطول يصل إلى 128 في
m.bufباستخدامscanf_s. - تضبط
m.xorإلى0x10وm.lenإلى طول السلسلة المقروءة منscanf_s. - تمرر إلى
sendMsgمؤشرًا إلىm.
- تُعلن عن متغير محلي
- لا يحتوي التجميع على اليمين على أي إشارات صريحة إلى بنية. للوهلة الأولى، البنية المخصصة على المكدس لا يمكن تمييزها عن ثلاثة متغيرات محلية مستقلة.
نظرًا لغياب معلومات البنية الصريحة، يجب على مهندس العكس استنتاج وجود بنية وإعادة بنائها. بعض الأدلة تشير إلى بنية عند الفحص الدقيق:
- حجم التخصيص: تُهيئ
memsetالمتغيرvar_88بحجم0x88. وبينما قد يكون هذا مخزنًا مؤقتًا واحدًا كبيرًا، فإنه يؤكد أن هذا النطاق من الذاكرة ينتمي معًا. - أنماط الوصول: تصل
mainإلىvar_80وvar_84. هذه الإزاحات تقع ضمن نطاق الذاكرة بدءًا منvar_88. التعليمات التي تصل إلى “متغيرات” عند إزاحات ثابتة داخل مساحة متغير آخر هي مؤشرات قوية على استخدام بنية.
| C | Assembly |
|---|---|
| |
القائمة 7.4: الدالة sendMsg تتلقى مؤشراً إلى بنية msg وتصل إلى أعضائها
تمرر الدالة main إلى sendMsg (0x401120) مؤشرًا إلى var_88. تجمع sendMsg، كما هو موضح في القائمة 7.4، مؤشر الوسيط (arg_0) مع الإزاحات التالية للوصول إلى أعضاء msg:
- الإزاحة 4: تصل
mov ecx, [eax+4]إلى الحقلlen. - الإزاحة 8: تحسب
add edx, 8عنوان الحقلbuf. لاحظ أن هذا العنوان يقابل عنوانvar_80فيmain. ويُمرر هذا العنوان إلىscanfمع سلسلة التنسيق%128s، مما يتيح لنا استنتاج أن حجم المخزن المؤقت 128 بايت. - الإزاحة 0: تصل
movzx ecx, byte ptr [eax]إلى الحقلxor.
عمليات الوصول إلى الذاكرة التي تضيف إزاحات ثابتة إلى عنوان قاعدة مشترك هي المؤشرات الأكثر موثوقية على استخدام بنية. بناءً على أنماط الوصول، يمكننا استنتاج ما يلي عن البنية:
- لها حقل بحجم 1 عند الإزاحة 0، وحقل بحجم 4 عند الإزاحة 4، وحقل بحجم
80hعند الإزاحة 8. - الحجم الكلي للبنية هو
88h(مستنتجًا من استدعاءmemset).
لإعادة بناء تعريف البنية في IDA، افتح التبويب الفرعي Local Types (View ➔ Open subviews ➔ Local types أو الاختصار Shift+F1).

الشكل 7.10: إنشاء نوع جديد في التبويب الفرعي Local Types
أنشئ بنية جديدة بالنقر بزر الفأرة الأيمن واختيار Add type….

الشكل 7.11: نافذة حوار إضافة نوع جديد
أدخل اسم البنية (msg). وإذا لم يكن لديك في البداية اسم وصفي مناسب، يمكنك استخدام خصائص أخرى مثل حجم البنية (مثل struct_0x88) وتحديث الاسم لاحقًا.
إذا كنت تعرف حجم البنية الدقيق وقت الإنشاء (كما نعرفه هنا)، فعّل خيار Fixed layout وأدخل Structure size. وإلا، ألغِ تفعيل Fixed layout لتعديل حجم البنية بشكل تكراري وإضافة عناصر جديدة كلما جمعت معلومات عنها.

الشكل 7.12: قائمة النقر الأيمن على نوع تعرض خيارات إضافة أعضاء بيانات جديدة
لإضافة عضو بيانات جديد، انقر بزر الفأرة الأيمن على الإزاحة المطلوبة واختر Data (الاختصار D). للتنقل بين أحجام البيانات (بايت، كلمة، كلمة مزدوجة)، انقر بزر الفأرة الأيمن على عضو واختر Data (D) مرة أخرى. املأ بنية msg بتعريف:
- حقل أحادي البايت عند الإزاحة 0.
- حقل رباعي البايتات عند الإزاحة 4.
- حقل أحادي البايت عند الإزاحة 8.

الشكل 7.13: نافذة حوار حقل المصفوفة في البنية
بعد ذلك، عرّف الحقل عند الإزاحة 8 كمصفوفة بالنقر بزر الفأرة الأيمن عليه واختيار Array…. تملأ نافذة حوار المصفوفة تلقائيًا حجم المصفوفة بناءً على الفجوة بين الحقل المحدد والحقل التالي (أو نهاية البنية). يجب أن تشبه البنية الآن ما يلي:
00000000 struct __fixed msg // sizeof=0x88
00000000 {
00000000 char field_0;
00000001 // padding byte
00000002 // padding byte
00000003 // padding byte
00000004 int field_4;
00000008 char field_8[128];
00000088 };
يمكننا الآن تطبيق بنية msg لتحويل الإزاحات الرقمية إلى أسماء حقول باستخدام إحدى الطريقتين:
- انقر بزر الفأرة الأيمن على إزاحة واختر من القائمة المنسدلة Structure.
- حدد إزاحة واضغط
Tلإظهار نافذة حوار البنية.

الشكل 7.14: تطبيق تعريف بنية على إزاحة
يؤدي وسم جميع الإزاحات الرقمية في sendMsg إلى إنتاج التجميع الموضح في القائمة 7.5.
| C | Assembly |
|---|---|
| |
القائمة 7.5: الدالة sendMsg بعد تطبيق msg على جميع الإزاحات
يمكننا تطبيق تعريف البنية نفسه في main بتعريف بنية msg على المكدس بدءًا من var_88. للقيام بذلك، انتقل إلى main وانقر نقرًا مزدوجًا على متغير المكدس للانتقال إلى عرض مكدس الدالة. ثم أعلن عن المتغير المحلي var_88 كمتغير بنية:
- ضع المؤشر على
var_88. - انتقل إلى
Edit ➔ Struct var…. - اختر بنية
msg.

الشكل 7.15: تعريف بنية msg على المكدس
الطريقة البديلة:
- انقر بزر الفأرة الأيمن على
var_88واخترSet field type…(الاختصارY). - أدخل اسم البنية
msg(يمكن حذف اسم المتغير).

الشكل 7.16: تحديث نوع متغير على المكدس إلى msg
تتحول عمليات الوصول إلى المتغير المحلي إلى عمليات وصول لأعضاء البنية، كما هو موضح في القائمة 7.6.
| C | Assembly |
|---|---|
| |
القائمة 7.6: الدالة main بعد تعريف بنية msg على المكدس
بمجرد أن يتحسن فهمنا لغرض كل حقل، يمكننا إعادة تسميته. ويمكن ذلك مباشرةً من قائمة التفكيك (أينما أُشير إلى الحقول) أو من التبويب الفرعي Local Types.
| C | Assembly |
|---|---|
| |
القائمة 7.7: الدالة sendMsg بعد تحديث جميع أسماء الحقول في msg
تنتشر أسماء الحقول المحدثة إلى جميع مراجع البنية الموجودة.
| C | Assembly |
|---|---|
| |
القائمة 7.8: الدالة main بعد تحديث جميع أسماء الحقول في msg
لاحظ أن تعريف msg في الشيفرة المصدرية يوضح أن الحقل الأول من نوع char، وهو نوع بيانات أحادي البايت. قد يقودك هذا إلى الاعتقاد بأن الحقل التالي len يجب أن يقع عند الإزاحة 1. لكن التجميع يوضح أن len يقع عند الإزاحة 4 بدلًا من ذلك. ويعود ذلك إلى قواعد حشو البنيات (Structure Padding). افتراضيًا، يفضل مترجم C محاذاة أنواع الأعضاء عند مضاعفات أحجامها لتحسين الوصول إلى الذاكرة. فعلى سبيل المثال، int، وهو نوع بيانات رباعي البايتات، سيوضع عند عناوين تمثل مضاعفات 4. يجب أن تكون مدركًا لقواعد الحشو الافتراضية عند إعادة بناء البنيات، ولكن يمكن أيضًا تحديد الحشو يدويًا باستخدام توجيهات المترجم (مثل #pragma pack).
تمرين اختراق: structs_make_bucks
ابدأ بالملف
structs_make_bucks.i64. يحتوي البرنامج على شيفرة تقوم بما يلي:
- مطالبة المستخدم بإدخال عدد البرغر والبطاطس المقلية والميلك شيك المطلوب طلبها.
- تخزين العناصر المدخلة في بنية
Orderعلى المكدس.- تمرير مؤشر إلى بنية
Orderإلى الدالةsub_4010B2.- تنتهي الدالة
sub_4010B2باستدعاء الدالةsub_401076، التي تأخذ كوسائط عدد البرغر والبطاطس والميلك شيك المطلوبة.- تحسب الدالة
sub_401076السعر الإجمالي (برغر: 7$؛ بطاطس: 4$؛ ميلك شيك: 3$) وتطبعه.
struct Order {
+0 int burgers;
+4 int fries;
+8 int shakes;
}
أكمل البرنامج بترقيع الدالة
sub_4010B2من أجل:
- استخدام مؤشر بنية
Orderالممرر كوسيط إلىsub_4010B2لاستدعاء الدالةsub_401076بعدد البرغر والبطاطس والميلك شيك الصحيح.عرّف بنية
Orderفي IDA وطَبِّقها على بنية المكدس في الدالةsub_401000.
الحل (اضغط للعرض)
sub_4010B2:
mov eax, [ebp+arg_0]
mov ecx, [eax] ; burgers
push ecx
mov edx, [eax+4] ; fries
push edx
mov ecx, [eax+8] ; shakes
push ecx
call sub_401076
add esp, 0Ch
(ثم عرّف بنية Order في Local Types وطَبِّقها على المكدس في sub_401000.)
9. عبارات Switch وجداول القفز (Switch Statements and Jump Tables)
عبارة switch هي بنية تحكم في التدفق تختار كتلة شيفرة واحدة للتنفيذ من بين حالات متعددة بناءً على قيمة تعبير ما. وفي لغة C، تكون الصيغة كما يلي:
switch(expression) {
case value1: statement_1; break;
case value2: statement_2; break;
...
default: default_statement; break;
}
عبارة switch:
- تُقيّم التعبير داخل عبارة switch.
- تطابق قيمة التعبير مع إحدى القيم المحددة في تسميات الحالات (case labels).
- تنفّذ كتلة الشيفرة المقابلة للحالة المطابقة؛ وإلا، إذا لم يوجد تطابق، تنفّذ الحالة الافتراضية (default).
قد تختار المترجمات استراتيجيات مختلفة لتنفيذ عبارة switch حسب الحالات:
- مع عدد صغير من الحالات، تُولّد المترجمات سلسلة من أزواج مقارنة-قفزة مشروطة، مطابقةً لسلسلة من عبارات if-else.
- مع حالات متسلسلة كثيرة، تستخدم المترجمات غالبًا جدول قفز (Jump Table). يستبدل هذا التحسين المقارنات بعملية فهرسة في جدول من العناوين لكل كتلة حالة.
switch (fruit) {
case 'a':
printf("apple"); break;
case 'b':
printf("banana"); break;
case 'c':
printf("cantalope"); break;
default:
printf("no fruit"); break;
}
القائمة 7.9: عبارة switch بثلاث حالات وحالة افتراضية

الشكل 7.17: التنفيذ الافتراضي لعبارة switch هو عادةً سلسلة من المقارنات
عبارة switch في القائمة 7.9 لها ثلاث حالات وحالة افتراضية. كما هو موضح في الشكل 7.17، يُصرَّف هذا إلى سلسلة من المقارنات يقيّمها البرنامج واحدة تلو الأخرى.
switch (fruit) {
case 'a':
printf("apple"); break;
case 'b':
printf("banana"); break;
case 'c':
printf("cantalope"); break;
case 'd':
printf("dates"); break;
default:
printf("no fruit"); break;
}
القائمة 7.10: عبارة switch بأربع حالات وحالة افتراضية. تستخدم المترجمات الاستدلال لتحديد الطريقة الأكثر كفاءة لتنفيذ عبارة switch ذات الحالات الكثيرة.

الشكل 7.18: عبارة switch منفَّذة كجدول قفز
تضيف القائمة 7.10 حالة إلى عبارة switch في القائمة 7.9. هذا يدفع المترجم لاستخدام جدول قفز لتنفيذ عبارة switch، كما هو موضح في الشكل 7.18. يقوم البرنامج بما يلي:
- يحسب فهرس الحالة بطرح
'a'(أصغر قيمة حالة) منfruit. - يحدد ما إذا كان الفهرس المحسوب يتجاوز أكبر فهرس حالة (
3). إذا حدث ذلك، يقفز إلى الحالة الافتراضية. - يفهرس في جدول القفز للعناوين الموجود عند
0x40113a. تكشف IDA جدول القفز وتضع عليه تسمية بالبادئةjpt_. المدخل الأول في جدول القفز هو عنوان كتلة الشيفرة للحالةa، والمدخل الثاني هو عنوان الحالةb، وهكذا.
10. معمل — guesser.exe (Lab - guesser.exe)
معلومات العينة
- اسم الملف: guesser.exe
- MD5:
d60f46728627e44ab8742e17e7cd4562
أهداف المعمل
هذا معمل متوسط المستوى للتدرب على تقنيات التنقيح وتحليل بنى التجميع التالية:
- المصفوفات
- الحلقات
- تعليمات السلاسل النصية وبادئات تكرار التعليمات
ابدأ بالتفاعل مع guesser.exe من سطر الأوامر. لهذا المعمل هدفان:
- تحليل
guesser.exeلمعرفة مفتاح التسجيل الصحيح الذي يتيح لك لعب لعبة تخمين الأرقام المخفية خلف نافذة التسجيل. - استخدام تقنيات التنقيح لإيجاد طرق للعب لعبة تخمين الأرقام دون مفتاح التسجيل الصحيح.
قبل بدء التحليل، أعد تسمية الدوال التالية:
sub_4014E8إلى_printfsub_4013CDإلى_srand
الأسئلة
- ما القيمة التي يجب أن ترجعها استدعاء الدالة
sub_401000عند0x4010F9حتى يقبل البرنامج مفتاح التسجيل؟ - ما وسيط الدالة الممرر إلى
sub_401000عند0x4010F9؟ - حلّل الدالة
sub_401000. ما العملية التي تطبقها على وسيط الدالة؟ - استعد مفتاح التسجيل.
- صِف طريقة باستخدام تعديل السجلات لتجاوز نافذة التسجيل دون مفتاح التسجيل.
- صِف طريقة باستخدام ترقيع القفزات لتجاوز نافذة التسجيل دون مفتاح التسجيل.
ملاحظة: الحل التفصيلي لهذا المعمل في درس حل معمل guesser.
خلاصة المقال
| المفهوم | النقاط الرئيسية |
|---|---|
| المؤشرات | متغير يخزن عنوان ذاكرة؛ LEA لحساب العنوان، MOV لقراءة القيمة |
MUL | ضرب غير موقّع؛ معامل ضمني AL/AX/EAX؛ الناتج في EDX:EAX |
IMUL | ضرب موقّع؛ ثلاث صيغ (معامل/معاملان/ثلاثة معاملات) |
| المصفوفات | سجل قاعدة + فهرس × حجم العنصر؛ ابحث عن حلقات وصول تسلسلي |
| الحلقات | تهيئة ← شرط (CMP + قفزة معكوسة) ← جسم ← تحديث؛ السهم الصاعد = حلقة |
MOVS/STOS/SCAS | نسخ/تخزين/مسح ضوئي؛ تستخدم ESI وEDI وEAX ضمنيًا |
| بادئات التكرار | REP (ECX)، REPE/REPZ (ECX+ZF)، REPNE/REPNZ (ECX+¬ZF) |
| الدوال الجوهرية | memcpy=rep movs، memset=rep stos، strlen=repne scasb |
| البنيات | معلوماتها تُفقد عند التصريف؛ أعد بناءها من أنماط الوصول (قاعدة+إزاحات ثابتة) |
| حشو البنيات | الأعضاء تُحاذى عند مضاعفات أحجامها؛ char ثم int يعني فجوة 3 بايتات |
| switch | حالات قليلة = سلسلة مقارنات؛ حالات كثيرة = جدول قفز (بادئة jpt_ في IDA) |
المصدر الأصلي: Malware Analysis Crash Course — FLARE Learning Hub