5.10 ספריות משותפות פתרון
פתרונות¶
פתרון 1¶
לאחר הרצת ldd על שלוש התוכניות:
- הספריות המשותפות לכולן:
libc.so.6- ספריית C הסטנדרטית. כמעט כל תוכנית משתמשת בהld-linux-x86-64.so.2- הdynamic linker עצמו-
linux-vdso.so.1- ספריה וירטואלית שהקרנל מזריק לכל תהליך -
הנתיב של הdynamic linker הוא
/lib64/ld-linux-x86-64.so.2 -
הספריה
linux-vdso.so.1(שמה המלא: Virtual Dynamic Shared Object) היא ספריה מיוחדת שהקרנל ממפה אוטומטית לזיכרון של כל תהליך. היא מכילה מימושים מהירים של כמה syscall-ים נפוצים (כמוgettimeofday) שיכולים לרוץ בuser mode בלי מעבר לkernel mode - מה שמאיץ אותם משמעותית.
פתרון 2¶
הקוד:
קימפול והשוואה:
פלט לדוגמה:
-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 בתוכו.
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...)
ההבדל העיקרי: dynamically linked מול statically linked.
פתרון 3¶
הקובץ 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;
}
קימפול והרצה:
פלט:
בדיקה עם ldd:
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;
}
קימפול והרצה:
פלט:
פתרון 5¶
- היתרון האבטחתי של ASLR:
ללא ASLR, ספריות משותפות נטענות תמיד לאותן כתובות בזיכרון. תוקף שרוצה לנצל פגיעות בתוכנית יכול לדעת מראש בדיוק באיזו כתובת נמצאת כל פונקציה בlibc (למשל system() שמאפשרת להריץ פקודות).
טבלת הGOT מכילה כתובות של פונקציות חיצוניות. אם תוקף מצליח לכתוב לGOT (למשל דרך buffer overflow), הוא יכול להחליף את הכתובת של פונקציה לגיטימית (כמו printf) בכתובת של system(), וכך כשהתוכנית קוראת ל-printf היא בעצם מריצה system().
עם ASLR, הספריות נטענות לכתובות אקראיות בכל הרצה. התוקף לא יודע באיזו כתובת system() נמצאת, ולכן לא יודע מה לכתוב לGOT.
- למה התוקף צריך לדעת את הכתובת:
כדי לבצע GOT overwrite, התוקף צריך לכתוב כתובת לתוך הGOT. הכתובת הזו צריכה להצביע על קוד שהתוקף רוצה להריץ. אם הוא רוצה לקפוץ ל-system() שנמצאת בlibc, הוא צריך לדעת את הכתובת המדויקת שלה.
ללא ASLR - הכתובת של system() תמיד זהה, אפשר לגלות אותה פעם אחת ולהשתמש בה תמיד.
עם ASLR - הכתובת משתנה בכל הרצה. התוקף יצטרך קודם למצוא דרך לגלות את הכתובת (למשל דרך דליפת מידע), ורק אז יוכל לבצע את ההתקפה. זה מקשה מאוד על הניצול.