שישה מתוך שבעת הקבצים בארכיון היו נקיים וחתומים: NetamLoader, מסגרת ההרצה שתקפה גרסת Windows 10 ממשלתית באסיה
התראה אוטומטית ממערך הטלמטריה של קספרסקי הצביעה על קובץ הגדרות. לא על קובץ הרצה, לא על סקריפט, אלא על קובץ .config קטן שיושב בתיקייה שבה Windows שומר חבילות עדכון. הוא היה דחוס בתוך ארכיון CAB בשם שהוא מחרוזת הקסדצימלית ארוכה, בנתיב C:\Windows\SoftwareDistribution\Download, והבדיקה הראשונה של תוכן הארכיון לא מצאה שום קוד זדוני.
חוקרי GReAT של קספרסקי חזרו לאותו קובץ הגדרות אחרי כמה חודשים, עקבו צעד צעד אחרי תהליך הטעינה שהוא מתאר, ובקצה מצאו שרשרת הדבקה שלמה: מסגרת טעינה של רכיבי .NET שבנויה כולה על XAML, עם מעקף לסורק האנטי-וירוס של המערכת, מחיקה יזומה של יומנים, ומעבר לתהליך מערכת אחר באמצע הדרך. הדוח, שפורסם ב-29 באוקטובר, קרא למסגרת הזאת NetamLoader. בהמשך נעבור על איך קובץ הגדרות אחד מגלגל את כל זה, למה המפתח להצפנה הוא שורת הפקודה עצמה, ומה הסגיר נתיב יומן אחד על זהות היעד.
שישה קבצים נקיים, אחד שלא
הארכיון פרש שבעה קבצים לתיקיית Install שמתחת לאותו נתיב עדכונים. רובם רכיבים אמיתיים של Patch My PC, כלי צד-שלישי לניהול עדכונים בארגונים, ואפילו רכיבי ה-.NET שמשמשים בהמשך להרצה הם assemblies חתומים של מיקרוסופט. קובץ אחד מכל השבעה הכיל קוד זדוני, והוא Resources.resx, קובץ משאבים. זאת הסיבה שהסריקה הראשונה חזרה נקייה: אין כאן קובץ הרצה חשוד לסמן, יש צירוף של קבצים כשרים וקובץ טקסט אחד שאיש לא נוהג לחפש בו לוגיקה.
את דרך ההגעה של הארכיון למכשיר החוקרים לא ראו. מה שכן נראה הוא התהליך PatchMyPC-ScriptRunner.exe רץ עם דגלי עדכון רגילים, כלומר הופעל כפי שכלי העדכונים מופעל בשגרה. קספרסקי כותבים שהתמונה הזאת, חבילה שמונחת בתיקיית העדכונים ומורצת בידי כלי ניהול לגיטימי, מצביעה בחוזקה על מתקפת שרשרת אספקה דרך ספק מהימן. זאת ההערכה שלהם, לא עובדה שנצפתה.
איך קובץ הגדרות מגלגל קוד זר לתוך תוכנה חתומה
ל-.NET יש מנגנון שמאפשר לקובץ ההגדרות שליד תוכנית לקבוע איזה רכיב ייטען לתוכה עוד לפני שהפונקציה הראשית של התוכנית מתחילה לרוץ. מי שיכול לכתוב קובץ קטן בתיקייה של תוכנית חתומה יכול, בלי לגעת בתוכנית עצמה ובלי לפגוע בחתימה שלה, לגרום לה לטעון רכיב משלו. לטכניקה הזאת קוראים AppDomainManager injection, והיא רשומה אצל MITRE כ-T1574.014.
מכאן השרשרת מתגלגלת דרך שלוש קפיצות, וכל אחת מהן מנצלת רכיב מיקרוסופט אמיתי. קובץ ההגדרות מפנה למחלקת תצורה של רכיב caching ותיק של מיקרוסופט. המחלקה הזאת, כשהיא נטענת, מחפשת מיוזמתה קובץ תצורה בשם fabric.config באותה תיקייה וקוראת אותו. ובקובץ הזה התוקף רשם טיפוס אחר לגמרי: ResXResourceSet, המחלקה שמפרסרת קבצי .resx, ושלחה אותה לפרסר את Resources.resx.
בתוך קובץ המשאבים יושב אובייקט בינארי מסורלל בקידוד Base64. הפרסור שלו קורא ל-BinaryFormatter, רכיב הסריאליזציה הוותיק של .NET שידוע כבעייתי, והקריאה הזאת מפעילה חולשת דה-סריאליזציה שמאפשרת לתוקף להפנות את זרימת ההרצה לפונקציה שהוא בחר. הפונקציה שנבחרה כאן היא XamlReader.Parse. XAML היא שפת סימון לבניית ממשקים ב-.NET, אבל בעזרת שני אובייקטים מובנים אפשר לבטא בה לוגיקה שקולה לקוד C# ממשי, וזה בדיוק מה שהתוקף עושה בכל שכבה מכאן והלאה.
השכבה הראשונה, שהחוקרים קוראים לה XAML-1, היא כולה מפענח. היא לוקחת את שורת הפקודה המלאה של התהליך המריץ, מורידה אותה לאותיות קטנות, מגבבת אותה ב-SHA-256, ומשתמשת בתוצאה כמפתח AES לפענוח השכבה הבאה. למי שמנסה לנתח את הקובץ במעבדה זה חוסם כמעט הכול: אם לא הרצתם את הקובץ בדיוק עם הנתיב והארגומנטים שהתוקף תכנן, המפתח יוצא אחר ושום דבר לא נפתח. ואם הפענוח נכשל, הקוד לא מדפיס שגיאה ולא קורס. הוא קורא ל-Environment.Exit(0) ומסיים את התהליך בשקט.
מעקף אחד לסורק, ואחד ליומנים
AMSI הוא הממשק שדרכו Windows מעביר לאנטי-וירוס תוכן שנוצר בזמן ריצה, כמו סקריפט שמפוענח בזיכרון, כדי שייסרק לפני שהוא מורץ. השכבה הבאה בשרשרת, 796 שורות באורכה, קיימת רק כדי לנטרל אותו. היא עובדת רק על מערכות 64 סיביות, המחרוזות הרגישות שבה פוצלו לרסיסים זעירים שמורכבים חזרה רק בזמן הפרסור כדי שלא ייתפסו בסריקת חתימות, והפעולה עצמה אלגנטית במיוחד: הקוד דורס את תחילתו של אחד מה-API הלגיטימיים של .NET במקטע קצר שמחזיר תמיד תשובה חיובית, ואז משנה בטבלת הפונקציות הווירטואליות של רכיב AMSI את ההפניה לפונקציית הסריקה כך שתצביע לאותו מקטע. מכאן כל סריקה מחזירה AMSI_RESULT_CLEAN. התיקון היחיד שנעשה בזיכרון יושב בתוך מודול כשר, ובמודול של AMSI נגעו רק בטבלת ההפניות, כך שבדיקות שלמות הקוד עוברות.
השכבה המקבילה טוענת רכיב .NET אמיתי, שהחוקרים מכנים .NET 1 Loader, ומעבירה לו את קובץ התצורה המוצפן. הרכיב הזה מתחיל בלדרוש הרשאות מנהל, מכבה את ספק האירועים של ה-CLR דרך משתנה סביבה כדי שהרצות .NET לא יירשמו ביומני המערכת, ואז עובר לנקות אחריו: הוא מוחק את שלושת הקבצים שנפרשו מהארכיון, מוחק את יומן השימוש של ה-CLR ואת קובצי ה-Prefetch של התהליך המריץ, ומוחק תשעה מפתחות רישום שהוא עצמו יצר תחת appid, כשהנתיב שלהם נגזר מכתובת שרת השליטה.
השרשרת עוברת דירה ל-dllhost.exe
עד כאן ראינו כיצד הקוד נכנס ומה הוא מוחק. השלב הבא הוא זה שמקשה על זיהוי התנהגותי. הרכיב יוצר דרך RPC אובייקט COM בתהליך אחר, מנצל בו חולשת דה-סריאליזציה שנייה שהחוקרים מציינים שלא הצליחו לזהות, ומשתיל את השכבה האחרונה לתוך dllhost.exe, תהליך מערכת שגרתי. קספרסקי מתארים את זה כדרך חשאית לפתוח תהליך חדש, ומסבירים שהיא מנתקת את שרשרת ההדבקה מהתהליך שבו התחילה, וכך מנטרלת מעקב התנהגותי של מוצרי EDR שבוחנים מה תהליך אחד עשה לאורך זמן.
בתוך dllhost.exe מתיישב הרכיב האחרון. הוא נועל את עצמו בזיכרון כדי שהמערכת לא תסגור את התהליך המאכסן, אוסף מהרישום את גרסת ה-.NET ואת מזהה הבילד של Windows, ואז ממתין בין 20 ל-40 דקות באופן אקראי לפני הפנייה הראשונה לשרת השליטה והבקרה (C2, השרת שממנו התוקף מוריד פקודות). ההמתנה הזאת נועדה להוציא sandbox-ים מסבלנות. במקביל נדלק טיימר של כ-28 שעות, ובסופו הרכיב מסיים את עצמו.
קצב התקשורת בנוי על אותו היגיון: תשובה תקפה מהשרת מקצרת את ההמתנה לחמש עד שש דקות, ואילו שתיקה שולחת את הרכיב לישון בין 22 ל-28 שעות, עם בדיקה כל עשר דקות אם הזמן חלף. התעבורה כולה מוצפנת ב-AES-128-CBC ומאומתת ב-HMAC-SHA256, הכתובת היא openaigpt4v[.]com, דומיין שנרשם עוד בספטמבר 2023 ועובר דרך Cloudflare, וכל מטען XAML שיורד משם מורץ ב-AppDomain מבודד בשם אקראי. בפועל זה אומר שהרכיב שיושב על המכשיר לא מכיל שום יכולת תקיפה בעצמו. הוא צינור.
קורבן אחד, והנתיב שהסגיר את היעד
לפי נתוני הטלמטריה של קספרסקי מדובר בקורבן בודד, באסיה. המדינה לא נקראת בשם, וגם לא הגוף. מה שהחוקרים כן אומרים הוא שהנוזקה נכתבה נגד גרסה ממשלתית מותאמת של Windows, ומכאן הם מסיקים שהיעדים הם גופי ממשל אסייתיים. העדות המעניינת לכך מגיעה מפעולת הניקוי: בין היומנים שהרכיב משנה יש נתיב תחת CmitUpdateAgent, שם שלא קיים בהתקנת Windows רגילה, והחוקרים מציינים שהוא עשוי להיות קשור לבילד ממשלתי כזה. כלומר התוקף הכיר את היומנים הספציפיים של אותו בילד וידע לנקות בהם שורה מסוימת.
את הזהות שמעבר לזה קספרסקי לא מספקים. "לאחר ניתוח ה-TTPs, דמיון בקוד והתשתית, לא הצלחנו לקשר את המתקפה לאף קבוצת APT מוכרת", כתבו החוקרים, והם מתייחסים לקבוצה כ-APT לא מזוהה. בפסקת הסיכום שלהם הם תולים את המקרה במגמה רחבה יותר, זאת של מדינות שבונות פלטפורמות תוכנה משל עצמן ומערכות הפעלה בראשן. השליטה המלאה בפיתוח ובתחזוקה מגיעה עם חובה להגן על כל המערכת לבד, ושרשרת האספקה הצעירה שנבנית סביבה חשופה בינתיים לווקטורים רבים.
מה זה אומר בפועל
המתקפה הזאת לא כוונה לארגונים בישראל, אבל הטכניקה שפותחת אותה גנרית לגמרי: כל יישום .NET שלתיקייה שלו יש הרשאות כתיבה הוא מועמד להרצת רכיב זר דרך קובץ הגדרות, בלי שהחתימה הדיגיטלית תישבר ובלי שקובץ ההרצה ישתנה. שווה לבדוק אצלכם מי יכול לכתוב לתיקיות של כלי ניהול ועדכונים, ולהתייחס להופעה של קובץ .config חדש ליד קובץ הרצה חתום כאירוע ששווה בדיקה. ושני סימנים מהדוח קלים במיוחד לחיפוש בלוגים: כיבוי ספק האירועים של .NET דרך משתנה סביבה, ותהליך dllhost.exe שפותח תעבורת HTTPS יוצאת אל דומיין שאף אחד לא מכיר.
תגובות