P01NT
0x07 الأساسيات 08/19

المصفوفات والسلاسل النصية

المصفوفات والسلاسل في الذاكرة، دوال string.h الخطيرة، وعلاقتها المباشرة بثغرات Buffer Overflow.

· 5 دقائق قراءة · 858 كلمة
المحتويات

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

في هذا المقال سنتعرف على المصفوفات والسلاسل النصية واستخداماتها وأهميتها في الهندسة العكسية.

من هذا المقال سنتوسع بالشرح — لأن المقالات المتبقية هي من أهم ما يفيدنا في الهندسة العكسية، ويجب إتقان التعامل معها.

المصفوفات والسلاسل النصية من أهم أنواع البيانات المستخدمة لتخزين البيانات ومعالجتها، وهما يختلفان في الاستخدام والهيكلية. فهم كيفية عملهما في الذاكرة هو أساس تحليل البرامج التنفيذية، خصوصاً فيما يتعلق بالكشف عن الثغرات.

1. المصفوفات (Arrays)

ما هي المصفوفة؟ مجموعة من العناصر المتشابهة (من نفس نوع البيانات) تُخزَّن في مواقع ذاكرة متجاورة. كل عنصر يُمكن الوصول إليه بفهرس (Index).

data_type array_name[size];
// data_type: نوع البيانات (مثل int، char، float).
// array_name: اسم المصفوفة.
// size: عدد العناصر التي يمكن تخزينها.

أمثلة

int numbers[5];          // مصفوفة لخمسة أعداد صحيحة
float prices[10];        // مصفوفة لعشرة أعداد عشرية
char letters[3];         // مصفوفة لثلاثة حروف

تهيئة المصفوفة

int numbers[5] = {1, 2, 3, 4, 5};  // تعيين كل القيم
int numbers[5] = {1, 2};           // الباقي يُصفَّر تلقائياً إلى 0

الوصول والتعديل

int x = numbers[2];    // الحصول على العنصر الثالث
numbers[0] = 10;       // تعديل العنصر الأول

ملاحظة هامة جداً: أول عنصر في المصفوفة فهرسه 0 — أي أن numbers[0] هو الأول.

مثال

#include <stdio.h>

int main() {
    int numbers[5] = {10, 20, 30, 40, 50};

    for (int i = 0; i < 5; i++) {
        printf("numbers[%d] = %d\n", i, numbers[i]);
    }

    return 0;
}

المصفوفات متعددة الأبعاد

مصفوفة مؤلفة من أسطر وأعمدة:

int matrix[2][3] = {
    {1, 2, 3},
    {4, 5, 6}
};

الوصول للعنصر:

int x = matrix[1][2]; // السطر الثاني، العمود الثالث

2. السلاسل النصية (Strings)

ما هي السلسلة النصية؟ مصفوفة من الأحرف (characters) تُنهيها لغة C برمز إنهاء السلسلة '\0' (الحرف الفارغ). تذكره من مقال سلاسل الهروب.

char string_name[size];

تهيئة السلاسل النصية

1. يدوياً حرفاً حرفاً:

char name[4] = {'A', 'l', 'i', '\0'};

لاحظ أننا خصصنا 4 مواضع: ثلاثة أحرف + \0.

2. كالنصوص المباشرة:

char name[] = "Ali";  // في هذه الحالة يُضاف \0 تلقائياً

الوصول للأحرف

char first_char = name[0]; // الحرف الأول
name[1] = 'e';             // تعديل الحرف الثاني

مثال

#include <stdio.h>

int main() {
    char name[] = "Ahmed";

    printf("Name: %s\n", name);

    for (int i = 0; name[i] != '\0'; i++) {
        printf("Character %d: %c\n", i, name[i]);
    }

    return 0;
}

دوال التعامل مع السلاسل النصية

هذه الدوال موجودة في المكتبة <string.h> — وتحتاج إتقانها لأنها الأكثر ظهوراً في تحليل الـ binaries:

الدالةوظيفتهاملاحظة أمنية
strlenمعرفة طول النصآمنة (قراءة فقط)
strcpyنسخ نص إلى آخرغير آمنة — لا تفحص حجم الوجهة
strcatدمج نصينغير آمنة — لا تفحص حجم الوجهة
strcmpمقارنة نصينآمنة (قراءة فقط)
#include <string.h>

int length = strlen(name);              // الطول
strcpy(destination, source);            // النسخ
strcat(string1, string2);               // الدمج
if (strcmp(string1, string2) == 0) {    // المقارنة
    printf("Strings are equal.\n");
}

مثال شامل

#include <stdio.h>
#include <string.h>

int main() {
    char str1[20] = "Hello";
    char str2[20] = "World";

    strcat(str1, " ");
    strcat(str1, str2);
    printf("Concatenated String: %s\n", str1);

    if (strcmp(str1, "Hello World") == 0) {
        printf("Strings are equal.\n");
    } else {
        printf("Strings are not equal.\n");
    }

    printf("Length of str1: %zu\n", strlen(str1)); // %zu لأن strlen ترجع size_t

    return 0;
}

الفرق بين المصفوفات والسلاسل النصية

المصفوفاتالسلاسل النصية
النوعأي نوع (int, float, char…)char فقط
النهايةلا رمز نهايةتنتهي بـ \0
الاستخدامبيانات منظمة (أرقام، قيم…)نصوص
في الذاكرةعناصر متجاورة بنفس الحجمأحرف متجاورة + \0

أهمية المصفوفات والسلاسل في الهندسة العكسية

أ. المصفوفة في الـ Assembly = عنوان بداية + إزاحة

المصفوفة لا تُمرَّر ككائن واحد، بل عنوان بدايتها في الذاكرة. الوصول numbers[2] يتحول إلى:

; numbers تبدأ عند العنوان X، و int = 4 بايت
lea eax, [X]          ; عنوان البداية
mov edx, [eax + 2*4]  ; numbers[2] = (العنوان + إزاحة)

الفهرس يتحول إلى إزاحة حسابية: index × حجم العنصر. لهذا تجد في الـ disassembly أنماطاً مثل [eax + ecx*4] — وهذا هو جوهر التعامل مع المصفوفات.

ب. العلاقة المباشرة بثغرات Buffer Overflow

مصفوفة char buf[10] على الـ stack ثم strcpy(buf, source) — إذا تجاوز المصدر 10 بايتات، ستكتب على ما يليها في الذاكرة (الـ EBP ثم عنوان الرجوع).

المصفوفة ليست “صندوقاً محمياً” — بل تتابع بايتات بلا حارس. فهم هذا هو الباب لفهم ثغرات Buffer Overflow التي ستشرحها في سلسلة الـ exploitation، ولهذا فإن strcpy و strcat تعتبران من أخطر الدوال في C (لهذا أنشئت strncpy و strlcpy الآمنتان).

ج. السلاسل في الـ Binaries

  • السلاسل تُخزَّن في الملف القابل للتنفيذ كما هي (ASCII/UTF) في قسم .rdata — لذلك تستخرجها أدوات مثل strings بسهولة.
  • عندما يجد المحلل نصاً في الـ binary، يحدد مكانه ثم يبحث عن الاستدعاءات المتقاطعة (Cross-References / XREFs) ليرى أي دالة تستخدمه — هذا أول خط في التحليل.

د. أدوات التعامل

أدوات مثل IDA Pro و Ghidra تحلل عناوين الذاكرة وتحدد المصفوفات والنصوص المخزنة تلقائياً — لكن فهم ما يحدث تحت هذه الأدوات هو ما يميز المحلل الحقيقي.

تمرين عملي

  1. اكتب char buf[16] وانسخ فيه نصاً أطول بـ strcpy (مثل 40 حرفاً) — شاهد البرنامج ينهار أو يتصرف بغرابة. هذا هو بدايات تجربة الـ overflow التي ستتوسع فيها لاحقاً.
  2. اطبع عنوان المصفوفة بـ %p (printf("%p", numbers)) — سترى عنواناً في الذاكرة، وهو نفس العنوان الذي ستراه في الـ assembly.
  3. افحص برنامجك في IDA/Ghidra ولاحظ نمط [eax + ecx*4] للوصول إلى numbers[i].