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

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

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

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

دليل الأدوات

الفئات

عرض جميع الفئات
Loading categories
vbox_cve_2017_10235 — [CVE-2017-10235] وصف وPoC لثغرة تجاوز سعة المخزن المؤقت في جهاز E1000 في VirtualBox | Kitploit
أدوات/GitHubGitHub/fundacion-sadosky/vbox_cve_2017_10235
أمان الأنظمة المدمجةتحليل الثغرات الأمنيةالاستغلالالاختبار العشوائيأمن الأجهزةاستغلال الملفات الثنائية
GitHubfundacion-sadosky/vbox_cve_2017_10235

vbox_cve_2017_10235

[CVE-2017-10235] وصف وPoC لثغرة تجاوز سعة المخزن المؤقت في جهاز E1000 في VirtualBox

الأكثر شعبية

عرض الكل →

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

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

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

عرض جميع الأدوات →
مشاركة
عرض المستودع
365منذ 8 سنواتتمت المراجعة من قبل Kitploit

CVE-2017-10235: تجاوز سعة المخزن المؤقت لجهاز VirtualBox E1000

مقدمة

توثق الوثيقة التالية خطأً تم العثور عليه في VirtualBox الإصدار v5.1.22 (تم إصلاحه الآن في v5.1.24)، في مكوّن محاكاة جهاز الضيف DevE1000 (محاكاة وحدة التحكم في الإيثرنت Intel 82540EM)، في الدالة e1kFallbackAddToFrame، والذي يؤدي إلى تجاوز سعة المخزن المؤقت في المضيف عندما يكون نظام تشغيل الضيف خاضعًا لسيطرة مهاجم.

تم اعتراف Oracle بالثغرة في التحديث الحرج لشهر يوليو 2017 مع إصدار CVE-2017-10235.

تم تأكيد الثغرة على كل من مضيف Linux (Ubuntu 16.04) ومضيف Windows (v8.1) يشغّلان ضيفًا بنظام Linux (أيضًا Ubuntu 16.04)، لكن يمكن تشغيل الثغرة في العديد من التركيبات المختلفة للمضيف/الضيف. في جميع السيناريوهات يُفترض إعداد الشبكة الافتراضي: محول شبكة واحد فقط موصول بـ NAT من النوع Intel PRO/1000 MT Desktop (82540EM).

بما أن بنى التحكم (بما في ذلك مؤشرات الدوال) يمكن استبدالها ببيانات يتحكم بها المهاجم، فمن الآمن الافتراض أن تنفيذ التعليمات البرمجية عن بُعد يمكن تحقيقه في العديد من السيناريوهات. خصصت Oracle درجة CVSS منخفضة لهذه الثغرة لأنها رأت أن لها خطر سرية None وسلامة Low، وهو ما نعتقد أنه لا يعكس الإمكانات الكاملة لاختراق هذه الثغرة (يوجد أدناه شرح لإمكانية تنفيذ التعليمات البرمجية عن بُعد).

وصف الثغرة واستغلالها

كود VirtualBox الذي ينفّذ محاكاة وحدة تحكم الإيثرنت Intel 82540EM (في src/VBox/Devices/Network/DevE1000.cpp)، في الدالة e1kFallbackAddToFrame، ينفّذ تجزئة TCP بالأجهزة (hardware TCP Segmentation):

root@kitploit:~

static int e1kFallbackAddToFrame(PE1KSTATE pThis, E1KTXDESC *pDesc,
                                bool fOnWorkerThread)
{
#ifdef VBOX_STRICT
   PPDMSCATTERGATHER pTxSg = pThis->CTX_SUFF(pTxSg);
   Assert(e1kGetDescType(pDesc) == E1K_DTYP_DATA);
   Assert(pDesc->data.cmd.fTSE);
   Assert(!e1kXmitIsGsoBuf(pTxSg));
#endif

   uint16_t u16MaxPktLen = pThis->contextTSE.dw3.u8HDRLEN +
                           pThis->contextTSE.dw3.u16MSS;
   Assert(u16MaxPktLen != 0);
   Assert(u16MaxPktLen < E1K_MAX_TX_PKT_SIZE);

تتحقق هذه الدالة بشكل صحيح من أن أقصى طول لحزمة الإرسال (u16MaxPktLen) أقل من الحد الأقصى القياسي البالغ 16288 بايتًا (E1K_MAX_TX_PKT_SIZE)، لكنها تفعل ذلك في شكل ماكرو Assert سيتم تعطيله في بناء الإصدار (release build)، مما يترك التحقق عديم الفائدة عمليًا للمستخدم النهائي. ويمكن مقارنة ذلك بالدالة المماثلة e1kAddToFrame، التي تفرض التحقق باستخدام if صريح بدلاً من Assert:

root@kitploit:~

static bool e1kAddToFrame(PE1KSTATE pThis, RTGCPHYS PhysAddr,
                         uint32_t cbFragment)
{
   PPDMSCATTERGATHER   pTxSg    = pThis->CTX_SUFF(pTxSg);
   bool const          fGso     = e1kXmitIsGsoBuf(pTxSg);
   uint32_t const      cbNewPkt = cbFragment + pThis->u16TxPktLen;

   if (RT_UNLIKELY( !fGso && cbNewPkt > E1K_MAX_TX_PKT_SIZE ))
   {
       E1kLog(("%s Transmit packet is too large: %u > %u(max)\n",
               pThis->szPrf, cbNewPkt, E1K_MAX_TX_PKT_SIZE));
       return false;
   }

يُحدَّد الفرق بين استخدام الدالة العادية (e1kAddToFrame) والدالة الاحتياطية (e1kFallbackAddToFrame) في e1kXmitDesc()، ويعتمد على عاملين: أن تكون علامة TSE مفعّلة في واصفات البيانات/السياق (التي يتحكم بها نظام التشغيل باستخدام جهاز الضيف)، وأن تكون علامة GSO معطّلة. يعتمد الأخير على عوامل كثيرة، وبالتالي هناك طرق عديدة لتعطيله، لكن الأكثر ملاءمة هو تفعيل وضع الاسترجاع (loopback)، الذي يُضبط عبر سجل التحكم في الاستقبال (بتات RCTL.LBM)، وهو أيضًا خاضع لسيطرة نظام تشغيل الضيف.

سيجعل تفعيل وضع الاسترجاع الدالة e1kXmitAllocBuf تستخدم المخزن المؤقت aTxPacketFallback (مخزن حزم الإرسال المستخدم للاسترجاع الاحتياطي TSE والاسترجاع) لتخصيص مخزن PDM للتشتت/التجميع (scatter/gather)، بالطول المذكور البالغ 16288 بايتًا (E1K_MAX_TX_PKT_SIZE)، والإشارة إلى أن GSO سيكون معطلاً (بتعيين NULL في pvUser).

root@kitploit:~

if (RT_LIKELY(GET_BITS(RCTL, LBM) != RCTL_LBM_TCVR))
{

  ...

}
else
{
 /* Create a loopback using the fallback buffer and preallocated SG. */
 AssertCompileMemberSize(E1KSTATE, uTxFallback.Sg, 8 * sizeof(size_t));
 pSg = &pThis->uTxFallback.Sg;
 pSg->fFlags      = PDMSCATTERGATHER_FLAGS_MAGIC |
                    PDMSCATTERGATHER_FLAGS_OWNER_3;
 pSg->cbUsed      = 0;
 pSg->cbAvailable = 0;
 pSg->pvAllocator = pThis;
 pSg->pvUser      = NULL; /* No GSO here. */
 pSg->cSegs       = 1;
 pSg->aSegs[0].pvSeg = pThis->aTxPacketFallback;
 pSg->aSegs[0].cbSeg = sizeof(pThis->aTxPacketFallback);
}

سيؤدي هذا إلى جعل استدعاء الدالة e1kXmitIsGsoBuf (داخل e1kXmitDesc) يُرجع False، ومع تفعيل TSE في واصف البيانات، سيتجه تدفق التنفيذ إلى e1kFallbackAddToFrame (بدلاً من الدالة الأكثر أمانًا e1kAddToFrame ذات التحقق الصحيح).

root@kitploit:~

/*
 * Add the descriptor data to the frame.  If the frame is complete,
 * transmit it and reset the u16TxPktLen field.
 */
if (e1kXmitIsGsoBuf(pThis->CTX_SUFF(pTxSg)))
{

  ...

}
else if (!pDesc->data.cmd.fTSE)
{

  ...

}
else
{
    STAM_COUNTER_INC(&pThis->StatTxPathFallback);
    rc = e1kFallbackAddToFrame(pThis, pDesc, fOnWorkerThread);
}

داخل e1kFallbackAddToFrame، ومع تعطيل التحقق المذكور أعلاه في بناء الإصدار (release build)، يمكن ضبط MSS على قيمة كبيرة بشكل تعسفي (حتى 64K ناقص HDRLEN)، مما يسمح بتمرير قيمة DTALEN كبيرة بشكل تعسفي إلى e1kFallbackAddSegment:

root@kitploit:~

/*
* Carve out segments.
*/
int rc;
do
{
 /* Calculate how many bytes we have left in this TCP segment */
 uint32_t cb = u16MaxPktLen - pThis->u16TxPktLen;
 if (cb > pDesc->data.cmd.u20DTALEN)
 {
     /* This descriptor fits completely into current segment */
     cb = pDesc->data.cmd.u20DTALEN;
     rc = e1kFallbackAddSegment(pThis, pDesc->data.u64BufAddr, cb,
                 pDesc->data.cmd.fEOP /*fSend*/, fOnWorkerThread);

ستستخدم الدالة e1kFallbackAddSegment هذه القيمة (الآن كوسيط u16Len) للنسخ من ذاكرة الضيف إلى المخزن المؤقت aTxPacketFallback في ذاكرة المضيف (عبر PDMDevHlpPhysRead) دون مزيد من التحقق من هذا الطول، مما يسبب تجاوز سعة المخزن المؤقت (بسعة مخزن تبلغ 16288 بايتًا مع حجم ذاكرة يصل إلى 64K).

root@kitploit:~

static int e1kFallbackAddSegment(PE1KSTATE pThis, RTGCPHYS PhysAddr,
                   uint16_t u16Len, bool fSend, bool fOnWorkerThread)
{
    int rc = VINF_SUCCESS;
    /* TCP header being transmitted */
    struct E1kTcpHeader *pTcpHdr = (struct E1kTcpHeader *)
            (pThis->aTxPacketFallback + pThis->contextTSE.tu.u8CSS);
    /* IP header being transmitted */
    struct E1kIpHeader *pIpHdr = (struct E1kIpHeader *)
            (pThis->aTxPacketFallback + pThis->contextTSE.ip.u8CSS);

    E1kLog3(("%s e1kFallbackAddSegment: Length=%x, remaining payload=%x,
             header=%x, send=%RTbool\n", pThis->szPrf, u16Len,
             pThis->u32PayRemain, pThis->u16HdrRemain, fSend));
    Assert(pThis->u32PayRemain + pThis->u16HdrRemain > 0);

    PDMDevHlpPhysRead(pThis->CTX_SUFF(pDevIns), PhysAddr,
                      pThis->aTxPacketFallback + pThis->u16TxPktLen, u16Len);

إمكانية تنفيذ التعليمات البرمجية عن بُعد (RCE)

لجعل هذه الثغرة أكثر قابلية للتحول إلى تنفيذ تعليمات برمجية عن بُعد (RCE)، تجدر الإشارة إلى أن المتغير الذي يلي المخزن المؤقت مباشرة هو فهرسه (u16TxPktLen)، الذي يُستخدم للكتابة فيه (كإزاحة في وسيط PDMDevHlpPhysRead). إن التحكم في هذه القيمة من خلال تجاوز سعة أولي للمخزن المؤقت (ناتج عن واصف بيانات أول بطول E1K_MAX_TX_PKT_SIZE + 2 بايت) سيسمح بعدها بالكتابة (في استدعاء ثانٍ لـ PDMDevHlpPhysRead مع واصف بيانات ثانٍ) إلى أي عنوان ذاكرة يبعد حتى 64K عن المخزن المؤقت، دون الحاجة إلى استبدال كل الذاكرة الواقعة بينهما (وهو ما كان سيجعل الهجوم أكثر تعقيدًا، في محاولة لتجنب انهيار محتمل).

بالقرب من المخزن المؤقت المستهدف aTxPacketFallback، بعد بضعة أسطر بالأسفل وضمن نطاق 64K، يُعرَّف الهيكل g_aE1kRegMap، الذي يتضمن مصفوفة من مؤشرات الدوال التي تنفّذ معالجات القراءة والكتابة (pfnRead وpfnWrite)، وهو ما سيكون هدفًا مثاليًا لتجاوز سعة المخزن المؤقت الثاني لتسهيل تنفيذ التعليمات البرمجية عن بُعد.

خطأ في e1kXmitAllocBuf

تجدر الإشارة من باب الاكتمال إلى تعقيد (بسيط) في ناقل الهجوم هذا: يبدو أن هناك خطأ في الدالة e1kXmitAllocBuf، حيث في حالة وضع الاسترجاع، لا يُعاد تعيين cbTxAlloc (عدد البايتات في الحزمة التالية) إلى الصفر، كما يحدث في الحالة العادية (في الفرع الآخر من جملة if الخاصة بها). يؤدي هذا إلى تعليق الخيط في حلقة while الخاصة بـ e1kLocateTxPacket (داخل e1kXmitPending):

root@kitploit:~

while (e1kLocateTxPacket(pThis))
{
   fIncomplete = false;
   /* Found a complete packet, allocate it. */
   rc = e1kXmitAllocBuf(pThis, pThis->fGSO);
   /* If we're out of bandwidth we'll come back later. */
   if (RT_FAILURE(rc))
       goto out;
   /* Copy the packet to allocated buffer and send it. */
   rc = e1kXmitPacket(pThis, fOnWorkerThread);
   /* If we're out of bandwidth we'll come back later. */
   if (RT_FAILURE(rc))
       goto out;
}

يبدو أن هذا يحدث لأن e1kLocateTxPacket يُرجع True بشكل مبكر في الحالة التي يكون فيها cbTxAlloc غير صفري، ولا يصل إلى الكود الذي يتحقق مما إذا كان iTxDCurrent مساويًا لـ nTxDFetched (الحالة المعتادة عند معالجة جميع الواصفات)، وهو ما كان سيجعل الدالة تُرجع False في الأحوال العادية، مما ينهي الحلقة المذكورة أعلاه فعليًا.

root@kitploit:~

static bool e1kLocateTxPacket(PE1KSTATE pThis)
{
   LogFlow(("%s e1kLocateTxPacket: ENTER cbTxAlloc=%d\n",
            pThis->szPrf, pThis->cbTxAlloc));
   /* Check if we have located the packet already. */
   if (pThis->cbTxAlloc)
   {
       LogFlow(("%s e1kLocateTxPacket: RET true cbTxAlloc=%d\n",
                pThis->szPrf, pThis->cbTxAlloc));
       return true;
   }

يترجم هذا إلى اشتراط أن تكون أول حزمة تُرسل إلى الجهاز (بعد ضبط وضع الاسترجاع) هي الحزمة التي تسبب التجاوز، وإلا ستتجمد الآلة الافتراضية (وينتهي الأمر بحرمان من الخدمة DoS بدلاً من تنفيذ التعليمات البرمجية عن بُعد RCE).

إثبات المفهوم

نظرًا لأن إعداد جهاز الشبكة بعيد كل البعد عن البساطة، ولتجنب بناء برنامج تشغيل مخصص له، تم تعديل برنامج تشغيل E1000 الخاص بنواة Linux عامة لتوليد الواصفات (السياق والبيانات معًا) التي تسبب التجاوز. هذه النواة المعدلة متاحة للـتنزيل من هذا المستودع. تم اختبارها على ضيف Ubuntu 16.04، مما تسبب في انهيار على كل من مضيفي Linux وWindows. يتوفر وصف مفصّل هنا.

الحلول الممكنة

تم إصلاح الثغرة في التغيير 67974 (bugref:8881). تم تحويل الفحوصات التي كانت على شكل Assert في e1kFallbackAddToFrame إلى فحوصات صريحة على شكل جمل if، تبقى الآن مفعّلة في بناء الإصدار (release build) (على غرار ما تم القيام به بالفعل في e1kAddToFrame). كما أصبح cbTxAlloc يُضبط الآن على الصفر في كلا الفرعين (وضع الاسترجاع والوضع العادي) في e1kXmitAllocBuf.

يمكن أن يكون الفحص الإضافي (الدفاعي) المقترح هنا، وغير المنفّذ في التغيير، هو وضع فحص في e1kFallbackAddSegment (وبالمثل في e1kAddToFrame)، قبل استدعاء PDMDevHlpPhysRead، للتحقق صراحةً من احتمال تجاوز المخزن المؤقت بذاكرة الضيف (بشكل أساسي أن يكون u16TxPktLen زائد u16Len أقل من طول المخزن المؤقت aTxPacketFallback).

تنزيل الأداة