טיול קצר בתכנות דרייברים
מדריך לכתיבת דרייבר בסיסי עבור כל גרסאות 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. זה עובד!
מה יצא לנו מזה?
הרצנו קוד ברמת הקרנל! לקוד שנכתוב שם יש פריבילגיות מלאות ואפילו על מרחב הזיכרון העליון של המערכת שהוא הזיכרון של הקרנל.
זו רק ההתחלה כמובן, ואני בנתיים ממשיך ללמוד את הנושא. אני אעדכן את הבלוג ברגע שאמצא משהו מעניין 🙂
תגיות: C++, Driver, ntddk, OSRLoader, WDK, Windows Driver Kit, דרייבר
פורסם בתאריך 5th מרץ 2011 ע"י vbCrLf
53 תגובות
אתה יכול לתת קישורים לכל מה שהורדת ?
שלום מפה ולשם,
WDK:
http://msdn.microsoft.com/en-us/windows/hardware/gg487463
OSRLoader:
http://www.osronline.com/article.cfm?article=157
אורי
נחמד. לא יודע אם זה תקף גם ל-Windows 7 – אבל אתה לא חייב להפעיל את הדרייבר דרך net start.
למעשה, לפעמים תרצה לכתוב אפליקצייה ברמת המשתמש בשביל ממשק על מנת לתקשר עם הדרייבר וייתכן ותרצה לטעון את הדרייבר בעצמך. דוגמה ל-Loader:
https://genesisdatabase.wordpress.com/2011/01/27/creating-your-own-driver-loader-in-c-driver-loader-source-code-rootkit/
(שים לב שיש הבדל בין טעינת דרייבר ויצירת שירות – שזה נראה לי מה ש-net start עושה)
תודה Symbol.
שיטה קלה מאוד. זו גם השיטה הראשונה שנתקלתי בה, אבל חיפשתי את הדרך הפשוטה ביותר (כדי להתמקד בכתיבת הדרייבר עצמו).
אני בהחלט צריך לנסות את השיטות האחרות עכשיו 🙂
אורי
1. תשמע, אם תוכל לעשות איזה הסבר קצר איך משתמשים בסביבת עבודה wdk, זה מאוד יעזור.
ככל הנראה הצלחתי להתקין בהצלחה (עשיתי install ואחר כך איזה משהו דרך הconsole שלקח חצי שעה), אבל אחר כך אני מגיע רק למסך של console, האם את כל הקוד שכתבת, כתבת דרך הconsole? אין איזה gui שאפשר להשתמש בו?
2. ישר כוח על הבלוג, הוא מאוד מעניין וברור.
שלום nry123,
1. השתמשתי ב-WDK רק עבור הקימפול. את הקבצים כתבתי ב-Notepad++ ואח"כ השתמשתי ב-Checked Build Environment מתוך ה-WDK כדי לקמפל (מתוך הפוסט):
"ב-SOURCES יש רשימת קבצי המקור (שמכילה רק קובץ אחד). כדי לקמל הכנסו ל-WDK\Build Environments\[Windows Version]\[Arch] Checked Build Environment שהתקנתם שהוא ממשק CLI שמוגדר כבר עבור כלי הפיתוח של הדרייברים, עברו לתיקיה שבה קוד המקור יושב ופשוט כתבו build – פקודה שתקמפל את הדרייבר לתוך תת תיקיה בקובץ בשם testdrive.sys. זה הכל, יש לנו דרייבר מקומפל."
2. תודה רבה.
יצא לך להשתמש ב ZwSetSystemInformation בשביל לטעון את הdriver ?
לא, אבל בגלל שאמרת נתתי לזה צ'אנס. הורדתי את הקוד של migbot מכאן:
http://www.dc406.com/index.php?option=com_phocadownload&view=file&id=3:migbot
העתקתי את הקוד של load_sysfile() וההגדרות של המבנים של הקרנל, שיניתי את שם הדרייבר והרצתי, וזה עבד.
אתה מוזמן לנסות 🙂
כן, גם אני השתמשתי בזה ככה
אבל נראה משהו מאוד מוזר
כתבתי דרייבר מאוד פשוט, כל כך פשוט שהוא רק לוקח קלט מהמשתמש דרך DEVICE_CONTROL (IRP_MJ_DEVICE_CONTROL)
אבל מה – כשאני טוען אותו עם ZwSetSystemInformation אני מקבל exception של invalid instruction
אבל כשאני טוען אותו עם SCM הכל בסדר
יצא לך להתקל בזה ?
לא יודע. ניסיתי את זה על דרייבר פשוט עוד יותר… בדיוק עכשיו אני עובד על דרייבר, אני אנסה לטעון אותו בשיטה הזו כשאסיים.
הוא גם נתקל בבעיה שלך, אבל הוא לא כתב פתרון…
http://shift32.wordpress.com/2011/04/28/named-pipes-kdbg/
נסה לעשות דיבאג בשיטה שהוא מציע, או שתשלח לו תגובה, אולי הוא יענה.
אחלה פוסט.
מקווה שיצא לך לכתוב עוד קצת על הנושא, הוא מאוד מעניין.
עכשיו בדיוק אני לומד על הקרנל של Linux וזה נחמד לראות את ההבדלים בינו לבין הקרנל של Windows.
נ.ב: הגעתי הנה דרך הכתבה על ה-Rootkits בגיליון 20 של Digital-Whisper, כתבה מצוינת!
ממש באיחור, וכנראה שכבר לא רלוונטי לך, אבל שהדרייבר לא רץ לך עד שביטלת את הבדיקה של החתימה היא מכיוון שאין קשר בין הroot certificates של דרייברים לבין מה שאתה התעסקת איתו.
אם תחשוב על זה, זו הייתה הגנה מטופשת, מכיוון שאתה יכול להוסיף certificate כאלה מuser mode, אז מה הטעם להגן על הkernel mode בעזרת חתימה כאשר אתה יכול להוסיף עוד CA מuser mode?
כדי לחתום על דרייברים אתה חייב להשתמש באחד מהCA-ים שמייקרוסופט מאשרים. ולצערי (וצערך) זה לא זול.