8.8 שירותי echo וheap משולב תרגול
תרגול - שירותי echo וheap משולב¶
בתרגול הזה תתקפו חמישה אתגרים קלאסיים מ-pwnable.kr שבהם צריך לשרשר טכניקות: echo1, echo2, loveletter, dragon ו-horcruxes. כל אחד מהם דורש לזהות את הבאג, לתרגם אותו לפרימיטיב, ולנווט את מכונת המצבים של השירות עד ל-shell. עבדו לפי הסדר - הקושי עולה בהדרגה, ו-horcruxes בסוף מחבר הכל לROP chain של שבע קריאות.
הכנה - התחברות ל-pwnable.kr¶
האתגרים האלה בקטגוריית Rookiss באתר pwnable.kr. מתחברים ב-SSH, כל אתגר עם המשתמש והסיסמה שמופיעים בעמוד שלו:
ssh echo1@pwnable.kr -p2222 # the password is on the challenge page
# similarly: echo2@ , loveletter@ , dragon@ , horcruxes@
בתיקיית הבית של כל אתגר יש בדרך כלל את הבינארי, את קוד המקור (*.c או *.cpp), ואת קובץ ה-flag שקריא רק למשתמש עם ההרשאות של הבינארי (setuid/setgid). זה הרעיון: אתם מריצים את הבינארי הvulnerable, משיגים דרכו הרצת קוד, וקוראים את הדגל. תמיד תתחילו מ-cat על המקור ומ-checksec על הבינארי.
מכיוון שקשה לפתח exploit ישירות על השרת, נעבוד בשיטה של דמו מקומי: תשחזרו את מבנה כל אתגר בקובץ C משלכם, תפתחו את ה-exploit מקומית, ורק אז תתאימו אותו לבינארי האמיתי. את קבצי הדמו מהתרגילים תמצאו כתשתית בהרצאה - קמפלו אותם בעצמכם.
תרגיל 1 - echo1: overflow בשירות echo ל-ret2win¶
בנו את echo1_demo.c מההרצאה וקמפלו: gcc -fno-stack-protector -no-pie -o echo1_demo echo1_demo.c.
- הריצו
checksec --file=./echo1_demo. ודאו:NX enabled,No canary,No PIE. מה כל אחד מהם אומר על האסטרטגיה? - מצאו את הכתובת של
get_shellעםnm echo1_demo | grep get_shell. - מצאו את ה-offset מהbuffer בפונקציית ה-echo עד return address, בשיטת ה-
cyclic. - כתבו exploit ב-pwntools שמנווט לאופציה 1 בתפריט, מציף את הbuffer, וקופץ ל-
get_shell. - אם ה-shell לא נפתח למרות שהכל נראה נכון - חשבו על מלכודת ה-
movapsמפרק 2.
רמז: get_shell קוראת ל-system, אז ב-64 ביט כמעט בטוח תצטרכו גאדג'ט ret בודד לalignment המחסנית ל-16 בתים לפני הקפיצה. השתמשו ב-rop.find_gadget(['ret'])[0].
תרגיל 2 - echo2: format string כפרימיטיב כתיבה¶
בנו את fsb_demo.c מההרצאה (הוא מדמה את חצי ה-format string של echo2) וקמפלו.
- מצאו את מיקום הקלט על המחסנית: שלחו
b'AAAA.%p.%p.%p.%p.%p.%p.%p.%p'ומצאו באיזה מיקום חוזר0x41414141. זה ה-offset ל-fmtstr_payload. - השתמשו ב-
fmtstr_payload(offset, {elf.got['printf']: elf.symbols['get_shell']})כדי לדרוס את הכניסה שלprintfב-GOT בכתובת שלget_shell. - הסבירו לעצמכם: למה ה-shell נפתח רק ב-
printfהבא אחרי הכתיבה, ולא מיד? - עכשיו החלק המחשבתי של echo2 האמיתי: פתחו את המקור של
echo2בשרת. שימו לב שbuffer ההודעה מוקצה ב-malloc(על ה-heap), לא על המחסנית. למה זה שובר את הטריק של תרגיל זה, ואיך ה-UAF שבאתגר עוזר להשלים את החסר?
רמז: הספציפייר %N$n מגיע רק לארגומנטים שעל המחסנית. אם מחרוזת הפורמט על ה-heap, אי אפשר להתייחס לכתובת שהטמעתם בתוכה. במקום זה משתמשים ב-format string כדי לדלוף כתובות שכבר על המחסנית, ובחצי ה-UAF כדי למחזר את הbuffer ולשתול מצביע נשלט.
תרגיל 3 - loveletter: overflow בשירות טקסט¶
האתגר loveletter הוא שירות שמעבד "מכתב". התרגיל כאן ממוקד בזיהוי סוג הקלט ובהתאמת הpayload אליו.
- בנו בעצמכם שני בינארים שמעתיקים קלט לbuffer מקומי בoverflow: אחד עם
read(0, buf, N)ואחד עםstrcpy(buf, input). הוסיפו לשניהם פונקצייתwinשמדפיסה דגל. - תקפו את שניהם ב-
ret2win. מה ההבדל בpayload שמותר בכל אחד? - נסו לכתוב payload שמכיל כתובת עם בית
\x00באמצע. באיזה מהבינארים הוא נחתך, ולמה?
רמז: read קורא בתים גולמיים כולל \x00, אז כתובות מלאות עוברות. strcpy, gets ו-scanf("%s") עוצרים ב-null (או ברווח), אז כתובת עם בית אפס נחתכת - תצטרכו לבחור גאדג'טים וכתובות בלי בתים אסורים, בדיוק כמו בפרק 3.
תרגיל 4 - dragon: שימוש לאחר שחרור בתפריט משחק¶
בנו את dragon_demo.c מההרצאה וקמפלו: gcc -m32 -fno-stack-protector -no-pie -o dragon_demo dragon_demo.c.
- זהו במקור את גודל אובייקט הדמות/מפלצת. למה חשוב שההקצאה ב-
lootתהיה מאותו גודל כמו האובייקט המשוחרר? - כתבו exploit שמבצע את הרצף:
fight(מקצה ומשחרר),loot(ממחזר וממלא),taunt(מפעיל את המצביע). - בשדה המצביע שתלו את הכתובת של
secret_levelוקבלו shell. - נסו לשנות את הסדר - מה קורה אם תקראו
tauntלפניloot? הסבירו את הקריסה.
רמז: אחרי free, המצביע monster הופך לתלוי אבל עדיין מצביע לבלוק. loot מקצה בלוק באותו גודל, אז המקצה מחזיר את אותו בלוק בדיוק (LIFO), ואתם כותבים את שדה attack. אם תקראו taunt לפני שמילאתם - תקפצו לזבל. הסדר הוא כל הסיפור.
תרגיל 5 - horcruxes: ROP chain של שבע פונקציות¶
בנו את horcruxes_demo.c מההרצאה וקמפלו: gcc -m32 -fno-stack-protector -no-pie -o horcruxes_demo horcruxes_demo.c.
- מצאו את הכתובות של שבע הפונקציות
AעדGושל פונקציית הבדיקה, עםnm horcruxes_demo. - מצאו את ה-offset מהbuffer ב-
ropmeעד return address, עםcyclic. - בנו ROP chain: padding עד ה-offset, ואז שבע כתובות הפונקציות בזו אחר זו, ואז כתובת פונקציית הבדיקה. שימו לב - אין צורך בגאדג'טים כי הפונקציות לא מקבלות ארגומנטים. למה?
- אספו את שבעת הערכים שהפונקציות מדפיסות, סכמו אותם, ושלחו את הסכום לפונקציית הבדיקה. השתמשו ב-
recvline_containsכדי לפרסר כל ערך. - הריצו וקבלו shell. אם קרס - הדפיסו את הchain עם
hexdumpוודאו שכל כתובת במקום.
רמז: בקונבנציית cdecl, פונקציה בלי ארגומנטים לא צריכה שום pop. כל פונקציה מסתיימת ב-ret שמוציא מהמחסנית את הכתובת הבאה - כלומר את הפונקציה הבאה בchain. אז הchain היא פשוט [A][B][C][D][E][F][G][check]. שימו לב שאת הסכום אתם יכולים לחשב רק אחרי שאספתם את הערכים המודפסים, אז סדר הפעולות ב-exploit הוא: לשלוח את הchain, לקרוא את שבעת הערכים, לסכם, ורק אז לשלוח את הסכום.
תרגיל 6 (בונוס) - מהדמו לשרת האמיתי¶
- קחו את ה-exploit של horcruxes והתאימו אותו לבינארי האמיתי ב-pwnable.kr: שלפו את הכתובות מהבינארי שעל השרת (הן שונות מהדמו), ושימו לב שהחזרה האחרונה בchain היא לכתובת בתוך
ropme(המקום שקורא את הניחוש), לא לפונקציה נפרדת. - הפכו את כל ה-exploit-ים לגמישים עם
args.REMOTE, כך שאותו סקריפט ירוץ מקומית ומול השרת. - לכל אחד מחמשת האתגרים, כתבו במשפט אחד: מה הבאג, מה הפרימיטיב, ומה הpayload. אם אתם יכולים למלא את שלושת השדות - הבנתם את האתגר.
רמז: ההבדל היחיד בין הדמו לשרת הוא הכתובות ונקודת החזרה האחרונה. שיטת מציאת ה-offset, בניית הchain, ואיסוף הערכים - זהים לחלוטין.