מחקתם את האפליקציה, והתהליך שהיא השאירה מתקין אותה בחזרה: כך RatHat מדליקה לעצמה ניפוי שגיאות אלחוטי ומשיגה shell
מי שמנסה למחוק את האפליקציה מקבל חלון שנראה כמו הודעת שגיאה של Google Play: ההסרה נכשלה. החלון הזה אינו של המערכת. הנוזקה תפסה את דיאלוג ההסרה, ביטלה אותו, וציירה מעליו משהו שנראה משכנע מספיק. ומי שבכל זאת מצליח למחוק אותה לא באמת מחק אותה, כי תהליך נפרד שהיא השאירה על המכשיר ממשיך לרוץ, בודק אם האפליקציה עדיין מותקנת, ומחזיר אותה עם כל ההרשאות.
ב-16 בספטמבר פרסמו חוקרי zLabs של Zimperium, Gianluca Braga, Vishnu Pratapagiri ו-Fernando Ortega, ניתוח מלא של משפחת נוזקות חדשה לאנדרואיד בשם RatHat. הממצא שמחזיק את כל השאר קטן: אפליקציה רגילה, בלי חולשה ובלי מחשב מחובר בכבל, משיגה על הטלפון הרשאות shell. בהמשך נראה איך היא מגיעה לשם, מה היא מפעילה משם, ולמה קוד הנעילה של המכשיר לא באמת מוגן מפניה גם כשהמסך מסתיר את הספרות.
איך אפליקציה רגילה משיגה הרשאות של shell
באנדרואיד כל אפליקציה יושבת בארגז חול משלה ורואה מעט מאוד ממה שקורה מחוץ לו. יש דרך מסודרת אחת לעקוף את זה, והיא נבנתה למפתחים: מדליקים במכשיר את מצב המפתחים, מדליקים ניפוי שגיאות אלחוטי, והטלפון מציג קוד בן שש ספרות ומספר port. מקלידים את שניהם במחשב, המחשב מתחבר לשירות שרץ בתוך הטלפון, ומאותו רגע הוא מריץ פקודות בהקשר של משתמש ה-shell של המערכת. זה הרבה מעבר למה שאפליקציה מקבלת אי פעם.
RatHat עושה את כל הריקוד הזה לבד, בלי שום מחשב בצד השני. הכול תלוי בהרשאה אחת: הרשאת נגישות, ההרשאה שנועדה במקור לאנשים עם מוגבלות ומאפשרת לאפליקציה לקרוא את המסך וללחוץ במקום המשתמש. כדי לקבל אותה הנוזקה מציגה עמוד הסבר מותאם לשפת הקורבן, ולפי Zimperium בחלק מהמדינות העמוד טוען שההרשאה נחוצה "בגלל הגבלות רשת", ובאחרות הוא מציע פיתיון כספי.
מהרגע שההרשאה ניתנת, רכיב בשם SystemHelperService מריץ את הרצף. לחיצות מסונתזות על מספר ה-build בעמוד פרטי הטלפון, שבע פעמים, פותחות את מצב המפתחים. משם הוא מדליק את ניפוי השגיאות האלחוטי. ואז מגיע החלק שהופך את זה לנוזקה אחרת מקודמותיה: היא קוראת את קוד ההתאמה בן שש הספרות ואת ה-port הדינמי ישירות מתוך החלון שהמערכת מציגה, ואם מזהי השדות בממשק אינם במקום הצפוי היא סורקת פשוט את כל שדות הטקסט בחלון. עם הקוד ביד היא נעזרת בספריית libadb-android שמוטמעת בתוכה, מתאמתת מול שירות ה-ADB של הטלפון עצמו, ומקבלת גישת shell לתיקייה /data/local/tmp.
בלי כבל, בלי מחשב, בלי חולשה.
שני קבצים שנראים כמו ספריות מערכת
מה שנשמר בתיקייה הזאת הם שני קבצי Go ששמותיהם נראים כמו ספריות מערכת תמימות. הראשון, liblocal-service.so, הוא ה-agent: הוא רץ בהקשר ה-shell ומריץ שם בדיוק את הפקודות שהאפליקציה עצמה אינה יכולה. הוא מוציא את האפליקציה ממצב חיסכון הסוללה של אנדרואיד דרך dumpsys deviceidle, מעביר אותה ל-bucket הפעיל כדי שהמערכת תתעדף אותה ברקע, מעניק לה את ההרשאה לשנות הגדרות מערכת מוגנות, ובאותה גישה בדיוק הוא יכול גם להשבית או להסיר חבילות אחרות במכשיר, כולל אפליקציות אבטחה. Zimperium מציינים שהפקודות האלה קיימות רק בתוך הבינארי של Go ואינן מופיעות בקוד ה-DEX של האפליקציה, כך שמי שבודק רק את ה-APK לא רואה אותן בכלל.
הקובץ השני, libmedia_codec.so, הוא בכלל frpc, הלקוח של פרויקט הקוד הפתוח frp. תפקידו היחיד הוא לפתוח מנהרה הפוכה אל שרת התוקף: במקום שהתוקף ינסה להתחבר פנימה אל מכשיר שיושב מאחורי NAT וחומת אש של ספק סלולר, המכשיר הוא זה שיוצא החוצה ומשאיר את הערוץ פתוח. את פרטי המנהרה, הכתובת, ה-port וה-token, ה-agent מושך מהשרת ומעביר ל-frpc. וזאת לא תוספת שירות לפקודות של הנוזקה, כפי שהחוקרים מנסחים זאת, אלא כביש כללי אל תוך המכשיר שנושא כל מה שהמפעיל ירצה, בנפרד מהיכולות המובנות של הנוזקה עצמה.
האפליקציה והסוכן מדברים ביניהם דרך שרת HTTP קטן שה-agent מרים על 127.0.0.1:7910, בלולאה המקומית של המכשיר. לכל אחד מהם יש גם ערוץ משלו מול השרת המרוחק, ול-agent יש תור משימות נפרד משלו, כולל תור לא מקוון שמצטבר כשהמכשיר מנותק ומתנקז ברגע שהחיבור חוזר.
ומה קורה כשמוחקים את האפליקציה?
כאן נכנס לתמונה החלק שבגללו כל המאמץ הזה השתלם לתוקף. חלון ההסרה המזויף שראינו בפתיחה הוא רק המחסום הראשון. המחסום השני הוא שה-agent, כמו שכותבים החוקרים, "רץ מחוץ למחזור החיים של החבילה, ולכן הוא אינו מוסר כשהאפליקציה מוסרת". כלומר, גם אחרי מחיקה מוצלחת נשאר לתוקף shell פעיל על המכשיר.
מה שקורה אחר כך אוטומטי לגמרי. ה-agent בודק אם האפליקציה עדיין נוכחת, ואם לא, מתקין מחדש את ה-APK ששמור אצלו עם דגל שמעניק אוטומטית את כל הרשאות הריצה, ואז כותב ישירות להגדרות המערכת המוגנות את השורות שמפעילות מחדש את שירות הנגישות. המשתמש לא רואה שום חלון הרשאה, כי אין יותר צורך לבקש. לכיוון השני קיים אותו מנגנון: כל עוד האפליקציה חיה, היא מריצה בדיקת דופק שמחזירה את ה-agent לאוויר אם הפסיק להגיב, סורקת מחדש את ה-port-ים של ה-ADB ומדליקה שוב את הגדרות ניפוי השגיאות אם כובו.
יש לזה עוד השלכה שקל לפספס. את סנכרון פרטי הנעילה של המסך אל השרת מנהל דווקא ה-agent ולא האפליקציה, ולפי הדוח זה נעשה בדיוק כדי שהחילוץ ימשיך לעבוד גם אחרי שהאפליקציה כבר לא שם.
איך מחלצים PIN בלי לראות את המסך
RatHat מריצה שלושה מנגנוני תיעוד הקלדה במקביל. הראשון מאזין לאירועי שינוי טקסט של שירות הנגישות ומשחזר מה הוקלד, כולל בשדות שמציגים רק נקודות. השני קורא את שורת הכתובת של הדפדפן לפי מזהה השדה, ומכסה את Chrome, Brave, Opera, Edge, DuckDuckGo והדפדפן של סמסונג. שניהם מוכרים היטב.
השלישי הוא הסיבה שהנוזקה טרחה להשיג shell מלכתחילה. הוא רץ בתוך ה-agent ומשתמש ב-getevent, כלי ניפוי שגיאות סטנדרטי של אנדרואיד שקורא את קובץ הקלט הגולמי שהקרנל כותב אליו. מה שהוא מחזיר זה X, זה Y וחותמת זמן, וזה הכול. בלי טקסט, בלי שם אפליקציה, בלי מושג מה היה על המסך. אפליקציה רגילה אינה מורשית לקרוא את קובצי הקלט האלה. משתמש ה-shell כן.
קואורדינטה לבדה חסרת ערך, ולכן הנוזקה נושאת איתה קובץ נתונים עם פריסות המקלדת המספרית של כל יצרן טלפונים מרכזי: איפה יושבת הספרה 0, איפה 5, ומה הגיאומטריה של רשת הנקודות של נעילת התבנית. מגע בנקודה מסוימת, בתוספת הידיעה שזאת מקלדת ה-PIN של סמסונג, שווה ספרה. רצף של נקודות מגע מושווה לרשת 3 על 3 ומחזיר את תבנית הנעילה. הנוזקה אפילו מתייגת את פרטי האימות שהגיעו בדרך הזאת בסיווג נפרד משלהם, כי היא יודעת שהם שוחזרו מגיאומטריה ולא מטקסט.
וזה בדיוק מה שהופך את השיטה לעקשנית. חלון שמסומן ב-FLAG_SECURE חוסם צילומי מסך וקריאה דרך שירות הנגישות, ולא נוגע בדרייבר הקלט. מקלדת מותאמת אישית לא מוסרת את הטקסט למערכת, אבל האצבע עדיין פוגעת במסך. מסך הנעילה מסתיר את הספרות משירות הנגישות בכוונה תחילה, והקואורדינטות לא מושפעות. ההגנות האלה שומרות על מה שנכתב. הן לא שומרות על איפה נגעתם.
למה נוזקה צריכה מודל שפה
הרכיב האחרון הוא זה שנתן לדוח את הכותרת. הנוזקה לוקחת את עץ הנגישות החי של המכשיר, כלומר את התיאור המובנה של מה שמוצג על המסך ברגע נתון, ממירה אותו ל-XML ושולחת אותו לאחד מעוזרי הבינה המלאכותית הנפוצים בעולם. Zimperium אינם נוקבים בשמו. מה שהיא מבקשת בחזרה הוא לא קוד ולא מטען, אלא הכוונה: מהן הקואורדינטות של רכיב מסוים על המסך, כדי שתדע לאן לשלוח לחיצה מסונתזת, מה הטקסט שמוצג בפועל, והאם כדאי לגלול למטה.
היתרון למפעיל ברור. אוטומציה שכתובה מראש נשברת ברגע שאפליקציית בנק משנה מסך; מודל שקורא את העץ בזמן אמת ממשיך לעבוד. הייחוס בדוח נשען בדיוק על החלק הזה: לפי Zimperium, ההנחיות שהנוזקה שולחת למודל מצביעות על מפעילים שפועלים ככל הנראה מסין, ו-BleepingComputer מדווח שההנחיות כתובות בסינית. זאת הערכה של החוקרים, לא ייחוס רשמי.
באותה רוח נבנו גם שכבות ההסוואה. קובץ ה-AndroidManifest של האפליקציה שוקל 61 מגהבייט, ו-99 אחוז ממנו הם שני גושים מסוג לא מתועד שנדחפו בין מאגר המחרוזות לבין תוכן ה-manifest האמיתי. אנדרואיד עצמו פשוט מדלג עליהם בזמן ריצה, ואילו כלי הניתוח הסטטי שמפרסרים את הקובץ נתקעים עליו או נגמר להם הזיכרון. אותו רעיון חוזר גם בקוד עצמו, עם פסאודו-הוראות שמנוע הריצה של אנדרואיד לעולם אינו מריץ אבל מפרקי הקוד מנסים לחשב את אורכן ונכשלים. מעל הכול יושבות שש בדיקות שמזהות אם מישהו מנסה לנפות את הנוזקה, בין השאר חיפוש אחר Frida ובדיקה אם ה-APK נארז מחדש לצורכי מחקר.
ההשתלטות המקומית על ADB עצמה אינה המצאה של RatHat. BleepingComputer מזכירים שהטכניקה נראתה כבר אצל ToxicPanda ואצל RedHook, משפחה שסוקרה כאן ביולי. מה שחדש כאן הוא מה שנבנה מעל: דיירים קבועים בתיקיית ה-shell, שמחזיקים את המכשיר גם כשהאפליקציה שהכניסה אותם נעלמה.
מה זה אומר בפועל
אם אתם מתקינים לעצמכם או למשפחה אפליקציות מחוץ ל-Google Play, זה הרגע לבדוק במכשיר שלושה מתגים: האם מצב המפתחים דלוק, האם ניפוי השגיאות האלחוטי דלוק, ואילו אפליקציות מופיעות ברשימת שירותי הנגישות. אפליקציה שביקשה נגישות ואינה כלי נגישות אמיתי היא סימן להסרה מיידית. בארגון, הדבר שכדאי לנטר הוא הדלקה של ניפוי שגיאות אלחוטי במכשיר מנוהל שאיש לא ביקש להדליק בו כלום, כי זה בדיוק האירוע שמסמן את המעבר מאפליקציה רגילה לגישת shell. ומכיוון שהסרה של האפליקציה אינה מנקה את המכשיר, מכשיר שהודבק ברמה הזאת צריך אתחול להגדרות היצרן ולא הסרה נקודתית.
תגובות