
Driver Reverse & Exploitation
⚠️ تحذير: هذا المشروع تعليمي واستعراضي بشكل صارم. ليس موجّهًا للاستخدام في سياق خبيث. الهدف هو تعلّم منهجية الهندسة العكسية وخطوات استغلال درايفر في نظام ويندوز.
أشرح هنا المنهجية التي اتبعتها لحل التمرين المقترح من طرف d1rk(SaadAhla) https://github.com/SaadAhla، والذي يتمثل في إجراء هندسة عكسية واستغلال على درايفر شرعي وموقّع وغير موجود في قوائم الحظر (HVCI، LOLBIN...). يتوفّر برنامج بلغة C يسمح بإنهاء أي عملية نشطة على النظام عبر هذا الدرايفر الخاص بوضع النواة، وسأفصّل طريقة عمله بعد قليل.

📃 الاستخدام: DriverKiller.exe <nom_processus.exe> [-d]
الخيار -d: يسمح بحذف الخدمة والدرايفر من النظام بعد الاستغلال.
يجب تفعيل وضع توقيع الاختبار (testsigning mode) على الجهاز الهدف لأن شهادة الدرايفر منتهية الصلاحية.
الجزء 1 - الهندسة العكسية:
يوفّر التمرين ملفًا بامتداد .sys، مُسمّى بهاش SHA-256 الخاص به.
تتمثل الخطوة الأولى في فتح هذا الملف باستخدام IDA.
IDA متاح مجانًا. يكفي الذهاب إلى موقع Hex-Rays لإنشاء ترخيص وتنزيل البرنامج.
نبدأ بسرد جدول عناوين الاستيراد (IAT) الخاص بالدرايفر والبحث عن استدعاء API الذي يهمّنا: ZwTerminateProcess.
عند النقر المزدوج على ZwTerminateProcess، يقوم IDA بإعادة توجيهنا إلى الكود المترجم لهذه الدالة. وباختيار المدخل ثم عرض المراجع المتقاطعة (cross-references)، نحصل على قائمة بدوال الدرايفر التي تستدعيها.
نلاحظ أن الدالة sub_12EF4، عند الإزاحة 1CE، هي التي تستخدم ZwTerminateProcess. بعد النقر المزدوج، يعرض IDA كودها المترجم.
يكشف الكود المُفكَّك عن استدعاءات ZwOpenProcess (الذي يفتح مقبضًا للعملية الهدف) وZwTerminateProcess (الذي ينهي العملية عبر هذا المقبض).
من خلال الاطلاع على توثيق ZwOpenProcess (https://learn.microsoft.com/fr-fr/windows-hardware/drivers/ddi/ntddk/nf-ntddk-zwopenprocess)، نلاحظ أن المعامل ClientID يشير إلى مؤشر يحدد PID العملية المستهدفة.
في السطر أعلاه، تتم تهيئة ClientId.UniqueProcess بالمتغير v22. هذا الأخير يُعرَّف مباشرةً بالأعلى:
v22 = (void )((_QWORD *)i + 10);
لفهم هذا الإسناد، يجب تحديد المتغير i والحقل +10.
أعلى هذه الدالة، نلاحظ استدعاءً لـ ZwQuerySystemInformation مع المعامل SYSTEM_PROCESS_INFORMATION. نفهم أيضًا أن i هو عداد التكرار على مداخل هذه البنية مع المتغير v6.
وفقًا لتوثيق ZwQuerySystemInformation: (https://learn.microsoft.com/en-us/windows/win32/sysinfo/zwquerysysteminformation)، تُرجع هذه الدالة مصفوفة تحتوي على مدخل لكل عملية نشطة على النظام.
البنية SYSTEM_PROCESS_INFORMATION موصوفة هنا: https://learn.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntquerysysteminformation
typedef struct _SYSTEM_PROCESS_INFORMATION {
ULONG NextEntryOffset;
ULONG NumberOfThreads;
BYTE Reserved1[48];
UNICODE_STRING ImageName;
KPRIORITY BasePriority;
HANDLE UniqueProcessId;
PVOID Reserved2;
ULONG HandleCount;
ULONG SessionId;
PVOID Reserved3;
SIZE_T PeakVirtualSize;
SIZE_T VirtualSize;
ULONG Reserved4;
SIZE_T PeakWorkingSetSize;
SIZE_T WorkingSetSize;
PVOID Reserved5;
SIZE_T QuotaPagedPoolUsage;
PVOID Reserved6;
SIZE_T QuotaNonPagedPoolUsage;
SIZE_T PagefileUsage;
SIZE_T PeakPagefileUsage;
SIZE_T PrivatePageCount;
LARGE_INTEGER Reserved7[6];
} SYSTEM_PROCESS_INFORMATION;
تذكير: أحجام بعض الأنواع في ويندوز x64
typedef struct _UNICODE_STRING {
USHORT Length; -> 2
USHORT MaximumLength; -> + 2 = 4
PWSTR Buffer; -> + 8 = 12 (12 n'est pas un multiple de 8 donc padding de 4 ajouté en amont de Buffer) = 16
} UNICODE_STRING;حساب إزاحة UniqueProcessId:
ULONG NextEntryOffset; -> 4
ULONG NumberOfThreads; -> + 4 = 8
BYTE Reserved1[48]; -> + 48 = 56
UNICODE_STRING ImageName; -> + 16 = 72
KPRIORITY BasePriority; -> + 4 = 76 (76 n'est pas un multiple de 8 donc padding de 4 ajouté) = 80
HANDLE UniqueProcessId; -> + 8 = 88
إذن العضو UniqueProcessId يقع عند الإزاحة 0x50 (80 بالنظام العشري).
بالنظر إلى إسناد متغيرنا v22، نجد أن i يُحوَّل إلى مؤشر من نوع QWORD (8 بايت)
v22 = (void )((_QWORD *)i + 10);
v22 يقابل عنوان i + 10 * 8 = 80 بايت. هذا المتغير يحتوي بالفعل على PID المسترجَع من بنية SYSTEM_PROCESS_INFORMATION.
لمعرفة أي PID سيتم تمريره إلى ZwTerminateProcess، يجب تحليل الشرط المحيط بهذا الإسناد.
نلاحظ أنه يتم أولاً استرجاع اسم صورة العملية:
v9 = (wchar_t )((_QWORD *)i + 8);
v9 = عنوان i + 8 × 8 = 64 بايت. وهذا يقابل الحقل Buffer للعضو ImageName، حيث يقع هذا العضو عند الإزاحة 56 + 2 (USHORT) + 2 (USHORT) + 4 (padding) = 64
بناءً على المعالجات والحلقات البرمجية أدناه، يمكننا افتراض أنه تتم مقارنة بين اسم العملية المُمرَّر كوسيط (a2) والعمليات النشطة على النظام v9/String.
sub_1C078(String, v9, (int)v13); v17 = strupr(a2); v18 = strupr(String);
إذن، من المفترض أن يحتوي الوسيط a2 على اسم العملية التي سيتم إنهاؤها عبر ZwTerminateProcess.
نلاحظ أن a2 هو معامل للدالة sub_12EF4. للمضي قدمًا، يجب فحص مراجع هذه الدالة (أعدت تسميتها إلى ZwTerminateProcessCaller من أجل قراءة أوضح).
نلاحظ أن الدالة ZwTerminateProcessCaller تُستدعى بواسطة الدالة sub_13624 عند الإزاحة 61A.
قبل تحليل هذا الكود المُفكَّك، سأبحث عن مراجع الدالة sub_13624 (أعدت تسميتها إلى ZwTerminateProcessCallerCaller) للتأكد من أن هذا الكود يُستخدم فعلًا بعد استدعاء API لـ DeviceIoControl من وضع المستخدم (UserMode).
نلاحظ أن ZwTerminateProcessCallerCaller تُستدعى بواسطة الدالة sub_14130 (أعدت تسميتها إلى ZwTerminateProcessCallerCallerCaller ...ولحسن حظنا، إنها الأخيرة قبل نقطة الدخول 😅).
نلاحظ أن ZwTerminateProcessCallerCallerCaller تُستدعى بواسطة الدالة sub_1A4A8 عند الإزاحة 306.
نجد تعيين الدالة ZwTerminateProcessCallerCallerCaller:
memset64(DriverObject->MajorFunction, (unsigned __int64)ZwTerminateProcessCallerCallerCaller, 0x1Cu);
قبل العودة إلى الدالة sub_13624 (الاسم المستعار ZwTerminateProcessCallerCaller)، نستعيد الاسم الرمزي (Symbolic Name) واسم الجهاز (Device Name) (متطابقان هنا): Viragtlt.
بالعودة إلى ZwTerminateProcessCallerCaller، نلاحظ أن معاملها الثاني (أي a2) يقابل MasterIrp->AssociatedIrp.SystemBuffer.
مباشرةً فوق استدعاء ZwTerminateProcessCaller نجد كود IOCTL: -2106392528 (بالنظام الست عشري: 0x82730030).
بفضل هذه المعلومات، يمكننا استنتاج أنه لاستغلال هذا الدرايفر، يجب إرسال استدعاء API DeviceIoControl إلى الدرايفر مع اسم العملية المراد إنهاؤها في SystemBuffer.
🔷 المعلومات المستخلصة من الهندسة العكسية:
0x82730030ViragtltViragtltالجزء 2 - الاستغلال
لاستغلال هذا الدرايفر (إذا كان مثبتًا ونشطًا على الجهاز الهدف)، من الضروري فتح مقبض (handle) إليه، ثم إجراء استدعاء API لـ DeviceIoControl مع Buffer يحتوي اسم العملية التي نرغب في إنهائها.
لهذا التمرين، قمت بتطوير مشروع بلغة C يقوم بـ:
لقد أضفت أيضًا خيارًا -d يسمح بحذف الخدمة والدرايفر من النظام بعد الاستغلال.
إليك سلوك البرنامج بلغة C في دورة تنفيذه الكاملة:
التهرب من AV/EDR
في هذه الحالة، لا يتم اكتشاف DriverKiller.exe بواسطة Microsoft Defender، لا في الوضع الثابت ولا في الوضع الديناميكي. التهرب ليس له معنى حقيقي هنا لأن الدرايفر المستغل يملك شهادة منتهية الصلاحية، لذا فإن استخدامه في ظروف حقيقية يصعب تصوّره. ولكن من أجل تمويه أفضل، كان يمكن تنفيذ:
نتيجة الكشف على الدرايفر بتاريخ 29/08/2025 (نتيجة موجودة مسبقًا، لم أقم بإرسال أي شيء على VirusTotal لأسباب واضحة):
⚠️ هذا المشروع أُنجز في إطار تعليمي. قد يحتوي على بعض عدم الدقة أو الأخطاء. أي اقتراح أو تصحيح أو نقاش مرحب به! 😃 شكرًا لـ d1rk(SaadAhla): https://github.com/SaadAhla!