טיול קצר בתכנות דרייברים

מדריך לכתיבת דרייבר בסיסי עבור כל גרסאות Windows

לפני כשבועיים פרסם אחד מחברי פורום ההאקינג הישראלי (Ratinho) אתגר מעניין. קראק-מי פשוט שמבקש סיסמה ומחזיר Good job או Wrong password בהתאם. המטרה הייתה לפרוץ את התוכנה שתחזיר תמיד Good job, גם אם הוכנסה סיסמה לא נכונה, ומכיוון שההשוואה מתבצעת בעזרת קריאה פשוטה ל-lstrcmp בלי שום נסיון להסתיר או לסבך משהו, כל מה שצריך לעשות זה לשנות את ה-JNZ שאחריה ל-JMP.

מה שהפך את האתגר למעניין היה התנאי – הפתרון חייב להיות חיצוני, ללא שום שינוי בתוכנה. ראיתי את האתגר בדיוק אחרי שכתבתי את המאמר על הזרקת DLL בשולה המוקשים, ובעזרת אותה טכניקה פשוט לשנות את קוד התוכנה בזיכרון.

אבל אז זה היה פשוט מדי, ורציתי להיות מקורי יותר. אז מה שעשיתי היה לשכתב (Inline Hooking) את lstrcmp שבתוך Kernel32 שתחזיר תמיד 0 (המחרוזות שוות). כל תהליך טוען את המודולים למרחב הכתובות שלו (באופן תאורטי לפחות), ככה שהשינוי השפיע רק על הקראק-מי (קוד מקור, לרשימת הפתרונות).

חשבתי שהייתי מקורי, עד שראיתי את הפתרון שהציע Zerith, משתמש אחר בפורום. הוא הפתיע את כולם בסיבוך של הפתרון שלו… הוא כתב דרייבר שעושה הוקינג להנדלר של ה-Page Fault (אפשר לקרוא על זה בוויקיפדיה, אבל מה שחשוב לנו הוא שכל פעם שמערכת ההפעלה צריכה קטע שלא נמצא בזיכרון בפועל, Page Fault קורה), שברגע שקורה Page Fault, אם הוא קוד הקראק מי, במקום לטעון את הקוד מהדיסק הוא מכניס בעצמו קוד שכבר פרוץ (שינוי של ה-JMP).

התלהבתי כשראיתי את זה, והחלטתי שאני חייב ללמוד את הנושא לעומק. אז התחלתי :)

עולם חדש

כתיבת דרייבר זה תחום מאוד מעניין – אתה רץ ברמת הקרנל (ליבת המערכת), הרשאות מלאות כולל גישה לטווח הזיכרון העליון (0x80000000 עד 0xFFFFFFFF) מרחב הזיכרון של הקרנל – מה שאומר שיש לך שליטה מלאה על המערכת (ואפילו החלפת ההנדלר של ה-Page Fault כמו שראיתם) ובנוסף אתה גורם ל-BSOD בקלי קלות.

בניית הדרייבר

הורדתי כל מיני מאמרים וסידרתי בתיקיה, הלכתי לאתר של מייקרוסופט והורדתי את Windows Driver Kit, התקנתי אותו במלואו (באופן מעצבן הוא מחייב להתקין בנתיב ללא רווחים…) והתחלתי ללמוד.

השלב הראשון הוא כתיבת וקימפול דרייבר בסיסי. כתבו את הקוד הבא בתוך קובץ בשם testdrive.c:

#include <ntddk.h>

void DriverUnload(PDRIVER_OBJECT pDriverObject)
{
	DbgPrint("TestDrive driver is unloading\n");
}

NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
	DriverObject->DriverUnload = DriverUnload;
	DbgPrint("TestDrive driver is loading\n");
	return STATUS_SUCCESS;
}

אנו משתמשים בקובץ Header בשם ntddk (ר"ת NT Device Driver Kit אאל"ט) כדי לכתוב את הדרייבר. כתבנו שתי פונקציות, השנייה והחשובה היא DriverEntry שהיא דומה ל-DllMain שנקראת ברגע שהדרייבר נטען. הפונקציה הראשונה – DriverUnload היא הפונקציה הנקראת ברגע שהדרייבר מתבקש להסגר. DriverUnload אינה חובה, אבל אם לא נכתוב אותה ניסיון כיבוי של הדרייבר יחזיר שגיאה שהדרייבר לא תומך בזה, ונאלץ לעשות ריסטרט כדי לכבות אותו…
בשורה הראשונה ב-DriverEntry אנו מגדירים (בעזרת ה-DriverObject שיכיל מידע אודותינו) איזו פונקציה תקרא כאשר מתבצעת סגירה לדרייבר.
כדי שנוכל לראות שהדרייבר פועל אנו משתמשים ב-DbgPrint, ומחזירים 'קוד שגיאה' שהטעינה סוימה בהצלחה.

לפני הקימפול יש לבצע הכנה קטנה. צרו קובץ makefile והכניסו את השורה הבאה:

!INCLUDE $(NTMAKEENV)\makefile.def

את קובץ ה-makefile לא משנים בכלל, אלא את קובץ ה-sources שזה תוכנו:

TARGETNAME = testdrive
TARGETPATH = obj
TARGETTYPE = DRIVER
INCLUDES = %BUILD%\inc
LIBS = %BUILD%\lib
SOURCES = testdrive.c

ב-SOURCES יש רשימת קבצי המקור (שמכילה רק קובץ אחד). כדי לקמל הכנסו ל-WDK\Build Environments\[Windows Version]\[Arch] Checked Build Environment שהתקנתם שהוא ממשק CLI שמוגדר כבר עבור כלי הפיתוח של הדרייברים, עברו לתיקיה שבה קוד המקור יושב ופשוט כתבו build – פקודה שתקמפל את הדרייבר לתוך תת תיקיה בקובץ בשם testdrive.sys. זה הכל, יש לנו דרייבר מקומפל.

התקנת הדרייבר

דרייבר, בשונה מתוכנה רגילה, אי-אפשר להפעיל על ידי לחיצה כפולה. צריך להתקין אותו לפני על ידי קריאה ל-CreateService או הוספת ערכים מסוימים לזיכרון. אבל כדי לחסוך את השלב הזה ולסבך כמה שפחות השתמשתי בתוכנה בשם OSRLoader כדי להתקין את הדרייבר.

לאחר ההתקנה (Register Service ב-OSRLoader) אפשר להפעיל בעזרת התוכנה, או בדרך אחרת – ב-cmd.exe שמופעל ע"י מנהל (אנחנו מפעילים דרייבר, זה לא צחוק :) ) להשתמש ב-net start:

net start testdrive

וכדי לעצור:

net stop testdrive

אבל – כאן נתגלתה בעייה. Windows סירב להפעיל את הדרייבר מכיוון שהוא לא חתום. ברור שהוא לא חתום, אנחנו כתבנו אותו. כדי לפתור את זה ניסיתי לחתום בעצמי – יצרתי Certificate (בעזרת MakeCert ו-CertMgr) וחתמתי (בעזרת SignTool) אבל עדיין הוא התלונן מכיוון שאי אפשר לסמוך על ה-Certificate, מכיוון שהיא לא הונפקה על ידי גורם מוסמך. ניסיתי להעביר ל-Trusted Root Certification Authorities אבל זה לא עזר (כנראה שלא עשיתי את זה טוב…).

בסוף עברתי לאופציה השנייה והפעלתי את ה-Windows במצב שמוותר על חיוב חתימה דיגיטלית בעזרת לחיצה על F8 ב-Boot ובחירה של Disable Driver Signature Enforcement.

וסוף סוף הצלחתי להפעיל את הדרייבר בעזרת net start ו-stop. כדי לוודא שהוא באמת פועל, הפעילו את DebugView של SysInternals כמנהל והפעילו את Capture Kernel ו-Enable Verbose Kernel Output (מתפריט Capture). הפעילו והפסיקו את הדרייבר וצפו בהודעות שמופיעות ב-DebugView. זה עובד!

מה יצא לנו מזה?

הרצנו קוד ברמת הקרנל! לקוד שנכתוב שם יש פריבילגיות מלאות ואפילו על מרחב הזיכרון העליון של המערכת שהוא הזיכרון של הקרנל.
זו רק ההתחלה כמובן, ואני בנתיים ממשיך ללמוד את הנושא. אני אעדכן את הבלוג ברגע שאמצא משהו מעניין :)

קטגוריות: מערכות הפעלה, תכנות
תגיות: , , , , , ,
פורסם בתאריך 5th מרץ 2011 ע"י vbCrLf