4.4 דליפת libc ועקיפת ASLR תרגול
תרגול - libc leak ועקיפת ASLR¶
בתרגול הזה תבנו בעצמכם בינארי vulnerable 64 ביט עם NX ובלי PIE, תשאירו את ASLR דלוק, ותכתבו תוקפה בשני שלבים: קודם דליפת כתובת libc אמיתית וחישוב הבסיס, ואז ret2libc בכתובות המחושבות עד shell. זה בדיוק דפוס העבודה של רוב אתגרי ה-pwn האמצעיים. עבדו לפי הסדר, כל תרגיל בונה על הקודם.
הכנה - הבינארי והסביבה¶
צרו קובץ מקור אחד עם overflow קלאסית ועם main שאפשר לחזור אליה:
// leak.c
#include <stdio.h>
#include <unistd.h>
void vuln() {
char buf[64];
puts("overflow me:");
read(0, buf, 200); // 200 bytes into a 64-byte buffer - overflow
}
int main() {
setvbuf(stdout, NULL, _IONBF, 0);
vuln();
return 0;
}
קמפלו בינארי 64 ביט, לא PIE, בלי canary, עם NX:
השאירו את ASLR דלוק - זה כל האתגר. ודאו:
הערה על ה-libc: לצורך התרגול, ה-libc של המטרה הוא ה-libc של המערכת שלכם. מצאו את הנתיב שלו עם ldd ./leak והשתמשו בו בסקריפט (למשל /lib/x86_64-linux-gnu/libc.so.6).
תרגיל 1 - להוכיח ש-ASLR באמת מזיז את libc¶
לפני שתוקפים, תרגישו את הבעיה.
- הריצו
ldd ./leak | grep libcשלוש פעמים ברצף. שימו לב שכתובת הבסיס של libc שונה בכל פעם. - הריצו
checksec --file=./leakוּודאו:NX enabled,No canary,No PIE. - מצאו את ה-offset עד הreturn address של
vulnבשיטת ה-cyclicמ-2.2. - שאלת מחשבה: אם ה-offset של
systemבתוך libc קבוע, למה בכל זאת אי אפשר לכתוב את כתובתsystemישירות בchain?
רמז: הבסיס זז, ה-offsets קבועים. את הבסיס של ההרצה הנוכחית אפשר לגלות רק בזמן ריצה, ולשם כך נועדה הleak.
תרגיל 2 - מיפוי ה-GOT וה-PLT¶
מטרת התרגיל: להבין מה בדיוק תדליפו.
- מצאו את הכתובות של
puts@pltו-puts@got. פעם עםobjdump -d leak | grep 'puts'ו-objdump -R leak(מציג את ה-GOT relocations), ופעם עםelf.plt['puts']ו-elf.got['puts']ב-pwntools. - הדליפו את הבינארי ב-gdb/pwndbg, הריצו עד אחרי הקריאה הראשונה ל-
puts(ה-puts("overflow me:")), והדפיסו את התוכן שלputs@gotעםx/gx <כתובת>. שימו לב שהיא מצביעה לתוך libc. - עצרו הפעם לפני הקריאה הראשונה ל-
putsוהשוו את הערך ב-puts@got. מה ההבדל, ולמה זה משנה לבחירת הכניסה שנדליף?
רמז: בקישור עצל הכניסה ב-GOT מכילה כתובת אמיתית של libc רק אחרי הקריאה הראשונה. puts בטוח כבר נקראה כי main השתמשה בה.
תרגיל 3 - שלב הleak וחישוב הבסיס¶
כתבו את החצי הראשון של התוקפה.
- בנו ROP chain שקוראת ל-
puts(&puts@got)ואז חוזרת ל-main. השתמשו ב-rop.call('puts', [elf.got['puts']])ואזrop.call('main'). - שלחו, קראו את שורת הleak, ופענחו:
u64(p.recvline().strip().ljust(8, b'\x00')). - חשבו את הבסיס:
libc.address = leaked - libc.symbols['puts']. - בדיקת שפיות: הדפיסו את הבסיס וּודאו שהוא נגמר ב-
000(aligned לעמוד). אם לא - משהו שגוי.
רמז: זכרו לסנכרן את הפלט. יש puts("overflow me:") לפני ה-read, אז p.recvuntil(b'overflow me:\n') לפני השליחה.
תרגיל 4 - זיהוי גרסת ה-libc מהleak¶
גם אם יש לכם את ה-libc מקומית, תרגלו את הזיהוי - זו המיומנות שתצטרכו מול שרת אמיתי.
- קחו את הכתובת שדלפה בתרגיל 3 והסתכלו רק על שלוש הספרות ההקסדצימליות התחתונות שלה. הריצו כמה פעמים וּודאו שהן קבועות בין הרצות למרות ASLR. הסבירו למה.
- הדליפו סמל שני (למשל חזרו על התרגיל עם
printfבמקוםputs) כדי לקבל טביעת אצבע חזקה יותר. - הכניסו את שני הסמלים ל-libc.rip (או
./find puts XXX printf YYYב-libc-database) וּודאו שהוא מזהה את ה-libc שלכם.
רמז: ASLR מערבב ברמת עמוד (0x1000), ולכן שלוש הספרות התחתונות של כל סמל קבועות. הן טביעת האצבע.
תרגיל 5 - שלב התקיפה ו-shell¶
השלימו את התוקפה.
- אחרי חישוב הבסיס, בנו chain שנייה:
system("/bin/sh")בכתובות המחושבות. השתמשו ב-libc.symbols['system']וב-next(libc.search(b'/bin/sh\x00')). - שלחו את הpayload השני בסבב השני (אחרי שחזרתם ל-
main,vulnרצה שוב). - אם ה-shell לא נפתח והתוכנית קורסת בתוך libc - זו כמעט בוודאות מלכודת ה-
movaps. הוסיפו גאדג'טretבודד לפני הקריאה ל-system. - קבלו shell וּודאו שהוא אינטראקטיבי (
id,cat).
רמז: rop2 = ROP(libc) אחרי שהצבתם libc.address, ואז rop2.find_gadget(['ret']) לalignment ו-rop2.call(libc.symbols['system'], [binsh]).
תרגיל 6 (בונוס) - הקשחה ושרת מרוחק¶
- אחדו את שני השלבים לסקריפט אחד גמיש עם
args.REMOTE. הריצו את הבינארי דרךsocat TCP-LISTEN:1337,reuseaddr,fork EXEC:./leakותקפו את עצמכם עםpython3 exploit.py REMOTE. - הוסיפו
assert libc.address & 0xfff == 0אחרי חישוב הבסיס, כדי שהסקריפט ייכשל בקול אם ה-libc לא תואם. - אתגר: החליפו את הקריאה ל-
systemב-one_gadget. מצאו כתובת מתאימה עםone_gadget ./libc.so.6, בדקו את התנאים שלה (ערכי אוגרים), והשתמשו בה במקום chain ה-system. (נרחיב על זה ב-4.5, אבל שווה לנסות כבר עכשיו.)
רמז: one_gadget מריץ execve("/bin/sh", 0, 0) בקפיצה אחת, אבל רק אם מתקיים תנאי מסוים על האוגרים ברגע הקפיצה. אם אחת לא עובדת, נסו את הבאה ברשימה.