Для подписчиков«Зорко одно лишь сердце. Самого главного глазами не увидишь». Эта фраза Антуана де Сент-Экзюпери как нельзя лучше описывает ситуацию, в которой оказывается администратор взломанной системы. Его глаза — ls, ps, netstat — показывают ему не реальность, а то, что им позволено. Чтобы найти правду, нужно смотреть глубже: в механизмы загрузки библиотек, GOT, PLT. В этой статье мы научимся видеть то, что скрыто от глаз.
«Зорко одно лишь сердце. Самого главного глазами не увидишь». Эта фраза Антуана де Сент‑Экзюпери как нельзя лучше описывает ситуацию, в которой оказывается администратор взломанной системы. Его глаза — ls, ps, netstat — показывают ему не реальность, а то, что им позволено. Чтобы найти правду, нужно смотреть глубже: в механизмы загрузки библиотек, GOT, PLT. В этой статье мы научимся видеть то, что скрыто от глаз.
Статья имеет ознакомительный характер и предназначена для специалистов по безопасности, проводящих тестирование в рамках контракта. Автор и редакция не несут ответственности за любой вред, причиненный с применением изложенной информации. Распространение вредоносных программ, нарушение работы систем и нарушение тайны переписки преследуются по закону.
Механизм релокаций определяет, как программа через PLT и GOT находит библиотечные функции и как ленивая привязка заполняет GOT реальными адресами из libc.so.6. Этот процесс можно наблюдать вживую через отладчик GDB.
Но что, если посмотреть на это с другой стороны: что, если в GOT окажется не адрес из libc, а адрес из нашей собственной библиотеки? Что, если мы хотим, чтобы стандартные утилиты делали не то, чего ожидает пользователь?
Именно так работает механизм LD_PRELOAD. В этой статье мы разберем технику работы userland-руткитов:
LD_PRELOAD заставляет компоновщик загружать нашу библиотеку вместо libc, и увидим это через GDB в реальном времени.dlsym для корректного перехвата.LD_PRELOAD не работает с setuid-программами, а также как атакующие обходят это через уязвимости (на примере CVE-2025-32463 в sudo)./etc/ld.so.preload.Механизм LD_PRELOAD появился в SunOS в конце 1980-х — начале 1990-х годов как инструмент для разработчиков, позволяющий тестировать новые версии библиотек без пересборки программ. Никто не предполагал, что этот механизм станет основой для целого класса вредоносного ПО!
Для воспроизведения всех экспериментов я подготовил единую лабораторию. Вот ее структура:
ld_preload/
├── Makefile
├── src/
│ ├── check_pass.c # Программа для перехвата strcmp
│ ├── empty_sleep.c # Программа для перехвата sleep
│ ├── hook_getuid.c # Перехватчик getuid
│ ├── hook_puts.c # Перехватчик puts
│ ├── hook_strcmp.c # Перехватчик strcmp
│ ├── hook.c # Перехватчик sleep
│ └── show_uid.c # Программа для перехвата getuid
├── bin/ # Скомпилированные программы
└── lib/ # Скомпилированные библиотеки-перехватчики
В директории src/ лежат исходники — тестовые программы и библиотеки‑перехватчики. После сборки исполняемые файлы попадают в bin/, а разделяемые библиотеки — в lib/. Исходный код ты найдешь в разделах, связанных с этими программами.
Makefile позволяет быстро все собрать одной командой:
CC = gcc
CFLAGS = -fPIC -shared
all: bin/empty_sleep bin/check_pass bin/show_uid lib/libhook.so lib/libhook_strcmp.so lib/libhook_getuid.so lib/libhook_puts.so
# Программа со sleep
bin/empty_sleep: src/empty_sleep.c | bin
$(CC) src/empty_sleep.c -o bin/empty_sleep -Wl,-z,lazy
# Программа с проверкой пароля
bin/check_pass: src/check_pass.c | bin
$(CC) src/check_pass.c -o bin/check_pass -Wl,-z,lazy
# Программа для проверки setuid
bin/show_uid: src/show_uid.c | bin
$(CC) src/show_uid.c -o bin/show_uid -Wl,-z,lazy
# Библиотеки-перехватчики
lib/libhook.so: src/hook.c | lib
$(CC) $(CFLAGS) src/hook.c -o lib/libhook.so
lib/libhook_strcmp.so: src/hook_strcmp.c | lib
$(CC) $(CFLAGS) src/hook_strcmp.c -o lib/libhook_strcmp.so -ldl
lib/libhook_puts.so: src/hook_puts.c | lib
$(CC) $(CFLAGS) src/hook_puts.c -o lib/libhook_puts.so
lib/libhook_getuid.so: src/hook_getuid.c | lib
$(CC) $(CFLAGS) src/hook_getuid.c -o lib/libhook_getuid.so
bin lib:
mkdir -p $@
clean:
rm -rf bin lib
.PHONY: all clean
Флаг -Wl,-z,lazy включает ленивую привязку — это позволяет наблюдать изменение GOT именно в момент первого вызова функции, а не при загрузке программы. Флаги -shared и -fPIC создают разделяемую библиотеку, которую динамический компоновщик сможет загрузить по любому адресу в памяти.
LD_PRELOAD — это переменная окружения, которую понимает динамический компоновщик (ld-linux.so). Библиотеки, указанные в ней, загружаются в адресное пространство процесса до всех остальных. Если в этой библиотеке есть функция с тем же именем, что и в любой загружаемой библиотеке (например, libc, libssl, libcurl), компоновщик при разрешении символов запишет в GOT адрес нашей функции, а не оригинальной. На этом и строится перехват.
В этой статье мы сосредоточимся на перехвате функций из libc — это самый распространенный случай, так как большинство программ используют стандартную библиотеку C для работы с файлами, сетью и процессами.
Готовим программу и перехватчикПодопытная программа src/empty_sleep.c предельно проста — она вызывает sleep(30) и завершается:
#include <unistd.h>
int main() {
sleep(30);
return 0;
}
Библиотека‑перехватчик src/hook.c определяет функцию с таким же именем и сигнатурой, как у оригинального sleep из libc:
#include <stdio.h>
#include <unistd.h>
unsigned int sleep(unsigned int seconds) {
printf("[HOOK] sleep(%u) перехвачен! Оригинал не вызывается.\n", seconds);
return 0;
}
Сигнатура unsigned int sleep(unsigned int seconds) должна точно совпадать с той, что объявлена в <unistd.h>. Если сигнатуры разойдутся, компоновщик не свяжет нашу функцию с вызовом sleep в программе и перехвата не произойдет.
Обе программы мы уже собрали через make на предыдущем шаге; empty_sleep лежит в bin/, libhook.so — в lib/.
Запускаем отладчик GDB и прямо в нем устанавливаем переменную окружения LD_PRELOAD, чтобы она действовала только на время отладки:
gdb ./empty_sleep
(gdb) set environment LD_PRELOAD=../lib/libhook.so
Дальше наша задача — зафиксировать три состояния GOT для функции sleep:
sleep был вызван.sleep.Ставим точку останова на _start. Это самая ранняя точка, куда может попасть отладчик. На самом деле _start находится не в нашей программе, а в динамическом компоновщике ld-linux-x86-64.so.2 — он загружается первым:
(gdb) break _start
(gdb) run
Breakpoint 1, 0x00007ffff7fdfa80 in _start () from /lib64/ld-linux-x86-64.so.2
| # | Наименование новости | Тональность | Информативность | Дата публикации |
|---|---|---|---|---|
| 1 | Идеальная отмычка. Российский разработчик показал 120 способов обойти защиту Linux | 0 | 15.88 | 22-07-2026 |
| 2 | 440 уязвимостей за 48 часов: в Linux случился рекордный всплеск CVE | 0 | 18.17 | 22-07-2026 |
| 3 | Многофункциональный троян под Linux атакует разработчиков. Во всем мире его видят только четыре антивируса | 0 | 7.9 | 17-08-2026 |
| 4 | Многофункциональный троян под Linux атакует разработчиков. Во всем мире его видят только четыре антивируса | 0 | 7.9 | 17-08-2026 |
| 5 | Создание загрузочного атомарно обновляемого образа Oracle Linux при помощи OSTree | 0 | 2.5 | 26-10-2025 |
| 6 | Идеальный взлом. Ошибка RefluXFS в Linux отдаёт права root за пару секунд, не оставляя следов | -1 | 10 | 23-07-2026 |
| 7 | Уязвимость SCTPhantom существовала в коде Linux 18 лет | 0 | 15.03 | 12-08-2026 |
| 8 | ПОДПОЛЬЕ ДУХА 👤. CRUCIFIXIO VERBI 🩸. न च सुखात् सुखं ... | 0 | 6 | 19-07-2026 |
| 9 | 🤒 (((((Видите👇👇👇 там👇👇 там👇 вы все доединого ходящие доединого во ... | 0 | 3.5 | 28-07-2026 |