הכול התחיל בקישור במייל, ובקובץ שנראה כמו "מסמכי תיק". במשרד עורכי דין הודעה כזאת לא מעוררת חשד. מי שלחץ קיבל ארכיון מוצפן, ובתוכו קיצור דרך של Windows בשם Case Documents.lnk. מרגע שהקובץ נפתח נכנסה לפעולה אחת משרשראות הריגול המסודרות שתועדו לאחרונה: רצף שמפזר את עצמו על פני שבעה שלבים, כדי שאף מוצר אבטחה בודד לא יראה את התמונה השלמה.

את הקמפיין תיעדו חוקרי Blackpoint Cyber, נבן ביל וסם דקר מצוות מרדף היריבים של החברה, בדוח שפורסם ב-30 ביולי. הם נתנו שני שמות לכלים שלא היו מוכרים קודם: HollowFrame, מסגרת טעינה שנכתבה בשפת Go, ו-Matryoshka, דלת אחורית בשתי גרסאות שנכתבה ב-Rust. השם לא מקרי. כמו בובת הבבושקה, כל רכיב עטוף בתוך רכיב אחר. בהמשך נראה איך כל שכבה מסתירה את זו שאחריה, ולמה דווקא הגרסה שמקבלת את הפקודות שלה דרך GitHub היא החלק שמעניין באמת.

הדרך פנימה: שבעה שלבים, כל אחד חף מפשע לכאורה

קיצור הדרך שהקורבן פתח לא הכיל את הנוזקה עצמה. הוא רק כתב קובץ טקסט מקודד לתיקיית %TEMP%, יצר לצדו סקריפט קצר, והריץ את certutil.exe, כלי לגיטימי של Windows, כדי לפענח את השלב הבא. אף אחד מהצעדים האלה לא נראה זדוני בפני עצמו.

השלב הבא הוריד סקריפט PowerShell משרת ביניים בכתובת 2.26.252[.]84, מפוענח בהצפנת XOR פשוטה עם מפתח חוזר. הסקריפט ביקש הרשאות מנהל, ואז עשה את הצעד שפותח את הדלת לכל השאר: הוא הוסיף שני חריגים ל-Microsoft Defender. חריג אחד על התיקייה שאליה יורדים הקבצים, וחריג שני על שם התהליך python.exe. מרגע זה, כל מה שירוץ תחת השם הזה כבר לא ייסרק.

וכאן מגיע החלק החכם. הנוזקה הורידה סביבת Python מזויפת: הקובץ python.exe הוא אמיתי וחתום, אבל הספרייה python311.dll שלצדו אינה ספריית Python. כשתוכנית טוענת ספרייה (DLL) מהתיקייה שלה, היא סומכת על כך שהקובץ עם השם הצפוי הוא הקובץ הנכון. התוקף מנצל בדיוק את האמון הזה, ומחליף את הספרייה בגרסה משלו עם אותו שם. לטכניקה קוראים DLL side-loading. במקרה הזה python311.dll הוא בכלל תוכנית Go, שמייצאת בדיוק את ארבע הפונקציות שהמריץ הלגיטימי מצפה למצוא (בהן Py_Main ו-Py_Initialize), וכך לוקחת לעצמה את השליטה ברגע ש-python.exe עולה. זהו HollowFrame.

עד כאן רק הגענו פנימה. מה ש-HollowFrame עושה לפני שהוא מריץ משהו מעניין יותר.

"נראה כמו סנדבוקס, יוצא"

לפני שהוא פורק את המטען האמיתי, HollowFrame עוצר ובודק היכן הוא רץ. הוא מודד כמה זמן המחשב דלוק, כמה זיכרון מותקן, כמה קבצים יש בפרופיל המשתמש, והאם העכבר זז בכלל. חוקרי אבטחה מריצים נוזקות בתוך מכונה וירטואלית מבודדת כדי לנתח אותן, ומכונה כזאת נוטה להיות טרייה, ריקה ודוממת. אם הבדיקות מצביעות על סביבה כזאת, הנוזקה פשוט מפסיקה לרוץ ומדפיסה לעצמה שורה אחת: "looks like a sandbox, exiting".

המטען עצמו ארוז בשכבות שנועדו להקשות על חילוץ. הוא שמור באזור מוצפן בפורמט משולב, שבו רק חצי מהבייטים נושאים מידע אמיתי והשאר רעש שנועד למנוע חיתוך פשוט של הקוד, ומעליו הצפנת XChaCha20 ודחיסת zlib. רק אחרי כל אלה מתקבל הקובץ שירוץ בפועל. ל-HollowFrame יש גם מגוון דרכים להזריק את המטען הזה לזיכרון, מהחלפת קוד של תהליך קיים ועד מיפוי ידני של הקובץ, כך שאין התנהגות אחת שאפשר לחכות לה. גם ההשתרשות מתפצלת לשלושה: משימה מתוזמנת שרצה בכל כניסה למערכת, מנוי לאירועי WMI שמופעל בכל חיבור משתמש חדש, ותיקיית Startup כגיבוי. אם מוחקים אחד, שניים נשארים.

הדלת האחורית שגרה בתוך GitHub

בסוף השרשרת יושבת Matryoshka, הדלת האחורית (backdoor, רכיב שמשאיר לתוקף גישה חוזרת ומאזין לפקודות). היא הגיעה בשתי גרסאות, ושתיהן נכנסו לזיכרון באותו טריק של DLL side-loading: אחת כ-version.dll שנטענת לתוך רכיב עדכון תמים, השנייה כ-wtsapi32.dll שמעבירה הלאה עשרות פונקציות Windows אמיתיות, כדי שהתהליך המארח ימשיך לעבוד כרגיל ולא יעורר חשד.

ההבדל ביניהן הוא בערוץ הפקודות. הראשונה מדברת עם שרת שליטה ובקרה (C2, השרת שדרכו התוקף שולח פקודות) בכתובת קבועה, 45.158.196[.]184, בערוץ HTTP רגיל. זה עובד, אבל זה גם קל לחסום: ברגע שכתובת ה-C2 ידועה, אפשר לחסום אותה בחומת האש. הגרסה השנייה פתרה את הבעיה אחרת. במקום שרת ייעודי, היא משתמשת ב-GitHub. התוקפים פתחו מאגר קוד פרטי בשם adioziaete/memio, ובתוכו תיקייה נפרדת לכל קורבן. הדלת האחורית לא "מתחברת לשרת" במובן הרגיל. היא קוראת ורושמת קבצים במאגר דרך ה-API הרשמי של GitHub: קובץ beacon.json כדי להירשם, cmd.json שממנו היא שולפת פקודות, ו-result.json שאליו היא כותבת את הפלט. כדי לעדכן קובץ קיים היא אפילו מושכת קודם את מזהה הגרסה שלו, בדיוק כמו כל כלי פיתוח לגיטימי.

למה זה עובד כל כך טוב? כי תעבורה אל api.github.com היא מהדברים הכי שגרתיים ברשת ארגונית. מפתחים, שרתי בנייה וכלי אוטומציה פונים ל-GitHub כל היום. דלת אחורית שמסתתרת בזרם הזה נראית כמו עוד מפתח שמושך קוד, לא כמו נוזקה שמדברת עם שרת עוין. הנוזקה אף מתחזה ל-OneDrive בכותרת הזיהוי שלה, כדי להשלים את המראה התמים.

מה התוקפים ראו ברגע שנכנסו

Matryoshka אינה רק צינור לפקודות. יש בה יכולות איסוף מובנות שמכוונות ישר לרשת ארגונית: מיפוי הגדרות הרשת, רשימת ההרשאות של המשתמש הנוכחי, איתור בקר הדומיין, מניית המחשבים בדומיין, ורשימת חברי הקבוצות המנהליות. במילים אחרות, הכלי בא מוכן למפות מי המשתמשים החזקים ואיפה הם, השלב שקודם לתנועה רוחבית בתוך הארגון. הוא גם יכול להוריד ולהריץ מטענים נוספים, כך שהגישה הראשונית היא רק נקודת הפתיחה.

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

ההקשר הרחב: פיצול במקום התחבאות

ההיגיון הזה חורג מהקמפיין הבודד. במשך שנים נוזקה ניסתה להיות בלתי נראית, קובץ אחד שמתחבא. הגישה כאן הפוכה: כל צעד גלוי, אבל אף צעד לא חושף לבדו מה מתרחש. הפענוח הוא כלי לגיטימי של Windows. ההרצה היא תחת שם של Python. הפקודות מגיעות מ-GitHub. כל אחד מהחלקים, בבידוד, נראה חף מפשע. "שרשראות חדירה מודרניות נשענות יותר ויותר על פיצול", כתבו החוקרים. זו בדיוק המגמה שהופכת גילוי לקשה: לא חתימה של קובץ אחד, אלא הצטברות של אירועים תמימים שרק ביחד מספרים סיפור.

מה זה אומר בפועל

ההגנה מול שרשרת כזאת לא נמצאת בקובץ בודד, אלא בקישור בין האירועים. שווה לצוותי אבטחה לחפש את הצירופים החשודים: certutil שמפענח קובץ ב-%TEMP%, חריגים חדשים שנוספו ל-Defender בלי סיבה, או python.exe שרץ בלי שום סקריפט. תעבורה יוצאת אל api.github.com מתחנות שאין להן שום קשר לפיתוח היא עוד דגל שכדאי לבדוק. ולעובדים, גם במשרדים קטנים: קובץ קיצור דרך (סיומת lnk) שמגיע כ"מסמך" בתוך ארכיון מוצפן הוא כמעט תמיד מלכודת, גם אם הנושא נראה קשור בול לעבודה שלכם.