Skip to content
KitploitKITPLOIT
أدواتالمدونة
إرسال
أدواتالمدونة
إرسال

أدوات الاختراق واختبار الاختراق والأمن السيبراني لترسانتك الأمنية!

Kitploit هو دليل لأدوات الاختراق والأمن السيبراني واختبار الاختراق. اكتشف آخر تحديثات المشاريع للعثور على الثغرات وتحليل الأنظمة وأتمتة الاختبارات وتعزيز أمنك.

··الخلاصات·اتصال·الخصوصية·© 2026 Kitploit

دليل الأدوات

الفئات

عرض جميع الفئات
Loading categories
page_table_walk — تجول في جداول الصفحات x86-64 يدويًا في qemu و gdb. حلل عنوانًا افتراضيًا، اتبع سجل cr3 عبر جميع مستويات الذاكرة الفعلية، واستخرج علمًا من البايتات الخام. | Kitploit
أدوات/GitHubGitHub/jazho76/page_table_walk
تحليل الذاكرة الجنائيالهندسة العكسيةمصممي الأخطاءCTFتحليل الملفات الثنائيةالتعلم والتعليممختبرات وتدريب عملي
GitHubjazho76/page_table_walk

page_table_walk

تجول في جداول الصفحات x86-64 يدويًا في qemu و gdb. حلل عنوانًا افتراضيًا، اتبع سجل cr3 عبر جميع مستويات الذاكرة الفعلية، واستخرج علمًا من البايتات الخام.

عرض المستودع
281منذ 5 أشهرتمت المراجعة من قبل Kitploit

الأكثر شعبية

عرض الكل →

اكتشف الأدوات الأكثر استخدامًا من قبل مجتمعنا.

استكشف جميع الأدوات

تصفح مجموعتنا من الأدوات

عرض جميع الأدوات →
مشاركة

التقاط العلم في الذاكرة الفعلية

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

ما نجح معي هو الجلوس أمام QEMU و gdb والقيام بالتنقل بنفسي: حساب كل مؤشر، قراءة كل إدخال من الذاكرة الفعلية، تتبع كل مؤشر يدويًا. ظهر واحد من ذلك يمكن أن يعلم أكثر من ساعات من المحاضرات.

هذه مجموعة من ملاحظاتي من تلك العملية. إذا كنت لا تزال تفتقر إلى الجانب المفاهيمي، شاهد محاضرة Zardus حول إدارة ذاكرة النواة أولاً. تلك هي النظرية. هذا هو المختبر.

الهدف: أخذ عنوان افتراضي وتعقبه عبر الذاكرة الفعلية الخام حتى نجد البيانات. لا مساعدات النواة. لا تجريدات. مجرد آلة افتراضية QEMU، gdb، وذاكرة فعلية خام.

بحلول النهاية، لن يكون التقسيم الصفحي شيئًا تقرأ عنه، بل سيكون شيئًا تعرفه لأنك قمت به يدويًا.


إعداد المختبر

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

Ubuntu/Debian

sudo apt install qemu-system-x86 gdb

Fedora/RHEL

sudo dnf install qemu-system-x86 gdb

macOS

brew install qemu gdb

root@kitploit:~
### The challenge binary

الهدف هو برنامج بسيط بلغة C يخزن علمًا (flag) في الذاكرة ويطبع عنوانه الافتراضي:```c
#include <stdio.h>
#include <unistd.h>

int main(void)
{
    char secret[] = "FLAG{p4g3_t4bl3_w4lk3r}";

    printf("secret @ %p\n", (void *)secret);
    printf("pid = %d\n", getpid());
    printf("Spinning. Walk the page tables to find the flag.\n");

    while (1)
    {
    }
}

الحلقة المزدحمة (busy loop) مقصودة. لقد استخدمت في الأصل pause()، لكن ذلك يضع العملية في حالة سكون في استدعاء نظام (syscall): عندما يوقف gdb الجهاز الافتراضي (VM)، فمن المحتمل أن وحدة المعالجة المركزية (CPU) تقوم بتشغيل مهمة الخمول (idle task) مع CR3 مختلف. الحلقة الدورانية (spinning loop) تبقي العملية على المعالج، لذا التوقف يضمن أنك في سياقها مع جداول الصفحات (page tables) الصحيحة.

تم تضمين initramfs مسبقة البناء مع هذا الثنائي بالفعل في initramfs.cpio.gz. إذا كنت بحاجة إلى إعادة بنائها (لنظام Linux فقط، يتطلب busybox و glibc-static)، قم بتشغيل make في هذا الدليل.

تشغيل QEMU```

./start.sh

root@kitploit:~
يقوم البرنامج النصي بتشغيل النواة المجمعة و initramfs تحت QEMU مع `-s`
(خادم gdb على `localhost:1234`) و `nokaslr` بحيث تظل عناوين النواة
ثابتة بين عمليات التشغيل.

يتم تشغيل الجهاز الافتراضي فورًا ويتم تشغيل binary التحدي. سترى عنوان
العلم الافتراضي مطبوعًا على وحدة التحكم.```
secret @ 0x7ffe08985c90
pid = 1
Spinning. Walk the page tables to find the flag.

اكتب ذلك العنوان الافتراضي. هذا هو هدفك.

طرفية QEMU بعد الإقلاع، تُظهر مخرجات الثنائي التحدي مع عنوان العلامة ومعرف العملية

مفتاح الهروب الافتراضي لـ QEMU هو Ctrl-a، لكنه يتعارض مع بادئة tmux الخاصة بي، لذلك يستخدم السكريبت -echr 0x11 لإعادة تعيينه إلى Ctrl-q. إذا كنت تستخدم Ctrl-q لشيء آخر، فغيّر القيمة السداسية العشرية في start.sh لتناسب إعداداتك.

إرفاق gdb

في طرفية ثانية:``` gdb -ex "target remote :1234"

root@kitploit:~
![طرفية gdb بعد الإرفاق، متوقفة وجاهزة](https://assets.kitploit.com/production/public/readmes/12435/9fb13e082596378677ea0d42cf7da3a86706df44127860fc724b2bad0a9f138c.png)

---

## تفكيك العنوان الافتراضي

لديك عنوان افتراضي. لكن أين البيانات، _حقًا_؟

العناوين الافتراضية هي الخيال المهذب لنظام التشغيل. كل عملية تعتقد أن لديها ذاكرة خاصة بها تبدأ من الصفر. في الواقع، تعيش البيانات في موقع غير مرتبط تمامًا في ذاكرة الوصول العشوائي الفعلية. جدول الصفحات هو الخريطة بينهما: بنية شجرية يقوم المعالج المركزي باجتيازها في كل وصول للذاكرة (أو يبحث عنها من ذاكرة التخزين المؤقت TLB الخاصة به).

لذا دعنا نفعل ما يفعله المعالج المركزي. يدويًا. لترجمة ذلك العنوان، نحتاج إلى تفكيكه إلى المؤشرات التي يستخدمها المعالج المركزي في كل مستوى.

العنوان الافتراضي في x86-64 بعرض 48 بت. يتم تقسيم تلك الـ 48 بت إلى خمسة حقول:```
 63    48 47    39 38    30 29    21 20    12 11       0
┌────────┬────────┬────────┬────────┬────────┬──────────┐
│ sign   │  PGD   │  PUD   │  PMD   │   PT   │  Offset  │
│ extend │ index  │ index  │ index  │ index  │          │
│ (16b)  │ (9b)   │ (9b)   │ (9b)   │ (9b)   │  (12b)   │
└────────┴────────┴────────┴────────┴────────┴──────────┘

كل فهرس 9 بت يختار واحدة من 512 مدخلة في جدول الصفحات في ذلك المستوى. الإزاحة ذات 12 بت تختار بايتًا داخل الصفحة النهائية البالغ حجمها 4 كيلوبايت (0x1000).

لاستخراج الفهارس، استخدم الإزاحة والقناع:``` PGD index = (VA >> 39) & 0x1FF PUD index = (VA >> 30) & 0x1FF PMD index = (VA >> 21) & 0x1FF PT index = (VA >> 12) & 0x1FF Offset = VA & 0xFFF

root@kitploit:~
في gdb، يمكنك حساب هذه مباشرة:```
(gdb) p/x (0x7ffe08985c90 >> 39) & 0x1ff
$1 = 0xff
(gdb) p/x (0x7ffe08985c90 >> 30) & 0x1ff
$2 = 0x1f8
(gdb) p/x (0x7ffe08985c90 >> 21) & 0x1ff
$3 = 0x44
(gdb) p/x (0x7ffe08985c90 >> 12) & 0x1ff
$4 = 0x185
(gdb) p/x 0x7ffe08985c90 & 0xfff
$5 = 0xc90

اكتبها. ستستخدم كل واحدة منها في المستوى المقابل لها.

ستختلف قيمك. العنوان 0x7ffe08985c90 هو مجرد مثال. استخدم أي عنوان يطبعه برنامج التحدي الخاص بك.

ملاحظة حول التقسيم بخمس مستويات. تدعم المعالجات والنواة الحديثة LA57، الذي يضيف مستوى خامسًا (PML5) فوق PGD ويمدد العناوين الافتراضية إلى 57 بتًا. المسار هو نفس النمط: مؤشر 9 بتات إضافي، بحث إضافي في جدول واحد. معظم الأنظمة لا تزال تستخدم التقسيم بأربعة مستويات. يمكنك التحقق من نظامك: cat /proc/cpuinfo | grep la57. كل شيء في هذه المقالة يفترض التقسيم بأربعة مستويات.


إيجاد CR3: جذر الشجرة

كل شجرة لها جذر. بالنسبة لجداول الصفحات، هذا الجذر موجود في المسجل CR3: فهو يحمل العنوان المادي للجدول الأعلى مستوى، وهو PGD. كل عملية تحصل على قيمة CR3 خاصة بها، وتقوم النواة بتبديلها عند تبديل السياق.

هذه هي نقطة دخولنا إلى المسار. اقرأها من gdb:``` (gdb) info registers cr3 cr3 0x66c7000 [ PDBR=26311 PCID=0 ]

root@kitploit:~
قاعدة جدول الصفحات هي `0x66c7000`. البتات الـ 12 السفلية هي PCID/أعلام (صفر هنا)، لذا فإن العنوان الأساسي هو القيمة كما هي.

هنا يبدأ التجوال.

---

## التجوال

إليك الحيلة: كل مستوى يتبع نفس النمط. تختلف الأعلام قليلاً بين المستويات، لكن العملية لا تتغير. النمط:

1. **حساب عنوان الإدخال:** `base + index * 8` (كل إدخال هو 8 بايت)
2. **قراءة الإدخال من الذاكرة الفعلية** باستخدام أمر `xp` في مراقب QEMU
3. **فك تشفير الأعلام** (انظر المرجع أدناه). إذا كانت بتة الوجود (bit 0) تساوي 0، فإن الصفحة غير معينة ويتوقف التجوال
4. **استخراج قاعدة الجدول التالي:** قناع الإدخال بـ `& 0x000FFFFFFFFFF000`
5. **الانتقال إلى المستوى التالي**

كل إدخال هو 64 بت. بتات الأعلام الشائعة:```
Bit   Name              Meaning when set
 0    Present           Page/table is mapped
 1    Read/Write        Writable
 2    User/Supervisor   Accessible from userspace
 3    Write-Through     Write-through caching
 4    Cache Disable     Caching disabled
 5    Accessed          CPU has read this entry
 6    Dirty             CPU has written to the page (final level only)
 7    Page Size         1 GB page (PUD) or 2 MB page (PMD)
63    NX                No-execute

بت [51:12] تحتوي على العنوان الفعلي للجدول التالي (أو إطار الصفحة في المستوى النهائي). البتات 9-11 يتم تجاهلها بواسطة العتاد وهي متاحة لنظام التشغيل. يستخدمها لينكس لأغراض حفظ الدفاتر (تتبع الأوساخ الناعمة، على سبيل المثال). البتات 52-62 محجوزة. ستواجه كليهما عند قراءة PTEs في كتابات الاستغلال.

احتفظ بجدول الأعلام هذا في متناول يدك أثناء السير.

لنبدأ.

المستوى 4: PGD (الدليل العام للصفحات)

لدينا قاعدة PGD من CR3: 0x66c7000. مؤشر PGD الخاص بنا هو 0xff.

احسب عنوان الإدخال:``` entry = 0x66c7000 + 0xff * 8 = 0x66c77f8

root@kitploit:~
اقرأها من gdb باستخدام أمر فحص الذاكرة الفعلية لـ QEMU:```
(gdb) monitor xp/1gx 0x66c77f8
000000066c77f8: 0x0000000006713067

المدخل: 0x6713067 [Present RW User Accessed Dirty].

القاعدة التالية: 0x6713067 & 0x000FFFFFFFFFF000 = 0x6713000.

المستوى 3: PUD (Page Upper Directory)

القاعدة التي استخرجناها من مدخل PGD (0x6713000) تشير إلى PUD. نفس العملية، الفهرس التالي: 0x1f8.``` entry = 0x6713000 + 0x1f8 * 8 = 0x6713fc0

root@kitploit:~
[No content provided in the INPUT section. Please paste the Markdown chunk you wish to have translated.]```
(gdb) monitor xp/1gx 0x6713fc0
00000006713fc0: 0x00000000066ac067

المدخل: 0x66ac067 [Present RW User Accessed Dirty]. حجم الصفحة (بت 7) = 0، ليست صفحة ضخمة بحجم 1 جيجابايت.

القاعدة التالية: 0x66ac067 & 0x000FFFFFFFFFF000 = 0x66ac000.

المستوى 2: PMD (Page Middle Directory)

القاعدة: 0x66ac000. مؤشر PMD: 0x44.``` entry = 0x66ac000 + 0x44 * 8 = 0x66ac220

root@kitploit:~
---```
(gdb) monitor xp/1gx 0x66ac220
000000066ac220: 0x00000000066c4067

إدخال: 0x66c4067 [Present RW User Accessed Dirty]. حجم الصفحة (بت 7) = 0، ليست صفحة ضخمة بحجم 2 ميجابايت.

القاعدة التالية: 0x66c4067 & 0x000FFFFFFFFFF000 = 0x66c4000.

المستوى 1: PT (جدول الصفحات)

القاعدة: 0x66c4000. فهرس PT: 0x185.``` entry = 0x66c4000 + 0x185 * 8 = 0x66c4c28

root@kitploit:~
المدخلات:```
(gdb) monitor xp/1gx 0x66c4c28
000000066c4c28: 0x80000000037fd867

المدخل: 0x80000000037fd867 [موجود RW مستخدم تم الوصول إليه متسخ NX]. هذا هو إدخال جدول الصفحات النهائي.

إطار الصفحة الفعلية: 0x80000000037fd867 & 0x000FFFFFFFFFF000 = 0x37fd000.

ماذا لو Present = 0؟

دعنا نرى ما يحدث عندما تصادف عملية التنقل صفحة غير معيّنة. اختر عنوانًا شبه مؤكد أنه غير معيّن، شيء في منتصف مساحة العناوين:``` (gdb) p/x (0x0000414141414000 >> 39) & 0x1ff $1 = 0x82

root@kitploit:~
هذه هي القطعة 39 من 69 من مستند Markdown أطول يتم ترجمته بالتسلسل.
اللغة المصدر هي en.
اللغة الهدف: ar.
نوع المحتوى: README chunk 39/69.

قواعد خاصة بالقطعة:
1. قم بترجمة النص الطبيعي فقط. لا تترجم أبدًا: كتل الأكواد، أوامر الشل، مسارات الملفات، روابط URL، أسماء الحزم، المعرفات التقنية، معرفات CVE، أسماء متغيرات البيئة.
2. حافظ على جميع تركيبات Markdown كما هي تمامًا.
3. لا تقم بإضافة عناوين تمهيدية مثل "## Chunk N"، "## Part N"، "## تابع من..." أو "## ترجمة القطعة...". لا تقم بإضافة علامات "نهاية القطعة N" أو "يستمر المحتوى...".
4. لا تقم بإضافة علامات حذف "..." للإشارة إلى الحذف. قم بترجمة النص المقدم بالضبط، حرفيًا في البنية.
5. حدود القطع مقصودة. حافظ على البنية بحيث يمكن ربط القطع بسلاسة دون آثار بصرية.
6. أعد النص المترجم فقط. لا مقدمة، لا تعليقات، لا تغليف في كتل أكواد، لا JSON/YAML/XML، لا مصفوفات، لا كائنات، لا مخططات، لا أزواج مفتاح/قيمة.
7. إذا بدأت القطعة في منتصف فقرة، تابع الترجمة من تلك النقطة. لا تقم بإضافة سطر جديد بادئ أو مسافة بادئة ما لم تكن موجودة في المصدر.

الإدخال:```
(gdb) monitor xp/1gx 0x66c7000 + 0x82 * 8
00000000066c7410: 0x0000000000000000

كلها أصفار. البت 0 (موجود) مسح. المسار يتوقف هنا. لا يوجد PUD، ولا PMD، ولا PT، ولا إطار صفحة. هذا العنوان لا يتعيّن على الذاكرة الفعلية.

إذا وصلت وحدة المعالجة المركزية إلى هذا أثناء التنفيذ العادي، فستؤدي إلى رفع خطأ في الصفحة (مقاطعة 14). معالج الأخطاء في النواة سيقرر بعد ذلك ما يجب فعله: تحميل الصفحة من القرص (المبادلة)، تخصيص صفحة جديدة (الترحيل عند الطلب)، أو قتل العملية بخطأ تجزئة.

النقطة: جدول الصفحات ليس مجرد هيكل ترجمة. إنه أيضًا الآلية التي تجعل الذاكرة الافتراضية افتراضية. ليس كل عنوان يحتاج إلى ذاكرة فعلية خلفه. تكتشف وحدة المعالجة المركزية هذا أثناء المسار، مستوى واحد في كل مرة.


الكشف

اجمع إطار الصفحة الفعلية مع الإزاحة من العنوان الافتراضي الأصلي:``` Physical address = 0x37fd000 | 0xc90 = 0x37fdc90

root@kitploit:~
الآن اقرأها:```
(gdb) monitor xp/6bx 0x37fdc90
00000000037fdc90: 0x46 0x4c 0x41 0x47 0x7b 0x70

هذه هي F, L, A, G, {, p: بداية علمنا. اقرأ المزيد:``` (gdb) monitor xp/24bx 0x37fdc90 00000000037fdc90: 0x46 0x4c 0x41 0x47 0x7b 0x70 0x34 0x67 00000000037fdc98: 0x33 0x5f 0x74 0x34 0x62 0x6c 0x33 0x5f 00000000037fdca0: 0x77 0x34 0x6c 0x6b 0x33 0x72 0x7d 0x00

root@kitploit:~
## التثبيت

كيفية البناء من المصدر لأي نظام تشغيل/معمارية:

1. **تنزيل الشفرة المصدرية**
````bash
git clone https://github.com/your-org/kitploit-tool.git
cd kitploit-tool

المستخدمون المتقدمون - هناك العديد من خيارات البناء المتاحة لتخصيص التثبيت.``` FLAG{p4g3_t4bl3_w4lk3r}

root@kitploit:~
![gdb showing the full page table walk from CR3 to the flag](https://assets.kitploit.com/production/public/readmes/12435/8293a5d8b420301aa093dd780f64159c00b383a72d8c16fccd5c592ce065e886.png)

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

قبل ذلك، كان التقسيم الصفحي مجرد رسم بياني في شريحة عرض. الآن هو سلسلة من القراءات يمكنك إعادة تشغيلها في ذهنك: القاعدة، الفهرس، الإزاحة، القناع، التابع. هذا الفرق مهم عندما تنظر إلى استغلال نواة وتحتاج إلى التفكير فيما يفعله الكتابة إلى PTE فعليًا.

يمكنك التحقق من نتيجتك باستخدام أمر المراقب `gva2gpa` (العنوان الافتراضي للضيف إلى العنوان الفعلي للضيف) الخاص بـ QEMU، والذي يقوم بالتجول داخليًا:```
(qemu) gva2gpa 0x7ffe08985c90
gpa: 0x37fdc90

الأعلام والأذونات

لقد قمنا بفك تشفير الأعلام في كل مستوى أثناء التنقل، لكننا تخطينا ما تعنيه من حيث الأمان. انظر إلى PTE النهائية:``` 0x80000000037fd867

root@kitploit:~
Please provide the Markdown content to translate.```
Bit  0 (Present)        = 1    Page is in physical memory
Bit  1 (Read/Write)     = 1    Page is writable
Bit  2 (User/Supervisor)= 1    Accessible from user mode
Bit  3 (Write-Through)  = 0    Write-back caching
Bit  4 (Cache Disable)  = 0    Caching enabled
Bit  5 (Accessed)       = 1    CPU has read this page
Bit  6 (Dirty)          = 1    CPU has written to this page
Bit  7 (Page Size)      = 0    4 KB page (not huge)
Bit 63 (NX)             = 1    No-Execute: cannot run code from this page

هذا منطقي: السر هو متغير مكدس. المكدس قابل للقراءة، والكتابة، ومتسخ (تمت الكتابة إليه). تم وضع علامة عدم التنفيذ لأن الأنظمة الحديثة تفرض W^X: الصفحة القابلة للكتابة لا ينبغي أن تكون قابلة للتنفيذ.

يتم دمج الأعلام في كل مستوى بواسطة العتاد. إذا كان إدخال PUD يحتوي على User=0، فلن يكون أي شيء تحته متاحًا للمستخدم، بغض النظر عما يقوله PTE. الإذن الأكثر تقييدًا هو الذي يسود.


TLB: عندما يتجاوز وحدة المعالجة المركزية عملية التنقل

أربع قراءات للذاكرة فقط للوصول إلى بايت واحد. هذا مكلف. لا تقوم وحدة المعالجة المركزية فعليًا بالتنقل عبر جدول الصفحات في كل وصول للذاكرة. تقوم بتخزين النتيجة مؤقتًا في مخزن الترجمة المؤقت (TLB).

بعد أول وصول إلى العنوان الظاهري للعلم، تخزن وحدة المعالجة المركزية التعيين 0x7ffe08985c90 -> 0x37fdc90 (تقريبًا) في TLB. تصيب عمليات الوصول اللاحقة ذاكرة التخزين المؤقت وتتجاوز عملية التنقل بالكامل. يظل جدول الصفحات دون تغيير في ذاكرة الوصول العشوائي.

هذا شفاف للتعليمات البرمجية العادية. لكنه يصبح مهمًا في اللحظة التي تعدل فيها إدخالاً في جدول الصفحات. إذا قمت بكتابة عنوان فعلي جديد في PTE، فإن وحدة المعالجة المركزية لا تلاحظ: TLB لا يزال يحمل التعيين القديم. يجب عليك إفراغه بشكل صريح.

تقوم النواة بذلك باستخدام تعليمة invlpg، التي تلغي إدخال TLB لعنوان ظاهري واحد. عند استدعاء mprotect من مساحة المستخدم، هذا ما يحدث تحت الغطاء: تقوم النواة بتحديث أعلام PTE، ثم تفرغ TLB حتى تلتقط وحدة المعالجة المركزية الأذونات الجديدة.

هذا له آثار أمنية مباشرة. في استغلال النواة، إذا تمكنت من الكتابة في PTE (على سبيل المثال، مسح بت NX لجعل المكدس قابلاً للتنفيذ)، فأنت بحاجة أيضًا إلى إفراغ TLB قبل أن تشرف وحدة المعالجة المركزية على التغيير. أحيانًا تقوم النواة بذلك كأثر جانبي لمسار التعليمات البرمجية الذي قمت بتشغيله. وأحيانًا تحتاج لترتيب ذلك بنفسك. في كلتا الحالتين، تحتاج لمعرفة أن TLB موجود، وإلا فإن استغلالك يعمل نظريًا ولكن ليس عمليًا.


الصفحات الضخمة: عندما تنتهي عملية التنقل مبكرًا

في النموذج أعلاه، مررنا عبر جميع المستويات الأربعة. لكن يمكن أن تنتهي عملية التنقل مبكرًا إذا تم تعيين بت حجم الصفحة (البت 7).

على المستوى 3 (PUD): إذا تم تعيين البت 7، فإن الإدخال يعين صفحة بحجم 1 جيجابايت مباشرة. يتم أخذ العنوان الفعلي من الإدخال، وتصبح البتات [29:0] من العنوان الظاهري هي الإزاحة (30 بت = 1 جيجابايت).

على المستوى 2 (PMD): إذا تم تعيين البت 7، فإن الإدخال يعين صفحة بحجم 2 ميجابايت. تصبح البتات [20:0] من العنوان الظاهري هي الإزاحة (21 بت = 2 ميجابايت).

سترى غالبًا صفحات ضخمة في تعيينات النواة. منطقة التعيين المباشر للنواة (0xffff888000000000 على معظم نواة 64 بت) تستخدم بشكل متكرر صفحات بحجم 2 ميجابايت أو 1 جيجابايت لتقليل الضغط على TLB.

إذا واجهت صفحة ضخمة أثناء تنقلك، فإن الصيغة تتغير:``` 2 MB page: phys = (PMD_entry & 0x000FFFFFFFE00000) | (VA & 0x1FFFFF) 1 GB page: phys = (PUD_entry & 0x000FFFFFC0000000) | (VA & 0x3FFFFFFF)

root@kitploit:~
---

## أتمتة المسار

الآن بعد أن عرفنا العملية، لنقم بترميزها. `pagewalk.py` هو سكريبت Python لـ gdb يقوم بنفس المسار الذي قمنا به للتو. المنطق الأساسي يتناسب مع دالة واحدة:```python
ADDR_MASK = 0x000FFFFFFFFFF000

def read_phys(addr):
    """Read a 64-bit value from guest physical memory via QEMU monitor."""
    result = gdb.execute(f"monitor xp/1gx {addr:#x}", to_string=True)
    return int(result.strip().split(":")[1].strip(), 16)

def pagewalk(va):
    cr3 = int(gdb.parse_and_eval("$cr3"))
    pgd_base = cr3 & ADDR_MASK

    # Decompose the virtual address
    pgd_idx = (va >> 39) & 0x1FF
    pud_idx = (va >> 30) & 0x1FF
    pmd_idx = (va >> 21) & 0x1FF
    pt_idx  = (va >> 12) & 0x1FF
    offset  =  va        & 0xFFF

    # Walk: each level is the same pattern
    pgd_entry = read_phys(pgd_base + pgd_idx * 8)
    if not (pgd_entry & 1): return None       # Not present
    pud_base = pgd_entry & ADDR_MASK

    pud_entry = read_phys(pud_base + pud_idx * 8)
    if not (pud_entry & 1): return None
    if pud_entry & (1 << 7):                   # 1 GB huge page
        return (pud_entry & 0x000FFFFFC0000000) | (va & 0x3FFFFFFF)
    pmd_base = pud_entry & ADDR_MASK

    pmd_entry = read_phys(pmd_base + pmd_idx * 8)
    if not (pmd_entry & 1): return None
    if pmd_entry & (1 << 7):                   # 2 MB huge page
        return (pmd_entry & 0x000FFFFFFFE00000) | (va & 0x1FFFFF)
    pt_base = pmd_entry & ADDR_MASK

    pt_entry = read_phys(pt_base + pt_idx * 8)
    if not (pt_entry & 1): return None

    return (pt_entry & ADDR_MASK) | offset

البرنامج النصي الكامل (مع فك العلامة وإخراج منسق) موجود في pagewalk.py. قم بتضمينه (source) واستخدمه للتحقق من عملك اليدوي، أو لاستكشاف عناوين أخرى:``` (gdb) source ./pagewalk.py Page walk command loaded. Usage: pagewalk (gdb) pagewalk 0x7ffe08985c90 Decoded Virtual Address: PGD=0x0ff PUD=0x1f8 PMD=0x044 PT=0x185 Offset=0xc90

CR3: 0x00000000066c7000 PGD[0x0ff]: 0x0000000006713067 [Present RW User Accessed Dirty] PUD[0x1f8]: 0x00000000066ac067 [Present RW User Accessed Dirty] PMD[0x044]: 0x00000000066c4067 [Present RW User Accessed Dirty] PT[0x185]: 0x80000000037fd867 [Present RW User Accessed Dirty NX]

Physical address: 0x00000000037fdc90

root@kitploit:~
![gdb showing pagewalk.py output with the full page table walk](https://assets.kitploit.com/production/public/readmes/12435/ef194b87049d2685d373c71b3450ee6e9939781c5f52931d6bd762ceffee5e07.png)

لاحظ كيف يتحقق السكريبت من الصفحات الضخمة (huge pages) على مستويي PUD و PMD قبل متابعة المسير. هذا هو نفس المنطق الذي ناقشناه في قسم الصفحات الضخمة: إذا تم تعيين بت حجم الصفحة (PageSize, بت 7)، ينتهي المسير مبكرًا ويصبح الإزاحة أوسع.

حاول مسير عنوان دالة: سترى أن بت NX غير معين (يجب أن يكون الكود قابلاً للتنفيذ). حاول مسير قسم بيانات للقراءة فقط: سترى أن بت R/W غير معين.

---

## قراءة الذاكرة الفعلية بدون QEMU

خلال هذا التمرين، استخدمنا `monitor xp` لقراءة الذاكرة الفعلية مباشرة. يعمل ذلك لأن شاشة QEMU (monitor) تقع خارج الجهاز الظاهري ويمكنها الوصول إلى مساحة العناوين الفعلية للضيف. في استغلال حقيقي، لا تتوفر لديك هذه الرفاهية.

يحل النواة هذه المشكلة لنفسها باستخدام **منطقة الخريطة المباشرة (direct-map region)**: تعيين افتراضي متصل لـ _كل_ ذاكرة الوصول العشوائي الفعلية. على معمارية x86-64، تبدأ هذه المنطقة تقليديًا من `0xffff888000000000`، ولكن مع تمكين KASLR يتم عشوائية القاعدة. تخزن النواة القاعدة الفعلية في رمز يسمى `page_offset_base`.

قمنا بالإقلاع باستخدام `nokaslr`، لذا فإن القاعدة في وضعها الافتراضي. دعنا نؤكد:```
(gdb) x/s 0xffff888000000000 + 0x37fdc90
0xffff888037fdc90: "FLAG{p4g3_t4bl3_w4lk3r}"

نفس الذاكرة الفعلية، يتم الوصول إليها عبر عنوان افتراضي للنواة. هكذا تقرأ النواة نفسها الذاكرة الفعلية التعسفية: phys_to_virt() هو مجرد page_offset_base + phys_addr.

وهذا أيضًا هو السبب الذي يجعل استغلالات النواة تهتم بتسريب page_offset_base. إذا تم تمكين KASLR، فإنك لا تعرف أين تبدأ الخريطة المباشرة، لذا لا يمكنك تحويل العناوين الفعلية إلى عناوين افتراضية للنواة. قم بتسريب الأساس، ويمكنك قراءة أو كتابة أي عنوان فعلي عبر الخريطة المباشرة، بما في ذلك إدخلات جدول الصفحات نفسها.


إيجاد جداول صفحات عملية أخرى

ضمنت إعداداتنا أن CR3 يشير إلى جداول صفحات الثنائي التحدي عندما أوقف gdb الجهاز الافتراضي. لكن ماذا لو احتجت إلى التنقل عبر جداول صفحات عملية مختلفة؟

يتم تخزين قيمة CR3 لكل عملية في task_struct الخاصة بها. المسار هو:``` task_struct -> mm_struct -> pgd -> physical page

root@kitploit:~
في gdb مع رموز النواة، يمكنك العثور على task_struct الخاصة بـ init (PID 1) واستخراج جذر جدول الصفحات الخاص بها:```
(gdb) p/x init_task.mm->pgd
$1 = 0xffff8880066c7000

هذا عنوان افتراضي للنواة في الخريطة المباشرة. قم بإزالة القاعدة للحصول على العنوان الفعلي:``` 0xffff8880066c7000 - 0xffff888000000000 = 0x66c7000

root@kitploit:~
هذا هو نفس CR3 الذي بدأنا به، وهذا منطقي: برنامجنا الثنائي للتحدي هو PID 1 في هذا initramfs الأدنى.

بالنسبة للعمليات الأخرى، ستجتاز قائمة المهام (`init_task.tasks` قائمة مترابطة)، وتجد الهدف، وتستخرج `mm->pgd` الخاص به بنفس الطريقة. كل عملية لها شجرة جدول صفحات خاصة بها متجذرة في CR3 الخاص بها. يقوم النواة بتبديل CR3 عند كل تبديل سياق، مما يعطي كل عملية وهم الذاكرة الخاصة.

---

## ما يعنيه هذا

إذا وصلت إلى هذا الحد من خلال القيام بالتجوال فعليًا (وليس مجرد القراءة)، فلديك الآن شيء لا يمكن لأي قدر من الرسوم البيانية أن يمنحك إياه: حدس لكيفية عمل الذاكرة فعليًا على مستوى العتاد. إليك أين يؤتي هذا الحدس ثماره:

**يعمل ASLR على عشوائية العنوان الافتراضي، وليس التجوال.** بنية جدول الصفحات هي نفسها دائمًا: أربعة مستويات، 512 إدخالًا لكل منها، نفس تخطيط البتات. يغير ASLR المؤشرات التي ستحسبها، لكن العملية متطابقة.

**يتم فرض W^X في جدول الصفحات.** بت R/W وبت NX في PTE هما ما يجعلان `mprotect` يعمل. عندما يحاول استغلال تنفيذ كود شيل على المكدس، تتحقق وحدة المعالجة المركزية من بت NX أثناء الترجمة وتحدث خطأ.

**SMEP و SMAP يتحققان من بت المستخدم.** يفحص منع تنفيذ/وصول وضع المشرف بت المستخدم/المشرف عبر جميع مستويات جدول الصفحات. إذا وضع أي إدخال علامة على العنوان على أنه وضع مستخدم وحاول كود النواة تنفيذه أو الوصول إليه، تحدث وحدة المعالجة المركزية خطأ. هذا هو السبب في أن استغلالات النواة الحديثة لا يمكنها ببساطة القفز إلى كود شيل في مساحة المستخدم.

**غالبًا ما تستهدف استغلالات النواة جداول الصفحات مباشرة.** إذا كان بإمكانك الكتابة إلى PTE، يمكنك تغيير الذاكرة الفعلية التي يعينها عنوان افتراضي، أو تغيير الأذونات، أو إعادة تعيين ذاكرة النواة على أنها قابلة للوصول من قبل المستخدم. فهم التجوال هو فهم سطح الهجوم.

**KPTI يقسم جدول الصفحات إلى اثنين.** بدلاً من مجموعة واحدة من جداول الصفحات لكل عملية، هناك الآن اثنان: واحد لوضع المستخدم (مع إلغاء تعيين جميع صفحات النواة تقريبًا) وواحد لوضع النواة (مع كل شيء). تقوم النواة بتبديل CR3 عند كل دخول وخروج من استدعاء النظام. يمكنك ملاحظة هذا: أوقف الجهاز الافتراضي أثناء وجودك في مساحة المستخدم واقرأ CR3، ثم ضع نقطة توقف على دخول استدعاء النظام واقرأ CR3 مرة أخرى. سيكونان مختلفين. جدول صفحات وضع المستخدم ببساطة لا يحتوي على إدخالات لذاكرة النواة، لذلك لا يوجد شيء لتسريبه حتى لو اكتمل التجوال.

في المرة القادمة التي يذكر فيها تقرير استغلال النواة "إعادة تعيين جداول الصفحات"، لن يكون الأمر مجردًا. ستعرف بالضبط أي بايتات يتحدثون عنها، لأنك قرأتها بنفسك.

---

## قراءة إضافية

- [Understanding Paging](https://blog.zolutal.io/understanding-paging/):
  البرنامج التعليمي الذي ألهم هذا المقال. يحاول هذا المقال دفع الاستكشاف أبعد قليلاً، لكن هذا هو المكان الذي بدأت منه بالنسبة لي
- [Intel SDM, Volume 3A, Chapter 4: "Paging"](https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html):
  المرجع الرسمي (مقروء بشكل مفاجئ بمجرد أن تقوم بتجوال يدوي)
- [pwn.college: Kernel Security](https://pwn.college/system-security/kernel-security/):
  التحديات التي جعلتني أستكشف هذه الأشياء فعليًا، موصى بها بشدة
تنزيل الأداة