- What is / does ld.so.preload do?
- 1 Answer 1
- Практическое применение LD_PRELOAD или замещение функций в Linux
- Реальный Use-Case #1: Блокируем mimeinfo.cache в Opera
- Реальный Use-Case #2: Превращаем файл в сокет
- Не совсем реальный Use-Case #3: Перехват C++-методов
- LD_PRELOAD — Introduction
- Function Hijacking
- Debugging & Reverse Engineering
- Controlling Application Behavior
- User-Land Rootkits
- A Few Security Use Cases
- Limitations
- Conclusion
- LD_PRELOAD Series Blog Post
What is / does ld.so.preload do?
I stumbled upon a file called ld.so.preload and can’t find any real usage for it. Does it have something to do with the env variable LD_PRELOAD ?
1 Answer 1
Good question! Actually, /etc/ld.so.preload replaces, in a way, LD_PRELOAD.
LD_PRELOAD is subject to severe restrictions due to a security concern: it cannot execute arbitrary setuid binaries because, if it could, you could substitute library routines with your own malicious code, see for instance here for a nice discussion. In fact, you can read in ld.so’user manual:
LD_PRELOAD
A list of additional, user-specified, ELF shared libraries to be loaded before all others. The items of the list can be separated by spaces or colons. This can be used to selectively override functions in other shared libraries. The libraries are searched for using the rules given under DESCRIPTION. For set-user-ID/set-group-ID ELF binaries, preload pathnames containing slashes are ignored, and libraries in the standard search directories are loaded only if the set-user-ID permission bit is enabled on the library file.
Instead, the file /etc/ld.so.preload suffers from no such limitation, the idea being that, if you can read/write to the directory /etc, you already have root credentials. Hence its use. Just keep in mind that you may use /etc/ld.so.preload even though you do not seem to have one at first: it is nothing but a feature of glibc, hence of all Linux distros (but not, to the best of my knowledge, of Unix flavors), thus you can create it and put into it the name of whichever setuid library in any Linux distro, and it will work.
Практическое применение LD_PRELOAD или замещение функций в Linux
Всем привет!
В 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)
#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); >
Как видите, все просто: объявляем функцию 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
Реальный Use-Case #2: Превращаем файл в сокет
Есть у меня проприетарное приложение, которое использует прямой доступ к принтеру (файл устройства /dev/usb/lp0). Захотел я написать для него свой сервер в целях отладки. Что возвращает open()? Файловый дескриптор. Что возвращает socket()? Такой же файловый дескриптор, на котором совершенно так же работают read() и write(). Приступаем:
#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); >
Не совсем реальный Use-Case #3: Перехват C++-методов
#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; >
Но функции не будут называться «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 #include typedef int (*orig_getvar_type)(void* instance); int _ZN9Testclass6getvarEv(void* instance)
Вот, собственно, и все, о чем хотелось рассказать. Надеюсь, это будет кому-то полезно.
LD_PRELOAD — Introduction
Today I wanted to start what I plan to be a small series of blog posts about LD_PRELOAD. LD_PRELOAD is related to Linux based systems and revolves around the loader system and how shared object libraries resolve linker symbols when loading a dynamically linked ELF binary and is loaded before any other shared object libraries. This is often referred to as a technique, but the namesake is in reference to the environment variable that is used by the loader system. There is also a global file that can be used to point to your shared object library globally, and it would affect all processes, not just the ones that can see the LD_PRELOAD environment variable. This causes some very interesting behavior and can be useful for a wide range of use cases. This blog post is designed to give a high-level overview of the use cases that will be covered in later blog posts.
Function Hijacking
This is one of the most common utilities of LD_PRELOAD. By having your shared object library loaded before the other libraries, it will search the libraries in the order they are loaded to find it. This happens when the ELF binary that is running calls a function that is to be imported from the shared object libraries such as libc. If there is a function in your shared object library that matches that name, your function will be invoked instead of the real one. This can be useful for several use cases.
Debugging & Reverse Engineering
This is primarily one of the ways that you see LD_PRELOAD get used. This can be used to hijack function calls and do a wide range of things. You can have it print out the parameters that were passed to the function, or even log them to a file instead. It’s also possible to lookup the real function address they were calling and pass it onto the real function. This allows you to have a sort of debugging shim between the ELF and the real function in the application. You can also review the stack when this is called to determine the caller address so you know where the call came from if you want to just pass it along on all calls except one address it gets called from.
Controlling Application Behavior
Function hijacking can also be used to control a processes behavior. Since the ELF binary will likely react to return codes from those function calls, it is possible to fake return values to get your desired behavior while being in control of what the function call does. For example, imagine a rand() call that always returns 42, since the rand or random function call is used to obtain a unique and hard to guess integer, or strcmp() that always returns 0 as opposed to comparing the strings and informing if matched or not, or a flock() call that doesn’t actually lock the file, but tells you it did.
User-Land Rootkits
All of this functionality also lends itself quite well to malware. It’s possible to use this to create a user-land rootkit. On top of the environment variables, there is also a global file that can be used that will make the loader preload the shared object library into every process it starts. This would include services and daemons that run as root. Some of the malware out there does interesting stuff with this.
For example, hijacking the library calls in libc to a function that gets a directory listing means you can hide files, hijacking read() can allow you to hide file content, hijacking the PAM authentication functions can allow you to log passwords, hijacking accept() can allow you to turn every network service on the machine into a bind shell backdoor under the right conditions. Hijacking function is libpcap can hide traffic you want to keep hidden. Hijacking functions in openssl/openssh could allow you to generate known keys, or log any loaded keys.
However it is worth noting that there is a flaw in this design that can make detection of these rootkits possible from user-land as well. The primary one being that it can only affect dynamically linked ELF binaries and a statically linked ELF binary wouldn’t be affected by it. We will explore this more in-depth in a later blog post.
A Few Security Use Cases
A while back ago, the local Linux User Group JaxLUG had a presentation that explored the possibility of using that same rootkit idea, but from a security standpoint of using it as a system-wide application whitelist technology. I have also seen LD_PRELOAD be used to block or log certain network connections in applications since you can control the DNS lookup and connect calls.
Limitations
LD_PRELOAD does have a few limitations to it. The first one is that it only works on dynamically loaded ELF binaries. If it was statically linked, then it doesn’t load external shared object libraries. The next limitation to keep in mind is that binaries with the setuid bit set will ignore the setuid bit if LD_PRELOAD is set. When a binary that has the setuid bit is loaded with LD_PRELOAD, it will be run as the user that invoked it, rather than changing to the file owner. This is for the obvious security issue that would come up if a user could just fire up a setuid binary owned by root such as sudo, and inject their own code that might just give them a shell. You can do that, but it would be running as your user, not the file owner.
Conclusion
This post was mostly intended to be a high-level overview. If some of this seems a little confusing don’t worry about it, this is just the introduction in the series. The blog post to follow will provide more examples and will expand upon the use cases outlined above.If you’re interested in security fundamentals, we have a Professionally Evil Fundamentals (PEF) channel that covers a variety of technology topics. We also answer general basic questions in our Knowledge Center. Finally, if you’re looking for a penetration test, professional training for your organization, or just have general security questions please Contact Us.
LD_PRELOAD Series Blog Post
Interested in more information about LD_PRELOAD? This blog is a part of a series and the full list of blogs in this series can be found below: