6.4 ret2dlresolve תרגול
תרגול - ret2dlresolve על בינארי ללא leak¶
בתרגול הזה תנצלו בינארי שבמכוון לא נותן לכם שום leak: אין puts להדפיס איתו את ה-GOT, אין format string, ואין אף כתובת של libc בהישג יד. בכל זאת - תפתחו shell. הכלי היחיד שיעבוד כאן הוא ret2dlresolve: תגרמו לפותר הדינמי למצוא בשבילכם את system ולקרוא לה עם "/bin/sh".
הכנת הסביבה¶
הבינארי כאן הוא דוגמה מקומית שתקמפלו בעצמכם, כדי שהכל יהיה רץ ובר-שחזור. שמרו את הקוד כ-vuln.c וקמפלו בדיוק כך:
// vuln.c
#include <stdio.h>
#include <unistd.h>
void vuln() {
char buf[64];
read(0, buf, 0x200); // generous overflow - no canary
}
int main() {
setvbuf(stdout, NULL, _IONBF, 0);
write(1, "go\n", 3);
vuln();
return 0;
}
לפני שנוגעים בכלום - בדקו מה מותקן:
יש כאן Partial RELRO, בלי canary, NX פעיל, בלי PIE. זה בדיוק המצב שבו ret2dlresolve עובד. שימו לב: בקוד אין system ואין "/bin/sh". אל תחפשו אותם בבינארי - הם לא שם, וזו כל הפואנטה.
תרגיל 1 - מדידת ה-offset¶
מצאו כמה בתים מפרידים בין תחילת buf לבין return address. השתמשו ב-cyclic כדי לייצר קלט, הריצו תחת gdb, וחלצו את ה-offset מה-rsp בזמן הקריסה.
רמז: cyclic(200) פנימה, ובזמן הקריסה cyclic_find(...) על הערך שב-rsp (או ב-pattern שראה pwndbg). הצפוי הוא 72.
תרגיל 2 - איתור הרכיבים בבינארי¶
אספו את כל מה שהטכניקה דורשת ישירות מהבינארי:
- את בסיסי הטבלאות הדינמיות. איזו פקודה מראה את
JMPREL,SYMTABו-STRTAB? - את הכתובת של
PLT0(הרשומה הראשונה בסקשן.plt). - גאדג'ט
pop rdi ; retלהכנת הארגומנט. - אזור כתיב שבו תניחו את המבנים המזויפים (למשל תוך
.bss).
רמז: readelf -d ./vuln לטבלאות, objdump -d -j .plt ./vuln ל-PLT0, ו-ROPgadget --binary ./vuln | grep 'pop rdi' לגאדג'ט. את גבולות ה-.bss תראו ב-readelf -S ./vuln.
תרגיל 3 - בניית המבנים המזויפים¶
זה הלב. אתם צריכים להניח באזור הכתיב שלושה דברים: מחרוזת "system\0", מבנה Elf64_Sym שמצביע אליה, ומבנה Elf64_Rela שמצביע לסמל. אחר כך תחשבו את reloc_arg כך שהוא יצביע מהטבלה המקורית אל ה-Elf64_Rela שלכם.
- מהם 24 הבתים של
Elf64_Rela? מה צריך להיות ב-r_infoכדי שהסוג יהיהR_X86_64_JUMP_SLOT(7) והאינדקס יצביע לסמל שלכם? - מהם 24 הבתים של
Elf64_Sym? איזה שדה מצביע למחרוזת, ומה האופסט שלו מולSTRTAB? - למה
reloc_argו-sym_indexחייבים לצאת מספרים שלמים אחרי חלוקה ב-24, ואיך מוסיפים padding שיבטיח את זה?
רמז: אם החישוב הידני מתחיל לכאוב - זה נורמלי, ובדיוק בשביל זה קיימת Ret2dlresolvePayload. נסו קודם להבין את המבנים ידנית, ואז תנו לספרייה לבנות אותם. ותזכרו את בדיקת הגרסאות מההרצאה: אינדקס סמל "יפה" חשוב.
תרגיל 4 - הפעלה ופתיחת shell¶
חברו הכל לchain אחת. בשלב הראשון קראו את המבנים אל האזור הכתיב (איזו פונקציה שכבר מיובאת בבינארי מאפשרת לכם לכתוב קלט לכתובת שתבחרו?). בשלב השני, חזרו ל-PLT0 עם ה-reloc_arg על המחסנית וכש-rdi מצביע על "/bin/sh".
- באיזה סדר חייבים לקרות שני השלבים, ולמה?
- איך מוודאים ש-
rdxמספיק גדול כדי שה-readיקלוט את כל המבנים? - אחרי
p.interactive(), האם קיבלתם shell? נסוidו-ls.
רמז: המבנה של הchain ב-pwntools הוא בדיוק rop.read(0, dlresolve.data_addr, len(dlresolve.payload)) ואז rop.ret2dlresolve(dlresolve). את הבתים המזויפים (dlresolve.payload) שולחים בקריאת ה-read השנייה, אחרי payload הoverflow.
אתגר נוסף - בנייה ידנית לגמרי¶
אחרי שהצלחתם עם Ret2dlresolvePayload, נסו לבנות את שלושת המבנים ביד, בלי המחלקה - רק עם p64/p32/p8 והחישובים מההרצאה. תיתקלו כנראה בבדיקת הגרסאות של glibc המודרני. חקרו: מה הערך של sym_index שגורם ל-.gnu.version להחזיר אפס, ולמה זה מציל אתכם. זה יעמיק לכם את ההבנה יותר מכל דבר אחר.
שאלות לבדיקה עצמית¶
- למה הטכניקה הזו נכשלת לחלוטין תחת
Full RELRO? מה בדיוק חסר שם? - למה אנחנו יכולים לקרוא ל-
systemלמרות שהיא לא מיובאת בבינארי שלנו בכלל? - אם הבינארי היה מקומפל עם PIE, מה היה חסר לנו, ומה היינו צריכים להשיג קודם?