8.5 מפרשים ומכונות וירטואליות תרגול
תרגול - exploit מפרש Brainfuck¶
בתרגול הזה תנצלו מפרש Brainfuck אמיתי ותהפכו אותו למעטפת. זה האתגר brainfuck מ-pwnable.kr. המטרה שלכם היא לקחת את הפרימיטיבים של המפרש - הזזת מצביע, קריאת בית וכתיבת בית - ולבנות מהם קריאה וכתיבה שרירותיות שיובילו לדריסת GOT ולהרצת system("/bin/sh").
הקשר האתגר - challenge context¶
השרות מאזין ב-nc pwnable.kr 9001. בעמוד האתגר תמצאו את הקבצים bf - הבינארי - ו-bf_libc.so - עותק ה-libc שרץ בשרת. הורידו את שניהם לתיקיית עבודה. פותחים חיבור כך:
התכנית מדפיסה מסך פתיחה, קוראת שורה אחת של פקודות Brainfuck, ומריצה אותן פקודה-פקודה מעל סרט של בתים. הפקודות [ ו-] מבוטלות. שאר השש - > < + - . , - פעילות.
לפני שנוגעים בקוד, הכינו את הכלים:
הריצו את הבינארי גם מקומית כדי להתנסות בלי לחץ. כדי להריץ אותו עם ה-libc של האתגר, השתמשו ב-pwntools:
תרגיל 1 - מיפוי הפרימיטיבים¶
לפני כתיבת אקספלויט, ודאו שאתם מבינים בדיוק מה כל פקודה עושה ברמת הזיכרון.
- פרקו את
do_brainfuckב-objdump -d ./bfוזהו איזו הוראת אסמבלי מתאימה לכל אחת מהפקודות> < + - . ,. - ענו לעצמכם: איזו פקודה נותנת לכם קריאה מהזיכרון, ואיזו נותנת כתיבה? דרך איזו פונקציית libc כל אחת עוברת?
- מהו הבאג המדויק שמאפשר למצביע
pלצאת מגבולות הסרט?
רמז: חפשו ב-do_brainfuck בדיקת גבולות על p. האם יש כזו?
תרגיל 2 - איתור הכתובות¶
כדי לכוון את המצביע צריך לדעת לאן. אספו את הכתובות הקבועות (הבינארי ללא PIE, אז הן לא משתנות).
- מצאו את הכתובת של
tapeושלmain. - מצאו את כתובות ה-GOT של
putchar,getchar,fgets,memset.
רמז: השתמשו בפקודות הבאות:
nm ./bf | egrep ' tape| main'
objdump -R ./bf | egrep 'putchar|getchar|fgets|memset'
readelf --relocs ./bf
רמז שני: שימו לב מי גדול יותר - tape או ה-GOT. הכיוון הזה קובע אם תזיזו את p עם < או עם >, וכמה פעמים. ההפרש tape - got_entry הוא מספר ה-< שתצטרכו.
תרגיל 3 - קריאה וכתיבה שרירותיות¶
בנו שתי פונקציות עזר מנטליות (או בקוד):
- קריאת ארבעה בתים מכתובת נתונה: הזיזו את
pלשם ואז השתמשו ב-.כדי להוציא בית-בית, עם>בין קריאה לקריאה. - כתיבת ארבעה בתים לכתובת נתונה: הזיזו את
pלשם ואז השתמשו ב-,כדי לכתוב בית-בית.
כתבו לעצמכם פונקציה שבונה את שרשרת ה-</> הדרושה כדי לעבור מכתובת נוכחית ליעד. עקבו אחרי המצביע: כל . או , שמלווה ב-> מקדם את p בבית.
רמז: החזיקו משתנה cur שמתעדכן בכל הזזה, כדי לחשב תמיד את ההפרש ליעד הבא.
תרגיל 4 - leak libc¶
עכשיו נשיג כתובת libc אמיתית.
- איזו רשומת GOT כבר מכילה כתובת libc ברגע שהתכנית שלכם רצה? רמזור: איזו פונקציה כבר נקראה על ידי
mainלפני הלולאה? - קראו את ארבעת הבתים של אותה רשומה בעזרת פרימיטיב הקריאה. חשבו את בסיס libc:
libc_base = leak - libc.sym['<אותה פונקציה>']. - מתוך הבסיס, חשבו את הכתובת של
system.
רמז: putchar ו-getchar עדיין לא נקראו בשלב הזה, אז ה-GOT שלהן עדיין מצביע ל-resolver ולא ל-libc. בחרו פונקציה שכן נקראה - למשל fgets.
תרגיל 5 - דריסת GOT והרצת מעטפת¶
זה החלק שסוגר את המעגל. חשבו: אחרי שהלולאה נגמרת main פשוט חוזר. איך גורמים ל-libc-פונקציה כלשהי להיקרא שוב עם ארגומנט שאתם שולטים בו?
- אילו פקודות מפעילות פונקציית libc בתוך הלולאה שלכם, ולכן אפשר להשתמש בהן כטריגר?
- אם תדרסו רשומת GOT של אחת מהן, מה יקרה בקריאה הבאה?
- חשבו איזו פונקציה ב-
mainמקבלת מצביע שאתם שולטים בתוכנו כארגומנט ראשון. אם תדרסו את רשומת ה-GOT שלה ל-system, מה יורץ?
רמז: יש כאן שני צעדים. אחד - לגרום ל-main לרוץ מחדש (טריק: דרסו רשומת GOT של פונקציה שנקראת מתוך הלולאה, כך שתקפוץ ל-main). שניים - לגרום לקריאה מוקדמת ב-main להפוך ל-system על מחרוזת שאתם שתלתם. חשבו איזו פונקציה ב-main מקבלת את tape כארגומנט הראשון, ולמה זה נוח.
רמז אחרון: אם הפונקציה שדרסתם ל-system היא זו שאמורה לאפס את הסרט - האם המחרוזת שלכם תשרוד עד הקריאה? כן, בדיוק בגלל שהיא כבר לא באמת מאפסת.
תרגיל 6 - אקספלויט מלא¶
חברו הכל לסקריפט pwntools יחיד:
- בנו את שורת ה-Brainfuck: leak, ואז שתי הדריסות, ואז שתילת
"/bin/sh", ואז פקודת טריגר. - שלחו את השורה, קראו את בתי הleak, חשבו את הכתובות.
- שלחו את בתי הכתיבה בסדר הנכון שבו פקודות ה-
,צורכות אותם. - קבלו מעטפת עם
io.interactive()והריצוcat flag.
רמז לתזמון: פקודת ה-, קוראת דרך getchar מ-stdin בנפרד משורת התכנית. לכן שלחו קודם רק את שורת התכנית, קראו את הleak, ורק אז שלחו את בתי הכתיבה. שימו את הleak ראשונה בתכנית כדי שהיא לא תיחסם על קלט.
בהצלחה. אם נתקעתם, הפתרון המלא נמצא בקובץ הפתרון, אבל נסו קודם להגיע לבד עד המעטפת - הבנייה של פרימיטיב הקריאה והכתיבה מהמפרש היא הלב של כל האתגר.