ארבעה בייטים עם ערך קבוע, ובסוף קוד שרץ במקום שהקרנל לא יכול להציץ לתוכו: כך נפרץ enclave של ווינדוס
תקלידו את סיסמת ההתחברות של ווינדוס בתוך Notepad, ואם המדיניות הנכונה דלוקה במכונה, תוך שנייה תקפוץ אזהרה שלא כדאי לשמור אותה שם. כדי שהבדיקה תעבוד ווינדוס צריכה משהו להשוות אליו, והיא באמת מחזיקה: את הסיסמה שאיתה התחברתם, בצורת hash, בתוך רכיב שאמור להישאר אטום גם אם מישהו השתלט על הקרנל. ב-13 באוקטובר פרסמה חברת המחקר Doyensec איך הריצה קוד משלה בתוך הרכיב הזה.
החולשה שבבסיס השרשרת, CVE-2026-83498, תוקנה בעדכון של 8 בספטמבר. מיקרוסופט מסמנת אותה כ-Critical עם ציון CVSS 7.8, מסווגת אותה כדריסה של מצביע שהגיע מגורם לא מהימן, ואומרת שלא נוצלה ולא הייתה ידועה בפומבי. הפרסום של Doyensec, בחתימת החוקר Adrian Denkiewicz, הגיע יותר מחודש אחרי העדכון. נראה כאן מה הרכיב הזה עושה, איך באג שכותב ארבעה בייטים הופך לשליטה מלאה עליו, ומה דווקא לא תוקן.
מה זה enclave, ולמה אי אפשר להציץ פנימה
המודל שכולנו למדנו אומר שיש user mode, יש kernel mode, וברגע שתוקף הגיע לקרנל המשחק נגמר. Virtualization-Based Security, או VBS, הוסיפה ציר שלישי. ההייפרוויזר של ווינדוס מחלק את המערכת לרמות אמון (Virtual Trust Levels), הקרנל הרגיל יושב ברמה התחתונה VTL0, וברמה שמעליה, VTL1, רצים ה-Secure Kernel והקוד שמוגן על ידו. קריאה שעוברת לשם פשוט נעלמת מנקודת המבט של VTL0: את הזיכרון הפרטי שלה אי אפשר לקרוא גם עם הרשאות קרנל.
enclave הוא יחידת ההרצה הקטנה שחיה שם. לפי התיעוד של מיקרוסופט מדובר בסביבת הרצה מבודדת בתוך מרחב הכתובות של תהליך מאחסן רגיל, שמפרידה את החלק הרגיש של האפליקציה גם ממנה עצמה וגם משאר המערכת. מצד המתכנת זה DLL חתום: התהליך טוען אותו, מאתחל, וקורא לפונקציות שהוא מייצא. מה שהוא לא יכול זה לקרוא או לכתוב לזיכרון הפרטי שלו.
היחסים האלה לא סימטריים, וזאת הנקודה שמחזיקה את כל המחקר. ה-enclave כן רשאי לקרוא ולכתוב בזיכרון של התהליך המאחסן, כמו כל DLL אחר, ולכן הדפוס המקובל הוא שהתהליך מעביר מצביעים וה-enclave עובד על הנתונים בעצמו. כלומר קוד שרץ ברמת האמון הגבוהה מקבל קלט מהרמה הנמוכה, וחייב לבדוק לבד שכל מצביע אכן מצביע למה שהוא מצפה. טעות שם היא בדיוק סוג הבאג שחיפשו.
רכיבי ווינדוס שנשענים על enclave-ים כוללים את Always Encrypted של SQL Server, את החיפוש, את Recall, ואת ההגנה המורחבת מפני פישינג ב-Microsoft Defender SmartScreen. האחרון הוא זה שנפרץ.
למה דווקא הרכיב שמחזיק את הסיסמה
SFAPE.dll הוא ה-enclave שמאחורי אותה אזהרה מה-Notepad. הסיסמה נקראת פעם אחת בזמן ההתחברות, נשמרת בתוכו בצורת hash, ומאז כל טקסט שמוקלד או מודבק מושווה אליה בלי שהערך המקורי ייצא משם. זה בדיוק מה ש-enclave-ים נבנו בשבילו: גם אם VTL0 כולו נפל, ההשוואה וגם מה שהיא משווה אליו אמורים להישאר שלמים.
הפרט שמחדד את העניין הוא מי מאחסן אותו. את SFAPE.dll טוען lsass.exe, אותו תהליך שמנהל את ההתחברות ורץ בהרשאות SYSTEM, דרך חבילת LSA בשם SFAPM.dll. במכונות ווינדוס 11 עדכניות הגנת LSA דלוקה כברירת מחדל ומונעת מתהליכים רגילים, גם כ-SYSTEM, לקרוא את הזיכרון של LSASS או להחדיר לתוכו קוד. אז להאזין לשיחה בין LSASS ל-enclave לא אמור להיות אפשרי.
מה שכן אפשרי זה להרים את ה-enclave לבד. Doyensec מציינים שרכיב כזה יכול לדחות תהליך מאחסן שהוא לא מכיר, אבל רוב אלה שהם בדקו לא טורחים. ומי שטוען את ה-DLL בתהליך משלו מאכיל את הפונקציות בכל קלט שירצה.
מארבעה בייטים לקריאה וכתיבה
הבאג הראשון יושב בפונקציית האתחול, והוא קטן. בקריאה הראשונה הפונקציה בודקת את מבנה הבקשה ואת שרשרת המצביעים שבתוכו בקפידה. בקריאה שנייה היא רואה שהרכיב כבר אותחל, כותבת קוד שגיאה קבוע לכתובת שמופיעה בבקשה, וחוזרת. הפעם, בלי לבדוק את הכתובת. ומכיוון שהבקשה באה מהתהליך המאחסן, מה שיש כאן הוא כתיבה של ארבעה בייטים עם ערך קבוע לכתובת שהתוקף בוחר, ב-VTL0 או ב-VTL1.
החוקרים עצמם לא מתלהבים ממנה. "לקרוא לזה כתיבה שרירותית תהיה הגזמה עצומה", כותבים ב-Doyensec, והם מפרטים למה: הערך קבוע, האורך ארבעה בייטים, והכתובת חייבת לסבול את שאר מבנה הבקשה שיושב סביבה.
מה שהפך אותה למשמעותית הוא לאן כיוונו אותה. לפני שה-enclave נוגע במצביע שהגיע מבחוץ הוא מריץ מסנן שבודק אם טווח הכתובות חופף לזיכרון הפרטי שלו, והמסנן נשען על כתובת הבסיס ועל הגודל שנשמרו אצלו במשתנים גלובליים. Doyensec מיקמו את הבקשה כך שארבעת הבייטים ייפלו בדיוק על המחצית העליונה של כתובת הבסיס השמורה. אחרי הכתיבה הזאת המסנן מקולקל, ומצביעים שמראים לתוך VTL1 נראים לו מחוץ לתחום.
וזה פתח את הבאג הבא. הפונקציה שמקבלת טקסט מלוח ההעתקה בודקת את המתאר ואת ה-buffer לפי שדה אורך אחד, אבל העבודה בפועל נעשית לפי שדה אחר, ובין חישוב ה-hash לאיפוס הטקסט הגלוי יש קריאה לפונקציית טלמטריה שבה ה-thread של ה-enclave נעצר לרגע. המתאר כל אותו זמן יושב בזיכרון של התהליך המאחסן, שיכול לכתוב אליו חופשי. כלומר בזמן ההמתנה התהליך מחליף את הכתובת ואת האורך, וכשהעבודה ממשיכה האיפוס מתבצע על כתובת שהוא בחר. לבאג מהסוג הזה, שבו ערך נבדק בנקודה אחת ומשתנה לפני שמשתמשים בו, קוראים TOCTOU.
משם זה מתגלגל. ה-enclave מנהל את אובייקטי הזהות שלו במבנה חיפוש גלובלי, ואיפוס ארבעה בייטים במצביע אליו מזיז אותו לאזור שהתהליך המאחסן יכול להכין מראש ולמלא בעצמו. כמה פונקציות מיוצאות מקבלות מזהה, מתרגמות אותו דרך המבנה, וסומכות על האובייקט שחוזר. אחת מחזירה שני ערכים מתוכו, וזאת קריאה של 16 בייטים מכתובת נבחרת ב-VTL1, ואחרת כותבת לתוכו 64 ביט. בשלב הזה למי ששולט בתהליך המאחסן יש קריאה וכתיבה בתוך האזור שאמור היה להיות אטום.
אז למה זה לא נגמר כאן?
עד כאן יש גישה לזיכרון. להריץ שם קוד זה סיפור אחר, כי לא מספיק לדעת כתובת של פונקציה שימושית ב-VTL1: השכבה שמנתבת קריאות לתוך enclave בודקת את הכתובת המבוקשת מול רשימת יעדים מאושרים, וכתובת שלא מופיעה בה נדחית. הרעיון דומה ל-Control Flow Guard, עם הבדל אחד שמכריע כאן: המטא-דאטה של הרשימה ניתנת לכתיבה.
מה שהחוקרים עשו הוא לא לשנות את הרשימה המקורית אלא לבנות אחת חדשה, בשטח כתיבה פנוי בקצה מקטע של SFAPE, ולהפנות אליה את המצביע ואת המונה (את הערכים המקוריים שמרו כדי להחזיר את המצב לקדמותו). אחרי ההחלפה אפשר לאשר כל פונקציה קיימת ב-DLL ובספריות שמותר לו לטעון, והיעד גם לא חייב להיות תחילת פונקציה.
נשארה מגבלה אחת. פונקציה שנקראת מבחוץ ל-enclave מקבלת פרמטר אחד, ורוב הפונקציות הפנימיות צריכות יותר. את הפתרון, שימוש ב-NtContinue כדי לשחזר מצב רגיסטרים שלם ובכך לקבל קריאה רגילה בקונבנציית x64, Doyensec מייחסים למחקר קודם של Matteo Malvica ו-Cedric Van Bockhaven מ-Outflank. כדי שהחזרה מהפונקציה לא תפיל את ה-enclave הם קודם צילמו את מצב ה-thread החי שרץ בתוכו והשתמשו במצביע המחסנית האמיתי שקיבלו משם, כך שהיעד חוזר בדיוק לנקודה שהמנתב מצפה לה.
ואז הגיעה האירוניה: ברגע שרץ שם קוד, שתי עטיפות העתקה של Secure Kernel נותנות ממשק נקי בהרבה מהפרימיטיבים המאולתרים שנבנו בדרך.
מה תוקן, ומה נשאר פתוח
ההשוואה בין גרסה 10.0.26100.8875 של SFAPE.dll לזו שנכנסה ב-KB5124008, עדכון ה-8 בספטמבר לווינדוס 11 בגרסאות 24H2 ו-25H2, מראה שלושה שינויים. נתיב האתחול החוזר בודק עכשיו את טווח הבקשה לפני שהוא כותב את קוד השגיאה, וזה חוסם את הכתיבה שפתחה את השרשרת. הפונקציה של לוח ההעתקה דוחה קלט ריק ואורכים לא עקביים, מגבילה את האורך ל-512 בייט, ומעתיקה את הקלט פנימה במקום לעבוד על מתאר שיושב בחוץ. ומדיניות שלמות הקוד של VBS דוחה מעתה כל גרסה ישנה של SFAPE.dll.
השינוי השלישי הוא המעניין, כי Doyensec התחילו את המחקר בהנחה שרכיבים חשופים יישארו שמישים גם אחרי שיתוקנו. ב-SFAPE ההנחה לא התקיימה. בדוגמה האחרת היא דווקא כן: ה-enclave של Edge, שבו נמצאו בעבר שתי חולשות עם השפעה דומה (CVE-2023-36880 ותיקון חלקי שאחריה שהצריך CVE נוסף), עדיין לא נחסם על ידי אותה מדיניות. "לא ברור אם זו הייתה החלטה מכוונת או פספוס", הם כותבים.
סביב השורה הזאת יש תמונה רחבה יותר. לווינדוס אין רשימת חסימה של enclave-ים חשופים כמו זו של דרייברים פגיעים, והחוקרים מציינים שהצליחו לטעון בילדים ישנים וחשופים על מערכות עדכניות. זה כבר שימש מחקר התקפי קודם: Ori David מ-Akamai הדגים שימוש בזיכרון הפרטי של enclave חשוף כדי להחזיק נתוני מטען במקום שבדיקות הזיכרון של מוצרי EDR לא מגיעות אליו.
גם ציר הזמן מספר משהו. Doyensec דיווחו על הכתיבה הקבועה ב-7 באוגוסט, ארבעה ימים אחר כך הודיעו ל-MSRC שהרחיבו אותה להרצת קוד מלאה בתוך ה-enclave, והתיקון יצא ב-8 בספטמבר. ב-21 בספטמבר מיקרוסופט אישרה שמדובר בכפילות של באג שמצאה בעצמה פנימית.
מה זה אומר בפועל
מה שחשוב לקחת מכאן הוא לא החולשה הספציפית. מיקרוסופט מדווחת שהיא לא נוצלה, הניצול דורש קוד שכבר רץ על המכונה (וקטור מקומי, הרשאות נמוכות, בלי אינטראקציה), והתיקון יצא לפני יותר מחודש. מי שמריץ ווינדוס 11 בגרסאות 24H2 או 25H2 ולקח את עדכון ספטמבר מכוסה.
מה שכן ראוי לתשומת לב הוא ש-VTL1 הפך לשטח עבודה. מי שבונה על enclave-ים כדי לשמור סודות צריך להתייחס לקוד שרץ שם כמו לכל קוד שמקבל קלט מגורם פחות מהימן, כולל אימות מצביעים והעתקה פנימה לפני עבודה על נתונים שיושבים בחוץ. ולמי שמנטר מכונות: הזיכרון הפרטי של enclave לא נראה מ-VTL0, ולכן הסימן שכדאי לחפש הוא לא מה שקורה בפנים, אלא תהליך לא צפוי שטוען DLL חתום של enclave שאין לו סיבה לגעת בו.
תגובות