לדלג לתוכן

8.1 קבצים ארוזים ו UPX תרגול

תרגול - קבצים ארוזים ו-UPX

בתרגול הזה נפתור את האתגר flag מ-pwnable.kr. זהו אתגר מקטגוריית Toddler's Bottle, ואחד המפורסמים באתר, כי הוא נראה קשה במבט ראשון ומתגלה כפשוט ברגע שמזהים מה קורה. שימו לב: flag אינו אתגר SSH. בעמוד האתגר באתר יש קישור להורדת בינארי בשם flag. מורידים אותו למכונה שלכם ועובדים מקומית - אין כאן שרת שמתחברים אליו, כל המשימה היא להוציא את מחרוזת הדגל מתוך הקובץ.

תיאור האתגר באתר הוא רמז בפני עצמו: "Papa brought me a packed present! let's open it." המילה packed לא מקרית.

מטרת התרגול: לזהות שהקובץ ארוז, לפרוק אותו בשתי דרכים (אוטומטית וידנית), ולחלץ את הדגל. עבדו בשלבים - כל תרגיל בונה על הקודם. אם אין לכם עדיין את upx ואת pwndbg, התקינו: sudo apt install upx-ucl ואת pwndbg מהמאגר שלו.

תרגיל 0 (חימום) - לבנות ולפרוק קובץ ארוז משלכם

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

  1. כתבו תוכנית C קטנה שמחזיקה מחרוזת סוד ב-.rodata (למשל מעתיקה אותה לbuffer עם strcpy) אבל לא מדפיסה אותה.
  2. הדרו בקישור סטטי: gcc -O0 -static -o secret secret.c.
  3. ודאו שהסוד גלוי: strings secret | grep -i flag.
  4. ארזו: upx --best -o secret.packed secret.
  5. ודאו שהסוד נעלם: strings secret.packed | grep -i flag (אמור להיות ריק).
  6. פרקו בחזרה עם upx -d וודאו שהסוד חזר.

רמז: אם עם -O2 המהדר מוחק את הbuffer כי לא השתמשתם בו, השתמשו ב--O0 או הדפיסו את אורך המחרוזת (strlen) כדי לשמור אותה חיה בלי לחשוף אותה.

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

תרגיל 1 - לזהות שהאתגר ארוז

הורידו את flag מעמוד האתגר ואל תריצו אותו עדיין. חקרו אותו סטטית וקבעו: האם הוא ארוז, ובמה?

  1. הריצו file ./flag. מה סוג הקובץ? האם הוא מקושר סטטי? האם יש לו טבלת סקשנים?
  2. הריצו strings ./flag | head -50. כמה מחרוזות קריאות ומשמעותיות אתם רואים ביחס לגודל הקובץ?
  3. הריצו readelf -S ./flag ואז readelf -l ./flag. מה חסר, ומה כן יש?
  4. חשבו את האנטרופיה של הקובץ (בסקריפט מההרצאה או עם binwalk -E ./flag). איפה היא קופצת?
  5. הריצו strings ./flag | grep -i upx. מה זה מגלה לכם באופן חד-משמעי?

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

בסוף התרגיל הזה אתם צריכים לדעת בוודאות: הקובץ ארוז ב-UPX.

תרגיל 2 - לפרוק את הקובץ

עכשיו פותחים את המתנה. פרקו את flag וקבלו את הבינארי המקורי.

  1. פרקו לקובץ פלט נפרד כדי לא לדרוס את המקור: upx -d ./flag -o ./flag.unpacked.
  2. הריצו file ./flag.unpacked. במה הוא שונה מהקובץ הארוז?
  3. הריצו readelf -S ./flag.unpacked. האם חזרה טבלת הסקשנים?
  4. השוו: strings ./flag | wc -l מול strings ./flag.unpacked | wc -l. הפרש משמעותי?

רמז: אם upx -t ./flag מאשר שזה UPX תקין, אז upx -d יעבוד בפקודה אחת בלי שום התעסקות.

תרגיל 3 - לחלץ את הדגל

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

  1. הדרך המהירה: הריצו strings ./flag.unpacked וחפשו מחרוזת שנראית כמו דגל. נסו grep על מילים סבירות.
  2. הדרך היסודית: פרקו את main עם objdump -d ./flag.unpacked | less ומצאו לאן ה-main מצביע. שימו לב לקריאה ל-malloc ואז ל-strcpy - מהיכן נלקח המקור של ה-strcpy?
  3. עקבו אחרי הכתובת שממנה strcpy מעתיק, ומצאו את המחרוזת שם. אפשר עם objdump או ב-gdb עם x/s.

רמז: התוכנית מקצה buffer ומעתיקה אליו משתנה גלובלי בשם flag. המשתנה הזה הוא מצביע למחרוזת הדגל שיושבת ב-.rodata. ב-gdb: p (char*)flag או x/s &flag.

רמז שני: אם strings לבד פותר לכם את זה בשנייה - יופי, זו בדיוק הנקודה. פירוק האריזה הפך משימה בלתי אפשרית לטריוויאלית.

הגישו את הדגל באתר. אבל אל תעצרו כאן - התרגיל הבא מלמד את מה שקורה כשאין upx -d.

תרגיל 4 - פריקה ידנית מהזיכרון

עכשיו העמידו פנים ש-upx -d לא קיים או לא עובד. חלצו את אותו הדגל מהקובץ הארוז המקורי flag בלי לפרוק אותו כלל, על ידי קריאה מהזיכרון אחרי שה-stub סיים לפרוק.

  1. פתחו את הקובץ הארוז ב-gdb ./flag (עם pwndbg).
  2. עצרו על נקודת הכניסה עם starti. אתם עכשיו בתוך ה-stub של UPX.
  3. תנו ל-stub לסיים את הפריקה: הבינארי מדפיס הודעה, וקריאת המערכת write בהכרח קורית רק אחרי שהכול פורק. השתמשו ב-catch syscall write ואז continue.
  4. עכשיו כל הזיכרון פרוק. חפשו את הדגל בזיכרון עם search של pwndbg.
  5. לחלופין, השליכו את מקטע הזיכרון הרלוונטי לקובץ (dump memory) והריצו עליו strings.

רמז: info proc mappings מראה לכם את טווחי הכתובות. הקוד והנתונים של הבינארי המקורי נמצאים באזור נמוך (סביב 0x400000).

רמז שני: search "sound" או search "delivery" יתפוס את הדגל אם ניחשתם מילה ממנו. אם לא, השליכו הכול ל-strings: dump memory /tmp/d.bin <start> <end> ואז strings /tmp/d.bin | less.

תרגיל 5 (העמקה) - כשהכותרת שובשה

טריק CTF נפוץ הוא לשבש בכוונה את כותרת ה-UPX כדי ש-upx -d יסרב לפרוק, אף שהקובץ עדיין רץ מצוין. תרגלו את התרחיש הזה על העותק שבניתם בתרגיל 0.

  1. קחו את secret.packed. מצאו את מספר הקסם UPX! בקובץ (למשל עם grep -abo 'UPX!' secret.packed או ב-hex editor).
  2. שנו בית אחד באחת ההופעות של החתימה (זהירות - עשו זאת על עותק).
  3. ודאו שהקובץ עדיין רץ (./secret.corrupt), אבל upx -d נכשל עכשיו.
  4. חלצו את הסוד בכל זאת, בשיטה הידנית מתרגיל 4.

רמז: upx -d נשען על החתימה כדי לזהות ומזפרק; ה-stub, לעומת זאת, לא בודק אותה - הוא פשוט מריץ את קוד הפריקה. לכן פריקה ידנית מהזיכרון עמידה לשיבוש הכותרת.

חשבו: מה עוד אפשר לעשות כדי ש-upx -d יעבוד שוב? (רמז: לתקן את הבית ששיברנו בחזרה). איך הייתם מאתרים איזה בית שונה מ-UPX תקין?

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