לדלג לתוכן

7.6 מבוא לניצול חולשות תרגול

תרגיל 1 - ניתוח תוכנית פגיעה

הנה קוד C עם חולשת אבטחה:

#include <stdio.h>
#include <string.h>

void secret() {
    printf("You found the secret function!\n");
}

void greet() {
    char name[32];
    printf("What is your name? ");
    gets(name);
    printf("Hello, %s!\n", name);
}

int main() {
    greet();
    return 0;
}
  1. מהי החולשה בקוד? הסבירו בדיוק למה היא מסוכנת.
  2. ציירו את מבנה המחסנית של הפונקציה greet - איפה name, איפה saved rbp, ואיפה כתובת החזרה?
  3. כמה בתים צריך להקליד כדי להגיע לכתובת החזרה? (הניחו מערכת 64 ביט, כלומר saved rbp הוא 8 בתים)
  4. אם כתובת הפונקציה secret היא 0x401156, הסבירו תיאורטית איך תוקף יכול לגרום לביצוע של secret.

תרגיל 2 - חישוב offset לכתובת חזרה

נתון הקוד הבא:

#include <stdio.h>

void vulnerable() {
    int x = 42;
    char buffer[128];
    int y = 17;
    printf("Enter input: ");
    gets(buffer);
    printf("x = %d, y = %d\n", x, y);
}

int main() {
    vulnerable();
    printf("Done!\n");
    return 0;
}
  1. ציירו את מבנה המחסנית של vulnerable (שימו לב: הקומפיילר עשוי לסדר את המשתנים בסדר שונה ממה שכתוב בקוד. הניחו שהסדר הוא: buffer בתחתית, אחריו y, אחריו x, אחריו saved rbp, אחריו כתובת חזרה).
  2. מה ה-offset מתחילת buffer עד כתובת החזרה? (הניחו: buffer=128, y=4, x=4, padding=8 לiuשור, saved rbp=8)
  3. אם נקליד 130 תווים 'A' - אילו ערכים יידרסו?
  4. מה הערך של y ו-x יהיה אחרי שנקליד 140 תווים 'A'?

הערה: בפועל, הקומפיילר יכול לסדר את המשתנים ולהוסיף padding בצורה שונה. בתרגיל הזה הניחו את הסדר שנתון למעלה.


תרגיל 3 - שימוש ב-checksec

קמפלו את התוכנית הבאה בכמה אופנים שונים ובדקו את ההגנות עם checksec:

#include <stdio.h>
#include <string.h>

int main() {
    char buf[64];
    printf("Input: ");
    fgets(buf, sizeof(buf), stdin);
    printf("You said: %s\n", buf);
    return 0;
}

התקינו checksec:

sudo apt install checksec

קמפלו בארבע דרכים שונות:

# גרסה 1: בלי שום הגנה
gcc -fno-stack-protector -z execstack -no-pie -o version1 program.c

# גרסה 2: רק canary
gcc -fstack-protector-strong -z execstack -no-pie -o version2 program.c

# גרסה 3: canary + NX
gcc -fstack-protector-strong -z noexecstack -no-pie -o version3 program.c

# גרסה 4: כל ההגנות
gcc -fstack-protector-strong -z noexecstack -pie -o version4 program.c

הריצו checksec על כל גרסה:

checksec --file=version1
checksec --file=version2
checksec --file=version3
checksec --file=version4

  1. מה ההבדלים בפלט של checksec בין הגרסאות?
  2. איזו גרסה הכי פגיעה? למה?
  3. איזו גרסה הכי מוגנת? למה?
  4. האם יש הגנות שלא שלטנו בהן דרך דגלי הקומפיילר אבל checksec עדיין מראה?

תרגיל 4 - מדוע gets() מסוכנת

הסבירו בפירוט למה הפונקציה gets() מסוכנת. בתשובה שלכם:

  1. ציירו דיאגרמה של המחסנית שמראה מה קורה כשקוראים ל-gets עם קלט ארוך מדי
  2. הסבירו למה fgets היא חלופה בטוחה:
    // מסוכן:
    gets(buffer);
    
    // בטוח:
    fgets(buffer, sizeof(buffer), stdin);
    
  3. הראו עוד 3 פונקציות "מסוכנות" שלא בודקות גבולות, ואת החלופה הבטוחה שלהן
  4. הסבירו מה ההבדל בין:
    strcpy(dst, src);     // מסוכן
    strncpy(dst, src, n); // בטוח יותר
    

תרגיל 5 - קנרי על המחסנית

נתון הקוד הבא:

#include <stdio.h>
#include <string.h>

void copy_input() {
    char buffer[16];
    printf("Enter text: ");
    gets(buffer);
    printf("You entered: %s\n", buffer);
}

int main() {
    copy_input();
    return 0;
}
  1. קמפלו עם stack protector:

    gcc -fstack-protector-strong -o canary_test canary_test.c
    

  2. הריצו עם קלט קצר (למשל "hello") - מה קורה?

  3. הריצו עם קלט ארוך שיגלוש (למשל 50 תווים 'A'):

    python3 -c "print('A' * 50)" | ./canary_test
    

  4. מה הודעת השגיאה שמופיעה? הסבירו מה קרה.

  5. ציירו דיאגרמה של המחסנית שמראה את מיקום הקנרי ביחס ל-buffer ולכתובת החזרה.

  6. הסבירו: למה הקנרי מתחיל בבית null (\x00)? באיזה מצב ספציפי זה עוזר?