לדלג לתוכן

5.2 קריאה שרירותית מהזיכרון תרגול

תרגול - קריאה שרירותית מהזיכרון

בתרגול הזה תבנו בעצמכם בינארי vulnerable ל-format string, ותשתמשו בקריאה שרירותית כדי לדלוף שני דברים: את תוכנו של גלובל מסוים, ואת כתובת libc האמיתית - הכל דרך קריאות %s ו-%p בלבד, בלי overflow של buffer ובלי כתיבה. זו המיומנות שמאפשרת לשבור ASLR, והיא הבסיס לשיעורי הכתיבה שאחריו. עבדו לפי הסדר, כל תרגיל בונה על הקודם.

הכנה - הבינארי

צרו את קובץ המקור הבא:

// leakme.c
#include <stdio.h>
#include <unistd.h>
#include <string.h>

char flag_store[64] = "FLAG{replace_me_at_runtime}";   // the global we want to leak

void vuln() {
    char buf[128];
    memset(buf, 0, sizeof(buf));
    read(0, buf, 127);
    printf(buf);            // format string vulnerability
    putchar('\n');
}

int main() {
    setvbuf(stdout, NULL, _IONBF, 0);
    puts("send format:");
    vuln();
    return 0;
}

קמפלו אותו בלי PIE ובלי canary, כדי שהכתובות של הגלובל ושל ה-GOT יהיו קבועות:

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

ודאו את מצב ההגנות והשאירו את ASLR של המערכת פעיל (זה מה שהופך את דליפת libc למשמעותית):

checksec --file=./leakme
cat /proc/sys/kernel/randomize_va_space     # should be 2

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

תרגיל 1 - מציאת האינדקס של הbuffer

לפני כל דליפה, צריך לדעת באיזה אינדקס פוזיציוני יושב buf[0].

  1. שלחו סמן ידוע של 8 בתים ואחריו כמה %p פוזיציוניים, למשל AAAAAAAA.%6$p.%7$p.%8$p.%9$p.%10$p.
  2. מצאו באיזה %N$p מופיע הערך 0x4141414141414141.
  3. רשמו את המספר הזה בתור BUF_INDEX. הסיקו מהו האינדקס של buf[8..15].

רמז: הכי נוח לשלוח את הקלט עם python3 -c '...' | ./leakme, או ישר מ-pwntools עם process ו-sendline. שימו לב שהתוכנית מדפיסה send format: לפני שהיא קוראת, אז סנכרנו עם recvuntil(b'send format:').

תרגיל 2 - דליפת תוכן הגלובל

עכשיו דלפו את תוכן flag_store דרך קריאה שרירותית בלבד.

  1. מצאו את הכתובת של flag_store (objdump -t leakme | grep flag_store או nm).
  2. בנו payload: ספציפייר %s שמכוון לסלוט שאחרי buf[0], padding לalignment, ואז p64 של הכתובת.
  3. שימו לב לalignment - אורך הקידומת חייב להיות כפולה של 8. נסו בכוונה בלי padding וראו שזה קורס, ואז תקנו.
  4. חתכו את הפלט וּודאו שאתם רואים את תוכן הגלובל.

רמז: אם BUF_INDEX יצא 6, שימו את הכתובת בסלוט 7 (כלומר %7$s) ורפדו את הקידומת ל-8 בתים בדיוק. הוסיפו בית סמן כמו # מיד אחרי הספציפייר כדי שיהיה קל לחתוך את הleak עם recvuntil(b'#').

תרגיל 3 - דליפת כתובת libc

כאן הלב. דלפו את הכתובת האמיתית של פונקציית libc מתוך ה-GOT, וחשבו את בסיס libc.

  1. בחרו פונקציה שכבר נקראה לפני ה-printf (למשל read או printf עצמה) - רק אז הרשומה ב-GOT מצביעה לכתובת האמיתית ב-libc ולא ל-PLT stub.
  2. השתמשו באותה פונקציית read_at מהתרגיל הקודם, הפעם על elf.got['read'].
  3. פרשו את הבתים שדלפו ככתובת: u64(leak.ljust(8, b'\x00')). למה משלימים ב-null? כי %s עוצר בבית האפס והכתובת ב-64 ביט היא בפועל 6 בתים משמעותיים.
  4. חשבו libc.address = leaked - libc.symbols['read'], והדפיסו את libc.symbols['system'] ואת הכתובת של /bin/sh.

רמז: השתמשו באותו קובץ libc שרץ אצלכם: libc = ELF('/lib/x86_64-linux-gnu/libc.so.6'). אם אתם לא בטוחים איזו גרסה, strings libc.so.6 | grep 'GNU C Library'. הריצו כמה פעמים וּודאו שבסיס libc משתנה בין הרצות (בגלל ASLR) אבל תמיד יוצא כתובת שמסתיימת ב-000 (aligned לעמוד).

תרגיל 4 - סקריפט דליפה שלם

אחדו את הכל לסקריפט אחד נקי.

  1. כתבו פונקציית read_at(io, addr) כללית שמקבלת כתובת ומחזירה את התוכן.
  2. הדליפו ברצף גם את flag_store וגם את בסיס libc, והדפיסו את שניהם יפה עם log.success.
  3. הוסיפו תמיכה ב-args.REMOTE כדי שאותו סקריפט ירוץ מקומית ומול שרת. הריצו את הבינארי כשירות עם socat TCP-LISTEN:1337,reuseaddr,fork EXEC:./leakme ותקפו את עצמכם.

רמז: שימו לב ל"היגיינת קלט" - כל leak שולחת payload ומקבלת שורת פלט. ודאו שאתם מסנכרנים נכון בין השליחות (עם recvuntil על סמן קבוע) כדי שהleak הראשונה לא תבלע חלק מהפלט של השנייה.

תרגיל 5 (בונוס) - הcanary ו-PIE

הרחיבו את היכולת לשני מקרים מציאותיים יותר.

  1. קמפלו גרסה עם canary: gcc -fstack-protector-all -no-pie -o leakme_canary leakme.c. מצאו את האינדקס הפוזיציוני של הcanary (ערך אקראי שמסתיים ב-00) ודלפו אותו עם %N$p. למה כאן משתמשים ב-%p ולא ב-%s?
  2. קמפלו גרסת PIE: gcc -fstack-protector-all -pie -o leakme_pie leakme.c. הפעם כתובת ה-GOT לא קבועה. דלפו קודם מצביע קוד מהמחסנית (כתובת שמתחילה ב-0x55/0x56), חשבו את elf.address, ורק אז נסו לדלוף libc דרך elf.got['read'].

רמז: הcanary לא יושב בכתובת שאפשר לכוון עליה %s, אבל הוא כן יושב על המחסנית עצמה, אז קוראים אותו כערך של סלוט עם %p. ב-PIE: את ההיסט הקבוע של מצביע הקוד תמצאו פעם אחת עם ASLR כבוי (echo 0 | sudo tee /proc/sys/kernel/randomize_va_space), וההיסט הזה נשאר קבוע גם כשמדליקים ASLR בחזרה.