Релокации в Linux: PLT/GOT за 60 секунд

Релокации в Linux: PLT/GOT за 60 секунд

Когда программа вызывает библиотечную функцию (например, sleep) в Linux, программа понятия не имеет, где эта функция лежит в памяти. ASLR каждый раз меняет адрес libc. Как она её находит?

Здесь работают два механизма в связке: PIE (Position Independent Executable) и ASLR (Address Space Layout Randomization).

PIE – это свойство исполняемого файла, которое позволяет ему загружаться по любому адресу в памяти. Без PIE программа была бы «привязана» к фиксированному адресу и ASLR не работал бы для кода самой программы (только для библиотек). PIE делает возможным применение ASLR ко всей программе целиком.

ASLR – это механизм операционной системы, который случайным образом выбирает адрес загрузки программы и библиотек при каждом запуске. Благодаря PIE программа может загружаться по этому случайному адресу.

Но даже зная, что программа загружена по случайному адресу, как она вызывает функции из динамических библиотек (например, printf, sleep или puts), адрес которых тоже неизвестен?

Ответ – релокации, PLT, GOT и ленивая привязка. В этой шпаргалке разложил всё по полочкам:

  • Куда на самом деле ведёт call.
  • Как работает GOT и PLT.
  • В чём разница между первым и вторым вызовом.
  • Команды для GDB и objdump.
  • Ленивая привязка против BIND_NOW.

Карточки

1. Что узнаете из заметки

Как программа в Linux находит функцию, если ее адрес неизвестен до запуска?

Релокации в Linux: PLT/GOT за 60 секунд
  • PLT (Procedure Linkage Table) – таблица заглушек для функций из динамических библиотек.
  • GOT (Global Offset Table) – таблица, куда динамический компоновщик позже запишет реальные адреса.

Куда на самом деле ведёт call sleep@plt и как работают релокации в Linux – наша цель в этой заметке.

2. Зачем это знать?

До знакомства с темой:

  • Непонятно, почему отладчик GDB прыгает в PLT, а не в динамическую библиотеку (например, libc).
  • Непонятно, почему stepi ведёт в странный код.
  • Не вижу реальный адрес библиотечной функции (например, sleep).

После прочтения заметки:

  • Полный контроль отладки.
  • Узнаете, где смотреть адреса.
  • Увидите ленивое связывание в действии.
  • Почувствуете себя настоящим хакером.

3. Пример простой программы

Разберемся с релокациями на примере простой программы, которая вызывает функцию sleep() (из стандартной динамической библиотеки libc) и завершается:

#include <unistd.h> int main() { sleep(30); return 0; }

Так выглядит дизассемблированное представление функции main нашей программы:

1149 <main>: 1149: endbr64 114d: push rbp 114e: mov rbp,rsp 1151: mov edi,0x1e 1156: call 1050 <sleep@plt> 115b: mov eax,0x0 1160: pop rbp

Компилятор не знает адрес sleep(), поэтому вставляет заглушку:

1156: call 1050 <sleep@plt>

4. Первый вызов функции sleep

Когда main вызывает sleep в первый адрес, в GOT ещё нет настоящего адреса. Магия первого вызова функции:

Релокации в Linux: PLT/GOT за 60 секунд

5. Повторный вызов библиотечной функции

При повторном вызове функции GOT уже содержит настоящий адрес из внешней библиотеки:

Релокации в Linux: PLT/GOT за 60 секунд

6. Полезные команды

Небольшая шпаргалка для GDB, objdump и readelf:

# Найти GOT для функции objdump -R ./binary | grep sleep # Посмотреть PLT-заглушки objdump -d ./binary | grep '@plt' # Таблица релокаций readelf -r ./binary # В GDB: посмотреть адрес в GOT x/gx 0x555555558000

7. Ленивая привязка и BIND_NOW

Особенности ленивой привязки:

  • Релокация при первом вызове.
  • Более быстрый старт программы.
  • Первый вызов функции медленнее.

Особенности разрешения всех релокаций при загрузке программы (BIND_NOW):

  • Все релокации разрешены при загрузке программы.
  • Медленнее запуск программы.
  • Все вызовы функций одинаково быстрые.

8. Итоги

Теперь вы знаете:

  • Куда на самом деле ведёт call sleep@plt.
  • Как работает GOT и PLT.
  • В чём разница между первым и вторым вызовом библиотечной функции.
  • Какие команды использовать.

Нужно больше подробностей, подробных примеров, живой отладки в GDB? Читайте статью в журнале Хакер: «Релокации. Разбираемся, как Linux-программа находит функцию во время выполнения».

Сохраняй, чтобы не потерять! Подписывайся в ВК, Telegram – там еще больше статей и карточек.

Добро пожаловать в кузницу!

1