7.1 פנימיות המקצה של glibc תרגול
תרגול - פנימיות המקצה של glibc¶
בתרגול הזה לא נתקוף כלום. המטרה היחידה היא לפתח אינטואיציה על מה שקורה בערמה כשמקצים ומשחררים נתחים. אתם תריצו תוכנית קטנה תחת pwndbg ותצפו בעיניים איך נתחים נוחתים ב-tcache, ב-fastbin, וב-unsorted bin. זה תרגול הכנה - השליטה בו תחסוך לכם שעות של תסכול בשיעורים הבאים.
הכנת הסביבה¶
צרו קובץ heap_lab.c:
/* heap_lab.c */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
char *chunks[16];
int main(void) {
/* group A: small chunks of tcache/fast size */
for (int i = 0; i < 9; i++)
chunks[i] = malloc(0x18); /* each one -> chunk 0x20 */
/* group B: chunk too big for tcache, with a guard that separates it from top */
char *big = malloc(0x500); /* -> chunk 0x510 */
char *guard = malloc(0x18);
/* mark the chunks so they're easy to identify in memory */
for (int i = 0; i < 9; i++)
memset(chunks[i], 'A' + i, 0x18);
memset(big, 'Z', 0x18);
puts("allocated"); /* breakpoint 1 */
/* free the 9 small ones one after another */
for (int i = 0; i < 9; i++)
free(chunks[i]);
free(big); /* -> unsorted bin */
puts("freed"); /* breakpoint 2 */
return (int)(long)guard;
}
הידור והרצה:
ודאו ש-pwndbg מותקן (הפקודות heap, bins, vis_heap_chunks צריכות להיות זמינות). בדקו גם את גרסת ה-glibc שלכם:
זכרו: malloc(0x18) נותן נתח 0x20, וה-tcache של גודל זה מחזיק לכל היותר 7 נתחים.
תרגיל 1 - צילום הערמה אחרי הקצאה¶
שימו breakpoint על puts ורוצו עד ה"allocated". עכשיו הסתכלו על מבנה הערמה לפני ששחררנו משהו.
- הריצו
heapוהביטו ברשימת הנתחים. כמה נתחים אתם רואים? באיזה סדר פיזי הם מסודרים? - מצאו את הנתח הגדול (
big). מה הגודל שלו בשדה ה-size, ומה הערך של הדגל PREV_INUSE שלו? - הריצו
vis_heap_chunksוהשוו את מה שאתם רואים ל-heap.
רמז: הכתובת שהתוכנית מחזיקה (mem) היא 0x10 בתים אחרי תחילת ה-chunk. ב-vis_heap_chunks תחילת כל שורה היא תחילת ה-chunk, לא ה-mem.
רמז: הגודל שתראו יהיה 0x21 ולא 0x20. למה? חזרו להסבר על שלושת הדגלים.
שאלה לתשובה בכתב: כמה בתים בסך הכל תפס malloc(0x18) בערמה, ולמה זה לא 0x18?
תרגיל 2 - מילוי ה-tcache וזליגה ל-fastbin¶
עכשיו המשיכו עד ה-breakpoint השני (אחרי כל ה-free-ים). כאן קורה הדבר המעניין.
- הריצו
bins. חלקו את מבטכם לשניים: מה יש ב-tcachebinsומה יש ב-fastbinsשל גודל0x20? - ספרו: כמה נתחים בכל אחד מהם? האם המספרים מסתדרים עם מה שאתם יודעים על גבול ה-7 של ה-tcache?
- הסתכלו על הסדר של הנתחים בתוך ה-tcache. הנתח הראשון ברשימה - הוא ששוחרר ראשון או אחרון?
רמז: שחררנו 9 נתחים בגודל 0x20. ה-tcache מחזיק 7. חשבו מה קורה לשניים הנוספים.
רמז: הרשימות האלה מתנהגות LIFO. עקבו אחרי ה-fd של הנתח הראשון בכל רשימה עם malloc_chunk <addr> וראו לאן הוא מצביע.
שאלה לתשובה בכתב: איזה מהנתחים המקוריים (chunks[i]) נמצא בראש ה-tcache, ואיזה נמצא ב-fastbin? הסבירו לפי סדר ה-free.
תרגיל 3 - ה-unsorted bin וleak libc¶
הנתח הגדול (big) לא נכנס ל-tcache כי הוא גדול מ-0x410, וגם לא ל-fastbin. הוא נחת ב-unsorted bin.
- ב-
bins, מצאו את שורת ה-unsortedbin. מה הכתובת שאליה מצביע ה-fdשלbig? - הריצו
malloc_chunkעל הכתובת שלbigוהסתכלו עלfdו-bk. מה מיוחד בכתובת הזו לעומת המצביעים ב-tcache? - ודאו: הכתובת נראית כמו כתובת ב-libc (טווח גבוה,
0x7f...), ולא כמו כתובת בערמה (0x55...).
רמז: נתח יחיד ב-unsorted bin משרשר את עצמו בחזרה ל-main_arena, שיושבת בתוך libc. חפשו את הביטוי main_arena בפלט של pwndbg.
רמז: כדי לאמת שזו אכן libc, הריצו vmmap וראו לאיזה אזור זיכרון הכתובת שייכת.
שאלה לתשובה בכתב: אם הייתם יכולים לקרוא את 8 הבתים האלה מהזיכרון של התוכנית (למשל דרך חולשת use-after-free), איך זה היה עוזר לכם לעקוף ASLR? כתבו את הרעיון בשתי שורות.
תרגיל 4 (אתגר) - איחוד לתוך ה-top chunk¶
שנו את התוכנית: הסירו את השורה של guard (הנתח שהפריד את big מ-top chunk). הדרו מחדש והריצו שוב עד אחרי free(big).
- הריצו
bins. האםbigעדיין ב-unsorted bin? - הסתכלו על ה-top chunk (
top_chunkאו בסוף פלטheap). מה הגודל שלו עכשיו לעומת קודם?
רמז: נתח שגובל ב-top chunk לא נכנס לאף פח כשמשחררים אותו.
שאלה לתשובה בכתב: הסבירו במשפט אחד למה השומר guard היה נחוץ בתרגילים הקודמים כדי לראות את big ב-unsorted bin.
מה למדתם¶
אם עניתם על ארבעת התרגילים, יש לכם עכשיו מודל מנטלי חי של המקצה: אתם יודעים לזהות נתח בזיכרון, לקרוא את הדגלים שלו, ולחזות לאיזה פח הוא ילך כשישוחרר. הפתרון המלא נמצא בקובץ הפתרון - אבל נסו קודם לבד. ההפרש בין "קראתי על tcache" ל"ראיתי נתח נוחת ב-tcache" הוא כל ההבדל.