לדלג לתוכן

5.10 ספריות משותפות פתרון

פתרונות

פתרון 1

לאחר הרצת ldd על שלוש התוכניות:

ldd /bin/ls
ldd /bin/bash
ldd /usr/bin/python3
  1. הספריות המשותפות לכולן:
  2. libc.so.6 - ספריית C הסטנדרטית. כמעט כל תוכנית משתמשת בה
  3. ld-linux-x86-64.so.2 - הdynamic linker עצמו
  4. linux-vdso.so.1 - ספריה וירטואלית שהקרנל מזריק לכל תהליך

  5. הנתיב של הdynamic linker הוא /lib64/ld-linux-x86-64.so.2

  6. הספריה linux-vdso.so.1 (שמה המלא: Virtual Dynamic Shared Object) היא ספריה מיוחדת שהקרנל ממפה אוטומטית לזיכרון של כל תהליך. היא מכילה מימושים מהירים של כמה syscall-ים נפוצים (כמו gettimeofday) שיכולים לרוץ בuser mode בלי מעבר לkernel mode - מה שמאיץ אותם משמעותית.

פתרון 2

הקוד:

#include <stdio.h>

int main() {
    printf("Hello World\n");
    return 0;
}

קימפול והשוואה:

gcc -o dynamic_hello hello.c
gcc -static -o static_hello hello.c
ls -la dynamic_hello static_hello

פלט לדוגמה:

-rwxr-xr-x 1 user user   16696 Mar  1 10:00 dynamic_hello
-rwxr-xr-x 1 user user  880472 Mar  1 10:00 static_hello

הקובץ הסטטי גדול פי 50 בערך מהדינמי, כי הוא מכיל את כל הקוד של libc בתוכו.

ldd ./dynamic_hello

linux-vdso.so.1 (0x00007ffd...)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f...)
/lib64/ld-linux-x86-64.so.2 (0x00007f...)

ldd ./static_hello

not a dynamic executable

file ./dynamic_hello

dynamic_hello: ELF 64-bit LSB pie executable, x86-64, dynamically linked, ...

file ./static_hello

static_hello: ELF 64-bit LSB executable, x86-64, statically linked, ...

ההבדל העיקרי: dynamically linked מול statically linked.

פתרון 3

הקובץ math_lib.c:

int add(int a, int b) {
    return a + b;
}

int multiply(int a, int b) {
    return a * b;
}

קימפול הספריה:

gcc -shared -fPIC -o libmath.so math_lib.c

הקובץ main.c:

#include <stdio.h>

int add(int a, int b);
int multiply(int a, int b);

int main() {
    int a = 7, b = 3;

    printf("%d + %d = %d\n", a, b, add(a, b));
    printf("%d * %d = %d\n", a, b, multiply(a, b));

    return 0;
}

קימפול והרצה:

gcc main.c -L. -lmath -o program
LD_LIBRARY_PATH=. ./program

פלט:

7 + 3 = 10
7 * 3 = 21

בדיקה עם ldd:

LD_LIBRARY_PATH=. ldd ./program

linux-vdso.so.1 (0x00007ffd...)
libmath.so => ./libmath.so (0x00007f...)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f...)
/lib64/ld-linux-x86-64.so.2 (0x00007f...)

אפשר לראות ש-libmath.so מופיעה ברשימת התלויות.

פתרון 4

#include <stdio.h>
#include <dlfcn.h>

int main() {
    /* טוענים את ספריית המתמטיקה */
    void *handle = dlopen("libm.so.6", RTLD_LAZY);
    if (handle == NULL) {
        printf("שגיאה בטעינה: %s\n", dlerror());
        return 1;
    }

    /* מחפשים את הפונקציה cos */
    double (*cos_func)(double) = dlsym(handle, "cos");
    char *error = dlerror();
    if (error != NULL) {
        printf("שגיאה במציאת הפונקציה: %s\n", error);
        dlclose(handle);
        return 1;
    }

    /* קוראים לפונקציה */
    double result1 = cos_func(0.0);
    printf("cos(0) = %f\n", result1);

    double result2 = cos_func(3.14159265);
    printf("cos(pi) = %f\n", result2);

    /* משחררים */
    dlclose(handle);
    return 0;
}

קימפול והרצה:

gcc solution4.c -ldl -o solution4
./solution4

פלט:

cos(0) = 1.000000
cos(pi) = -1.000000

פתרון 5

  1. היתרון האבטחתי של ASLR:

ללא ASLR, ספריות משותפות נטענות תמיד לאותן כתובות בזיכרון. תוקף שרוצה לנצל פגיעות בתוכנית יכול לדעת מראש בדיוק באיזו כתובת נמצאת כל פונקציה בlibc (למשל system() שמאפשרת להריץ פקודות).

טבלת הGOT מכילה כתובות של פונקציות חיצוניות. אם תוקף מצליח לכתוב לGOT (למשל דרך buffer overflow), הוא יכול להחליף את הכתובת של פונקציה לגיטימית (כמו printf) בכתובת של system(), וכך כשהתוכנית קוראת ל-printf היא בעצם מריצה system().

עם ASLR, הספריות נטענות לכתובות אקראיות בכל הרצה. התוקף לא יודע באיזו כתובת system() נמצאת, ולכן לא יודע מה לכתוב לGOT.

  1. למה התוקף צריך לדעת את הכתובת:

כדי לבצע GOT overwrite, התוקף צריך לכתוב כתובת לתוך הGOT. הכתובת הזו צריכה להצביע על קוד שהתוקף רוצה להריץ. אם הוא רוצה לקפוץ ל-system() שנמצאת בlibc, הוא צריך לדעת את הכתובת המדויקת שלה.

ללא ASLR - הכתובת של system() תמיד זהה, אפשר לגלות אותה פעם אחת ולהשתמש בה תמיד.

עם ASLR - הכתובת משתנה בכל הרצה. התוקף יצטרך קודם למצוא דרך לגלות את הכתובת (למשל דרך דליפת מידע), ורק אז יוכל לבצע את ההתקפה. זה מקשה מאוד על הניצול.