3.2 הזרקה והרצה ללא NX תרגול
תרגול - הזרקה והרצה ללא NX¶
בתרגול הזה תבנו בעצמכם בינארי vulnerable עם מחסנית ניתנת להרצה, ותפרצו אותו בשתי דרכים שונות: פעם בשיטת הכתובת הישירה (עם מזחלת NOP), ופעם בשיטת הטרמפולינה jmp esp. לבסוף, כבונוס, תשתמשו במשתנה סביבה. המטרה היא שתרגישו על הידיים את ההבדל בין שיטה ששבירה מול ASLR לבין שיטה שעמידה בפניו. עבדו לפי הסדר - כל תרגיל בונה על הקודם.
הכנה - הבינארי והסביבה¶
צרו קובץ מקור vuln.c:
// vuln.c
#include <stdio.h>
#include <unistd.h>
void vuln() {
char buf[64];
puts("send your payload:");
read(0, buf, 400); // overflow: 400 bytes into a 64-byte buffer
}
int main() {
setvbuf(stdout, NULL, _IONBF, 0);
vuln();
return 0;
}
קמפלו אותו ל-32 וגם ל-64 ביט, עם מחסנית ניתנת להרצה, בלי canary ובלי PIE (אם חסרות ספריות 32 ביט: sudo apt install gcc-multilib):
gcc -m32 -z execstack -fno-stack-protector -no-pie -o vuln32 vuln.c
gcc -z execstack -fno-stack-protector -no-pie -o vuln64 vuln.c
כבו ASLR (לרוב התרגילים אנחנו מסתמכים על כתובת יציבה):
תרגיל 1 - אימות התנאים¶
לפני שתוקפים, ודאו שהתקיפה בכלל אפשרית.
- הריצו
checksec --file=./vuln32וּ-checksec --file=./vuln64. ודאו שאתם רואיםNX disabledו-Has RWX segments, וגםNo canaryו-No PIE. - בדקו את דגלי מקטע המחסנית:
readelf -l vuln64 | grep GNU_STACK. ודאו שאתם רואיםRWEולאRW. - ודאו ש-ASLR כבוי:
cat /proc/sys/kernel/randomize_va_spaceצריך להחזיר0.
רמז: אם checksec מראה NX enabled, שכחתם את -z execstack. קמפלו מחדש. בלי התנאי הזה כל שאר התרגול לא יעבוד.
תרגיל 2 - מציאת offset וכתובת ישירה (64 ביט)¶
עכשיו נפרוץ את vuln64 בשיטת הכתובת הישירה.
- מצאו את ה-offset עד הreturn address עם
cyclic(בשיטה מ-2.2): שלחוcyclic 200, ראו איזה ערך דרס אתrip, והריצוcyclic_find(...). - ב-gdb, שימו breakpoint אחרי ה-
read, שלחו קלט מזהה (למשל הרבהA), וחפשו איפה הוא נחת:search -s "AAAAAAAA". רשמו את כתובת תחילת הbuffer. - כתבו exploit ב-pwntools: padding עד ה-offset, ואז return address שמצביעה לתחילת הbuffer, ואז ה-shellcode. השתמשו ב-shellcode של 64 ביט (היד-כתוב מ-3.1, או
asm(shellcraft.amd64.linux.sh())). - הריצו וּודאו שנפתח shell. נסו
id.
רמז: כדי שהקפיצה תנחת על ה-shellcode, שימו את ה-shellcode בתחילת הbuffer, וכוונו את הreturn address בדיוק לשם. אבל כתובת הbuffer שראיתם ב-gdb כנראה שונה מההרצה האמיתית - זה בדיוק מה שתרגיל 3 פותר.
תרגיל 3 - מזחלת NOP והתמודדות עם אי-דיוק¶
תרגיל 2 עובד ב-gdb אבל אולי קורס בטרמינל, או להפך. הסיבה: הסביבה שונה, וכתובת המחסנית זזה. עכשיו נעשה את ההתקפה אמינה.
- שנו את המבנה: שימו את ה-shellcode אחרי הreturn address, עם מזחלת גדולה של
\x90לפניו (למשל 512 בתים). - כוונו את הreturn address אל אמצע המזחלת, לא אל ההתחלה המדויקת.
- חברו
gdb.attach(p)לתהליך, חפשו את המזחלת (search -s "\x90\x90\x90\x90"), ובחרו כתובת עמוק בתוכה. - הריצו את ה-exploit מחוץ ל-gdb (סתם
python3 exploit.py) וּודאו שהוא עדיין עובד. אם לא, הגדילו את המזחלת.
רמז: המזחלת סופגת את ההפרש בין הסביבה של gdb לזו של הרצה רגילה. אם המזחלת גדולה מספיק, אותה כתובת תעבוד בשני המקרים. זו הסיבה שהמזחלת הומצאה.
רמז: ככל שהbuffer קטן, עדיף לשים את המזחלת וה-shellcode אחרי הreturn address - שם יש לכם את כל מרחב ה-read (400 בתים) ולא רק 64.
תרגיל 4 - טרמפולינה jmp esp (32 ביט)¶
עכשיו הטכניקה החזקה: פריצה בלי לדעת בכלל את כתובת המחסנית, על vuln32.
- מצאו גאדג'ט
jmp espבבינארי:ROPgadget --binary vuln32 | grep 'jmp esp', או חפשו את הבתיםff e4עםobjdump -d vuln32 | grep 'ff e4'. - בנו payload: padding עד ה-offset, ואז כתובת הגאדג'ט
jmp espבהreturn address, ואז ה-shellcode של 32 ביט מיד אחריה. - הריצו וּודאו שנפתח shell.
- עכשיו הדבר המעניין: הדליקו ASLR מחדש (
echo 2 | sudo tee /proc/sys/kernel/randomize_va_space) והריצו שוב. ההתקפה עדיין עובדת! הסבירו לעצמכם למה.
רמז: ברגע ה-ret, esp מצביע בדיוק על הסלוט שאחרי הreturn address - שם שמתם את ה-shellcode. הגאדג'ט jmp esp קופץ לשם. לא נגעתם בכתובת מחסנית בכלל.
רמז: הסיבה שזה עובד גם עם ASLR: הבינארי לא PIE, אז כתובת הגאדג'ט jmp esp קבועה. ה-ASLR מערבב את המחסנית, אבל לא אכפת לנו כי esp ממילא מצביע לאן שצריך.
תרגיל 5 (בונוס) - shellcode במשתנה סביבה¶
מה עושים כשהbuffer קטן מכדי להכיל shellcode + מזחלת? מכניסים אותם למשתנה סביבה.
- הכניסו למשתנה סביבה
SCמזחלת גדולה של\x90ואחריה ה-shellcode. - חשבו את כתובת המשתנה על המחסנית. כתבו את תוכנית העזר
getenvaddr(מההרצאה), או אמתו את הכתובת ישירות ב-gdb עםsearch -s SC. - כתבו exploit שדורס את הreturn address בכתובת של
SC(מכוונת לתוך מזחלת ה-NOP שבתוכו). הריצו וּודאו shell.
רמז: אם משתמשים ב-pwntools, אפשר להעביר את הסביבה ישירות: process('./vuln32', env={'SC': b'\x90'*400 + sc}), ואז לחפש את הכתובת של SC ב-gdb.attach.
רמז: כתובת משתנה הסביבה תלויה באורך שם התוכנית (argv[0]) ובגודל שאר הסביבה. אם הכתובת קופצת, מזחלת גדולה יותר בתוך SC תבלע את הסטייה.
תרגיל 6 (מחשבה) - מתי כל שיטה נכשלת¶
בלי לכתוב קוד, ענו:
- אילו משלוש השיטות שורדות הדלקת ASLR, ואילו מתות? למה?
- מה קורה לכל השיטות אם המתכנת יקמפל מחדש בלי
-z execstack(כלומר עם NX פעיל)? - התוכנית קוראת עם
scanf("%s", buf)במקוםread. איזו בעיה חדשה נוצרת ל-shellcode ולכתובות, ומדועreadהיה נוח יותר?
רמז: חשבו מה כל שיטה מניחה - כתובת מחסנית יציבה? כתובת קוד יציבה? מחסנית ניתנת להרצה? קלט שמעביר בתים אפסיים?