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

Как это работает?

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

Реальный Use-Case #1: Блокируем mimeinfo.cache в Opera


Мне очень нравится браузер Opera. А еще я использую KDE. Opera не очень уважает приоритеты приложений KDE, и, зачастую, так и норовит открыть скачанный ZIP-архив в mcomix, PDF в imgur-uploader, в общем, вы уловили суть. Однако, если ей запретить читать файл mimeinfo.cache, то она все будет открывать через «kioclient exec», а он-то уж лучше знает, в чем я хочу открыть тот или иной файл.

Чем может приложение открывать файл? На ум приходят две функции: fopen и open. В моем случае, opera использовала 64-битный аналог fopen — fopen64. Определить это можно, воспользовавшись утилитой ltrace, или просто посмотрев таблицу импорта утилитой objdump.

Что нам нужно для написания библиотеки? Первым делом, нужно составить прототип оригиналь��ой функции.
Судя по man fopen, прототип у этой функции следующий:
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

И запускаем opera:
LD_PRELOAD=./opera-block-mime.so opera
И видим:
Blocking mimeinfo.cache read
Blocking mimeinfo.cache read
Blocking mimeinfo.cache read
Blocking mimeinfo.cache read

Успех!

Реальный Use-Case #2: Превращаем файл в сокет


Есть у меня проприетарное приложение, которое использует прямой доступ к принтеру (файл устройства /dev/usb/lp0). Захотел я написать для него свой сервер в целях отладки. Что возвращает open()? Файловый дескриптор. Что возвращает socket()? Такой же файловый дескриптор, на котором совершенно так же работают 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);
}


Не совсем реальный Use-Case #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
Это называется name mangling.
Тут есть два выхода: либо сделать библиотеку-перехватчик на 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;
    
}


Вот, собственно, и все, о чем хотелось рассказать. Надеюсь, это будет кому-то полезно.