Анатомия эльфов 2. Разбираем устройство ELF-файлов в подробностях - «Новости» » Самоучитель CSS
Меню
Наши новости
Учебник CSS

Невозможно отучить людей изучать самые ненужные предметы.

Введение в CSS
Преимущества стилей
Добавления стилей
Типы носителей
Базовый синтаксис
Значения стилевых свойств
Селекторы тегов
Классы
CSS3

Надо знать обо всем понемножку, но все о немногом.

Идентификаторы
Контекстные селекторы
Соседние селекторы
Дочерние селекторы
Селекторы атрибутов
Универсальный селектор
Псевдоклассы
Псевдоэлементы

Кто умеет, тот делает. Кто не умеет, тот учит. Кто не умеет учить - становится деканом. (Т. Мартин)

Группирование
Наследование
Каскадирование
Валидация
Идентификаторы и классы
Написание эффективного кода

Самоучитель CSS

Вёрстка
Изображения
Текст
Цвет
Линии и рамки
Углы
Списки
Ссылки
Дизайны сайтов
Формы
Таблицы
CSS3
HTML5

Новости

Блог для вебмастеров
Новости мира Интернет
Сайтостроение
Ремонт и советы
Все новости

Справочник CSS

Справочник от А до Я
HTML, CSS, JavaScript

Афоризмы

Афоризмы о учёбе
Статьи об афоризмах
Все Афоризмы

Видео Уроки


Наш опрос



Наши новости

       
4-09-2022, 00:02
Анатомия эльфов 2. Разбираем устройство ELF-файлов в подробностях - «Новости»
Рейтинг:
Категория: Новости

Ана­томия эль­фов. Раз­бира­емся с внут­ренним устрой­ством ELF-фай­лов», в которой мы начали изу­чать сек­реты фор­мата исполня­емых ELF-фай­лов. В ней мы опре­дели­лись с инс­тру­мен­тари­ем ана­лиза, соз­дали нес­коль­ко подопыт­ных экзем­пля­ров ELF-фай­лов, разоб­рались с фор­матом заголов­ка ELF-фай­ла, узна­ли про таб­лицы заголов­ков сек­ций и сег­ментов, а так­же заг­лянули внутрь некото­рых сек­ций и сег­ментов.
 

Виды связывания


Ос­новная проб­лема, воз­ника­ющая при ком­понов­ке исполня­емо­го фай­ла, — опре­деле­ние адре­сов вызыва­емых в прог­рамме фун­кций, рас­положен­ных во внеш­них биб­лиоте­ках. Если для фун­кций, которые опре­деле­ны в самом исполня­емом фай­ле, такой проб­лемы не наб­люда­ется (адре­са этих фун­кций опре­деля­ются уже на эта­пе ком­пиляции), то внеш­ние биб­лиоте­ки могут находить­ся в памяти по боль­шому сче­ту где угод­но. Это получа­ется бла­года­ря воз­можнос­ти фор­мировать позици­онно незави­симый код. С ходу, на эта­пе ком­пиляции, опре­делить адрес той или иной фун­кции, содер­жащей­ся в такой биб­лиоте­ке, невоз­можно. Это мож­но сде­лать либо ста­тичес­ки (вклю­чив нуж­ные биб­лиоте­ки непос­редс­твен­но в ELF-файл), либо динами­чес­ки (во вре­мя заг­рузки или выпол­нения прог­раммы).


Ис­ходя из это­го, мож­но выделить три вида свя­зыва­ния исполня­емо­го ELF-фай­ла с биб­лиоте­ками:



  • ста­тичес­кое свя­зыва­ние;

  • ди­нами­чес­кое свя­зыва­ние во вре­мя заг­рузки фай­ла;

  • ди­нами­чес­кое свя­зыва­ние во вре­мя исполне­ния фай­ла.


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


Для ста­тичес­ки лин­куемых биб­лиотек исполь­зует­ся рас­ширение .a (в Windows это фай­лы с рас­ширени­ем .lib), для динами­чес­ких — рас­ширение .so (в Windows это фай­лы .dll).


 

Статическое связывание


Здесь все дос­таточ­но прос­то. Внеш­няя биб­лиоте­ка лин­кует­ся с исполня­емым фай­лом, обра­зуя с ним еди­ное целое. Если обра­тить­ся к при­меру из пре­дыду­щей статьи (файл с хел­ловор­лдом example.c), то для того, что­бы сде­лать из него прог­рамму, ста­тичес­ки свя­зан­ную с биб­лиоте­кой glibc, нуж­но наб­рать в кон­соли сле­дующее:


gcc -oexample_static_linked -staticexample.c

В ито­ге получим исполня­емый файл со ста­тичес­ки при­лин­кован­ной к нему биб­лиоте­кой glibc. Если ты обра­тишь вни­мание на раз­мер получен­ного фай­ла, то уви­дишь, что он сущес­твен­но боль­ше, чем раз­мер фай­лов example_pie и example_no_pie, которые были ском­пилиро­ваны методом динами­чес­кой лин­ковки с биб­лиоте­кой glibc. У меня, нап­ример, получи­лось целых 872 Кбайт для ста­тичес­кой лин­ковки, в то вре­мя как динами­чес­кая дала все­го 17.


Собс­твен­но говоря, это и есть основной недос­таток ста­тичес­кого свя­зыва­ния. Нес­мотря на то что из glibc мы исполь­зуем все­го одну фун­кцию puts(), при ста­тичес­ком свя­зыва­нии при­ходит­ся тащить в исполня­емый файл еще мно­го чего ненуж­ного. Так­же мож­но отме­тить еще один, не сов­сем явный недос­таток ста­тичес­кой лин­ковки: если появ­ляет­ся новая вер­сия биб­лиоте­ки (в которой, нап­ример, устра­нена та или иная уяз­вимость), то нам при­дет­ся переком­пилиро­вать прог­рамму уже с новой вер­сией нуж­ной нам биб­лиоте­ки. Если это­го не делать, то наша прог­рамма будет поль­зовать­ся фун­кци­ями, в которых уяз­вимость не устра­нена.


Пос­мотреть тип свя­зыва­ния в исполня­емом фай­ле мож­но, при­менив ути­литу file или ldd.


Анатомия эльфов 2. Разбираем устройство ELF-файлов в подробностях - «Новости»
Оп­ределя­ем тип свя­зыва­ния в ELF-фай­ле (в дан­ном слу­чае видим, что при­мене­на ста­тичес­кая лин­ковка)

Об­рати вни­мание, что для при­мера example_static из пре­дыду­щей статьи ути­лита file покажет динами­чес­кую лин­ковку. Все дело в том, что в этом слу­чае мы ста­тичес­ки лин­ковали с прог­раммой нашу самопис­ную биб­лиоте­ку lib_static_example.a, в которой содер­жится фун­кция hello_world_function(). Одна­ко в этой фун­кции исполь­зует­ся фун­кция puts(), которая берет­ся из биб­лиоте­ки glibc, свя­зан­ной с lib_static_example.a уже динами­чес­ки.


 

Динамическое связывание во время загрузки файла


Как мы уже говори­ли, бла­года­ря позици­онно незави­симо­му коду динами­чес­кие (или раз­деля­емые) биб­лиоте­ки могут быть заг­ружены в память один раз, а все нуж­дающиеся в этой биб­лиоте­ке прог­раммы ста­нут поль­зовать­ся этой раз­деля­емой копи­ей биб­лиоте­ки. На эта­пе ком­понов­ки исполня­емо­го фай­ла адре­са, по которым будут заг­ружены динами­чес­кие биб­лиоте­ки, неиз­вес­тны, и поэто­му адре­са содер­жащих­ся в них фун­кций опре­делить невоз­можно.


В этом слу­чае динами­чес­кий ком­понов­щик (если пом­нишь, то путь к нему лежит в сек­ции .interp) опре­деля­ет адре­са нуж­ных фун­кций и перемен­ных в ходе заг­рузки прог­раммы в память. Если говорить точ­нее, во мно­гих слу­чаях даже не в ходе заг­рузки, а во вре­мя пер­вого обра­щения к фун­кции, но об этом более под­робно погово­рим чуть ниже.


 

Динамическое связывание во время исполнения файла


Раз­деля­емые биб­лиоте­ки могут быть заг­ружены в память и во вре­мя выпол­нения прог­раммы. В этом слу­чае при­ложе­ние обра­щает­ся к динами­чес­кому лин­ковщи­ку с прось­бой заг­рузить и при­лин­ковать динами­чес­кую биб­лиоте­ку. В Linux для это­го пре­дус­мотре­ны сис­темные фун­кции dlopen(), dlsym() и dlclose(), пер­вая заг­ружа­ет раз­деля­емую биб­лиоте­ку, вто­рая ищет в ней нуж­ную фун­кцию, третья зак­рыва­ет ее файл.


Ес­ли покопать­ся во внут­реннос­тях Windows, там мож­но обна­ружить ана­логич­ные API-фун­кции: LoadLibrary() и GetProcAddress() (либо LdrLoadDll() и LdrGetProcAddress()).


Этот вид свя­зыва­ния (как в Linux, так и в Windows) исполь­зует­ся дос­таточ­но ред­ко, во мно­гих слу­чаях его при­меня­ют для того, что­бы скрыть от иссле­дова­телей истинную фун­кци­ональ­ность прог­раммы.


 

Продолжаем разбираться с секционным представлением ELF-файла


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


 

Секция .shstrtab


Эта сек­ция пред­став­ляет собой мас­сив строк, закан­чива­ющих­ся нулем, с име­нами всех сек­ций ELF-фай­ла. Ука­зан­ная таб­лица поз­воля­ет раз­личным ути­литам (нап­ример, таким, как readelf) находить име­на сек­ций. Для прос­мотра этой сек­ции в сим­воль­ном или шес­тнад­цатерич­ном виде мож­но исполь­зовать опции -p или -x ути­литы readelf соот­ветс­твен­но. Нап­ример, вот так:


readelf -x.shstrtab example_pie
Сек­ция .shstrtab в шес­тнад­цатерич­ном пред­став­лении 

Символьные секции


Как мож­но догадать­ся по наз­ванию, сим­воль­ные сек­ции хра­нят какие‑то сим­волы. В нашем слу­чае под сим­волами понима­ются име­на фун­кций и перемен­ных. Эти име­на исполь­зуют­ся в качес­тве сим­воль­ных имен для пред­став­ления опре­делен­ного мес­тополо­жения в фай­ле или в памяти. Все это вмес­те и обра­зует то, что мы называ­ем сим­волами фун­кций и дан­ных. (Да, мы при­вык­ли счи­тать, что сим­вол, как пра­вило, занима­ет одно зна­комес­то в виде бук­вы, циф­ры или зна­ка пре­пина­ния, одна­ко здесь это не так.)


Что­бы пос­мотреть информа­цию о сим­волах, мож­но вос­поль­зовать­ся уже зна­комой нам ути­литой readelf и наб­рать в кон­соли что‑нибудь вро­де это­го:


readelf -s -Wexample_pie

На выходе уви­дим содер­жимое двух сек­ций .symtab и .dynsym.


Вы­вод сим­воль­ной информа­ции из ELF-фай­ла
Секция .symtab

Для начала необ­ходимо отме­тить, что наличие этой сек­ции в ELF-фай­ле необя­затель­но. Более того, в боль­шинс­тве встре­чающих­ся в дикой при­роде фай­лов она отсутс­тву­ет. Основное ее наз­начение — помощь при отладке прог­раммы, в то вре­мя как для исполне­ния фай­ла она не тре­бует­ся. По умол­чанию эта сек­ция соз­дает­ся во вре­мя ком­пиляции прог­раммы, одна­ко ее мож­но уда­лить с помощью коман­ды strip, нап­ример так:


strip example_pie

Те­перь, если попытать­ся пос­мотреть сим­воль­ную информа­цию в этом фай­ле, будет выведе­но толь­ко содер­жимое сек­ции .dynsym.


Вы­вод сим­воль­ной информа­ции из ELF-фай­ла, на который воз­дей­ство­вали ути­литой strip

Все же, хоть эта сек­ция и необя­затель­на в фай­ле, оста­новим­ся на ней чуть под­робнее.


Каж­дая запись этой сек­ции пред­став­ляет собой струк­туру вида Elf32_Sym или Elf64_Sym. Внут­реннее устрой­ство этой струк­туры (как, впро­чем, и содер­жимое всех осталь­ных струк­тур и зна­чений кон­стант ELF-фай­лов) мож­но пос­мотреть в фай­ле /usr/include/elf.h.


В ука­зан­ной сек­ции содер­жатся все сим­волы, которые ком­понов­щик исполь­зует как во вре­мя ком­пиляции, так и во вре­мя выпол­нения при­ложе­ния. В нашем при­мере example_pie сре­ди все­го, что содер­жится в дан­ной сек­ции, мож­но уви­деть сим­воль­ное имя зна­комой нам фун­кции main(), которая при­сутс­тву­ет в любой прог­рамме, а так­же сим­воль­ное имя фун­кции puts().


Фун­кции main() и puts() в сек­ции .symtab

Фун­кции main() соот­ветс­тву­ет адрес 0x1149, и имен­но с это­го адре­са фун­кция будет начинать­ся пос­ле заг­рузки фай­ла в память перед выпол­нени­ем. Так­же вид­но, что раз­мер фун­кции main() сос­тавля­ет 27 байт, ее тип — FUNC (то есть фун­кция), а сама фун­кция раз­меща­ется в сек­ции с номером 16 (это сек­ция .text, в которой находит­ся непос­редс­твен­но исполня­емый код прог­раммы).


С фун­кци­ей puts() такой опре­делен­ности не отме­чает­ся: нет ни адре­са, ни сек­ции. Так про­исхо­дит потому, что фун­кция puts() находит­ся в биб­лиоте­ке glibc и, как мы уже говори­ли, адрес этой фун­кции на эта­пе ком­пиляции опре­делить нель­зя.


Со­дер­жимое сек­ции .symtab так­же мож­но пос­мотреть и с помощью коман­ды nm. Что­бы узнать под­робнос­ти исполь­зования этой коман­ды, набери в тер­минале


nm man

Ес­ли мы поп­робу­ем пос­мотреть содер­жимое сек­ции .symtab ELF-фай­ла, в котором при­мене­но ста­тичес­кое свя­зыва­ние, то уви­дим, что, помимо уже зна­комых нам сим­волов фун­кций main() и puts(), в сек­ции при­сутс­тву­ет боль­шое количес­тво сим­волов дру­гих фун­кций, вхо­дящих в сос­тав биб­лиоте­ки glibc.


Фун­кции main() и puts() в сек­ции .symtab в ELF-фай­ле со ста­тичес­кой ком­понов­кой

На рисун­ке мы видим, что у фун­кции main() приз­нак свя­зыва­ния (который содер­жится в поле st_info струк­туры Elf32_Sym или Elf64_Sym) име­ет зна­чение STB_GLOBAL, а фун­кция puts() — зна­чение STB_WEAK. Зна­чение приз­нака свя­зыва­ния сим­вола, рав­ное STB_WEAK, говорит о том, что дан­ный сим­вол име­ет самый низ­кий при­ори­тет при свя­зыва­нии (так называ­емый сла­бый сим­вол). Сим­волы с дру­гими приз­наками свя­зыва­ния, нап­ример STB_LOCAL или STB_GLOBAL, име­ют более высокий при­ори­тет (их называ­ют силь­ными сим­волами).


При свя­зыва­нии нес­коль­ких биб­лиотек в ходе ком­понов­ки одно­го ELF-фай­ла в нес­коль­ких биб­лиоте­ках может ока­зать­ся опре­деле­на фун­кция с оди­нако­вым име­нем (то есть сим­волы этой фун­кции в двух или более биб­лиоте­ках будут сов­падать). В этом слу­чае ком­понов­щик дол­жен выб­рать одну из этих фун­кций. Ког­да есть одна фун­кция с силь­ными сим­волами и одна или нес­коль­ко фун­кций со сла­быми сим­волами, будет выб­рана фун­кция с силь­ными сим­волами. Если име­ется нес­коль­ко оди­нако­вых фун­кций со сла­быми сим­волами, ком­понов­щик выберет слу­чай­ным обра­зом. Если же в ходе ком­понов­ки обна­ружит­ся две или более оди­нако­вые фун­кции с силь­ными сим­волами, то ком­понов­ка прер­вется и будет кон­ста­тиро­вана ошиб­ка.


В дан­ном слу­чае в биб­лиоте­ке glibc сим­волы мно­гих стан­дар­тных фун­кций опре­деле­ны с низ­ким при­ори­тетом (сла­бые сим­волы). Это дела­ется для того, что­бы дать воз­можность прог­раммис­там написать собс­твен­ную биб­лиоте­ку с пере­опре­деле­нием некото­рых стан­дар­тных фун­кций (и, соот­ветс­твен­но, с опре­деле­нием сим­волов этих пере­опре­делен­ных фун­кций как силь­ных). Затем они смо­гут исполь­зовать вмес­те и биб­лиоте­ку glibc, и свою биб­лиоте­ку с пере­опре­делен­ными фун­кци­ями. При этом, пос­коль­ку у пере­опре­делен­ных фун­кций сим­волы име­ют более высокий при­ори­тет, будут вызывать­ся имен­но они, а не те, которые опре­деле­ны в glibc. Более под­робно про силь­ные и сла­бые сим­волы мож­но почитать в докумен­тации.


Секция .dynsym

За­писи в дан­ной сек­ции име­ют такую же струк­туру, что и в сек­ции .symtab. Глав­ное отли­чие в том, что в этой сек­ции содер­жатся толь­ко сим­волы фун­кций или перемен­ных, которые необ­ходимы для динами­чес­кой ком­понов­ки. На эту сек­цию коман­да strip никако­го вли­яния не ока­зыва­ет (что, в общем‑то, понят­но). Сек­ция .dynsym име­ется в тех фай­лах, где исполь­зует­ся динами­чес­кое свя­зыва­ние во вре­мя заг­рузки ELF-фай­ла. Соот­ветс­твен­но, если попытать­ся пос­мотреть наличие этой сек­ции в фай­ле со ста­тичес­кой лин­ковкой, то мы ее там не уви­дим.


Ес­ли вни­матель­но изу­чить сек­ции .symtab и .dynsym, мож­но заметить, что в сек­цию .dynsym из сек­ции .symtab переко­чева­ли сим­волы c нулевы­ми адре­сами и неоп­ределен­ными номера­ми сек­ций. Зна­чения этих адре­сов и номеров сек­ций как раз и будут опре­деле­ны во вре­мя заг­рузки прог­раммы.


Сек­ция .symtab име­ет тип SHT_SYMTAB, а сек­ция .dynsym — тип SHT_DYNSYM. Собс­твен­но, дан­ный факт и поз­воля­ет ути­лите strip разоб­рать­ся, что мож­но зачис­тить в ELF-фай­ле, а что нель­зя.


Секции .strtab и .dynstr

Ука­зан­ные сек­ции содер­жат непос­редс­твен­но стро­ковые зна­чения сим­волов, на которые ука­зыва­ет зна­чение st_name из струк­туры Elf32_Sym или Elf64_Sym. Они, как было показа­но выше, явля­ются эле­мен­тами сек­ций .symtab или .dynsym. То есть в сек­циях .symtab и .dynsym непос­редс­твен­но самих стро­ковых зна­чений сим­волов не содер­жится, а при­сутс­тву­ет толь­ко индекс, по которо­му и находит­ся нуж­ное стро­ковое зна­чение сим­вола в сек­циях .strtab или .dynstr (этот индекс как раз и лежит в поле st_name струк­туры Elf32_Sym или Elf64_Sym).


Пос­мотреть содер­жимое этих сек­ций, так же как и для сек­ции .shstrtab, мож­но с исполь­зовани­ем опций -x или -p ути­литы readelf.


Вы­вод содер­жимого сек­ций .dynstr и .strtab из ELF-фай­ла в шес­тнад­цатерич­ном и стро­ковом виде

Бо­лее под­робно про сим­воль­ные сек­ции ELF-фай­лов мож­но почитать в докумен­тации Oracle.


 

Динамическое связывание и секции .plt и .got


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


Для чего это нуж­но? Поз­днее свя­зыва­ние поз­воля­ет не тра­тить без необ­ходимос­ти вре­мя на раз­решение адре­сов при запус­ке прог­раммы. Для ее фун­кци­они­рова­ния может пот­ребовать­ся мно­го фун­кций из раз­деля­емых биб­лиотек, и опре­делять их адре­са имен­но тог­да, ког­да это дей­стви­тель­но необ­ходимо, — впол­не раци­ональ­ное решение. В опе­раци­онных сис­темах семей­ства Linux режим поз­дне­го свя­зыва­ния реали­зует­ся динами­чес­ким ком­понов­щиком по умол­чанию. Мож­но зас­тавить динами­чес­кий ком­понов­щик про­изво­дить раз­решение адре­сов фун­кций из раз­деля­емых биб­лиотек непос­редс­твен­но во вре­мя заг­рузки прог­раммы, задав перемен­ную сре­ды LD_BIND_NOW:


export LD_BIND_NOW=1

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


Для начала рас­смот­рим базовый прин­цип поз­дне­го свя­зыва­ния в ELF-фай­лах, который был реали­зован изна­чаль­но. Пос­ле чего погово­рим о том, какие изме­нения были вне­сены в этот базовый прин­цип, ког­да появи­лись новые тех­нологии защиты прог­рамм от атак.


Для боль­шей наг­ляднос­ти нем­ного изме­ним наш «хел­ловорлд» и добавим в него еще одну фун­кцию (нап­ример, exit()):



#include <stdlib.h>
void main(int argc, char* argv[])
{printf("Hello worldn");exit(0);
}

Что­бы получить ELF-файл с базовым прин­ципом поз­дне­го свя­зыва­ния, откомпи­лиру­ем дан­ный при­мер c исполь­зовани­ем опции -fcf-protection=none. Эта опция показы­вает ком­пилято­ру, что нуж­но соб­рать прог­рамму без исполь­зования защит­ной тех­нологии IBT (indirect branch tracking):


gcc -oexample_exit_notrack -fcf-protection=none example_exit.c

Об опци­ях gcc мож­но почитать в докумен­тации.


Итак, в базовом вари­анте в ELF-фай­лах поз­днее свя­зыва­ние реали­зует­ся с помощью двух спе­циаль­ных сек­ций:




  • .plt — таб­лица свя­зей и про­цедур (Procedure Linkage Table);


  • .got — таб­лица гло­баль­ных сме­щений (Global Offset Table).


 

Секция .got


Нач­нем с сек­ции .got. Для начала обра­тим вни­мание, что эта сек­ция име­ет тип SHT_PROGBITS (то есть содер­жит либо код, либо дан­ные, и в нашем слу­чае это дан­ные), а так­же флаг SHF_WRITE (то есть ее содер­жимое может менять­ся в ходе выпол­нения прог­раммы).


Ана­томия эль­фов. Раз­бира­емся с внут­ренним устрой­ством ELF-фай­лов», в которой мы начали изу­чать сек­реты фор­мата исполня­емых ELF-фай­лов. В ней мы опре­дели­лись с инс­тру­мен­тари­ем ана­лиза, соз­дали нес­коль­ко подопыт­ных экзем­пля­ров ELF-фай­лов, разоб­рались с фор­матом заголов­ка ELF-фай­ла, узна­ли про таб­лицы заголов­ков сек­ций и сег­ментов, а так­же заг­лянули внутрь некото­рых сек­ций и сег­ментов. Виды связывания Ос­новная проб­лема, воз­ника­ющая при ком­понов­ке исполня­емо­го фай­ла, — опре­деле­ние адре­сов вызыва­емых в прог­рамме фун­кций, рас­положен­ных во внеш­них биб­лиоте­ках. Если для фун­кций, которые опре­деле­ны в самом исполня­емом фай­ле, такой проб­лемы не наб­люда­ется (адре­са этих фун­кций опре­деля­ются уже на эта­пе ком­пиляции), то внеш­ние биб­лиоте­ки могут находить­ся в памяти по боль­шому сче­ту где угод­но. Это получа­ется бла­года­ря воз­можнос­ти фор­мировать позици­онно незави­симый код. С ходу, на эта­пе ком­пиляции, опре­делить адрес той или иной фун­кции, содер­жащей­ся в такой биб­лиоте­ке, невоз­можно. Это мож­но сде­лать либо ста­тичес­ки (вклю­чив нуж­ные биб­лиоте­ки непос­редс­твен­но в ELF-файл), либо динами­чес­ки (во вре­мя заг­рузки или выпол­нения прог­раммы). Ис­ходя из это­го, мож­но выделить три вида свя­зыва­ния исполня­емо­го ELF-фай­ла с биб­лиоте­ками: ста­тичес­кое свя­зыва­ние; ди­нами­чес­кое свя­зыва­ние во вре­мя заг­рузки фай­ла; ди­нами­чес­кое свя­зыва­ние во вре­мя исполне­ния фай­ла. Ес­ли обра­тить­ся к миру Windows, то там наб­люда­ется при­мер­но такая же кар­тина и основные прин­ципы фун­кци­они­рова­ния этих видов свя­зыва­ния с внеш­ними биб­лиоте­ками ана­логич­ны. Для ста­тичес­ки лин­куемых биб­лиотек исполь­зует­ся рас­ширение .a (в Windows это фай­лы с рас­ширени­ем .lib), для динами­чес­ких — рас­ширение .so (в Windows это фай­лы .dll). Статическое связывание Здесь все дос­таточ­но прос­то. Внеш­няя биб­лиоте­ка лин­кует­ся с исполня­емым фай­лом, обра­зуя с ним еди­ное целое. Если обра­тить­ся к при­меру из пре­дыду­щей статьи (файл с хел­ловор­лдом example.c), то для того, что­бы сде­лать из него прог­рамму, ста­тичес­ки свя­зан­ную с биб­лиоте­кой glibc, нуж­но наб­рать в кон­соли сле­дующее: gcc -o example_ static_ linked -static example. c В ито­ге получим исполня­емый файл со ста­тичес­ки при­лин­кован­ной к нему биб­лиоте­кой glibc. Если ты обра­тишь вни­мание на раз­мер получен­ного фай­ла, то уви­дишь, что он сущес­твен­но боль­ше, чем раз­мер фай­лов example_pie и example_no_pie, которые были ском­пилиро­ваны методом динами­чес­кой лин­ковки с биб­лиоте­кой glibc. У меня, нап­ример, получи­лось целых 872 Кбайт для ста­тичес­кой лин­ковки, в то вре­мя как динами­чес­кая дала все­го 17. Собс­твен­но говоря, это и есть основной недос­таток ста­тичес­кого свя­зыва­ния. Нес­мотря на то что из glibc мы исполь­зуем все­го одну фун­кцию puts(), при ста­тичес­ком свя­зыва­нии при­ходит­ся тащить в исполня­емый файл еще мно­го чего ненуж­ного. Так­же мож­но отме­тить еще один, не сов­сем явный недос­таток ста­тичес­кой лин­ковки: если появ­ляет­ся новая вер­сия биб­лиоте­ки (в которой, нап­ример, устра­нена та или иная уяз­вимость), то нам при­дет­ся переком­пилиро­вать прог­рамму уже с новой вер­сией нуж­ной нам биб­лиоте­ки. Если это­го не делать, то наша прог­рамма будет поль­зовать­ся фун­кци­ями, в которых уяз­вимость не устра­нена. Пос­мотреть тип свя­зыва­ния в исполня­емом фай­ле мож­но, при­менив ути­литу file или ldd. Оп­ределя­ем тип свя­зыва­ния в ELF-фай­ле (в дан­ном слу­чае видим, что при­мене­на ста­тичес­кая лин­ковка)Об­рати вни­мание, что для при­мера example_static из пре­дыду­щей статьи ути­лита file покажет динами­чес­кую лин­ковку. Все дело в том, что в этом слу­чае мы ста­тичес­ки лин­ковали с прог­раммой нашу самопис­ную биб­лиоте­ку lib_static_example.a, в которой содер­жится фун­кция hello_world_function(). Одна­ко в этой фун­кции исполь­зует­ся фун­кция puts(), которая берет­ся из биб­лиоте­ки glibc, свя­зан­ной с lib_static_example.a уже динами­чес­ки. Динамическое связывание во время загрузки файла Как мы уже говори­ли, бла­года­ря позици­онно незави­симо­му коду динами­чес­кие (или раз­деля­емые) биб­лиоте­ки могут быть заг­ружены в память один раз, а все нуж­дающиеся в этой биб­лиоте­ке прог­раммы ста­нут поль­зовать­ся этой раз­деля­емой копи­ей биб­лиоте­ки. На эта­пе ком­понов­ки исполня­емо­го фай­ла адре­са, по которым будут заг­ружены динами­чес­кие биб­лиоте­ки, неиз­вес­тны, и поэто­му адре­са содер­жащих­ся в них фун­кций опре­делить невоз­можно. В этом слу­чае динами­чес­кий ком­понов­щик (если пом­нишь, то путь к нему лежит в сек­ции .interp) опре­деля­ет адре­са нуж­ных фун­кций и перемен­ных в ходе заг­рузки прог­раммы в память. Если говорить точ­нее, во мно­гих слу­чаях даже не в ходе заг­рузки, а во вре­мя пер­вого обра­щения к фун­кции, но об этом более под­робно погово­рим чуть ниже. Динамическое связывание во время исполнения файла Раз­деля­емые биб­лиоте­ки могут быть заг­ружены в память и во вре­мя выпол­нения прог­раммы. В этом слу­чае при­ложе­ние обра­щает­ся к динами­чес­кому лин­ковщи­ку с прось­бой заг­рузить и при­лин­ковать динами­чес­кую биб­лиоте­ку. В Linux для это­го пре­дус­мотре­ны сис­темные фун­кции dlopen(), dlsym() и dlclose(), пер­вая заг­ружа­ет раз­деля­емую биб­лиоте­ку, вто­рая ищет в ней нуж­ную фун­кцию, третья зак­рыва­ет ее файл. Ес­ли покопать­ся во внут­реннос­тях Windows, там мож­но обна­ружить ана­логич­ные API-фун­кции: LoadLibrary() и GetProcAddress() (либо LdrLoadDll() и LdrGetProcAddress()). Этот вид свя­зыва­ния (как в Linux, так и в Windows) исполь­зует­ся дос­таточ­но ред­ко, во мно­гих слу­чаях его при­меня­ют для того, что­бы скрыть от иссле­дова­телей истинную фун­кци­ональ­ность прог­раммы. Продолжаем разбираться с секционным представлением ELF-файла В пре­дыду­щей статье мы рас­смот­рели нес­коль­ко сек­ций, которые может содер­жать ELF-файл, одна­ко изу­чили мы их далеко не все. Сегод­ня мы про­дол­жим иссле­довать этот воп­рос и пос­мотрим в том чис­ле, какие сек­ции в ELF-фай­ле пре­дус­мотре­ны для орга­низа­ции свя­зыва­ния и раз­решения адре­сов фун­кций, которые содер­жатся в раз­деля­емых биб­лиоте­ках. Секция .shstrtab Эта сек­ция пред­став­ляет собой мас­сив строк, закан­чива­ющих­ся нулем, с име­нами всех сек­ций ELF-фай­ла. Ука­зан­ная таб­лица поз­воля­ет раз­личным ути­литам (нап­ример, таким, как readelf) находить име­на сек­ций. Для прос­мотра этой сек­ции в сим­воль­ном или шес­тнад­цатерич­ном виде мож­но исполь­зовать опции -p или -x ути­литы readelf соот­ветс­твен­но. Нап­ример, вот так: readelf -x . shstrtab example_ pie Сек­ция .shstrtab в шес­тнад­цатерич­ном пред­став­лении Символьные секции Как мож­но догадать­ся по наз­ванию, сим­воль­ные сек­ции хра­нят какие‑то сим­волы. В нашем слу­чае под сим­волами понима­ются име­на фун­кций и перемен­ных. Эти име­на исполь­зуют­ся в качес­тве сим­воль­ных имен для пред­став­ления опре­делен­ного мес­тополо­жения в фай­ле или в памяти. Все это вмес­те и обра­зует то, что мы называ­ем сим­волами фун­кций и дан­ных. (Да, мы при­вык­ли счи­тать, что сим­вол, как пра­вило, занима­ет одно зна­комес­то в виде бук­вы, циф­ры или зна­ка пре­пина­ния, одна­ко здесь это не так.) Что­бы пос­мотреть информа­цию о сим­волах, мож­но вос­поль­зовать­ся уже зна­комой нам ути­литой readelf и наб­рать в кон­соли что‑нибудь вро­де это­го: readelf -s -W example_ pie На выходе уви­дим содер­жимое двух сек­ций .symtab и .dynsym. Вы­вод сим­воль­ной информа­ции из ELF-фай­ла Секция .symtab Для начала необ­ходимо отме­тить, что наличие этой сек­ции в ELF-фай­ле необя­затель­но. Более того, в боль­шинс­тве встре­чающих­ся в дикой при­роде фай­лов она отсутс­тву­ет. Основное ее наз­начение — помощь при отладке прог­раммы, в то вре­мя как для исполне­ния фай­ла она не тре­бует­ся. По умол­чанию эта сек­ция соз­дает­ся во вре­мя ком­пиляции прог­раммы, одна­ко ее мож­но уда­лить с помощью коман­ды strip, нап­ример так: strip example_ pie Те­перь, если попытать­ся пос­мотреть сим­воль­ную информа­цию в этом фай­ле, будет выведе­но толь­ко содер­жимое сек­ции .dynsym. Вы­вод сим­воль­ной информа­ции из ELF-фай­ла, на который воз­дей­ство­вали ути­литой stripВсе же, хоть эта сек­ция и необя­затель­на в фай­ле, оста­новим­ся на ней чуть под­робнее. Каж­дая запись этой сек­ции пред­став­ляет собой струк­туру вида Elf32_Sym или Elf64_Sym. Внут­реннее устрой­ство этой струк­туры (как, впро­чем, и содер­жимое всех осталь­ных струк­тур и зна­чений кон­стант ELF-фай­лов) мож­но пос­мотреть в фай­ле /usr/include/elf.h. В ука­зан­ной сек­ции содер­жатся все сим­волы, которые ком­понов­щик исполь­зует как во вре­мя ком­пиляции, так и во вре­мя выпол­нения при­ложе­ния. В нашем при­мере example_pie сре­ди все­го, что содер­жится в дан­ной сек­ции, мож­но уви­деть сим­воль­ное имя зна­комой нам фун­кции main(), которая при­сутс­тву­ет в любой прог­рамме, а так­же сим­воль­ное имя фун­кции puts(). Фун­кции main() и puts() в сек­ции .symtabФун­кции main() соот­ветс­тву­ет адрес 0x1149, и имен­но с это­го адре­са фун­кция будет начинать­ся пос­ле заг­рузки фай­ла в память перед выпол­нени­ем. Так­же вид­но, что раз­мер фун­кции main() сос­тавля­ет 27 байт, ее тип — FUNC (то есть фун­кция), а сама фун­кция раз­меща­ется в сек­ции с номером 16 (это сек­ция .text, в которой находит­ся непос­редс­твен­но исполня­емый код прог­раммы). С фун­кци­ей puts() такой опре­делен­ности не отме­чает­ся: нет ни адре­са, ни сек­ции. Так про­исхо­дит потому, что фун­кция puts() находит­ся в биб­лиоте­ке glibc и, как мы уже говори­ли, адрес этой фун­кции на эта­пе ком­пиляции опре­делить нель­зя. Со­дер­жимое сек­ции .symtab так­же мож­но пос­мотреть и с помощью коман­ды nm. Что­бы узнать под­робнос­ти исполь­зования этой коман­ды, набери в тер­минале nm man Ес­ли мы поп­робу­ем пос­мотреть содер­жимое сек­ции .symtab ELF-фай­ла, в котором при­мене­но ста­тичес­кое свя­зыва­ние, то уви­дим, что, помимо уже зна­комых

Теги: CSS

Просмотров: 516
Комментариев: 0:   4-09-2022, 00:02
Уважаемый посетитель, Вы зашли на сайт как незарегистрированный пользователь. Мы рекомендуем Вам зарегистрироваться либо войти на сайт под своим именем.

 
Еще новости по теме:



Другие новости по теме: