לדלג לתוכן

1.2 באגים לוגיים והשוואות תרגול

תרגול - באגים לוגיים והשוואות

בתרגול הזה נפתור שלושה אתגרים מ-pwnable.kr, כולם מקטגוריית Toddler's Bottle, וכולם באגים לוגיים טהורים - בלי overflows זיכרון: mistake, lotto ו-blackjack. לכל אחד יש קוד מקור זמין, אז המשחק הוא בעיקר משחק קריאה. קראו את ה-.c לאט, שורה-שורה, וחפשו את התנאי או ההשוואה שנכתבו לא נכון.

התחברות - connecting

רוב אתגרי הרמה הזו נגישים ב-SSH. הסיסמה לכל המשתמשים היא guest:

ssh mistake@pwnable.kr -p2222   # password: guest
ssh lotto@pwnable.kr   -p2222   # password: guest

אחרי ההתחברות תמצאו בתיקיית הבית את הבינארי, את קובץ המקור המתאים (mistake.c, lotto.c), ואת קובץ ה-flag. הבינארים הם setuid, כך שהרצה שלהם קוראת את הדגל בשמכם. הריצו ls -l וראו את התו s בהרשאות. את אתגר blackjack אפשר לקרוא מקוד המקור המקושר בעמוד האתגר (blackjack.c); הריצו את המשחק ושחקו אותו עד שתגיעו לסכום הזכייה שחושף את הדגל.

לפני כל אתגר, קראו את הקוד והריצו checksec על הבינארי. תשימו לב שההגנות (canary, NX, PIE) לא רלוונטיות בכלל פה - זו נקודה שכדאי להפנים.


תרגיל 1 - mistake

המטרה: להריץ את mistake ולקבל את תוכן ה-flag.

פתחו את mistake.c וקראו אותו מתחילתו. שימו לב במיוחד לשורה שפותחת את קובץ הסיסמה, ולשורה שקוראת ממנו. אחר כך שימו לב ל-xor שמופעל על הקלט שלכם ולהשוואה הסופית עם strncmp.

  • רמז 1: הביטו היטב בשורה if(fd=open(...) < 0). איזה אופרטור נדבק חזק יותר, ה-= או ה-<? מה בעצם מקבל המשתנה fd?
  • רמז 2: אם fd יצא 0, מאיפה קוראת אחר כך הפונקציה read(fd, pw_buf, ...)? מי מספק עכשיו את "הסיסמה"?
  • רמז 3: אתם שולטים גם ב-pw_buf (מה שנקרא מ-stdin ראשון) וגם ב-pw_buf2 (מה שנקרא ב-scanf). על הקלט השני מופעל xor עם המפתח 1 לפני ההשוואה. איזה זוג קלטים יגרום ל-pw_buf להיות שווה ל-pw_buf2 אחרי ה-XOR? רמז קטן: '0' XOR 1 שווה '1'.
  • רמז 4: זכרו שה-read הראשון בולע בדיוק 10 בתים, ואילו ה-scanf("%10s") מדלג על רווח לבן וקורא את המילה הבאה. סדרו את הקלט בהתאם.

יש בקוד גם sleep(time(0)%20) עם ההערה "do not bruteforce". אתם לא צריכים bruteforce בכלל - פתרון אחד מדויק מספיק, אז ההשהיה לא מפריעה.


תרגיל 2 - lotto

המטרה: לקבל Jackpot! ולקרוא את ה-flag.

פתחו את lotto.c והתמקדו בשני חלקים: איך נקראים הבתים שלכם (read(0, submit, 6)), ואיך מחושב הניקוד (הלולאה המקוננת עם match).

  • רמז 1: הניקוד מחושב בלולאה מקוננת שמשווה כל מספר מוגרל מול כל מספר שהגשתם. האם ההשוואה תלוית-מיקום, או שכל התאמה נספרת בלי קשר למקום?
  • רמז 2: מה קורה ל-match אם כל ששת הבתים שהגשתם זהים, וקרה שאחד מהמספרים המוגרלים שווה להם? כמה יעלה match מהתאמה בודדת כזו?
  • רמז 3: המספרים המוגרלים הם בטווח 1 עד 45 (בגלל (byte % 45) + 1). אם תקלידו את הספרות "123456" במקלדת, אילו ערכי בית באמת נשלחים, והאם הם בכלל יכולים להתאים? חייבים לשלוח בתים גולמיים בטווח החוקי.
  • רמז 4: ההימור על שישה בתים זהים מנצח בהסתברות של בערך 12.5% לניסיון. כלומר, לא תמיד תזכו בניסיון הראשון. כתבו סקריפט (עם pwntools, או אפילו לולאת bash על השרת) שמריץ את האתגר שוב ושוב עד שמופיע Jackpot!.

שימו לב לתפריט של האתגר (בחירת "Play Lotto"). כשאתם מזינים את בחירת התפריט ואחריה את ששת הבתים, היזהרו לא להשאיר תו שורה חדשה שייבלע בטעות לתוך ששת הבתים של ההגשה.


תרגיל 3 - blackjack

המטרה: לצבור מספיק מזומן (cash) כדי לחצות את סף הזכייה שחושף את הדגל.

קראו את blackjack.c והתמקדו בפונקציה שקוראת את ההימור (betting) ובמקום שבו מתעדכן ה-cash אחרי זכייה.

  • רמז 1: איך בדיוק נבדק ההימור? יש רק בדיקת גבול עליון (bet > cash), או גם גבול תחתון? מה קורה אם תהמרו מספר שלילי?
  • רמז 2: כשאתם מהמרים יותר ממה שיש לכם, התוכנית מתלוננת וקוראת את ההימור פעם שנייה. האם היא בודקת שוב את הערך החדש הזה, או מקבלת אותו כמו שהוא?
  • רמז 3: הזינו בפרומפט הראשון סכום גדול מהמזומן שלכם כדי לעורר את הבקשה החוזרת, ובפרומפט השני הזינו מספר ענק. אחר כך נצחו יד אחת - cash יעודכן בתוספת ההימור ויקפוץ מעל הסף.
  • רמז 4: הזינו בבטחה מספר גדול מאוד (למשל בסביבות מיליון או יותר), ושחקו יד אחת בזהירות עד לזכייה. כמה שהמזומן שלכם גבוה יותר לפני הזכייה, כך אתם קרובים יותר לסף.

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