9.2 syscall מותאם וret2usr תרגול
תרגול - exploit syscall מותאם וret2usr¶
בתרגול הזה נתקוף kernel אמיתי. האתגר המרכזי הוא syscall מ-pwnable.kr, שבו הוסיפו לkernel ARM קריאת מערכת מותאמת עם באג, ואנחנו נשתמש בו כדי להפוך ל-root ולקרוא את הדגל. בנוסף נבנה מודול kernel vulnerable משלנו ל-x86-64 ונתקוף אותו מקצה לקצה, כדי לתרגל ret2usr בסביבה שאתם שולטים בה לגמרי.
אל תסתכלו בפתרון לפני שניסיתם בעצמכם. עבדו לפי הרמזים בסדר. חלק גדול מהעבודה בkernel exploit הוא סבלנות וקריאה זהירה של קוד.
התחברות לאתגר - pwnable.kr syscall¶
מתחברים כך:
שימו לב: זה אתגר kernel, אז יש שם סביבה מיוחדת. בתיקיית הבית תמצאו קובץ הפעלה, את קוד המקור של המטפל sys_upper, וסקריפט שמריץ מכונת ARM אמולציה (qemu) עם הkernel vulnerable. תקראו את ה-README ואת הסקריפט שבתיקייה לפני הכל, הם מסבירים בדיוק איך מקמפלים את ה-exploit ואיך מריצים אותו על המכונה. הקומפילציה היא ל-ARM, אז יש שם קומפיילר צולב (למשל arm-linux-gnueabi-gcc).
תרגיל 1 - להבין את הקריאה המותאמת¶
המטרה: להבין מה בדיוק עושה הקריאה החדשה ומה מספרה.
מצאו בקוד המקור של הkernel (או בקבצים שסופקו) את הפונקציה sys_upper. ענו לעצמכם:
- מה מספר הקריאה שלה בטבלת ה-syscalls?
- מה שני הפרמטרים שהיא מקבלת, ומה כל אחד מייצג?
- מה בדיוק היא כותבת, ולאן?
רמז: חפשו את המחרוזת upper בקבצים שסופקו, וגם את המספר 223 ליד sys_call_table.
תרגיל 2 - לזהות את הבאג¶
המטרה: להצביע במדויק על השורה vulnerable ולהסביר למה היא מסוכנת.
השוו את sys_upper לקוד kernel תקין שקראנו עליו בהרצאה. ענו:
- אילו בדיקות חסרות כאן? (חשבו על
access_ok,copy_from_user,copy_to_user.) - מה קורה אם
outהוא כתובת בתוך מרחב הkernel במקום במרחב המשתמש? - איזה פרימיטיב exploit זה נותן לנו? (כתיבה? קריאה? לאן?)
רמז: הkernel שרץ ב-ring 0 מבצע out[i] = .... מי בחר את out?
תרגיל 3 - להשיג את כתובות הסמלים¶
המטרה: להוציא מהמכונה את שלוש הכתובות שנצטרך.
על המכונה המורצת, השיגו את הכתובות של commit_creds, prepare_kernel_cred ושל sys_call_table. בדקו קודם שההגנה kptr_restrict כבויה.
cat /proc/sys/kernel/kptr_restrict
cat /proc/kallsyms | grep -E ' commit_creds| prepare_kernel_cred| sys_call_table'
רשמו לעצמכם את שלוש הכתובות. תצטרכו אותן ב-exploit.
רמז: אם הפלט של kallsyms מלא באפסים, ההגנה דלוקה, ואז תצטרכו דרך אחרת. באתגר הזה היא אמורה להיות כבויה.
תרגיל 4 - לפצח את אילוץ הכתיבה¶
המטרה: להבין למה אי אפשר סתם לכתוב את כתובת getroot, ואיך פותרים.
הסתכלו שוב בקוד sys_upper. הוא הופך כל בית בטווח 0x61-0x7a (האותיות a-z) לאות גדולה, ונעצר על בית אפס בגלל ה-strlen. עכשיו חשבו: כשנרצה לכתוב את כתובת getroot לתוך sys_call_table[223], אילו בתים בכתובת הזו יגרמו לבעיה?
- כתבו לעצמכם את שלושת סוגי הבתים הבעייתיים.
- חשבו: איך אפשר לבחור כתובת ל-
getrootשכל ארבעת הבתים שלה יהיו "נקיים" (שונים מאפס ומחוץ לטווח האותיות הקטנות)?
רמז: הפונקציה mmap עם הדגל MAP_FIXED נותנת לכם למקם עמוד בכתובת שאתם בוחרים. אם תעתיקו את קוד getroot לאופסט מתאים בתוך העמוד, תוכלו לקבל כתובת נקייה כמו למשל אחת שכל הבתים שלה בטווח בטוח.
תרגיל 5 - לבנות את ה-getroot ולדרוס את הטבלה¶
המטרה: לכתוב את קוד ה-exploit שמבצע את הדריסה.
- כתבו פונקציית
getrootבמרחב המשתמש שקוראת ל-commit_creds(prepare_kernel_cred(0))באמצעות מצביעי פונקציה לכתובות מתרגיל 3. - מקמו אותה בכתובת נקייה (תרגיל 4).
- קראו ל-
syscall(223, in, out)כך ש-inמכיל את ארבעת הבתים של כתובתgetrootו-outמצביע עלsys_call_table[223].
רמז: ודאו שהעמוד שאתם ממפים ל-getroot הוא גם ניתן להרצה (PROT_EXEC), אחרת הkernel יקפוץ לעמוד ולא יוכל להריצו.
תרגיל 6 - להפעיל ולקבל root¶
המטרה: להפעיל את ה-payload ולקרוא את הדגל.
אחרי הדריסה, קראו שוב ל-syscall(223, ...). הפעם, במקום sys_upper, תרוץ פונקציית getroot שלכם בהקשר kernel. בדקו את getuid(), ואם הוא 0, פתחו shell:
מתוך ה-shell, קראו את הדגל. איפה הוא? (חשבו: הרשאות של איזה משתמש צריך כדי לקרוא אותו, ומאיפה מריצים.)
רמז: אם הקריאה השנייה מפילה את הkernel במקום לרוץ, כנראה הכתובת שכתבתם לטבלה לא נכונה (בדקו את עיוות האותיות ואת בתי האפס מתרגיל 4).
תרגיל 7 - אימון ret2usr מקומי על x86-64¶
המטרה: לתרגל את אותה טכניקה בסביבה שלכם, בלי אמולציית ARM.
בנו מכונת לינוקס וירטואלית לאימון (למשל עם qemu ותמונת buildroot), הכינו מודול kernel קטן שחושף ioctl vulnerable שמבצע קפיצה למצביע פונקציה שהמשתמש מספק (מדמה את "הקריאה המותאמת חסרת הבדיקה"). כבו את SMEP ו-KASLR בשורת הפקודה של הkernel:
עכשיו:
- טענו את המודול, מצאו את כתובות
commit_credsו-prepare_kernel_credמ-/proc/kallsyms. - כתבו פונקציית
privescבמרחב המשתמש שקוראת לנוסחה, ובסופה חוזרת נקי עםswapgs; iretq. - הפעילו את ה-
ioctlכך שיקפוץ ל-privesc, וקבלו root.
רמז: שמרו את cs, ss, rflags, rsp של המשתמש בתחילת התוכנית, לפני הקפיצה לkernel. תצטרכו אותם למסגרת ה-iretq.
הפתרון המלא, כולל קוד ה-exploit לשני האתגרים, נמצא בקובץ הפתרון. נסו קודם בעצמכם.