2.1 אנטומיה של גלישה ושליטה בRIP תרגול
תרגול - אנטומיה של overflow ושליטה ב-RIP¶
בתרגול הזה נעבור מהתיאוריה לידיים. נתחיל בהבנת המבנה של המסגרת דרך GDB, נפתור את האתגר bof מ-pwnable.kr על ידי דריסת משתנה מקומי, ונסגור בשליטה מלאה ב-RIP על דמו מקומי. אל תדלגו על תרגיל 1, הוא הבסיס לשני האחרים.
רקע - האתגר bof מ-pwnable.kr¶
האתגר bof הוא אתגר בן 5 נקודות בקטגוריה Toddler's Bottle. בעמוד האתגר תמצאו שני קבצים להורדה: את הבינארי bof ואת קוד המקור bof.c. השירות הvulnerable מאזין ברשת:
כלומר, בשונה מאתגרים שדרשו התחברות ב-ssh, כאן פשוט מתחברים לפורט 9000 ושולחים את ה-payload. הבינארי הוא 32 ביט, אז השתמשו ב-p32 ולא ב-p64.
קוד המקור, בקצרה: main קורא ל-func(0xdeadbeef), הפונקציה func קוראת קלט עם gets לתוך buffer מקומי, ואז בודקת אם הארגומנט key שווה ל-0xcafebabe. אם כן, היא מריצה system("/bin/sh").
תרגיל 1 - לקרוא את המסגרת ב-GDB¶
לפני שתוקפים משהו, צריך לראות אותו. הידרו את הדמו מהשיעור והציצו לתוך המסגרת.
צרו קובץ demo.c:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void win() {
puts("[+] you took control!");
system("/bin/sh");
}
void vuln() {
char buf[64];
printf("send: ");
read(0, buf, 256);
}
int main() {
setvbuf(stdout, NULL, _IONBF, 0);
vuln();
puts("bye");
return 0;
}
הידרו אותו בלי הגנות:
המשימות שלכם:
- הריצו
checksec --file=./demoורשמו את מצב ה-RELRO, Canary, NX ו-PIE. האם התוצאה תואמת למה שציפיתם מדגלי ההידור? - מצאו את כתובת הפונקציה
winבשתי דרכים שונות: פעם עםobjdump -d demo | grep win, ופעם מתוך pwntools עםELF('./demo').symbols['win']. ודאו ששתי הדרכים נותנות אותה כתובת. - פתחו את הבינארי ב-GDB (עדיף עם pwndbg), שימו breakpoint על הפונקציה
vuln, הריצו, ופרקו אותה עםdisassemble vuln. באיזו כתובת יחסית ל-rbpיושב הbufferbuf? - חשבו: כמה בתים צריך לכתוב מתחילת
bufכדי להגיע בדיוק לreturn address? רשמו את המספר, נשתמש בו בתרגיל 3.
רמז: בדיסאסמבלי חפשו את ההוראה lea rax, [rbp-0x??] שממש לפני הקריאה ל-read. המרחק לreturn address הוא הערך הזה ועוד 8 בתים (בשביל ה-rbp השמור).
רמז: אם אתם רוצים לאמת את החישוב במקום לנחש, שוברים אחרי ה-read, מדפיסים את כתובת הbuffer ואת $rbp+8, ומחסרים. את השיטה האוטומטית עם cyclic נראה בשיעור הבא.
תרגיל 2 - לפתור את bof¶
זה התרגיל המרכזי. המטרה: לפתוח shell על השרת של pwnable.kr ולקרוא את קובץ ה-flag.
המשימות:
- הורידו את
bofואתbof.cמעמוד האתגר. קראו את המקור והבינו מדועkeyניתן לדריסה למרות שהוא הוזרק כקבוע בקוד. - פרקו את
funcעםobjdump -d bof(או ב-GDB). מצאו שני מספרים: באיזו כתובת יחסית ל-ebpיושב הbuffer, ובאיזו כתובת יושב הארגומנטkey. - חשבו את ה-offset מהbuffer ועד
key. זה מספר הבתים לpadding לפני שכותבים את הערך הקסום. - כתבו סקריפט pwntools שמתחבר ל-
pwnable.krפורט 9000, שולח את ה-payload, ופותח מעטפת אינטראקטיבית. בתוך המעטפת הריצוcat flag.
רמז: זו מערכת 32 ביט עם cdecl. הארגומנט key עבר על המחסנית, מעל הreturn address. הbuffer נמצא מתחת. כלומר גם כאן הbuffer שאתם שולטים בו נמצא מתחת ליעד.
רמז: בדיסאסמבלי חפשו lea eax, [ebp-0x??] (הbuffer) ו-cmp dword [ebp+0x?], 0xcafebabe (הבדיקה על key). ה-offset הוא ההפרש בין שתי הכתובות האלה.
רמז: לחיבור השתמשו ב-remote('pwnable.kr', 9000), לערך הקסום ב-p32(0xcafebabe), ולסיום ב-p.interactive(). שימו לב: כאן אתם דורסים את key בלבד, לא את הreturn address.
תרגיל 3 - שליטה מלאה ב-RIP¶
עכשיו נשתלט על מצביע ההוראות. נחזור לדמו מתרגיל 1, ונגרום ל-vuln לחזור אל win במקום אל main.
המשימות:
- השתמשו ב-offset שחישבתם בתרגיל 1 (המרחק מהbuffer לreturn address).
- בנו payload: הpadding באורך ה-offset, ואחריו הכתובת של
winכ-p64. - הריצו את הסקריפט מול
process('./demo'), ובדקו אם קיבלתם shell. - אם ה-shell נפתח ומיד קורס עם SIGSEGV, אל תיבהלו. חשבו על alignment המחסנית ותקנו.
רמז: השתמשו ב-ELF('./demo').symbols['win'] כדי לא לרשום כתובת קשיחה בקוד.
רמז: מלכודת ה-movaps. אם ה-shell מת בתוך system, הכניסו כתובת של גאדג'ט ret בודד לפני הכתובת של win. ב-pwntools אפשר להשיג אותו עם ROP(elf).find_gadget(['ret'])[0]. הוא מזיז את המחסנית ב-8 בתים ומיישר אותה ל-16.
רמז: אם אתם רוצים לוודא שאתם באמת דורסים את הreturn address בכתובת הנכונה, הריצו את הסקריפט עם gdb.attach(p) או פשוט הריצו dmesg אחרי קריסה כדי לראות לאיזו כתובת ניסיתם לקפוץ.
בונוס - כשה-offset לא עגול¶
הידרו את הדמו שוב, הפעם עם buffer בגודל אחר, למשל char buf[72], או בלי -g. חשבו מחדש את ה-offset. האם הוא בדיוק 72 + 8? הקומפיילר לפעמים מוסיף alignment padding שמשנה את התמונה. נסו למצוא את ה-offset האמיתי ידנית ב-GDB. אם זה מתחיל להיות מעצבן, זה בדיוק הרגע להעריך את cyclic, שנפגוש בשיעור 2.2.