קובץ אחד יורד מהאתר, ובחלון ההורדות הוא נראה כמו מסמך. אין לו שם, רק סיומת .scpt, ומכיוון ש-macOS מסתירה סיומות קבצים כברירת מחדל, מה שמופיע ב-Finder הוא אייקון של מסמך גנרי. לחיצה כפולה פותחת את Script Editor, ומי שממשיך לפי ההוראות שעל המסך מריץ את שרשרת ההדבקה בעצמו.

חוקרי Jamf Threat Labs פרסמו ב-22 בספטמבר ניתוח של הווריאנט השלישי הידוע של PamStealer, משפחת נוזקות גניבת מידע ל-macOS שהם עוקבים אחריה מיולי. לפי החוקר Thijs Xhaflaire, מה שמייחד את הגרסה הזאת הוא לא מה שהיא אוספת אלא איך היא מגיעה: המטען המוצפן שיורד למכשיר נשאר סגור בלי שהשרת של התוקף ישתף פעולה ברגע ההרצה. The Hacker News סיקר את הדוח ב-25 בספטמבר.

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

המשפחה שקיבלה את שמה ממנגנון של macOS

Jamf תיעדה את הווריאנט הראשון ביולי 2026, ואז הפיתיון היה אתר מזויף של Maccy, מנהל לוח העתקה פופולרי למק. לפי הדוח החדש הופיעו מאז גם וריאנטים בשמות Scoppr ו-Nancy, וכולם השתמשו באותו פורמט חיצוני.

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

הפיתיון: ארנק קריפטו שלא קיים

האתר wavel[.]app מתחזה לאפליקציית ארנק קריפטו רב-שרשראות בשם Wavel. הכפתור להורדה למק מוביל לקובץ דיסק בשם Wavel.dmg שמתארח בדומיין אחר, ובתוכו אותו AppleScript מקומפל בלי שם.

מה שהשתנה בשלב הזה הוא התוכן. בווריאנטים הקודמים קוד ה-JXA עשה את העבודה האמיתית: פענח מטען מוטמע בהצפנת RC4, פנה ל-frameworks של macOS דרך הגשר של JXA, וניהל את כל ההורדה וההתקנה. בגרסת Wavel לא נשאר מזה כלום. הקוד מחזיק מחרוזת base64 אחת באורך 24,868 תווים וקריאה אחת ל-shell שמפענחת אותה ומזרימה את התוצאה ל-zsh. שכבת ה-JXA היא בסך הכול מוביל.

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

למה המטען נשאר סגור בלי השרת

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

את החילוף מבצעת תוכנית נפרדת שהסקריפט מוריד לפני הכול, קובץ Mach-O קטן בשם pkgunpack שתומך ב-arm64 וב-x86_64. כל תפקידה קריפטוגרפי: מימוש של curve25519_donna מקושר אליה סטטית, לשכבות הסימטריות היא משתמשת ב-AES-256-GCM וב-SHA-256 מ-CommonCrypto, ויש לה שתי פקודות בלבד.

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

ההתראה שהושתקה, ואחר כך ארבע שכבות

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

מה שנרשם הוא LaunchAgent בשם com.apple.finder.agent, שרץ בהתחברות ואחר כך כל 15 שניות ומופעל מחדש אם קרס. הוא מפנה לחבילה שהועתקה אל ~/Library/Application Support/System ושונתה שם ל-Finder.app, בעוד שקובץ ההרצה שבתוכה נשאר r8afup9un0.

ואז מגיעות שכבות התיקון. סקריפט zsh בשם .repair-run יושב באותה תיקייה, בודק אם החבילה במקומה ואם ה-LaunchAgent רשום, ואם אחד מהם חסר משחזר את שניהם מארכיון גיבוי מקומי ששמור שם לידם, בלי שום פנייה לרשת. בלוק קצר נוסף לקובץ ~/.zshrc ומריץ אותו ברקע בכל פתיחה של shell אינטראקטיבי. והשכבה האחרונה הופכת פעולה שגרתית של מפתח למכונת שיקום: הסקריפט מועתק לתיקייה שמשמשת כ-git hooks גלובליים, בשמות post-checkout ו-pre-commit, וההגדרה הגלובלית של git מופנית לשם. מכאן כל checkout וכל commit בכל מאגר מריצים אותו בשקט.

כל שכבה נושאת הערת סימון זהה, # sysnotif-repair. בסיכום הדוח מונה Jamf ארבע שכבות שיקום, שתיים מהן נדלקות מפעילות רגילה של המשתמש. זה הופך הסרה לעבודה.

עד כאן איך הנוזקה נכנסת ונשארת. מה שהיא אוספת מעניין לא פחות.

מה יוצא מהמכשיר

השלב השני, קובץ בשם r8afup9un0 בגודל 205 קילובייט בערך, כתוב ב-Swift. הדור הראשון של המשפחה נכתב ב-Rust, כך שמדובר בהחלפת שרשרת כלים שלמה בלי שינוי במטרות האיסוף. שם הפרויקט הפנימי שנשאר בקובץ הוא MacClient, וארבע פונקציות מיוצאות ממנו בשמן: נקודת הכניסה, בדיקת הסיסמה מול PAM, שליפת פריטים מה-keychain וגיבוי מסדי נתונים של דפדפנים.

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

לחילוץ פרטי האימות מה-keychain הנוזקה לא מסתמכת רק על ה-API: היא מפעילה קודם את כלי הפקודה של המערכת כדי לפתוח את keychain ההתחברות ולשנות את רשימת ההרשאות שלו, ורק אז שולפת פריטים דרך Security.framework. קובץ מסד הנתונים עצמו מועתק גם הוא.

את פרטי האימות מהדפדפנים היא מעתיקה בדרך נקייה: היא משתמשת ב-API הגיבוי המובנה של SQLite, ולפני כן הורגת את תהליכי העזר של הדפדפנים כדי לשחרר את הנעילות על קבצי הפרופיל. בקובץ מזוהים 17 דפדפנים לפי מזהי החבילה שלהם, ולצד המובנים מאליהם מופיעים גם Arc, Zen, Waterfox, LibreWolf, Yandex Browser ו-CocCoc. "ההכללה של Arc, Zen ושל הדפדפנים האזוריים וממוקדי-הפרטיות הפחות נפוצים מרחיבה את רשימת המטרות באופן ניכר מעבר למה שאופייני לנוזקות גניבת מידע מסחריות ל-macOS", כתב Xhaflaire.

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

ובין הקבצים יש אחד שלא מופיע ברשימות הרגילות. התמונה שהמשתמש הגדיר לחשבון שלו, זו שרואים במסך ההתחברות ובהגדרות המערכת, נשמרת ב-macOS כנתוני JPEG בשירות הספריות המקומי ולא כקובץ בנתיב שמישהו היה מחפש בו. הנוזקה קוראת אותה משם ישירות, ולפי Jamf קריאה כזאת נדירה גם בנוזקות גניבת מידע וגם בשימוש לגיטימי. בפועל, יחד עם פרטי האימות ועוד 21 מאפיינים של המכשיר, יוצאת מהמכשיר תמונה אמיתית של מי שיושב מולו.

לגבי שאר קבצי המשתמש כדאי לדייק. Jamf מצאה בטבלת המחרוזות ארבעה יעדים, .zsh_history, .zshrc, .bash_history ו-.gitconfig, אבל בניתוח הדינמי אישרה רק את הראשון. שלושת האחרים לא היו קיימים בסביבת הבדיקה.

בסוף האיסוף התיקייה נארזת לארכיון ZIP, והסקריפט המקורי, שעוד רץ מאז הלחיצה הראשונה, מעלה אותו בבקשה אחת. כל פנייה שלו לשרת נושאת את אותה כותרת token, ש-Jamf מסמנת כמזהה יציב לכל הפעילות מהבילד הזה.

הקו שמחבר את הווריאנטים

לפי Jamf, השינויים בגרסה הזאת מרוכזים בתשתית ההפצה ולא ביכולות האיסוף. חילוף המפתחות החי קושר את פענוח המטען לזמינות השרת ומעביר חלק מהשליטה התפעולית למי שמפעיל אותו, והשכבות שעל המכשיר מכוונות לאותו כיוון של עמידות. המעבר ל-Swift, כשבדיקת ה-PAM נשארת פונקציה מיוצאת בשם, הוא מה שמאפשר לחוקרים לקשור בכלל בין הגרסאות.

Jamf גם מציינת שהמפתחים ממשיכים לעבוד, ומפנה לפרסום של חוקר בשם L0psec ברשת X מתחילת החודש עם קווי דמיון לניתוח שלה. את הפרסום ההוא לא בדקנו בעצמנו.

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

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

אם אתם משתמשי מק, הסימן המעשי שכדאי לקחת מהמקרה הזה הוא שקובץ הורדה שנפתח ב-Script Editor אינו מתקין אפליקציות. למי שמנהל צי מקים יש כאן רשימת בדיקה קונקרטית: חבילה בשם Finder.app מתחת ל-~/Library/Application Support/System, LaunchAgent עם התווית com.apple.finder.agent, הגדרת git גלובלית שמפנה את תיקיית ה-hooks לשם, ובלוק שמסומן # sysnotif-repair בתוך ~/.zshrc. וחשוב מזה: מחיקה של האפליקציה לבדה לא פותרת כלום. סקריפט התיקון ישחזר אותה מהגיבוי המקומי ב-shell הבא או בפקודת git הבאה, ולכן צריך לנקות את כל השכבות באותה פעולה.