לדלג לתוכן

8.6 אתגרי לוגיקה תרגול

תרגול - אתגרי לוגיקה

בתרגול הזה נפתור ארבעה אתגרים מ-pwnable.kr שכל אחד מהם מדגים דפוס אחר של "לוגיקה + exploit קטן": simple login, otp, md5 calculator ו-blukat. המטרה היא לא רק להגיע לדגל, אלא לזהות בכל אחד את ההנחה השגויה של המתכנת ולתקוף בדיוק אותה.

כל האתגרים נמצאים על השרת של pwnable.kr ופותרים אותם דרך SSH. הסיסמה ההתחלתית לחשבונות היא לרוב guest (אמתו בעמוד האתגר). בתוך כל חשבון תמצאו את הבינארי, לרוב את קוד המקור (.c), וקובץ flag שאין לכם הרשאה לקרוא ישירות - המטרה היא לגרום לבינארי עצמו לחשוף אותו.

ssh simple_login@pwnable.kr -p2222
ssh otp@pwnable.kr           -p2222
ssh md5calculator@pwnable.kr -p2222
ssh blukat@pwnable.kr        -p2222

עבודה מסודרת: לכל אתגר, קודם checksec על הבינארי, אחר כך קריאת המקור (אם קיים) או פירוק עם objdump -d, ורק אז בניית הקלט. אל תנחשו לפני שקראתם.


תרגיל 1 - simple login

התחברו לחשבון simple_login וקראו את המקור. שימו לב לזרימה: התוכנית מבקשת מחרוזת, מפענחת אותה מ-base64, ומעתיקה את התוצאה עם memcpy לbuffer קטן על המחסנית בתוך פונקציית האימות. יש בבינארי פונקציית "הצלחה" שלא נקראת בזרימה הרגילה.

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

  • רמז 1: הריצו checksec. אין canary, אין PIE, זה 32 ביט. מה זה אומר על היציבות של הכתובת שאליה נרצה לקפוץ?
  • רמז 2: כמה בתים מפענחת מחרוזת base64 באורך 4 תווים? אם אתם שולטים באורך המפוענח, אתם שולטים בכמה בתים ה-memcpy יכתוב. עד איפה צריך להגיע כדי לדרוס את return address?
  • רמז 3: מצאו את הכתובת של פונקציית ההצלחה. נסו nm ./login | grep -i correct, או objdump -d ./login, או מ-pwntools עם elf.symbols.
  • רמז 4: מדדו את ה-offset עד return address של פונקציית האימות. buffer קטן + saved ebp של 4 בתים. אמתו עם cyclic אם אתם לא בטוחים.
  • רמז 5: הצעד שהכי קל לשכוח - התוכנית מפענחת מ-base64, אז אתם צריכים לקודד את הpayload שלכם ל-base64 לפני השליחה. בפייתון: base64.b64encode(payload).

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


תרגיל 2 - otp

התחברו לחשבון otp וקראו את המקור. התוכנית מייצרת ערך אקראי מ-/dev/urandom, כותבת אותו לקובץ ב-/tmp, קוראת אותו בחזרה למשתנה passcode, ומשווה אותו ל-argv[1] שאתם נותנים כמספר הקסדצימלי.

המטרה: לגרום להשוואה להצליח בלי לדעת את הערך האקראי.

  • רמז 1: לנחש ערך אקראי של 64 ביט זה חסר סיכוי. אל תנסו. תסתכלו במקום זה על מה קורה אם משהו במסלול נכשל.
  • רמז 2: לאיזה ערך מאותחל passcode לפני ה-fread? מה יקרה לערך הזה אם ה-fread יקרא 0 בתים?
  • רמז 3: איך גורמים ל-fread לקרוא 0 בתים? אם הקובץ נכתב ריק, אין מה לקרוא. איך גורמים לכתיבת הקובץ להיכשל בלי לגעת בתוכנית?
  • רמז 4: חפשו את מגבלת המשאב RLIMIT_FSIZE (ulimit -f). מה קורה כשתהליך מנסה לכתוב קובץ גדול מהמגבלה? איזה אות הוא מקבל, ומה פעולת ברירת המחדל של האות הזה?
  • רמז 5: אם פעולת ברירת המחדל של האות הורגת את התהליך, איך גורמים לו במקום זה רק להיכשל בכתיבה? חשבו על SIG_IGN, ועל מה עובר בירושה דרך execve (רמז: גם מגבלות משאב וגם התעלמות מאותות עוברות).
  • רמז 6: אחרי שהקובץ נשאר ריק ו-passcode נשאר בערך ההתחלתי, איזה ערך תעבירו ב-argv[1]?

בנו עטיפה קטנה (בפייתון או ב-C) שמגדירה את מגבלת המשאב ואת ההתעלמות מהאות, ואז מריצה את otp עם הארגומנט הנכון.


תרגיל 3 - md5 calculator

התחברו לחשבון md5calculator. זה שירות תפריטי שמחשב md5 של מחרוזת. מאחורי פיצ'ר החישוב מסתתרת גלישת מחסנית: הקלט מפוענח מ-base64 לתוך buffer קבוע, בלי בדיקה שהאורך המפוענח לא חורג מגודל הbuffer.

המטרה: לנצל את הoverflow כדי לנתב הרצה ולהדפיס את הדגל, תוך התמודדות עם הcanary.

  • רמז 1: checksec ראשון. הפעם יש canary. מה זה משנה בבניית הpayload? איזה ערך אסור לכם לדרוס בטעות?
  • רמז 2: היכן בדיוק גולשים? מצאו את גודל הbuffer שאליו מפענחים, ומדדו את המרחק ממנו עד הcanary ועד return address. cyclic ו-gdb/pwndbg הם חבריכם.
  • רמז 3: כדי לדרוס את return address חייבים לכתוב את הcanary הנכון במקומו. איך משיגים את ערך הcanary? יש שתי אפשרויות - leak או ניחוש. איזו מהן מתאימה למבנה של האתגר הזה?
  • רמז 4 (leak): האם יש בשירות פיצ'ר שמחזיר לכם בתים מהמחסנית (הדפסה של הbuffer, החזרת ה-digest, וכו')? אם כן, מלאו עד לפני הcanary בלי לדרוס אותו, וגרמו לתוכנית להדפיס עד שתראו את בתי הcanary.
  • רמז 5 (ניחוש): אם השירות עושה fork לכל חיבור, הcanary זהה בין החיבורים. נחשו אותו בית-בית: לכל בית, אם החיבור לא קורס - הבית נכון. זכרו שהcanary ב-64 ביט מתחיל בבית \x00.
  • רמז 6: אחרי שיש לכם את הcanary, הpayload הוא padding + canary + saved rbp + כתובת יעד. אל תשכחו לקודד את הכל ל-base64 לפני השליחה, כי התוכנית מפענחת.

התחילו מבינארי דמו מקומי עם canary (יש בפתרון) לפני שאתם נלחמים בסביבה של השרת.


תרגיל 4 - blukat

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

המטרה: לעבור את בדיקת הסיסמה ולקבל את הדגל.

  • רמז 1: מאיפה מגיעה הסיסמה? התוכנית קוראת אותה מקובץ. בדקו את ההרשאות של הקובץ הזה עם ls -l. האם אתם יכולים לקרוא אותו בעצמכם?
  • רמז 2: אם אתם יכולים לקרוא את הקובץ - קראו אותו, והזינו בדיוק את מה שיש בו. שימו לב לבית ה-newline בסוף: fgets שומר אותו, ו-strcmp דורש התאמה מדויקת. בדקו את הבתים המדויקים עם xxd.
  • רמז 3: אם הקובץ לא קריא לכם, חפשו זווית אחרת. הרבה מהאתגרים האלה מפענחים את הדגל ב-XOR עם מפתח שמוטמע בבינארי. חפשו מחרוזות ומקטעי נתונים עם strings ./blukat ו-objdump -s -j .data ./blukat.
  • רמז 4: XOR הוא הפיך. אם מצאתם טקסט מוצפן ומפתח בתוך הבינארי, טקסט_מוצפן XOR מפתח מחזיר את המקור. כתבו לולאת XOR קצרה בפייתון. אתם יכולים לקבל את הדגל בלי לעבור את הבדיקה בכלל.

בונוס: פתרו את blukat בשתי דרכים - גם דרך קריאת הקובץ הישירה וגם דרך גזירת ה-XOR - וודאו שאתם מבינים למה כל אחת עובדת.


תרגיל 5 - סיכום דפוסים (בונוס)

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