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.
Операция «Предзагрузка». Создаем userland-руткиты в Linux с помощью LD_PRELOAD
В никсах существует переменная среды, при указании которой твои библиотеки будут загружаться раньше остальных. А это значит, что появляется возможность подменить системные вызовы. Называется переменная LD_PRELOAD , и в этой статье мы подробно обсудим ее значение в сокрытии (и обнаружении!) руткитов.
Официально главное предназначение LD_PRELOAD — отладка или проверка функций в динамически подключаемых библиотеках. Если не хочется исправлять и перекомпилировать саму библиотеку, то можно воспользоваться переменной среды.
К примеру, если нам нужно предзагрузить библиотеку ld.so, то у нас будет два способа:
- Установить переменную среды LD_PRELOAD с файлом библиотеки.
- Записать путь к библиотеке в файл / etc/ ld. so. preload .
В первом случае мы объявляем переменную с библиотекой для текущего пользователя и его окружения. Во втором же наша библиотека будет загружена раньше остальных для всех пользователей системы.
Нам интересен как раз второй способ: именно он часто используется в руткитах для перехвата некоторых вызовов, таких как чтение файла, листинг директории, процессов и прочих функций, позволяющих злоумышленнику скрывать свое присутствие в системе.
www
В основе этого исследования — публикация Абхинава Тхакура Crafting LD_PRELOAD Rootkits in Userland.
Переопределение системных вызовов
Прежде чем мы начнем сближение с реальными функциями руткитов, давай на небольшом примере покажу, как можно перехватить вызов стандартной функции malloc().
Для этого напишем простую программу, которая выделяет блок памяти с помощью функции malloc( ) , затем помещает в него функцией strncpy( ) строку «I’ll be back» и выводит ее посредством fprintf( ) по адресу, который вернула malloc( ) .
Создаем файл call_malloc. c :
Теперь напишем программу, переопределяющую malloc( ) . Внутри — функция с тем же именем, что и в libc. Наша функция не делает ничего, кроме вывода строки в STDERR c помощью fprintf( ) .
Создадим файл libmalloc. c :
Теперь с помощью GCC скомпилируем наш код:
$ ls
call_malloc.c libmalloc.c
$ gcc -Wall -fPIC -shared -o libmalloc. so libmalloc. c -ldl
$ gcc -o call_malloc call_malloc. c
$ ls
call_malloc call_malloc.c libmalloc.c libmalloc.so
Выполним нашу программу call_malloc:
$ ./ call_malloc
malloc(): 0x5585b2466260
Str: I’ll be back
Посмотрим, какие библиотеки использует наша программа, с помощью утилиты ldd:
$ ldd ./ call_malloc
linux-vdso.so.1 (0x00007fff0cd81000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f0d35c74000)
/lib64/ld-linux-x86-64.so.2 (0x00007f0d35e50000)
Отлично видно, что без использования предзагрузчика LD_PRELOAD стандартно загружаются три библиотеки:
- linux-vdso. so. 1 — представляет собой виртуальный динамический разделяемый объект (Virtual Dynamic Shared Object, vDSO), который применяется для оптимизации часто используемых системных вызовов. Его можно игнорировать (подробнее — man 7 vdso ).
- libc. so. 6 — библиотека libc с используемой нами функцией malloc( ) в программе call_malloc .
- ld-linux-x86-64. so. 2 — сам динамический компоновщик.
Теперь давай определим переменную LD_PRELOAD и попробуем перехватить malloc( ) . Здесь я не буду использовать export и ограничусь однострочной командой для простоты:
$ LD_PRELOAD=./ libmalloc. so ./ call_malloc
Hijacked malloc(256)
Ошибка сегментирования
Мы успешно перехватили malloc( ) из библиотеки libc. so , но сделали это не совсем чисто. Функция возвращает значение указателя NULL, что при разыменовании strncpy( ) в программе ./ call_malloc вызывает ошибку сегментирования. Исправим это.
Обработка сбоев
Чтобы иметь возможность незаметно выполнить полезную нагрузку руткита, нам нужно вернуть значение, которое вернула бы первоначально вызванная функция. У нас есть два способа решить эту проблему:
- наша функция malloc( ) должна реализовывать функциональность malloc( ) библиотеки libc по запросу пользователя. Это полностью избавит от необходимости использовать malloc( ) из libc. so ;
- libmalloc. so каким‑то образом должна иметь возможность вызывать malloc( ) из библиотеки libc и возвращать результаты вызывающей программе.
Каждый раз при вызове malloc( ) динамический компоновщик вызывает версию malloc( ) из libmalloc. so , поскольку это первое вхождение malloc( ) . Но мы хотим вызвать следующее вхождение malloc( ) — то, что находится в libc. so .
Так происходит потому, что динамический компоновщик внутри использует функцию dlsym( ) из / usr/ include/ dlfcn. h для поиска адреса, загруженного в память.
По умолчанию в качестве первого аргумента для dlsym( ) используется дескриптор RTLD_DEFAULT , который возвращает адрес первого вхождения символа. Однако есть еще один псевдоуказатель динамической библиотеки — RTLD_NEXT , который ищет следующее вхождение. Используя RTLD_NEXT , мы можем найти функцию malloc( ) библиотеки libc. so .
Отредактируем libmalloc. с . Комментарии объясняют, что происходит внутри программы: