לדלג לתוכן

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;
}
gcc -fno-stack-protector -no-pie -o vuln vuln.c
checksec ./vuln

ודאו שיש לכם pwntools ו-pwndbg מותקנים. לאורך התרגול הגדירו context.arch = 'amd64' בכל סקריפט Python.


תרגיל 1 - offset עם cyclic ב-gdb

מצאו את ה-offset עד הreturn address בעזרת gdb בלבד.

  1. ייצרו קובץ קלט שמכיל רצף דה-ברוין באורך 200 בתים.
  2. הריצו את vuln תחת gdb כשהקובץ מוזן כקלט התקני, וגרמו לקריסה.
  3. קראו את הערך שנחת על RIP.
  4. תרגמו את הערך ל-offset.

מה צריך להחזיר: המספר שקיבלתם, וצילום/העתקה של השורה שממנה קראתם אותו.

רמז: את הרצף מייצרים עם cyclic(200) וכותבים לקובץ במצב בינארי ('wb').

רמז: בתוך gdb מריצים run < pat. אחרי הקריסה, pwndbg מציג את האוגרים למעלה.

רמז: pwndbg יודע לתרגם חלון ל-offset עם cyclic -l <value>. במערכת 64 ביט קחו את 4 הבתים התחתונים של הערך שנחת על RIP.


תרגיל 2 - offset מתוך core dump

עכשיו בלי gdb מחובר בזמן הקריסה. גרמו למערכת לייצר core, ואז חלצו ממנו את ה-offset.

  1. אפשרו כתיבת core dumps ובדקו לאן הם נכתבים.
  2. הריצו את vuln עם הרצף כקלט (מחוץ ל-gdb) כך שייווצר קובץ core.
  3. חלצו את הערך שנחת על RIP מתוך ה-core, פעם אחת עם gdb ופעם אחת עם pwntools.
  4. ודאו שקיבלתם את אותו 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 ישירות מהדיסאסמבלי.

  1. פרקו את הפונקציה vuln.
  2. מצאו איפה buf יושב יחסית ל-rbp.
  3. חשבו כמה בתים יש מתחילת buf עד הreturn address.
  4. ודאו שהמספר תואם את מה שקיבלתם בתרגילים 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").

מה לעשות:

  1. הבינו איזה שדה אתם רוצים לדרוס. כאן זה לא הreturn address אלא הארגומנט key.
  2. השתמשו באותה שיטת cyclic כדי למצוא את ה-offset מתחילת overflowme עד השדה של key. אתם יכולים לעשות זאת מקומית על הבינארי שהורדתם, תחת gdb.
  3. בנו payload שממלא עד ה-offset ואז כותב 0xcafebabe. שימו לב שזה 32 ביט, אז השתמשו ב-p32.
  4. שגרו מול השירות המרוחק וקבלו 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 סופר אותם בשבילכם.