Код, независимый от позиции

Последнее обновление: 30.01.2024

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

Но это означает, что библиотеки и приложение должны быть написаны специально, чтобы загрузка их в память не приводила к ошибкам. Данный тип кода приложения известен как позиционно-независимый код или PIC (position-independent code).

Теперь рассмотрим, как сделать наш код независимым от позиции. Есть три основные области, где код необходимо изменить, чтобы код был независимым от позиции:

  • Ссылки на внешние функции

  • Ссылки на раздел .data библиотеки

  • Ссылки на внешние объекты

Ссылки на внешние функции

Ссылки на внешние функции обрабатываются компоновщиком/загрузчиком автоматически с помощью таблиц PLT/GOT. Так, вызов call print заменяется на вызов call print wrt ..plt, который обращается по адресу функции printf, указанному в таблице GOT.

call print wrt ..plt

wrt является сокращением от "with regard to" (cс учетом PLT)

Ссылки на секцию .data

Ссылки на адреса в разделе .data библиотеки обрабатываются в режиме адресации, известном как PC-relative addressing или адресация относительно регистра PC (Program Counter) он же регистр rip (Instruction Pointer). Этот режим адресации записывает адреса данных как смещение относительно указателя текущей команды.

Например, пусть у нас есть некоторые данные в секции .data:

section .data 
message: db "Hello World!",10, 0
count: db $ - message

Для загрузки этих данных в коде применяется адресация относительно rip. И если глобальные переменные применяются в текущем файле, то для обращения к ним применяется оператор rel. Например, получим значение переменной count

mov rdi, [rel count]

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

Если нам нужен адрес переменной, можно использовать инструкцию lea:

lea rsi, [rel message]

То есть опять же мы получаем адрес переменной message относительно регистра rip.

Ссылки на внешние объекты

Если глобальные переменные определены в одном файле (например, приложении), а используются в другом (например в библиотеке или наоборот), то применяется адресация относительно таблицы GOT с помощью выражения:

rel переменная wrt ..got

Например:

mov rsi, [rel message wrt ..got]
mov rdx, [rel count wrt ..got]  

Таким образом, мы получаем адрес обоих переменных.

Независимые от позиции приложения

Теперь определим независимое от позиции приложение (Position Independent Executable) или PIE (которое используют независимый от позиции код (PIC)). Пусть у нас будет следующий файл print.asm, который определяет функцию print для вывода строки на консоль:

global print:function

; Функция print выводит текст на консоль
; Параметры
; RDI - количество символов
; RSI - ссылка на строку
section .text
print:
    mov rdx, rdi
    mov rdi, 1
    mov rax, 1
    syscall
    ret

Эта функция принимает через регистр RSI ссылку на строку и через регистр RDI количество символов строки и с помощью системного вызова номер 1 (системный вызов write) выводит строку на консоль.

Скомпилируем этот файл в динамическую библиотеку print.so

nasm -felf64 print.asm -o print.o
ld -shared print.o -o print.so

И пусть в файле app.asm расположен основной код программы, который использует функцию print:

global _start      
extern print
 
section .data 
message: db "Hello World!",10, 0
count: db $ - message

section .text
_start:
    mov rdi, [rel count]
    lea rsi, [rel message]
    call print wrt ..plt

    mov rax, 60    
    syscall 

Здесь вызывается функция print, в которую передается строка и ее размер из секции .data. Так, в качестве второго параметра в регистр rsi загружается адрес переменной message. И поскольку идет обращение к данным в секции .data, применяется адрес относительно регистра rip:

lea rsi, [rel message]

А для вызова функции print применяется адресация с помощью PLT:

call print wrt ..plt

Для компоновки приложения, независимое от позиции, применяется флаг -pie. Так, скомпилируем объектный файл app.o и файл исполняемого приложения:

nasm -felf64 app.asm -o app.o
ld --dynamic-linker=/lib64/ld-linux-x86-64.so.2 -pie app.o print.so -o app

После этого мы можем запускать исполняемый файл app.

Полный вывод:

root@Eugene:~/asm# nasm -felf64 print.asm -o print.o
root@Eugene:~/asm# ld -shared print.o -o print.so
root@Eugene:~/asm# nasm -felf64 app.asm -o app.o
root@Eugene:~/asm# ld --dynamic-linker=/lib64/ld-linux-x86-64.so.2 -pie app.o print.so -o app
root@Eugene:~/asm# export LD_LIBRARY_PATH=.
root@Eugene:~/asm# ./app
Hello World!
root@Eugene:~/asm# 

Мы также можем проверить заголовок исполняемого файла с помощью команды readelf -h

root@Eugene:~/asm# readelf -h app
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              DYN (Position-Independent Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x1020
  Start of program headers:          64 (bytes into file)
  Start of section headers:          12800 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         8
  Size of section headers:           64 (bytes)
  Number of section headers:         16
  Section header string table index: 15
root@Eugene:~/asm# 

В поле Type мы можем увидеть соответствующую запись о типе файл - "Position-Independent Executable file"

Независимые от позиции библиотеки

Все, что касается приложения, относится и к библиотекам, где применяются такие же способы обращения к функциям и глобальным переменным, как и в исполняемом приложении. Например, изменим код библиотеки print следующим образом:

global print:function

extern message  ; импортируем строку для вывода
extern count    ; импортируем размер строки

; Функция print выводит текст на консоль
section .text
print:
    mov rsi, [rel message wrt ..got]    ; загружаем адрес message из GOT
    mov rdx, [rel count wrt ..got]      ; загружаем адрес count из GOT
    mov rdx, [rdx]                      ; загружаем значение count по вышезагруженному адресу
    mov rdi, 1
    mov rax, 1
    syscall
    ret

Здесь библиотека ожидает получить извне строку для вывода на консоль и ее размер в виде глобальных объектов message и count. Для обращения к этим объектам применяется адресация относительно таблицы GOT. Например, получим адрес переменной count:

mov rdx, [rel count wrt ..got]

В GOT хранятся адреса переменных, то есть, чтобы получить значение count, нам надо снова обратиться по полученному адресу:

mov rdx, [rdx]

В коде исполняемого приложения в app.asm определим соответствующие глобальные переменные:

global _start   

global message:data
global count:data
  
extern print
 
section .data 
count: dq message.end - message
message: db "Hello METANIT.COM!",10, 0
.end:

section .text
_start:
    call print wrt ..plt
    mov rax, 60    
    syscall 

Стоит отметить, что для обоих переменных устанавливается тип data:

global message:data
global count:data

Аналогичным образом, как в предыдущем примере, скомпилируем библиотеку и приложение и запустим программу на выполнение:

root@Eugene:~/asm# nasm -felf64 app.asm -o app.o
root@Eugene:~/asm# nasm -felf64 print.asm -o print.o
root@Eugene:~/asm# ld -shared print.o -o print.so
root@Eugene:~/asm# ld --dynamic-linker=/lib64/ld-linux-x86-64.so.2 -pie app.o print.so -o app
root@Eugene:~/asm# ./app
Hello METANIT.COM!
root@Eugene:~/asm#
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850