
استخدم CVE-2016-3308 لفساد ذاكرة heap لسطح المكتب في win32k
المؤلف : @55-AA، 18 سبتمبر 2016
##مقدمة
كومة سطح المكتب (Desktop heap) هي تجمع نواة يستخدمه win32k، ويمكن استغلالها بواسطة تطبيق وضع المستخدم. سأصف هنا بالتفصيل كيفية تنفيذ استغلال موثوق بحيث يمكن قراءة/كتابة عنوان عشوائي في النواة. هذا الشرح والتحليل المرتبط به تم على تثبيت win7_sp1_x86 (build 17842).
##الثغرة
في 9 أغسطس 2016، أصدرت Microsoft MS16-098. كود الثغرة موجود داخل الدالة win32k!xxxInsertMenuItem، النموذج الأولي للدالة هو:
BOOL xxxInsertMenuItem(
PMENU pMenu,
UINT wIndex,
BOOL fByPosition,
LPMENUITEMINFOW lpmii,
PUNICODE_STRING pstrItem
);
أولاً لنلق نظرة على كود الخلل الوهمي في xxxInsertMenuItem:
if (pMenu->cItems >= pMenu->cAlloced) {
if (pMenu->rgItems) {
pNewItems = (PITEM)DesktopAlloc(
pMenu->head.rpdesk,
(pMenu->cAlloced + CMENUITEMALLOC) * sizeof(ITEM),
DTAG_MENUITEM);
......
pMenu->cAlloced += CMENUITEMALLOC;
pMenu->rgItems = pNewItems;
if (wIndex != MFMWFP_NOITEM)
pItem = MNLookUpItem(pMenu, wIndex, fByPosition, &pMenuItemIsOn);
......
pMenu->cItems++;
if (pItem != NULL) {
RtlMoveMemory(pItem + 1, pItem, (pMenu->cItems - 1) *
sizeof(ITEM) - ((char *)pItem - (char *)pMenu->rgItems));
} else {
في الكود أعلاه، عندما تمت إضافة العنصر التاسع (من الأول) إلى pMenu، تم استدعاء DesktopAlloc() لإعادة تخصيص pMenu->rgItems جديد. ثم تم استدعاء MNLookUpItem() للحصول على موقع العنصر في pMenu->rgItems. لكن pItem الذي تم إرجاعه بواسطة MNLookUpItem() هو rgItems لقائمة فرعية أخرى (pSubMenu) بدلاً من pMenu، لذلك عندما تم استدعاء RtlMoveMemory()، تم الكتابة فوق pItem للقائمة الفرعية والباييتات التالية بسبب الحجم الخاطئ للنقل.
التالي هو كود التفكيك حول الخلل، والذي قد يؤدي إلى كتابة فوق الكومة، ويمكن استغلاله لبناء كتلة مزيفة:
0: kd> u win32k!xxxInsertMenuItem+0x1f5 l8
win32k!xxxInsertMenuItem+0x1f5:
95d295af 6bc06c imul eax,eax,6Ch
95d295b2 2bc3 sub eax,ebx
95d295b4 034634 add eax,dword ptr [esi+34h]
95d295b7 50 push eax
95d295b8 8d436c lea eax,[ebx+6Ch]
95d295bb 53 push ebx
95d295bc 50 push eax
95d295bd e85ea40100 call win32k!memmove (95d43a20)
لتتبع الخلل، أستخدم نقاط التوقف هذه في WinDbg:
ba e1 win32k!xxxInsertMenuItem
ba e1 win32k!xxxInsertMenuItem+0xf3
95d294e3 e843e70200 call win32k!DesktopAlloc (836d7bf5)
ba e1 win32k!xxxInsertMenuItem+0x129
95d294e3 e80de70200 call win32k!DesktopAlloc (836d7bf5)
ba e1 win32k!xxxInsertMenuItem+0x1f5
95d295af 6bc06c imul eax,eax,6Ch
من أجل تشغيل الخلل، يجب تنفيذ المراحل التالية:
##كومة سطح المكتب (Desktop Heap)
كومة سطح المكتب هي تجمع عام تستخدمه جميع عمليات واجهة المستخدم الرسومية (GUI). جميع كائنات GUI، مثل النافذة والقائمة، مخزنة في كومة سطح المكتب، وتتم إدارتها بواسطة موزع كومة النواة. يستخدم موزع كومة النواة دوال مألوفة مثل RtlAllocateHeap و RtlFreeHeap. على عكس كومة وضع المستخدم، لا تستخدم كومة سطح المكتب أي موزعات أمامية، لذلك لا توجد كومة تجزئة منخفضة (LFH)، ولا قائمة جانبية (Lookaside list)، إلخ. كما لا يوجد ترميز للكومة (Heap Encoding) حتى Windows 8 وما بعده. فيما يلي هيكل الكتلة (trunk) على win7_sp1_x86:
typedef struct _HEAP_ENTRY {
USHORT Size;
UCHAR Flags;
UCHAR SegmentIndex;
USHORT PreviousSize;
UCHAR SegmentOffset;
UCHAR UnusedBytes;
} HEAP_ENTRY, *PHEAP_ENTRY;
يمثل الحقلان Size و PreviousSize حجم الكتل بعد الإزاحة لليمين بمقدار HEAP_GRANULARITY_SHIFT (المعرف بـ 3 في نظام 32 بت)، يحدد الحقل Size الكتلة الحالية، ويحدد PreviousSize الكتلة الأمامية. عادةً ما يتم تعيين البت الأدنى من Flags إلى HEAP_ENTRY_BUSY (0x01)، مما يمثل أن الكتلة قيد الاستخدام، وإلا إلى 0x00.
يوضح الشكل التالي العلاقة بين هذه الحقول وكتل الكتل. الكلمة المزدوجة (WORD) السفلية الخضراء الثانية (0x000f) تمثل أن حجم الكتلة الحالية هو 0x78 بايت، الكلمة المزدوجة (WORD) السفلية السوداء الثانية (0x0003) تمثل أن حجم الكتلة الأمامية هو 0x18 بايت، والكلمات المزدوجة (WORD) بالخط الأحمر (0x0001) تمثل أن الكتل الحالية قيد الاستخدام. هنا، حجم الكتلة يشمل حجم الرأس، والرأس معرف بهيكل HEAP_ENTRY أعلاه.

هذه هي الميزة الأكثر أهمية لفساد الكومة، حيث يحصل موزع الكومة دائمًا على الكتلة التي تم تحريرها مؤخرًا. هذا يعني أنه يمكننا فعليًا تخصيص كتلة بأي حجم وفي موقع معين نريده.
##الفساد (Corruption)
باستغلال الخلل، يمكنني الكتابة فوق بعض البايتات في كومة سطح المكتب، وبالتالي سأحصل على كتلة مزيفة تحل محل كتلة عادية، ثم أحرر الكتلة المستبدلة، بحيث يتم دفع الكتلة المزيفة إلى أعلى قائمة الكتل الحرة. بعد ذلك يتم إعادة استخدام الكتلة المزيفة، يمكنني كتابة أي بايتات فيها، وتتداخل المنطقة القابلة للكتابة مع عدة كتل عادية، لكنها لا تغطي مساحة النواة بأكملها. لذا أحتاج إلى بناء عملية قراءة/كتابة أولية (R/W primitive) أخرى في المنطقة المتداخلة، تستفيد من tagWND.strName لكتابة عنوان عشوائي. يمكن لمؤشر strName.Buffer أن يقودنا إلى أي مكان بما في ذلك مساحة النواة ومساحة المستخدم. بالطبع، هدفنا هو فقط nt!HalDispatchTable.
يوضح الشكل التالي إجراء تغيير الكومة:

وفقًا للتوضيحات، أقوم بإفساد كومة سطح المكتب خطوة بخطوة، وأنفذ الاستغلال عبر المراحل التالية:
الخطوات الرئيسية المذكورة أعلاه لبناء فنغ شوي الكومة (heap fengshui):