Обходим проверку на наличие прав суперпользователя

Иногда возникает необходимость запустить исполняемый файл Linux, имеющий проверку на наличие прав суперпользователя, но без предоставления их ему. В данной статье мы подробно рассмотрим данную возможность.

Введение

В большинстве случаев, проверка на наличие прав суперпользователя выполняется при помощи вызова функции getuid() из состава glibc. Она возвращает ID пользователя, от имени которого выполняется вызывающий процесс.

Таким образом, необходимо и достаточно всего лишь проверить равенство возвращаемого этой функцией значения нулю (ID суперпользователя всегда 0).

Варианты обхода

Для того, чтобы обойти проверку, нам необходимо сделать так, чтобы функция getuid() всегда возвращала нуль вне зависимости от контекста выполнения процесса.

Так как мы не хотим ни модифицировать библиотеку glibc, ни перекомпилировать исходное приложение с отключением проверки (исходников может попросту не быть), ни создавать особую проксирующую библиотеку, воспользуемся особым механизмом LD_PRELOAD.

Механизм LD_PRELOAD

Механизм LD_PRELOAD позволяет загрузить указанные динамические библиотеки в процесс ещё до начала проверки и поиска импортируемых функций, что позволит переопределить лишь необходимые нам функции, не затрагивая все остальные.

Поддерживается передача списка библиотек-заглушек при помощи одноимённой переменной окружения среды, либо особого файла /etc/ld.so.preload.

Если их несколько, в качестве разделителя может использоваться символ двоеточия (:).

Приложение с проверкой прав root

Рассмотрим простое приложение example-root, имеющее проверку на наличие прав суперпользователя и лишь выводящее эту информацию в стандартный поток вывода:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main()
{
    if (getuid() != 0)
    {
        printf("%s", "This program must be run as root!\n");
        return EXIT_FAILURE;
    }
    else
    {
        printf("%s", "Root check bypassed.\n");
        return EXIT_SUCCESS;
    }
}

Библиотека-заглушка

Зная, что для проверки используется функция getuid(), создадим свою динамическую библиотеку libtricks с в точности таким же именем функции и прототипом, но всегда возвращающую нулевое значение:

#include <unistd.h>

uid_t getuid()
{
    return 0;
}

При необходимости такая заглушка может содержать любое количество функций, в т.ч. принадлежащим разным библиотекам.

Сборка демонстрационного проекта

Полные исходники приложения, библиотеки, а также файл сценария сборки CMake доступны в нашем специальном репозитории на GitHub.

Клонируем репозиторий:

git clone https://github.com/xvitaly/preload-tricks.git

Соберём проект при помощи CMake:

cd preload-tricks
mkdir build
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build

В результате в каталоге preload-tricks/build появятся следующие артефакты: библиотека libtricks.so, а также исполняемый файл example-root.

Запуск приложения штатным способом

Запустим приложение example-root стандартным способом:

cd preload-tricks/build
./example-root

В терминал будет выведено сообщение об ошибке This program must be run as root!

Запуск приложения с LD_PRELOAD

Теперь запустим его же, но с установленной переменной окружения LD_PRELOAD, указывающей на нашу библиотеку libtricks.so:

cd preload-tricks/build
LD_PRELOAD="./libtricks.so" ./example-root

Проверка будет пройдена, а в терминал выведено Root check bypassed. Задача выполнена.

Литература

При написании данной статьи использовалась литература из следующих источников: