לדלג לתוכן

מבוא ל-ROP - Return Oriented Programming, מוסבר בפשטות

אם הבנתם מה זה גלישת חוצץ, בטח שאלתם את עצמכם - אם היום יש הגנות כמו NX שמונעות הרצת קוד שהוזרק לזיכרון, איך בכלל עדיין מנצלים חולשות כאלה? התשובה היא טכניקה שנקראת ROP, ואם יש טכניקה אחת שממחישה עד כמה עולם ניצול החולשות הוא יצירתי, זו היא.

קודם, למה בכלל צריך את זה

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

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

וכאן נכנסת השאלה שהולידה את ROP - אם אני לא יכול להריץ קוד חדש, אולי אני יכול להשתמש בקוד שכבר קיים בתוכנית?

הרעיון המרכזי מאחורי ROP

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

הקסם הוא שאם אתם שולטים על הסטאק (בזכות אותה גלישת חוצץ מהפוסט הקודם), אתם יכולים לסדר על הסטאק רצף של כתובות ל-gadgets האלה, אחד אחרי השני. כשהתוכנית "חוזרת" מהפונקציה הראשונה, היא קופצת ל-gadget הראשון, מבצעת אותו, ואז מגיעה ל-ret שלו - שגורם לה לקפוץ לכתובת הבאה שהכנתם על הסטאק. וכך הלאה, gadget אחרי gadget, עד שהרכבתם שרשרת שלמה של פעולות שביחד עושות בדיוק מה שרציתם - למשל, לפתוח shell.

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

דוגמה קונספטואלית

תחשבו על שרשרת ROP כמו על מתכון שמורכב אך ורק ממה שכבר יש לכם בארון המטבח. אתם לא יכולים לקנות רכיב חדש (זה מה ש-NX מונע), אבל אתם יכולים לשלב בין רכיבים קיימים בסדר חכם כדי ליצור משהו חדש שלא היה שם קודם. gadget אחד מכניס ערך לרגיסטר מסוים, gadget שני קורא לפונקציה מערכתית עם הערך הזה, ובסוף אתם השגתם הרצת פקודה - בלי להריץ אף שורת קוד "חדשה".

למה זה נחשב לאחת הטכניקות היפות בתחום

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

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

מה קורה אחרי ROP

חשוב לדעת ש-ROP הוא לא סוף הסיפור. תוקפים משכללים את הטכניקה עוד יותר עם וריאציות כמו JOP (Jump Oriented Programming) שמשתמש בקפיצות במקום ב-ret, ויש גם הגנות נגד ROP עצמו, כמו CFI (Control Flow Integrity), שמנסות לוודא שהזרימה של התוכנית נשארת "הגיונית". זה בדיוק המשחק המתמשך בין תוקפים למגינים שהופך את התחום הזה לכל כך דינמי.

איך לומדים לבנות שרשראות ROP בעצמכם

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

יש קהילה שלמה שעוברת את זה יחד בדיסקורד, ותמיד יש מישהו שיכול לעזור כשהשרשרת שלכם פשוט לא עובדת (וזה קורה לכולם, כל הזמן).

הצטרפו לקהילה בדיסקורד

לסיכום

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