מה זה ניצול JIT ברמה מושגית¶
כל דפדפן שאתם פותחים היום מריץ קוד JavaScript במהירות שהייתה נשמעת דמיונית לפני עשור וחצי, וחלק גדול מהקסם הזה קורה בזכות טכניקה שנקראת קימפול תוך כדי ריצה - JIT, קיצור של Just-In-Time compilation. אותה טכניקה שהופכת את הדפדפן שלכם למהיר כל כך, היא גם אחד היעדים הכי מבוקשים בעולם מחקר החולשות, ויש לזה סיבה מושגית מעניינת מאוד.
מה זה בעצם JIT¶
בשפות תכנות מסוימות, כמו JavaScript או קוד שרץ על ה-JVM של Java, הקוד לא מתקמפל מראש לשפת מכונה לפני ההרצה, כפי שקורה למשל ב-C. במקום זה, הוא מתחיל את חייו כקוד שמתפרש שורה אחר שורה, וזה איטי יחסית. מנוע ה-JIT עוקב אחרי הקוד תוך כדי ריצה, מזהה חלקים שרצים שוב ושוב (למשל לולאות או פונקציות שנקראות פעמים רבות), ומקמפל אותם "תוך כדי תנועה" לקוד מכונה אמיתי שהמעבד יכול להריץ ישירות, במקום לפרש אותו מחדש בכל פעם. התוצאה היא שיפור ביצועים דרמטי, ולכן כמעט כל מנוע JavaScript מודרני, וגם JVM וסביבות ריצה נוספות, בנויים סביב הרעיון הזה.
למה יש בכלל זיכרון שהוא גם כתיב וגם בר-הרצה¶
כדי שמנגנון כזה יעבוד, חייב להתקיים משהו חריג יחסית מבחינה אבטחתית - אזור בזיכרון שהמנוע יכול גם לכתוב אליו (כי הוא מייצר שם קוד מכונה חדש, "בזמן אמת") וגם להריץ ממנו (כי בסופו של דבר המעבד צריך לבצע את הקוד הזה). זה בדיוק המצב שהגנות אבטחה בסיסיות במערכות הפעלה מודרניות נועדו למנוע.
ה-DEP/NX ולמה זיכרון כתיב-ובר-הרצה הוא חריג מעניין¶
הגנה שנקראת DEP, או Data Execution Prevention (ומוכרת גם ברמת החומרה בתור סיביות NX, Non-Executable), מבוססת בדיוק על העיקרון ההפוך - אזור זיכרון שמשמש לנתונים ואפשר לכתוב אליו, לא אמור להיות ניתן להרצה, ולהיפך. ההגנה הזו נועדה למנוע בדיוק את התרחיש הקלאסי שבו תוקף מצליח להזריק קוד זדוני לתוך אזור נתונים, ואז לגרום למעבד להריץ אותו. אבל מנוע JIT, מעצם ההגדרה שלו, חייב לשבור את הכלל הזה - הוא צריך אזור זיכרון אחד שגם כתיב וגם בר-הרצה, לפחות באופן זמני, כדי שהוא יוכל לייצר קוד מכונה חדש ואז להריץ אותו.
הרעיון המושגי מאחורי JIT Spraying¶
בגלל שמנוע ה-JIT מייצר קוד מכונה על סמך קלט שמגיע בסופו של דבר מהמשתמש - למשל, ערכים וקבועים שמופיעים בקוד ה-JavaScript עצמו - נוצרת שאלה מעניינת: האם תוקף יכול להשפיע, ולו בעקיפין, על תוכן קוד המכונה שנוצר? הרעיון המושגי שנקרא JIT Spraying מבוסס בדיוק על התובנה הזאת - ניסיון לנצל את העובדה שחלק מהתוכן של קוד המכונה שנוצר דינמית מושפע מקלט שהתוקף שולט בו, ולכוון את זה כך שבאזור הזיכרון בר-ההרצה יימצא תוכן שימושי לתוקף, אם הוא בכלל יצליח להפנות את זרימת הריצה לשם דרך חולשה נפרדת. חשוב להדגיש - זהו רעיון עקרוני בלבד, לא מתכון טכני, וניצול בפועל דורש שילוב עם חולשות נוספות ומנגנוני הגנה מודרניים שמקשים עליו מאוד היום.
למה מנועי JIT מודרניים משקיעים כל כך הרבה בהגנות¶
בדיוק בגלל הפער האינהרנטי הזה - הצורך בזיכרון שהוא גם כתיב וגם בר-הרצה - חברות הדפדפנים משקיעות משאבים עצומים בהגנה על אזורי הזיכרון האלה. בין הטכניקות הנפוצות אפשר למצוא הפרדה קפדנית בין השלב שבו קוד המכונה נכתב לבין השלב שבו הוא הופך לבר-הרצה (כך שלעולם אין רגע שבו הוא גם וגם בו זמנית), אקראיות בפריסת הזיכרון שמקשה על תוקף לחזות איפה בדיוק קוד מסוים יישב, ובדיקות אימות שמוודאות שהקוד שנוצר תואם למה שהמנוע התכוון לייצר. השילוב של כל ההגנות האלה הוא הסיבה שמציאת וניצול חולשת JIT אמיתית נחשבת היום להישג משמעותי בעולם מחקר החולשות, ומתוגמלת בהתאם בתוכניות גילוי חולשות.
איפה ממשיכים ללמוד את התחום¶
הבנה של JIT ושל האתגרים האבטחתיים הייחודיים שהוא יוצר דורשת בסיס טוב גם בארכיטקטורת מחשבים וגם בעולם ניצול החולשות. בקורס מחקר חולשות אנחנו בונים את הבסיס הזה בהדרגה, כדי שתוכלו להגיע להבנה עמוקה של איך המנועים האלה עובדים ולמה הם כל כך מעניינים לחוקרים.
לסיכום¶
ה-JIT הוא מנגנון שמקמפל קוד תוך כדי ריצה כדי להאיץ ביצועים, אבל בדיוק בגלל זה הוא צריך זיכרון שהוא גם כתיב וגם בר-הרצה - חריגה מהעיקרון שהגנות כמו DEP ו-NX נועדו לאכוף. הפער הזה יוצר משפחה שלמה של אתגרי אבטחה מושגיים, ולכן מנועי JIT מודרניים בנויים היום סביב שכבות הגנה רבות שמקשות מאוד על כל ניסיון לנצל אותם.
מסקרן אתכם איך זה נראה בפועל ברמת המחקר? בואו לדבר על זה בקהילה.