לדלג לתוכן

מה זה stack pivoting, כשאין מספיק מקום לשרשרת ROP

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

הבעיה במילים פשוטות

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

הרעיון מאחורי Pivoting

הפתרון הוא לגרום למעבד "להאמין" שהמחסנית (stack pointer, או rsp/esp) נמצאת במקום אחר לגמרי - מקום שבו כן יש לכם שליטה על הרבה זיכרון, למשל אזור ה-heap, או buffer גדול יותר שנמצא במקום אחר בתוכנית. אם אתם מצליחים לשנות את ערך מצביע המחסנית לכתובת הזאת, כל שרשרת ה-ROP הבאה תתבצע מהמיקום החדש, שבו כן יש לכם מקום להכין שרשרת ארוכה ומורכבת ככל שתרצו.

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

איך עושים את זה בפועל

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

יש גם גדג'טים ייעודיים יותר כמו pop rsp; ret (בארכיטקטורת x86-64), שמאפשרים לקבוע ישירות את ערך מצביע המחסנית מתוך ערך שכתבתם על הסטאק המקורי.

מתי בדיוק צריך את זה

Stack pivoting נדרש בעיקר במצבים כאלה:

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

למה זה מרגיש כמו שלב מתקדם

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

איך זה מתחבר לשאר הידע

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

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

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

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

לסיכום

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