2.2 מציאת offset עם cyclic תרגול
תרגול - מציאת ה-offset בשלוש דרכים¶
בתרגול הזה ניקח בינארי vulnerable אחד ונמצא את ה-offset עד הreturn address (RIP) בשלוש שיטות עצמאיות: עם cyclic תחת gdb, מתוך core dump, ובחישוב ידני מה-frame. המטרה היא שתקבלו את אותו מספר בשלוש הדרכים, וכך גם תבינו למה כל שיטה עובדת וגם תדעו לבחור את המתאימה בשטח. בסוף יש תרגיל בונוס על bof מ-pwnable.kr, שבו ה-offset הוא לארגומנט ולא לreturn address.
הכנה - בניית הבינארי¶
צרו קובץ vuln.c והדרו אותו. הנחות ההגנה: NX פעיל, בלי canary, בלי PIE.
// vuln.c
#include <stdio.h>
#include <unistd.h>
void vuln(void) {
char buf[64];
read(0, buf, 256);
}
int main(void) {
vuln();
return 0;
}
ודאו שיש לכם pwntools ו-pwndbg מותקנים. לאורך התרגול הגדירו context.arch = 'amd64' בכל סקריפט Python.
תרגיל 1 - offset עם cyclic ב-gdb¶
מצאו את ה-offset עד הreturn address בעזרת gdb בלבד.
- ייצרו קובץ קלט שמכיל רצף דה-ברוין באורך 200 בתים.
- הריצו את
vulnתחת gdb כשהקובץ מוזן כקלט התקני, וגרמו לקריסה. - קראו את הערך שנחת על RIP.
- תרגמו את הערך ל-offset.
מה צריך להחזיר: המספר שקיבלתם, וצילום/העתקה של השורה שממנה קראתם אותו.
רמז: את הרצף מייצרים עם cyclic(200) וכותבים לקובץ במצב בינארי ('wb').
רמז: בתוך gdb מריצים run < pat. אחרי הקריסה, pwndbg מציג את האוגרים למעלה.
רמז: pwndbg יודע לתרגם חלון ל-offset עם cyclic -l <value>. במערכת 64 ביט קחו את 4 הבתים התחתונים של הערך שנחת על RIP.
תרגיל 2 - offset מתוך core dump¶
עכשיו בלי gdb מחובר בזמן הקריסה. גרמו למערכת לייצר core, ואז חלצו ממנו את ה-offset.
- אפשרו כתיבת core dumps ובדקו לאן הם נכתבים.
- הריצו את
vulnעם הרצף כקלט (מחוץ ל-gdb) כך שייווצר קובץ core. - חלצו את הערך שנחת על RIP מתוך ה-core, פעם אחת עם gdb ופעם אחת עם pwntools.
- ודאו שקיבלתם את אותו offset כמו בתרגיל 1.
מה צריך להחזיר: הפקודות שהרצתם, שם קובץ ה-core שנוצר, וה-offset.
רמז: ulimit -c unlimited פותח את מגבלת הגודל. בדקו את cat /proc/sys/kernel/core_pattern. אם הוא מתחיל ב-|, ה-core נשלח לשירות מערכת ולא לקובץ.
רמז: כדי לקבל קובץ core פשוט בתיקייה: echo core | sudo tee /proc/sys/kernel/core_pattern. הכיתוב (core dumped) בפלט הקריסה מאשר שהצליח.
רמז: עם gdb פותחים gdb ./vuln core ומריצים info registers rip. עם pwntools משתמשים במחלקה Coredump ובתכונה .rip, או נותנים ל-pwntools לאסוף את ה-core דרך io.corefile.
תרגיל 3 - חישוב ידני מה-frame¶
בלי להריץ כלום. חשבו את ה-offset ישירות מהדיסאסמבלי.
- פרקו את הפונקציה
vuln. - מצאו איפה
bufיושב יחסית ל-rbp. - חשבו כמה בתים יש מתחילת
bufעד הreturn address. - ודאו שהמספר תואם את מה שקיבלתם בתרגילים 1 ו-2.
מה צריך להחזיר: החישוב שלכם והמספר הסופי.
רמז: objdump -d ./vuln ומחפשים את הבלוק של vuln. שימו לב להוראת lea שמחשבת את כתובת buf.
רמז: הreturn address נמצאת ב-rbp+8, ו-saved rbp נמצא ב-rbp. אם buf ב-rbp-0x40, מה המרחק עד rbp+8?
רמז: אל תתנו לערך של sub rsp, ... לבלבל אתכם. מדדו יחסית ל-rbp דרך ה-lea.
תרגיל 4 (בונוס) - bof מ-pwnable.kr¶
האתגר bof מ-pwnable.kr הוא buffer overflow קלאסית ב-32 ביט. הפונקציה קוראת קלט לתוך buffer עם gets, ואחריו על המחסנית יושב ארגומנט שהתוכנית משווה לערך קסם. המטרה: לדרוס את הארגומנט הזה כך שההשוואה תצליח ותפתח shell.
מקורות ופרטים:
- אפשר להוריד את הקוד והבינארי מדף האתגר, ולתקוף את השירות עם
nc pwnable.kr 9000. - מבנה הקוד (מקוצר): פונקציה שמקבלת ארגומנט
key, מגדירהchar overflowme[32], קוראת אליו עםgets, ואז בודקתif (key == 0xcafebabe) system("/bin/sh").
מה לעשות:
- הבינו איזה שדה אתם רוצים לדרוס. כאן זה לא הreturn address אלא הארגומנט
key. - השתמשו באותה שיטת
cyclicכדי למצוא את ה-offset מתחילתoverflowmeעד השדה שלkey. אתם יכולים לעשות זאת מקומית על הבינארי שהורדתם, תחת gdb. - בנו payload שממלא עד ה-offset ואז כותב
0xcafebabe. שימו לב שזה 32 ביט, אז השתמשו ב-p32. - שגרו מול השירות המרוחק וקבלו shell.
מה צריך להחזיר: ה-offset שמצאתם, סקריפט ה-pwntools, ותוכן ה-flag.
רמז: זה בינארי 32 ביט. הגדירו context.arch = 'i386', וקראו את eip (או את השדה שנדרס) ישירות עם cyclic_find.
רמז: לחיבור מרוחק השתמשו ב-remote('pwnable.kr', 9000) במקום process. את ה-offset מצאו מקומית, ואז שגרו את אותו payload מול ה-remote.
רמז: אם ה-offset יוצא סתמי, זכרו שבין הbuffer לארגומנט יש עוד שדות (saved ebp והreturn address) שגם אותם צריך לדלג עליהם, ו-cyclic סופר אותם בשבילכם.