Practical use of LD_PRELOAD or function substitution in Linux

    Hello!
    In 2010, shoumikhin wrote a great article , Feature Redirection in Shared ELF Libraries . That article is very well written, complete, but it describes a more hardcore way of replacing functions. In this article, we will use the standard feature of the dynamic linker - the environment variable LD_PRELOAD, which can load your library before loading the rest.

    How it works?

    Yes, it’s very simple - the linker loads your library with your “standard” functions first, and who the first - that and slippers. And you can download the real one from your library, and “proxy” calls, simultaneously doing whatever you want.

    Real Use-Case # 1: Blocking mimeinfo.cache in Opera


    I really like the Opera browser. I also use KDE. Opera does not really respect the priorities of KDE applications, and often tries to open the downloaded ZIP archive in mcomix, PDF in imgur-uploader, in general, you catch the gist. However, if she is forbidden to read the mimeinfo.cache file, then she will open everything through “kioclient exec”, and he knows better what I want to open this or that file in.

    How can an application open a file? Two functions come to mind: fopen and open . In my case, opera used the 64-bit fopen analog - fopen64 . You can determine this by using the ltrace utility, or simply by looking at the objdump utility import table.

    What do we need to write a library? First of all, you need to make a prototype of the original function.
    Judging by man fopen , the prototype for this function is as follows:
    FILE *fopen(const char *path, const char *mode)
    And it returns a pointer to FILE, or NULL if the file cannot be opened. Ok, write the code:
    #define _GNU_SOURCE
    #include 
    #include 
    #include 
    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);
    }

    As you can see, everything is simple: we declare the fopen64 function, load the “next” (original) function in relation to ours, and check if we are opening the mimeinfo.cache file. Compile it with the following command:
    gcc -shared -fPIC -ldl -O2 -o opera-block-mime.so opera-block-mime.c

    And run opera:
    LD_PRELOAD=./opera-block-mime.so opera
    And we see:
    Blocking mimeinfo.cache read
    Blocking mimeinfo.cache read
    Blocking mimeinfo.cache read
    Blocking mimeinfo.cache read

    Success!

    Real Use-Case # 2: Turning a file into a socket


    I have a proprietary application that uses direct access to the printer (device file / dev / usb / lp0). I wanted to write my server for him for debugging purposes. What does open () return? File descriptor. What does socket () return? The same file descriptor on which read () and write () work in exactly the same way. Getting started:
    #define _GNU_SOURCE
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    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);
    }


    Not-So-Real Use-Case # 3: Intercepting C ++ Methods


    With C ++, things are a little different. Let's say we have a class:
    class Testclass {
        int var = 0;
    public:
        int setvar(int val);
        int getvar();
    }; 

    #include 
    #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;
    }

    But the functions will not be called “Testclass :: getvar” and “Testclass :: setvar” in the resulting file. To find out the names of functions, just look at the export table:
    nm -D libclass.so
    …
    0000000000000770 T _Z9Testclassv
    00000000000007b0 T _ZN9Testclass6getvarEv
    0000000000000780 T _ZN9Testclass6setvarEi
    This is called name mangling.
    There are two ways out: either make the C ++ interceptor library, describing the class as it was in the original, but in this case, you will most likely have problems accessing a particular instance of the class, or make the library in C, naming the function as it is exported, in this case, the first parameter will pass you a pointer to the instance:
    #define _GNU_SOURCE
    #include 
    #include 
    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;
    }


    That, in fact, was all I wanted to talk about. I hope this will be useful to someone.

    Also popular now: