מה זה Double Free, וכמה נזק שחרור כפול יכול לגרום¶
חלק מהחולשות הכי מסוכנות בעולם ה-heap נשמעות פשוטות באופן מטעה. Double Free היא בדיוק כזאת - קוראים ל-free פעמיים על אותה כתובת זיכרון. זה נשמע כמו טעות תמימה, אבל היא פותחת דלת לחלק מטכניקות הניצול המשוכללות ביותר שקיימות היום.
למה בכלל קורה Double Free¶
בתוכנות גדולות ומורכבות, קל מאוד לאבד מעקב אחרי מי אחראי לשחרר איזו חתיכת זיכרון. תרחיש נפוץ הוא פונקציה ששוחררת chunk, אבל לא מאפסת את המצביע אחרי זה. אם מצביע כזה ("dangling pointer") מועבר שוב בהמשך לקריאה נוספת ל-free, אותה כתובת משתחררת פעם שנייה. תרחיש נפוץ נוסף הוא טיפול בשגיאות שמשחרר את אותו משאב פעמיים בנתיבי קוד שונים, כשהמתכנת לא זוכר שהוא כבר שוחרר קודם.
למה זה מבלבל את מנהל הזיכרון¶
מבנה הבינים ב-heap מבוסס על רשימות מקושרות של chunks פנויים. כש-chunk משתחרר, הוא נכנס לרשימה כזאת. אם אותו chunk משתחרר פעם שנייה, הוא נכנס לאותה רשימה שוב - מה שיוצר מצב שבו אותה כתובת מופיעה פעמיים באותה רשימה של זיכרון "פנוי". מבחינת האלוקטור, הוא עכשיו מנהל מבנה נתונים לא עקבי, שמניח שכל כתובת ברשימה שייכת רק לחתיכת זיכרון אחת.
הבעיה האמיתית מתחילה כשמבקשים זיכרון חדש אחרי זה. האלוקטור עלול להחזיר את אותה כתובת פעמיים, לשני חלקים שונים ולא קשורים בתוכנית - למשל אובייקט אחד שהתוכנית חושבת שהוא ייחודי לה, ואובייקט אחר שמישהו אחר בתוכנית מקבל לשימוש. אם התוקף שולט על אחד מהם, הוא בפועל שולט גם על השני, כי הם חולקים את אותו זיכרון פיזי.
מה זה מאפשר בפועל¶
- בלבול טיפוסים (type confusion). שני חלקים שונים בתוכנית "חושבים" שיש להם אובייקט ייחודי, בזמן שבפועל הם משתפים את אותה כתובת זיכרון, מה שמאפשר לתוקף לתפעל אובייקט דרך הקונטקסט השני.
- שרשור לניצול tcache poisoning. בגרסאות מודרניות של glibc, Double Free הוא לרוב לא סוף הסיפור אלא נקודת הפתיחה - הוא משמש כדי להשיג את אותה כתובת פעמיים ברשימת ה-tcache, מה שמאפשר בהמשך לתפעל את מצביע ה-forward שלה ולגרום ל-
mallocלהחזיר כתובת שרירותית שהתוקף בוחר. - כתיבה שרירותית עקיפה. ברגע ש-
mallocיכול להיות מרומה להחזיר כתובת שאתם קבעתם, כל כתיבה לזיכרון שהוקצה "נראית" בעיני התוכנית ככתיבה תמימה, אבל בפועל היא כותבת לכל מקום שבחרתם.
איך glibc מנסה להתגונן¶
גרסאות מודרניות יותר של glibc מוסיפות בדיקות שמנסות לזהות Double Free ישיר ומיידי - למשל בדיקה אם ה-chunk שרוצים לשחרר כבר נמצא בראש רשימת ה-tcache. אם כן, התוכנית תיפול עם שגיאה כמו "double free detected in tcache 2". אבל ההגנה הזאת פשוטה יחסית לעקיפה - מספיק להקצות ולשחרר chunk אחר בין שני השחרורים הכפולים, כדי שהוא לא יהיה בראש הרשימה ברגע הבדיקה.
למה זה עדיין נפוץ¶
Double Free נחשב לאחת השגיאות הנפוצות ביותר בתוכנות C ו-C++ בקנה מידה גדול, בדיוק כי ניהול זיכרון ידני דורש מהמתכנת לזכור בעצמו מי אחראי על כל חתיכת זיכרון, בלי רשת ביטחון אוטומטית. בפרויקטים גדולים עם הרבה נתיבי קוד, קל מאוד שהמעקב הזה יישבר.
איך לומדים לזהות ולנצל את זה¶
הצעד הראשון הוא לזהות בקוד נתיבים שבהם אותו מצביע עשוי להשתחרר פעמיים, ואז להבין איזו גרסת glibc רצה, כי ההתנהגות המדויקת של הבאג משתנה בין גרסאות. תרגול על debugger כדי לצפות בפועל ברשימת ה-tcache אחרי שחרור כפול הוא הדרך הכי טובה להפוך את זה למובן.
בקורס מחקר חולשות אנחנו עוברים על Double Free כשלב שקושר בין הבנת מבנה ה-heap הבסיסי לבין טכניקות ניצול מתקדמות כמו tcache poisoning.
מצאתם Double Free בקוד אבל לא בטוחים איך לנצל אותו בגרסת glibc הספציפית שלכם? זה בדיוק סוג השאלה שהקהילה בדיסקורד שלנו אוהבת.
לסיכום¶
Double Free מוכיח שלפעמים הבאג הכי מסוכן הוא לא חריגה מגבולות זיכרון, אלא בלבול לגבי מי בעלים של איזה זיכרון. שחרור כפול לכאורה תמים פותח דלת לתפעול מבני הנתונים הפנימיים של מנהל הזיכרון, ובגרסאות מודרניות של glibc הוא לרוב הצעד הראשון בדרך לניצול חכם ומלא.