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 (חימום) - לבנות ולפרוק קובץ ארוז משלכם¶
לפני שנוגעים באתגר, תרגלו על קובץ שאתם שולטים בו לגמרי. כך תדעו איך נראה כל שלב כשהתשובה ידועה מראש.
- כתבו תוכנית C קטנה שמחזיקה מחרוזת סוד ב-
.rodata(למשל מעתיקה אותה לbuffer עםstrcpy) אבל לא מדפיסה אותה. - הדרו בקישור סטטי:
gcc -O0 -static -o secret secret.c. - ודאו שהסוד גלוי:
strings secret | grep -i flag. - ארזו:
upx --best -o secret.packed secret. - ודאו שהסוד נעלם:
strings secret.packed | grep -i flag(אמור להיות ריק). - פרקו בחזרה עם
upx -dוודאו שהסוד חזר.
רמז: אם עם -O2 המהדר מוחק את הbuffer כי לא השתמשתם בו, השתמשו ב--O0 או הדפיסו את אורך המחרוזת (strlen) כדי לשמור אותה חיה בלי לחשוף אותה.
המטרה כאן היא שתזהו את החתימות, את קפיצת האנטרופיה, ואת ההבדל בין strings לפני ואחרי פריקה - על קובץ שהתשובה שלו ידועה.
תרגיל 1 - לזהות שהאתגר ארוז¶
הורידו את flag מעמוד האתגר ואל תריצו אותו עדיין. חקרו אותו סטטית וקבעו: האם הוא ארוז, ובמה?
- הריצו
file ./flag. מה סוג הקובץ? האם הוא מקושר סטטי? האם יש לו טבלת סקשנים? - הריצו
strings ./flag | head -50. כמה מחרוזות קריאות ומשמעותיות אתם רואים ביחס לגודל הקובץ? - הריצו
readelf -S ./flagואזreadelf -l ./flag. מה חסר, ומה כן יש? - חשבו את האנטרופיה של הקובץ (בסקריפט מההרצאה או עם
binwalk -E ./flag). איפה היא קופצת? - הריצו
strings ./flag | grep -i upx. מה זה מגלה לכם באופן חד-משמעי?
רמז: שלוש שורות טקסט קבועות שה-UPX משאיר בקובץ עונות על כל השאלה. אחת מהן היא מספר הקסם, ואחת מזכירה את אתר הפרויקט.
בסוף התרגיל הזה אתם צריכים לדעת בוודאות: הקובץ ארוז ב-UPX.
תרגיל 2 - לפרוק את הקובץ¶
עכשיו פותחים את המתנה. פרקו את flag וקבלו את הבינארי המקורי.
- פרקו לקובץ פלט נפרד כדי לא לדרוס את המקור:
upx -d ./flag -o ./flag.unpacked. - הריצו
file ./flag.unpacked. במה הוא שונה מהקובץ הארוז? - הריצו
readelf -S ./flag.unpacked. האם חזרה טבלת הסקשנים? - השוו:
strings ./flag | wc -lמולstrings ./flag.unpacked | wc -l. הפרש משמעותי?
רמז: אם upx -t ./flag מאשר שזה UPX תקין, אז upx -d יעבוד בפקודה אחת בלי שום התעסקות.
תרגיל 3 - לחלץ את הדגל¶
הבינארי פרוק. עכשיו מוצאים את מחרוזת הדגל. יש שתי דרכים, ושתיהן שוות לימוד.
- הדרך המהירה: הריצו
strings ./flag.unpackedוחפשו מחרוזת שנראית כמו דגל. נסוgrepעל מילים סבירות. - הדרך היסודית: פרקו את
mainעםobjdump -d ./flag.unpacked | lessומצאו לאן ה-mainמצביע. שימו לב לקריאה ל-mallocואז ל-strcpy- מהיכן נלקח המקור של ה-strcpy? - עקבו אחרי הכתובת שממנה
strcpyמעתיק, ומצאו את המחרוזת שם. אפשר עםobjdumpאו ב-gdbעםx/s.
רמז: התוכנית מקצה buffer ומעתיקה אליו משתנה גלובלי בשם flag. המשתנה הזה הוא מצביע למחרוזת הדגל שיושבת ב-.rodata. ב-gdb: p (char*)flag או x/s &flag.
רמז שני: אם strings לבד פותר לכם את זה בשנייה - יופי, זו בדיוק הנקודה. פירוק האריזה הפך משימה בלתי אפשרית לטריוויאלית.
הגישו את הדגל באתר. אבל אל תעצרו כאן - התרגיל הבא מלמד את מה שקורה כשאין upx -d.
תרגיל 4 - פריקה ידנית מהזיכרון¶
עכשיו העמידו פנים ש-upx -d לא קיים או לא עובד. חלצו את אותו הדגל מהקובץ הארוז המקורי flag בלי לפרוק אותו כלל, על ידי קריאה מהזיכרון אחרי שה-stub סיים לפרוק.
- פתחו את הקובץ הארוז ב-
gdb ./flag(עם pwndbg). - עצרו על נקודת הכניסה עם
starti. אתם עכשיו בתוך ה-stub של UPX. - תנו ל-stub לסיים את הפריקה: הבינארי מדפיס הודעה, וקריאת המערכת
writeבהכרח קורית רק אחרי שהכול פורק. השתמשו ב-catch syscall writeואזcontinue. - עכשיו כל הזיכרון פרוק. חפשו את הדגל בזיכרון עם
searchשל pwndbg. - לחלופין, השליכו את מקטע הזיכרון הרלוונטי לקובץ (
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.
- קחו את
secret.packed. מצאו את מספר הקסםUPX!בקובץ (למשל עםgrep -abo 'UPX!' secret.packedאו ב-hex editor). - שנו בית אחד באחת ההופעות של החתימה (זהירות - עשו זאת על עותק).
- ודאו שהקובץ עדיין רץ (
./secret.corrupt), אבלupx -dנכשל עכשיו. - חלצו את הסוד בכל זאת, בשיטה הידנית מתרגיל 4.
רמז: upx -d נשען על החתימה כדי לזהות ומזפרק; ה-stub, לעומת זאת, לא בודק אותה - הוא פשוט מריץ את קוד הפריקה. לכן פריקה ידנית מהזיכרון עמידה לשיבוש הכותרת.
חשבו: מה עוד אפשר לעשות כדי ש-upx -d יעבוד שוב? (רמז: לתקן את הבית ששיברנו בחזרה). איך הייתם מאתרים איזה בית שונה מ-UPX תקין?
בפתרון תמצאו את כל הפקודות והפלטים, את הדגל המלא, ואת שתי דרכי הפריקה עד הסוף.