4.2 ret2libc קלאסי תרגול
תרגול - ret2libc קלאסי¶
בתרגול הזה תבנו בעצמכם בינארי 32 ביט vulnerable שאין בו שום פונקציית win, ותשיגו ממנו shell בטכניקת ret2libc - חזרה לתוך system שבתוך libc. זה הבסיס לחצי מאתגרי ה-pwn הקלאסיים, וזה מה שיאפשר לכם בהמשך הפרק לעקוף ASLR אמיתי (4.4). עבדו לפי הסדר: כל תרגיל בונה על הקודם, ומ-recon ועד shell.
הכנה - הבינארי והסביבה¶
צרו את קובץ המקור הבא:
// ret2libc.c
#include <stdio.h>
#include <unistd.h>
void vuln() {
char buf[80];
puts("give me your bytes:");
read(0, buf, 400); // overflow: 400 bytes into an 80-byte buffer
}
int main() {
setvbuf(stdout, NULL, _IONBF, 0);
vuln();
return 0;
}
קמפלו ל-32 ביט, בלי canary ובלי PIE (ה-NX נשאר פעיל - זו כל הנקודה). אם חסרות ספריות 32 ביט: sudo apt install gcc-multilib.
כבו ASLR לזמן התרגול, כדי שהכתובות יהיו יציבות ותוכלו להתרכז במכניקה:
זכרו להחזיר אותו ל-2 כשתסיימו (או פשוט לרבט). שימו לב שאין כאן פונקציה שקופצים אליה בתוך הבינארי - כל הפתרון חייב להגיע מ-libc.
תרגיל 1 - סיור ומדידה¶
לפני שתוקפים, מיפוי.
- הריצו
checksec --file=./ret2libcוּודאו שאתם רואיםNX enabled,No canary,No PIE. הבינו למה כל אחת מההגנות (או היעדרה) חשובה למתקפה שלנו. - הריצו
ldd ./ret2libc. איזה קובץlibcנטען, ומה הנתיב שלו? רשמו אותו - נצטרך אותו. - מצאו את ה-offset מתחילת הbuffer עד הreturn address, בשיטת ה-
cyclicמ-2.2. שלחוcyclic 200, תפסו את הקריסה, והשתמשו ב-cyclic_findעל הערך שדרס את מצביע ההוראות.
רמז: ב-32 ביט saved ebp הוא 4 בתים, אבל המהדר עלול להוסיף padding alignment מעבר לגודל הbuffer. אל תניחו ש-offset = 80 + 4. תמדדו את הערך האמיתי.
תרגיל 2 - מציאת הכתובות ב-libc¶
עכשיו נאסוף את שלוש הכתובות שנצטרך.
- פתחו את הבינארי ב-gdb, עצרו ב-
mainוהריצו (חשוב לעצור אחרי ש-libcנטענה). מצאו את הכתובת שלsystemעםp systemואת זו שלexitעםp exit. - מצאו את הכתובת של המחרוזת
"/bin/sh"בתוךlibc. עם pwndbg:search -t string "/bin/sh". בלי pwndbg:find &system, +9999999, "/bin/sh". - הריצו את הבינארי פעמיים ובדקו שהכתובות זהות בין ההרצות. למה הן יציבות עכשיו, ומה היה קורה אילו ASLR היה פעיל?
רמז: כל שלוש הכתובות שייכות למרחב של libc, ולכן כולן זזות יחד עם בסיס ה-libc. לכן leak של כתובת אחת מספיקה כדי לחשב את כולן - רעיון שנחזור אליו ב-4.4.
תרגיל 3 - exploit ידני ל-shell¶
הרכיבו את הpayload עם הכתובות שמצאתם ידנית.
- סדרו את שלושת הערכים לפי המבנה:
[system] [return address מזויפת] [מצביע ל-"/bin/sh"], אחרי padding באורך ה-offset. - בסלוט החזרה המזויף שימו את הכתובת של
exit(כדי שהיציאה מה-shell תהיה נקייה). - הריצו וּודאו שקיבלתם shell. הריצו
idו-lsכדי לאשר שהוא אינטראקטיבי.
רמז: השתמשו ב-flat({offset: [system, exit_a, binsh]}). מכיוון שהקלט נקרא עם read, בתים אפסיים בכתובות ה-libc לא ייחתכו - אין צורך בטריקים.
תרגיל 4 - אותו exploit בלי כתובות קשיחות¶
עכשיו נהפוך את הסקריפט לנקי ומקצועי, בלי אף כתובת שכתובה ביד.
- טענו את ה-
libcדרך pwntools:libc = elf.libc. - כשה-ASLR כבוי, שלפו את בסיס ה-
libcשל התהליך והציבו ב-libc.address. רמז:p.libs()מחזיר מילון של נתיב-לבסיס. - חשבו את הכתובות מתוך האובייקט:
libc.symbols['system'],libc.symbols['exit'], ו-next(libc.search(b'/bin/sh\x00')). הרכיבו את אותו payload כמו בתרגיל 3. - הדפיסו את שלוש הכתובות והשוו לאלה שמצאתם ידנית ב-gdb. הן צריכות להיות זהות.
רמז: ברגע שתציבו libc.address, גם libc.symbols וגם libc.search יחזירו כתובות ריצה אמיתיות ולא היסטים. זה בדיוק הסקריפט שתרחיבו ב-4.4, רק ששם את הבסיס תחשבו מדליפה במקום מ-p.libs().
תרגיל 5 - מה קורה כשמשמיטים דברים¶
הפעם נשבור בכוונה כדי להבין.
- הסירו את סלוט החזרה המזויף (שלחו
[system] [binsh]בלבד אחרי הpadding). הריצו תחת gdb, צפו בקריסה, והסבירו במדויק למהsystemנכשלה. - החזירו את הסלוט, אבל שנו את ה-offset ב-4 בתים (למעלה ולמטה). מה קורה לכל אחד מהמקרים, ולמה?
- הפעילו מחדש את ASLR (
echo 2), הריצו את ה-exploit של תרגיל 3 (עם הכתובות הקשיחות) כמה פעמים ברצף. מה אחוז ההצלחה, ולמה?
רמז: בסעיף 3 אתם אמורים לראות כישלונות כמעט תמיד. זו בדיוק ההמחשה לכך ש-ret2libc עם כתובות קבועות תלוי ב-ASLR כבוי - הגשר לשיעור הleak.
תרגיל 6 (בונוס) - מחרוזת /bin/sh משלכם והכללה לשרת¶
- במקום המחרוזת שבתוך
libc, הכניסו"/bin/sh"דרך משתנה סביבה:process('./ret2libc', env={'BINSH':'/bin/sh'}). מצאו את הכתובת שלה על המחסנית עםsearch -t string "/bin/sh"ב-gdb, והשתמשו בה בpayload. מתי הטריק הזה שימושי, ומתי הוא שביר? - הפכו את ה-exploit של תרגיל 4 לגמיש עם
args.REMOTE. הריצו את הבינארי כשירות עםsocat TCP-LISTEN:1337,reuseaddr,fork EXEC:./ret2libcותקפו את עצמכם דרךremote('127.0.0.1', 1337).
רמז: כתובת משתנה הסביבה על המחסנית זזה אם משתנה אורך שם התוכנית או מספר משתני הסביבה, ולכן היא שברירית יותר מהמחרוזת שבתוך libc. מול שרת אמיתי עם ASLR פעיל היא כבר לא דטרמיניסטית - ואז חוזרים למחרוזת שב-libc, שכתובתה נגזרת מדליפה.