بسم الله الرحمن الرحيم.
في هذا المقال سنتحدث عن: Bit Fields (الحقول البتية)، Unions (الاتحادات)، و Enums (التعدادات).
1. الحقول البتية (Bit Fields)
تعريفنا في المقال السابق أن الـ struct يوزع بايتات — لكن ماذا لو أردنا تقسيم بايت واحد إلى بتات؟ هنا يأتي الـ Bit Field: نحدد عدد البتات لكل عضو.
typedef struct {
u8 a : 4; // 4 بتات
u8 b : 2; // 2 بتات
u8 c : 1; // 1 بت
} student; // المجموع 7 بتات ← داخل بايت واحد
+---+---+---+---+---+---+---+---+
| a (4 بت) | b (2) | c | (فارغ) |
+---+---+---+---+---+---+---+---+
ملاحظة: المترجم يحزم الـ bit fields داخل البايت، لكن لا يضمن سلوكاً واحداً عبر جميع المترجمات (implementation-defined). عند التحليل، قراءة الـ bit fields في الـ assembly تتضمن عمليات
and/shift.
2. الاتحادات (Unions)
الـ Union تعني أن جميع الأعضاء يتقاطعون في نفس عنوان الذاكرة — كل الأعضاء يبدأون من نفس الموقع، فيتشابه شكلها مع الـ struct لكن بسلوك مختلف.
عناوين عالية
+-----------------+
| |
+-----------------+
| u32 z |
| + u16 y |
| + u8 x | ← كلهم من نفس العنوان
+-----------------+
عناوين منخفضة
سلوك التداخل
- تغيير الـ
u8يغير الـu16والـu32(كلهم فوق بعض). - تغيير الـ
u16لا يغير بالضرورة الـu8(قد يكون خارجه)، لكنه يغير الـu32.
Syntax
union unionName {
member1;
member2;
};
حجم الـ Union
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;
union myUnion {
u8 x;
u16 y;
u32 z;
};
printf("the size of the myUnion is: %zu bytes\n", sizeof(myUnion));
الناتج: 4 بايت — لأن الـ union يأخذ حجم أكبر عضو فيه (وليس مجموعهم).
للمقارنة: لو كانت struct، الحجم = 7 بايت (packed) أو 8 بايت (مع padding). أما الـ union فاكتفى بأكبر عضو لأنه يشترك بالعنوان.
مثال عملي: سجل Register
typedef union {
struct {
u8 B0 : 1; // البت الأول
u8 B1 : 1; // البت الثاني
} Bits; // struct من bit fields = 1 بايت
u8 Byte; // البايت كاملاً
} Register;
الحجم = 1 بايت — لأن الـ struct (1 بايت) والـ Byte (1 بايت).
الوصول
1. الوصول للبايت كاملاً:
Register x;
x.Byte = 30; // تعيين القيمة كاملة
2. الوصول بتاً بتاً:
x.Bits.B0 = 1; // تعيين البت الأول فقط
تغيير
B0يغيّر قيمة الـByte— لأنهما في نفس العنوان.
3. التعداد (Enums)
التعداد وسيلة لإعطاء أسماء لقيم عددية — يجعل الكود أكثر تعبيراً (representative). بدلاً من أن يرى المستخدم/المبرمج 7، يرى apple.
enum fruit {
mango, // 0 (افتراضياً)
apple // 1
};
يمكن تحديد القيم يدوياً:
enum fruit {
mango = 2,
apple = 7
};
مثال متدرج
enum weekdays {
sunday = 1, // إخبار المترجم أن البداية من 1
monday, // 2
tuesday // 3
};
enum weekdays w;
w = monday;
printf("the value of w is: %d\n", w); // 2
ملاحظة RE: في C، الـ enum مجرد عدد صحيح (خلافاً لبعض اللغات) — يظهر في الـ assembly كقيمة ثابتة (immediate).
ربط الـ RE — لماذا هذا المقال كنز تحليلي
1. Bit Fields في الـ Assembly = and + shift
قراءة أي bit field تتحول إلى نمط واضح:
mov al, [ebp-4] ; قراءة البايت كاملاً
and al, 0x0F ; استخراج البتات (مثل B0)
shr al, 2 ; إزاحة إلى الموضع الصحيح
عند التحليل، رؤية
and+shrمتتالية تكشف وجود bit fields — مثل أعلام TCP/IP، حقول الـ PE headers، وأعلام السجلات.
2. Union = نفس العنوان بأحجام مختلفة
في الـ disassembly، الـ union هو نفس العنوان يُفسَّر بعروض مختلفة:
mov eax, [esi] ; يقرأ u32
mov al, [esi] ; يقرأ u8 — من نفس العنوان!
هذا يهم في فك بروتوكولات الشبكة و packet headers — نفس البايت يُقرأ كعلم أو كقيمة كاملة.
3. Enum = ثوابت مدمجة (Immediates)
سترى cmp eax, 7 بدل cmp eax, apple — والمحلل يسمي هذه القيم بأسماء الـ enum في IDA/Ghidra لجعل الكود مقروءاً. القيم تظهر كـ immediate في الـ cmp/switch.
4. مثال Register = حقيقي في الـ RE
في التحليل العملي، السجلات وتنسيقات الأجهزة تُقرأ عبر مثل هذه الـ unions (بايت/بت من سجل) — وسترى النمط في كود السائقين (drivers) وتهيئة الأجهزة.
خلاصة
- Bit Field: تقسيم البايت لبتات — يظهر في الـ asm كـ
and/shift. - Union: أعضاء تشترك بنفس العنوان — يظهر كتفسيرات مختلفة لنفس الموقع.
- Enum: أسماء لقيم عددية — يظهر كقيم مدمجة في الـ cmp/switch.
الثلاثة أدوات يومية في قراءة الـ binaries — أتقنها وستتسارع قراءتك لأي كود تحلله.