Библиотека сайта rus-linux.net
Перехват функций разделяемых библиотек
Оригинал: Let's Hook a Library FunctionАвтор: Dibyendu Roy
Дата публикации: 1 Августа 2011 г.
Перевод: А.Панин
Дата публикации перевода: 19 октября 2012 г.
В том случае, если вы являетесь разработчиком и хотите изменить принцип работы функции из разделяемой библиотеки, вы можете использовать информацию из этой статьи для того, чтобы понять с чего начать работу по перехвату функции библиотеки и провести ряд экспериментов с функциями вашей собственной разделяемой библиотеки. Исходные коды программ и библиотек разработаны на языке C, программы собраны при помощи компилятора GCC и протестированы в ОС Linux.
В соответствии с информацией из Wikipedia: "под термином перехват в программировании понимается ряд техник, используемых для изменения поведения или расширения возможностей операционных систем, приложений или других программных компонентов путем подмены вызовов функций, сообщений или событий, передаваемых между программными компонентами. Код, обрабатывающий перехваченные вызовы функций, события и сообщения, называется кодом перехвата".
Перехват функций разделяемых библиотек и использование кода для замещения также называется "функциональным посредничеством" (Function Interposition).
- Вам не нужно искать объявление функции в коде библиотеки, такой, как libc (glibc (GNU C Library) - это реализация библиотеки языка C от проекта GNU, при этом кодовая база libc составляет едва ли не половину glibc) и изменять его. Серьезно, это очень неприятная техническая задача (во всяком случае, для меня!).
- Вам не нужно перекомпилировать библиотеку.
Библиотечные функции и системные вызовы
На рисунках 1 и 2 показан процесс перехвата библиотечной функции. Пожалуйста, рассмотрите их.
Рисунок 1: Вызов библиотечной функции
Рисунок 2: Вызов библиотечной функции с перехватом
#include<stdio.h> #include<malloc.h> #include<stdlib.h> int main(void) { int *p; printf("calling from main...\n"); p=(int *)malloc(10); if(!p) { printf("Got allocation error...\n"); exit(1); } printf("returning to main...\n"); free(p); /* освобождение зарезервированной памяти */ printf("freeing memory...\n"); return 0; }
[root@workbenchsvr malloc_hook]# gcc -o prog1 prog1.c [root@workbenchsvr malloc_hook]# ./prog1 calling from main... returning to main... freeing memory... [root@workbenchsvr malloc_hook]#
#define _GNU_SOURCE #include <stdio.h> #include <stdint.h> #include <dlfcn.h> /* заголовочный файл необходим для вызва dlsym() */ /* lcheck() - функция, необходимая для проверки на утечки памяти; ее код не приведен в данном листинге*/ void lcheck(void); void* malloc(size_t size) { static void* (*my_malloc)(size_t) = NULL; printf("inside shared object...\n"); if (!my_malloc) my_malloc = dlsym(RTLD_NEXT, "malloc"); /* возвращает указатель на функцию malloc */ void *p = my_malloc(size); /* вызов malloc() с использованием указателя my_malloc */ printf("malloc(%d) = %p\n", size, p); lcheck(); /* вызов функции do_your_stuff */ printf("returning from shared object...\n"); return p; } void lcheck(void) { printf("displaying memory leaks...\n"); /* выполнение необходимых действий */ }
[root@workbenchsvr malloc_hook]# gcc -shared -ldl -fPIC prog2.c -o libprog2.so [root@workbenchsvr malloc_hook]# LD_PRELOAD=/home/dibyendu/malloc_hook/libprog2.so ./prog1 calling from main... inside shared object... malloc(10) = 0x8191008 displaying memory leaks... returning from shared object... returning to main... freeing memory... [root@workbenchsvr malloc_hook]#
Теперь мы можем подробнее рассмотреть наш первый пример перехвата функции разделяемой библиотеки. Функция dlsym() принимает два параметра: первый параметр - это идентификатор, возвращенный функцией dlopen(). В нашем случае мы должны использовать параметр RTLD_NEXT для перехвата функции.
Этот параметр сообщает динамическому компоновщику о том, что необходимо найти следующую ссылку на заданную функцию, а не ту, которая находится при вызове dlsym(). Вторым параметром является имя функции (в нашем случае malloc) в форме строки. Функция dlsym() возвращает адрес функции, указанной в качестве второго параметра. Во время компиляции параметр fPIC используется для создания независимого от позиции объектного кода (position-independent object).
Переменная окружения LD_PRELOAD передает загрузчику динамических библиотек список библиотек, которые должны быть загружены в первую очередь. В нашем примере при помощи этой переменной мы подгружаем библиотеку libprog2.so и динамически связываем ее с программой prog1. Не забудьте, что при передаче параметра переменной окружения LD_PRELOAD, необходимо приводить абсолютный путь до файла разделяемой библиотеки. И конечно же, не забудьте объявление #define _GNU_SOURCE, если хотите использовать соответствующие расширения во время работы с GNU C Library, поскольку некоторые расширения могут быть недоступны на операционных системах, не имеющих отношения к проекту GNU, и добавление этого объявления улучшит переносимость на другие системы.
Можем ли мы перехватить любую функцию?
/* file1.c */ void file1(int *i) { *i=100; }
/* file2.c */ void file2(int *i) { *i=200; }
#include<stdio.h> #include<dlfcn.h> #include<stdlib.h> void file1(int *i); void file2(int *i); int main(void) { void *handler; int (*fn) (int *); int x; char *error; handler = dlopen("/home/dibyendu/dlsym_hook/libfile.so", RTLD_LAZY); if (!handler) { fprintf(stderr,"%s\n", dlerror()); exit(1); } fn = dlsym(handler,"file1"); /* получение идентификатора при помощи dlsym() */ if ((error = dlerror()) != NULL) /* проверка на наличие ошибок с помощью dlerror() */ { fprintf(stderr,"%s\n", error); exit(1); } (*fn)(&x); /* вызов функции file1() для получения значения x */ printf("The value of x is %d\n", x); dlclose(handler); /* освобождение идентификатора */ return 0; }
#define _GNU_SOURCE #include <stdio.h> #include <stdint.h> #include <dlfcn.h> void *dlsym(void *handle, const char *name) { void *(*dlsym_fn)(void *, const char *)=NULL; printf("inside shared object::before dlsym()...\n"); dlsym_fn=dlsym(RTLD_NEXT, "dlsym"); /* функция будет вызывать себя снова и снова */ printf("inside shared object::after dlsym()...\n"); return (*dlsym_fn)(handle, name); }
[root@workbenchsvr dlsym_hook]# gcc -shared -ldl -fPIC dl_prog2.c -o libdl_prog2.so [root@workbenchsvr dlsym_hook]# LD_PRELOAD=/home/dibyendu/dlsym_hook/libdl_prog2.so ./dl_prog1 inside shared object::before dlsym()... .................................................................. inside shared object::before dlsym()... Segmentation fault [root@workbenchsvr dlsym_hook]#
#define __USE_GNU #include <stdio.h> #include <stdlib.h> #include <dlfcn.h> extern void *__libc_dlsym (void *, const char *); void *dlsym(void *handle, const char *symbol) { printf("Ha Ha...dlsym() Hooked\n"); void* result = __libc_dlsym(handle, symbol); /* теперь библиотечная функция dlsym() будет корректно вызвана */ return result; }
[root@workbenchsvr dlsym_hook]# gcc -shared -ldl -fPIC dl_prog3.c -o libdl_prog3.so [root@workbenchsvr dlsym_hook]# LD_PRELOAD=/home/dibyendu/dlsym_hook/libdl_prog3.so ./dl_prog1 Ha Ha...dlsym() Hooked The value of x is 100 [root@workbenchsvr dlsym_hook]#
Что еще можно сделать?
- Остерегайтесь функций, которые вызывают dlsym(), когда вам необходимо вызывать __libc_dlsym (идентификатор, символ) в коде перехвата.
- Удостоверьтесь в том, что бит SUID не установлен, так как в этом случае использование переменной окружения LD_PRELOAD невозможно.
- Вызовы внутренних функций в рамках библиотеки формируются на этапе статической компоновки - например, если какая-нибудь функция в библиотеке libc вызывает getaddrinfo() или malloc(), то код перехвата из другой библиотеки не будет исполнен ни при каких обстоятельствах.