Практическое Применение Ld_Preload Или Замена Функции В Linux

Всем привет! В 2010 году, шоумихин написал замечательную статью Перенаправление функций в общих библиотеках ELF .

Эта статья очень хорошо написана и полна, но в ней описан более сложный метод замены функций.

В этой статье мы будем использовать стандартную функцию динамического компоновщика, переменную среды LD_PRELOAD, которая может загружать вашу библиотеку перед загрузкой других.



Как это работает?
Да, все очень просто — компоновщик сначала загружает вашу библиотеку с вашими «стандартными» функциями, а загрузку получает тот, кто первым.

И вы можете загрузить настоящий из своей библиотеки и «прокси» вызывать, делая по пути все, что захотите.



Реальный пример использования № 1: Блокировка mimeinfo.cache в Opera

Мне очень нравится браузер Opera. Я также использую KDE. Opera не очень уважает приоритеты приложений KDE, и часто норовит открыть скачанный ZIP-архив в mcomix, PDF в imgur-uploader, в общем, вы поняли.

Однако если ей запретить читать файл mimeinfo.cache, то она все откроет через «kioclient exec», а ему виднее, в чем я хочу открыть тот или иной файл.

Как приложение может открыть файл? На ум приходят две функции: открыть И открыть .

В моем случае опера использовала 64-битный аналог fopen — fopen64 .

Определить это можно с помощью утилиты ltrace или просто просмотрев таблицу импорта с помощью утилиты objdump. Что нам нужно для написания библиотеки? Прежде всего вам необходимо создать прототип исходной функции.

Судя по мужчина открыт , прототип этой функции следующий:

  
  
  
  
  
  
  
  
  
   

FILE *fopen(const char *path, const char *mode)

И он возвращает указатель на FILE или NULL, если файл невозможно открыть.

Отлично, напишем код:

#define _GNU_SOURCE #include <stdio.h> #include <string.h> #include <dlfcn.h> static FILE* (*fopen64_orig)(const char * path, const char * mode) = NULL; FILE* fopen64(const char * path, const char * mode) { if (fopen64_orig == NULL) fopen64_orig = dlsym(RTLD_NEXT, "fopen64"); if (strstr(path, "mimeinfo.cache") != NULL) { printf("Blocking mimeinfo.cache read\n"); return NULL; } return fopen64_orig(path, mode); }

Как видите, все просто: объявляем функцию fopen64, загружаем «следующую» (оригинальную) функцию по отношению к нашей и проверяем, открываем ли мы файл «mimeinfo.cache».

Мы компилируем его с помощью следующей команды:

gcc -shared -fPIC -ldl -O2 -o opera-block-mime.so opera-block-mime.c

И запускаем оперу:

LD_PRELOAD=.

/opera-block-mime.so opera

И мы видим:

Blocking mimeinfo.cache read Blocking mimeinfo.cache read Blocking mimeinfo.cache read Blocking mimeinfo.cache read

Успех!

Реальный пример использования № 2: Превращение файла в сокет

У меня есть собственное приложение, использующее прямой доступ к принтеру (файл устройства /dev/usb/lp0).

Я хотел написать для него собственный сервер в целях отладки.

Что возвращает open()? Дескриптор файла.

Что возвращает сокет()? Тот же файловый дескриптор, с которым read() и write() работают совершенно одинаково.

Давайте начнем:

#define _GNU_SOURCE #include <stdio.h> #include <stdlib.h> #include <dlfcn.h> #include <strings.h> #include <sys/socket.h> #include <netinet/in.h> static int (*orig_open)(char * filename, int flags) = NULL; int open(char * filename, int flags) { if (orig_open == NULL) orig_open = dlsym(RTLD_NEXT, "open"); if (strcmp(filename, "/dev/usb/lp0") == 0) { //opening tcp socket struct sockaddr_in servaddr, cliaddr; int socketfd = socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr,sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr=inet_addr("127.0.0.1"); // addr servaddr.sin_port=htons(32000); // port if (connect(socketfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == 0) printf("[Open] TCP Connected\n"); else printf("[Open] TCP Connection failed!\n"); return socketfd; } return orig_open(filename, flags); }



Не совсем реальный вариант использования №3: перехват методов C++

С C++ дела обстоят немного иначе.

Допустим, у нас есть класс:

class Testclass { int var = 0; public: int setvar(int val); int getvar(); };



#include <stdio.h> #include "class.h" void Testclass() { int var = 0; } int Testclass::setvar(int val) { printf("setvar!\n"); this->var = val; return 0; } int Testclass::getvar() { printf("getvar! %d\n", this->var); return this->var; }

Но в результирующем файле функции не будут называться «Testclass::getvar» и «Testclass::setvar».

Чтобы узнать названия функций, достаточно посмотреть таблицу экспорта:

nm -D libclass.so … 0000000000000770 T _Z9Testclassv 00000000000007b0 T _ZN9Testclass6getvarEv 0000000000000780 T _ZN9Testclass6setvarEi

Это называется искажением имен.

Здесь два варианта: либо сделать библиотеку-перехватчик на C++, описывающую класс так же, как это было в оригинале, но в этом случае у вас, скорее всего, возникнут проблемы с доступом к конкретному экземпляру класса, или сделать библиотеку на C, назвав функцию по мере ее экспорта, в этом случае первым параметром будет указатель на экземпляр:

#define _GNU_SOURCE #include <stdio.h> #include <dlfcn.h> typedef int (*orig_getvar_type)(void* instance); int _ZN9Testclass6getvarEv(void* instance) { printf("Wrapped getvar! %d\n", instance); orig_getvar_type orig_getvar; orig_getvar = (orig_getvar_type)dlsym(RTLD_NEXT, "_ZN9Testclass6getvarEv"); printf("orig getvar %d\n", orig_getvar(instance)); return 0; }

Вот, собственно, и все, о чем я хотел поговорить.

Надеюсь, это будет кому-то полезно.

Теги: #ld_preload #dlsym #подстановка функции #перехват функции #Аномальное программирование #C++

Вместе с данным постом часто просматривают: