Всем привет! В 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 или NULL, если файл невозможно открыть.FILE *fopen(const char *path, const char *mode)
Отлично, напишем код: #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++
-
Бесплатные Ноты В Сети — Правда Или Вымысел?
19 Oct, 24 -
Обзор Программы Ppc Coach
19 Oct, 24 -
Скажите Пару Слов О Плохой Терминологии
19 Oct, 24