לדלג לתוכן

1.4 התנגשות גיבוב ומספרים תרגול

תרגול - התנגשות גיבוב ומספרים

בתרגול הזה נפצח את האתגר collision מ-pwnable.kr, וגם נחדד את האינטואיציה שלנו לגבי גלישת מספרים וסדר בתים. המטרה היא שתגיעו לפתרון בעצמכם - הפתרון המלא נמצא בקובץ הנפרד, אבל נסו קודם.

התחברות לאתגר - connecting

האתגר collision שווה 3 נקודות. מתחברים אליו כך:

ssh col@pwnable.kr -p2222
# password: guest

בתוך המכונה תמצאו שלושה קבצים:

  • הבינארי col - קובץ עם הרשאות setuid (רץ בהרשאות הבעלים שיכול לקרוא את הדגל).
  • קובץ המקור col.c - קראו אותו לעומק.
  • הדגל flag - אין לכם הרשאה לקרוא אותו ישירות. רק col יכול.

התחילו מ-cat col.c והבינו בדיוק מה הפונקציה check_password עושה ומה main בודק.


תרגיל 1 - פענוח הדרישה

לפני שנוגעים במקלדת בשביל הפתרון, ענו לעצמכם בכתב על השאלות הבאות. אם אתם יודעים לענות עליהן, הפתרון כבר בכיס.

  1. כמה בתים בדיוק חייב להיות אורך הקלט? מאיזו בדיקה זה נובע?
  2. הפונקציה check_password מקבלת מצביע ל-char. איך היא הופכת 20 בתים לחמישה מספרים? מה גודל כל מספר?
  3. מהו הערך המדויק שהסכום צריך להגיע אליו? כתבו אותו גם בהקסה וגם בעשרוני.
  4. האם הקלט שלכם יכול להכיל את הבית 0x00? הסבירו למה, מתוך ההתנהגות של strlen.

רמז: הבדיקה strlen(argv[1]) != 20 היא זו שקובעת גם את האורך וגם את האיסור על בית null.


תרגיל 2 - חשבון ההתנגשות

עכשיו החלק המתמטי. אתם צריכים למצוא חמישה מספרים של 4 בתים שסכומם הוא היעד מהתרגיל הקודם.

  1. חשבו את היעד חלקי 5. מה השארית?
  2. הציעו חמישה מספרים שסכומם בדיוק היעד. הדרך הפשוטה: ארבעה מספרים זהים, ואחד שסופג את השארית.
  3. ודאו שאף אחד מחמשת המספרים, כשהוא נכתב כארבעה בתים, לא מכיל בית 0x00.

רמז: 0x21DD09EC / 5 נכנס בול כמעט. חשבו כמה צריך להוסיף לאחד המספרים כדי לכסות את השארית.

רמז שני: היזהרו מoverflow. הסכום מצטבר במשתנה int מסומן. אם תבחרו מספרים גדולים מדי, הסכום עלול לגלוש למספר שלילי - ואז הרחבת הסימן ל-unsigned long תהרוס את ההשוואה. מספרים קטנים וחיוביים שומרים אתכם בטוחים.


תרגיל 3 - סדר הבתים

קחו כל אחד מחמשת המספרים והמירו אותו לרצף בתים בסדר little-endian.

  1. עבור המספר 0x06C5CEC8, מהו סדר ארבעת הבתים בזיכרון? כתבו אותם משמאל לימין כפי שהם יופיעו.
  2. הרכיבו את 20 הבתים המלאים.
  3. אמתו בפייתון שהאורך הוא בדיוק 20 ושאין אף בית null:
import struct
payload = struct.pack('<I', 0x06C5CEC8) * 4 + struct.pack('<I', 0x06C5CECC)
print(len(payload), 0x00 in payload)

רמז: הבית הראשון בזיכרון הוא הבית הכי פחות משמעותי של המספר. 0x06C5CEC8 מתחיל בבית 0xC8.


תרגיל 4 - הזרמת הקלט והרצה

הריצו את col עם הקלט שבניתם. הקלט הוא בתים גולמיים לא מודפסים, אז אתם צריכים דרך להזרים אותם כארגומנט.

  1. בנו שורת פקודה עם python -c ו-command substitution שמעבירה את 20 הבתים כ-argv[1].
  2. הריצו את ./col עם הקלט. מה מודפס?
  3. אם קיבלתם wrong passcode, בדקו את סדר הבתים. אם קיבלתם passcode length should be 20 bytes, בדקו אם נכנס בטעות בית null שקיצר את המחרוזת.

רמז: שימו לב לגרסת הפייתון על המכונה. אם python הוא פייתון 2, ההדפסה של מחרוזת בתים פשוטה יותר. אם זה פייתון 3, השתמשו ב-sys.stdout.buffer.write כדי לא להרוס את הבתים.


תרגיל 5 - אימות עם GDB (העמקה)

כדי באמת להבין מה קורה, ולא רק לקבל דגל, שווה לוודא בעיניים שהמספרים נקראים כמו שחשבתם.

  1. הריצו את col תחת GDB עם הקלט שלכם כארגומנט.
  2. שימו נקודת עצירה על check_password והריצו.
  3. בתוך הפונקציה, הסתכלו על 20 הבתים שהתקבלו ופרשו אותם כחמישה מספרים של 4 בתים. האם כל אחד מהם שווה למה שתכננתם?
  4. עקבו אחרי הצטברות res בלולאה. האם הערך הסופי שווה ל-0x21DD09EC?

רמז: הפקודה x/5xw $rdi (או האוגר שמחזיק את המצביע לקלט) תדפיס חמש מילים של 4 בתים מהכתובת של הקלט. השוו אותן למספרים שבחרתם.


שאלת העמקה - למחשבה

באתגר הזה הסכום נשאר חיובי ולא גלש. אבל דמיינו וריאציה שבה היעד הוא 0xFFFFFFF0, והמשתנה עדיין int מסומן. האם עדיין הייתם יכולים לפתור? איך תבחרו את חמשת המספרים כך שהסכום, מודולו 2^32, ייתן את הבתים הנכונים, ובלי שההשוואה ל-unsigned long תיפול על הרחבת סימן? חשבו על זה - זו בדיוק ההבנה שתפריד אתכם ממי שרק משנן פתרונות.