Категория > Новости > Анатомия эльфов. Разбираемся с внутренним устройством ELF-файлов - «Новости»

Анатомия эльфов. Разбираемся с внутренним устройством ELF-файлов - «Новости»


12-05-2022, 00:00. Автор: Ариадна
GNU Binutils. Если это­го пакета в тво­ей сис­теме нет, его лег­ко уста­новить. К при­меру, в Ubuntu это выг­лядит сле­дующим обра­зом:
sudo apt install binutils

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


Читайте также - Профессиональный ремонт плавательных бассейнов в Москве и Московской области, ремонт бассейна по доступным ценам.

Ана­лиз заголов­ка ELF-фай­ла в IDA Freeware

Так­же неп­лохо было бы исполь­зовать вмес­то hexdump что‑то поудоб­нее, нап­ример 010 Editor или wxHex Editor. Пер­вый hex-редак­тор — дос­той­ная аль­тер­натива Hiew для Linux (в том чис­ле и бла­года­ря воз­можнос­ти исполь­зовать в нем боль­шое количес­тво шаб­лонов для раз­личных типов фай­лов, сре­ди них и шаб­лон для пар­синга ELF-фай­лов). Одна­ко он небес­плат­ный (сто­имость лицен­зии начина­ется с 49,95 дол­лара, при этом есть 30-днев­ный три­аль­ный пери­од).


Ана­лиз заголов­ка ELF-фай­ла в 010 Editor

Го­воря о допол­нитель­ных инс­тру­мен­тах, которые облегча­ют ана­лиз ELF-фай­лов, нель­зя не упо­мянуть Python-пакет lief. Исполь­зуя этот пакет, мож­но писать Python-скрип­ты для ана­лиза и модифи­кации не толь­ко ELF-фай­лов, но и фай­лов PE и MachO. Ска­чать и уста­новить этот пакет получит­ся тра­дици­онным для Python-пакетов спо­собом:


pip install lief
 

Подопытные экземпляры


В Linux (да и во мно­гих дру­гих сов­ремен­ных UNIX-подоб­ных опе­раци­онных сис­темах) фор­мат ELF исполь­зует­ся в нес­коль­ких типах фай­лов.




  • Ис­полня­емый файл — содер­жит все необ­ходимое для соз­дания сис­темой обра­за про­цес­са и запус­ка это­го про­цес­са. В общем слу­чае это инс­трук­ции и дан­ные. Так­же в фай­ле может при­сутс­тво­вать опи­сание необ­ходимых раз­деля­емых объ­ектных фай­лов, а так­же сим­воль­ная и отла­доч­ная информа­ция. Исполня­емый файл может быть позици­онно зависи­мым (в этом слу­чае он гру­зит­ся всег­да по одно­му и тому же адре­су, для 32-раз­рядных прог­рамм обыч­но это 0x8048000, для 64-раз­рядных — 0x400000) и позици­онно незави­симым исполня­емым фай­лом (PIE — Position Independent Execution или PIC — Position Independent Code). В этом слу­чае адрес заг­рузки фай­ла может менять­ся при каж­дой заг­рузке. При пос­тро­ении позици­онно незави­симо­го исполня­емо­го фай­ла исполь­зуют­ся такие же прин­ципы, как и при пос­тро­ении раз­деля­емых объ­ектных фай­лов.


  • Пе­реме­щаемый файл — содер­жит инс­трук­ции и дан­ные, при этом они могут быть ста­тичес­ки свя­заны с дру­гими объ­ектны­ми фай­лами, в резуль­тате чего получа­ется раз­деля­емый объ­ектный или исполня­емый файл. К это­му типу отно­сят­ся объ­ектные фай­лы ста­тичес­ких биб­лиотек (как пра­вило, для ста­тичес­ких биб­лиотек имя начина­ется с lib и при­меня­ется рас­ширение *.a), одна­ко, как мы уже говори­ли, рас­ширение в Linux прак­тичес­ки ничего не опре­деля­ет. В слу­чае ста­тичес­ких биб­лиотек это прос­то дань тра­диции, а работос­пособ­ность биб­лиоте­ки будет обес­печена с любым име­нем и любым рас­ширени­ем.


  • Раз­деля­емый объ­ектный файл — содер­жит инс­трук­ции и дан­ные, может быть свя­зан с дру­гими переме­щаемы­ми фай­лами или раз­деля­емы­ми объ­ектны­ми фай­лами, в резуль­тате чего будет соз­дан новый объ­ектный файл. Такие фай­лы могут выпол­нять фун­кции раз­деля­емых биб­лиотек (по ана­логии с DLL-биб­лиоте­ками Windows). При этом в момент запус­ка прог­раммы на выпол­нение опе­раци­онная сис­тема динами­чес­ки свя­зыва­ет эту раз­деля­емую биб­лиоте­ку с исполня­емым фай­лом прог­раммы, и соз­дает­ся исполня­емый образ при­ложе­ния. Опять же тра­дици­онно раз­деля­емые биб­лиоте­ки име­ют рас­ширение *.so (от англий­ско­го Shared Object).


  • Файл дам­па памяти — файл, который содер­жит образ памяти того или ино­го про­цес­са на момент его завер­шения. В опре­делен­ных ситу­ациях ядро может соз­давать файл с обра­зом памяти ава­рий­но завер­шивше­гося про­цес­са. Этот файл так­же соз­дает­ся в фор­мате ELF, одна­ко мы о такого рода фай­лах говорить не будем, пос­коль­ку задача иссле­дова­ния дам­пов и содер­жимого памяти дос­таточ­но объ­емна и тре­бует отдель­ной статьи.


Для наших изыс­каний нам желатель­но иметь все воз­можные вари­анты исполня­емых фай­лов из перечис­ленных выше, чем мы сей­час и зай­мем­ся.


 

Делаем исполняемые файлы


Не будем выдумы­вать что‑то свер­хориги­наль­ное, а оста­новим­ся на клас­сичес­ком хел­ловор­лде на С:


#include <stdio.h>
int main(int argc, char* argv[]) {
 printf("Hello world");
 return 0;
}

Ком­пилиро­вать это дело мы будем с помощью GCC. Сов­ремен­ные вер­сии Linux, как пра­вило, 64-раз­рядные, и вхо­дящие в их сос­тав по умол­чанию средс­тва раз­работ­ки (в том чис­ле и ком­пилятор GCC) генери­руют 64-раз­рядные при­ложе­ния. Мы в сво­их иссле­дова­ниях не будем отдель­но вни­кать в 32-раз­рядные ELF-фай­лы (по боль­шому сче­ту отли­чий от 64-раз­рядных ELF-фай­лов в них не очень мно­го) и основные уси­лия сос­редото­чим имен­но на 64-раз­рядных вер­сиях прог­рамм. Если у тебя воз­никнет желание поэк­спе­римен­тировать с 32-раз­рядны­ми фай­лами, то при ком­пиляции в GCC нуж­но добавить опцию -m32, при этом, воз­можно, пот­ребу­ется уста­новить биб­лиоте­ку gcc-multilib. Сде­лать это мож­но при­мер­но вот так:


sudo apt-get install gcc-multilib

Итак, назовем наш хел­ловорлд example.c (кста­ти, здесь как раз один из нем­ногих слу­чаев, ког­да в Linux рас­ширение име­ет зна­чение) и нач­нем с исполня­емо­го позици­онно зависи­мого кода:


gcc -no-pieexample.-oexample_no_pie

Как ты уже догадал­ся, опция -no-pie как раз и говорит ком­пилято­ру соб­рать не позици­онно незави­симый код.


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


В целом мож­но выделить четыре эта­па работы GCC:



  • преп­роцес­сирова­ние;

  • тран­сля­ция в ассем­блер­ный код;

  • пре­обра­зова­ние ассем­блер­ного кода в объ­ектный;

  • ком­понов­ка объ­ектно­го кода.


Что­бы пос­мотреть на про­межу­точ­ный резуль­тат, к при­меру в виде ассем­блер­ного кода, исполь­зуй в GCC опцию -S:


gcc -S -masm=intel example.c

Об­рати вни­мание на два момен­та. Пер­вый — мы в дан­ном слу­чае не зада­ем имя выход­ного фай­ла с помощью опции -o (GCC сам опре­делит его из исходно­го, добавив рас­ширение *.s, что и озна­чает при­сутс­твие в фай­ле ассем­блер­ного кода). Вто­рой момент — опция -masm=intel, которая говорит о том, что ассем­блер­ный код в выход­ном фай­ле необ­ходимо генери­ровать с исполь­зовани­ем син­такси­са Intel (по умол­чанию будет син­таксис AT&T, мне же, как и, навер­ное, боль­шинс­тву, син­таксис Intel бли­же). Так­же в этом слу­чае опция -no-pie не име­ет смыс­ла, пос­коль­ку ассем­блер­ный код в любом слу­чае будет оди­нако­вый, а перено­симость обес­печива­ется на эта­пе получе­ния объ­ектно­го фай­ла и сбор­ки прог­раммы.


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


.file "example.c"
 .intel_syntax noprefix
 .text
 .section .rodata
.LC0:
 .string "Hello world"
 .text
 .globl main
 .type main, @function
main:
.LFB0:
 .cfi_startproc
 endbr64
 push rbp
 .cfi_def_cfa_offset 16
 .cfi_offset 6, -16
 mov rbp, rsp
 .cfi_def_cfa_register 6
 lea rdi, .LC0[rip]
 call puts@PLT
 mov eax, 0
 ...


Перейти обратно к новости