📜 ⬆️ ⬇️

Practical application of LD_PRELOAD or replacement of functions in Linux

Hello!
In 2010, shoumikhin wrote a wonderful article Redirecting Functions in Shared ELF Libraries . That article is very well written, complete, but it describes a more hard way to replace functions. In this article, we will use the standard dynamic linker feature - the environment variable LD_PRELOAD, which can load your library before loading the rest.

How it works?

It's very simple - the linker loads your library with your “standard” functions first, and who is the first, the one and the sneaker. And from your library you can download the real ones, and “proxy” calls, while doing whatever you want.

Real Use-Case # 1: Block 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 it also strives to open the downloaded ZIP archive in mcomix, the PDF in imgur-uploader, in general, you get 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.
')
How can an application open a file? Two functions come to mind: fopen and open . In my case, opera used the 64-bit analog of fopen - fopen64 . You can determine this by using the ltrace utility, or simply by looking at the import table with the objdump utility.

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 of 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 <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); } 

As you can see, everything is simple: we declare the function fopen64, load the “next” (original), in relation to our, function, and check if we open 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 the 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 own server for debugging purposes. What does open () return? File descriptor What does socket () return? The same file descriptor on which read () and write () work exactly the same. Getting Started:
 #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); } 


Not really real Use-Case # 3: Intercepting C ++ methods


With C ++, it's a little different. Let's say we have a class:
 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; } 

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 to create an interceptor library in C ++, describing the class in the same way it was in the original, but in this case, you are likely to have problems accessing a particular instance of the class, or else make a library on C, calling the function as it is exported; in this case, the first parameter is the pointer to the instance:
 #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; } 


That is, in fact, all that I wanted to talk about. I hope it will be useful to someone.

Source: https://habr.com/ru/post/199090/


All Articles