Как да надникнете вътре в двоичните файлове от командния ред на Linux

Имате ли мистериозен файл? Командата на Linux файл бързо ще ви каже какъв тип файл е. Ако обаче това е двоичен файл, можете да научите още повече за него. файлът има цял набор от приятели, които ще ви помогнат да го анализирате. Ще ви покажем как да използвате някои от тези инструменти.

Идентифициране на типове файлове

Файловете обикновено имат характеристики, които позволяват на софтуерните пакети да идентифицират кой тип файл е, както и какво представляват данните в него. Не би имало смисъл да се опитвате да отворите PNG файл в MP3 музикален плейър, така че е полезно и прагматично файлът да носи със себе си някаква форма на идентификационен номер.

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

Някои операционни системи, като Windows, се ръководят изцяло от разширението на файла. Можете да го наречете лековерен или доверчив, но Windows предполага, че всеки файл с разширението DOCX наистина е файл за текстообработка на DOCX. Linux не е такъв, както скоро ще видите. Иска доказателство и търси във файла, за да го намери.

Инструментите, описани тук, вече бяха инсталирани в дистрибуциите Manjaro 20, Fedora 21 и Ubuntu 20.04, които използвахме, за да изследваме тази статия. Нека започнем нашето разследване с помощта командата файл.

Използване на файла Command

Имаме колекция от различни типове файлове в текущата ни директория. Те са смесица от документи, изходен код, изпълними и текстови файлове.

Командата ls ще ни покаже какво има в директорията, а опцията -hl (размери, четени от човека, дълъг списък) ще ни покаже размера на всеки файл:

ls -hl

Нека опитаме да файл на няколко от тях и да видим какво получаваме:

file build_instructions.odt
file build_instructions.pdf
file COBOL_Report_Apr60.djvu

Трите файлови формата са правилно идентифицирани. Където е възможно, файлът ни дава малко повече информация. Съобщава се, че PDF файлът е в версия 1.5 формат.

Дори ако преименуваме ODT файла, за да има разширение с произволна стойност XYZ, файлът все още е правилно идентифициран, както в браузъра на файловете, така и в командния ред с помощта на файл.

В браузъра на файловете му е дадена правилната икона. В командния ред файлът игнорира разширението и търси вътре във файла, за да определи неговия тип:

file build_instructions.xyz

Използването на файл върху медии, като файлове с изображения и музика, обикновено дава информация относно техния формат, кодиране, разделителна способност и т.н.:

file screenshot.png
file screenshot.jpg
file Pachelbel_Canon_In_D.mp3

Интересното е, че дори при файлове с обикновен текст, файлът не преценява файла по неговото разширение. Например, ако имате файл с разширение „.c“, съдържащ стандартен обикновен текст, но не и изходен код, файлът не го бърка с истински C файл с изходен код:

file function+headers.h
file makefile
file hello.c

file правилно идентифицира заглавния файл (.h”) като част от колекция от файлове с изходен код на C и знае, че makefile е скрипт.

  Как да изтегляте файлове на Linux с Curl

Използване на файл с двоични файлове

Двоичните файлове са по-скоро „черна кутия“ от другите. Файловете с изображения могат да се разглеждат, звуковите файлове могат да се възпроизвеждат и файловете с документи могат да се отварят от съответния софтуерен пакет. Двоичните файлове обаче са по-голямо предизвикателство.

Например, файловете “hello” и “wd” са двоични изпълними файлове. Те са програми. Файлът, наречен „wd.o“, е обектен файл. Когато изходният код се компилира от компилатор, се създават един или повече обектни файлове. Те съдържат машинния код, който компютърът в крайна сметка ще изпълни, когато се изпълни готовата програма, заедно с информация за линкера. Линкерът проверява всеки обектен файл за извиквания на функции към библиотеки. Той ги свързва с всякакви библиотеки, които програмата използва. Резултатът от този процес е изпълним файл.

Файлът „watch.exe“ е двоичен изпълним файл, който е кръстосано компилиран за работа в Windows:

file wd
file wd.o
file hello
file watch.exe

Като вземем последното първо, файлът ни казва, че файлът „watch.exe“ е PE32+ изпълнима конзолна програма за семейството процесори x86 в Microsoft Windows. PE означава преносим изпълним формат, който има 32- и 64-битови версии. PE32 е 32-битовата версия, а PE32+ е 64-битовата версия.

Всички останали три файла са идентифицирани като Изпълним и свързващ формат (ELF) файлове. Това е стандарт за изпълними файлове и споделени обектни файлове, като библиотеки. Скоро ще разгледаме формата на заглавката на ELF.

Това, което може да привлече вниманието ви, е, че двата изпълними файла („wd“ и „hello“) са идентифицирани като Стандартна база на Linux (LSB) споделени обекти, а обектният файл „wd.o“ се идентифицира като LSB преместваем. Думата изпълним е очевидна в нейното отсъствие.

Обектните файлове са преместваеми, което означава, че кодът в тях може да бъде зареден в паметта на всяко място. Изпълнимите файлове са изброени като споделени обекти, тъй като са създадени от линкера от обектните файлове по такъв начин, че да наследяват тази възможност.

Това позволява на Рандомизиране на оформлението на адресното пространство (ASMR) система за зареждане на изпълними файлове в паметта на адреси по негов избор. Стандартните изпълними файлове имат адрес за зареждане, кодиран в техните заглавки, които диктуват къде да бъдат заредени в паметта.

ASMR е техника за сигурност. Зареждането на изпълними файлове в паметта на предвидими адреси ги прави податливи на атака. Това е така, защото техните входни точки и местоположението на техните функции винаги ще бъдат известни на нападателите. Независими от позицията изпълними файлове (PIE), позициониран на произволен адрес, преодолява тази чувствителност.

  Как да инсталирате Linux на Chromebook с Crouton

Ако ние компилирайте нашата програма с gcc компилатора и предоставяме опцията -no-pie, ще генерираме конвенционален изпълним файл.

Опцията -o (изходен файл) ни позволява да предоставим име за нашия изпълним файл:

gcc -o hello -no-pie hello.c

Ще използваме файла в новия изпълним файл и ще видим какво се е променило:

file hello

Размерът на изпълнимия файл е същият като преди (17 KB):

ls -hl hello

Двоичният файл вече е идентифициран като стандартен изпълним файл. Правим това само за демонстрационни цели. Ако компилирате приложения по този начин, ще загубите всички предимства на ASMR.

Защо изпълнимият файл е толкова голям?

Нашата примерна програма hello е 17 KB, така че едва ли може да се нарече голяма, но тогава всичко е относително. Изходният код е 120 байта:

cat hello.c

Какво натрупва двоичния файл, ако всичко, което прави, е да отпечата един низ в прозореца на терминала? Знаем, че има ELF заглавка, но това е само 64-байта за 64-битов двоичен файл. Ясно е, че трябва да е нещо друго:

ls -hl hello

Нека да сканирайте двоичния файл с strings команда като проста първа стъпка, за да откриете какво има вътре в нея. Ще го разпределим в по-малко:

strings hello | less

В двоичния файл има много низове, освен „Здравей, Geek world!“ от нашия изходен код. Повечето от тях са етикети за региони в двоичния файл, както и имената и информацията за свързване на споделени обекти. Те включват библиотеките и функциите в тези библиотеки, от които зависи двоичният файл.

В ldd команда ни показва зависимостите на споделен обект на двоичен файл:

ldd hello

Има три записа в изхода и два от тях включват път към директорията (първият не):

linux-vdso.so: Виртуален динамичен споделен обект (VDSO) е механизъм на ядрото, който позволява достъп до набор от рутинни процедури в пространството на ядрото от двоичен файл в потребителското пространство. Това избягва излишните разходи за превключване на контекста от режима на потребителското ядро. VDSO споделените обекти се придържат към формата за изпълним и свързващ формат (ELF), което им позволява да бъдат динамично свързани с двоичния файл по време на изпълнение. VDSO се разпределя динамично и се възползва от ASMR. Възможността VDSO се осигурява от стандарта Библиотека GNU C ако ядрото поддържа ASMR схемата.
libc.so.6: The Библиотека GNU C споделен обект.
/lib64/ld-linux-x86-64.so.2: Това е динамичният линкер, който двоичният файл иска да използва. Динамичният линкер разпитва двоичния файл, за да открие какви зависимости има. Той стартира тези споделени обекти в паметта. Той подготвя двоичния файл да работи и да може да намира и осъществява достъп до зависимостите в паметта. След това стартира програмата.

Заглавката на ELF

Ние можем прегледайте и декодирайте заглавката на ELF с помощта на помощната програма readelf и опцията -h (заглавка на файла):

readelf -h hello

Заглавката се интерпретира за нас.

  Как да използвате командата dmesg в Linux

Първият байт от всички ELF двоични файлове е настроен на шестнадесетична стойност 0x7F. Следващите три байта са зададени на 0x45, 0x4C и 0x46. Първият байт е флаг, който идентифицира файла като ELF двоичен файл. За да стане това кристално ясно, следващите три байта изписват „ELF“. ASCII:

Клас: Показва дали двоичният файл е 32- или 64-битов изпълним файл (1=32, 2=64).
Данни: Показва endianness в употреба. Endian кодирането определя начина, по който се съхраняват многобайтови числа. При кодиране с голям ендиан числото се съхранява първо с най-значимите му битове. При кодиране с малък ендиан числото се съхранява първо с най-малките битове.
Версия: Версията на ELF (в момента е 1).
OS/ABI: Представлява типа на бинарен интерфейс на приложението в употреба. Това дефинира интерфейса между два двоични модула, като програма и споделена библиотека.
Версия на ABI: Версията на ABI.
Тип: Типът на двоичния файл ELF. Общите стойности са ET_REL за преместваем ресурс (като обектен файл), ET_EXEC за изпълним файл, компилиран с флага -no-pie, и ET_DYN за изпълним файл с информация за ASMR.
Машина: The архитектура на набора от инструкции. Това показва целевата платформа, за която е създаден двоичният файл.
Версия: Винаги настройвайте на 1 за тази версия на ELF.
Адрес на входна точка: Адресът на паметта в двоичния файл, от който започва изпълнението.

Другите записи са размери и брой на региони и секции в двоичния файл, така че техните местоположения могат да бъдат изчислени.

Бърз поглед към първите осем байта на двоичния файл с hexdump ще покаже подписния байт и низа „ELF“ в първите четири байта на файла. Опцията -C (канонична) ни дава ASCII представяне на байтовете заедно с техните шестнадесетични стойности, а опцията -n (число) ни позволява да посочим колко байта искаме да видим:

hexdump -C -n 8 hello

objdump и гранулираният изглед

Ако искате да видите най-дребните подробности, можете да използвате командата objdump с опцията -d (разглобяване):

objdump -d hello | less

Това разглобява изпълнимия машинен код и го показва в шестнадесетични байтове заедно с еквивалента на асемблерния език. Местоположението на адреса на първото чао във всеки ред е показано най-вляво.

Това е полезно само ако можете да четете асемблер или сте любопитни какво се случва зад завесата. Има много продукция, така че я включихме в по-малко.

Компилиране и свързване

Има много начини за компилиране на двоичен файл. Например разработчикът избира дали да включи информация за отстраняване на грешки. Начинът, по който двоичният файл е свързан, също играе роля в неговото съдържание и размер. Ако двоичните препратки споделят обекти като външни зависимости, тя ще бъде по-малка от една, към която зависимостите се свързват статично.

Повечето разработчици вече познават командите, които разгледахме тук. За други обаче те предлагат някои лесни начини да се ровиш и да видиш какво се крие в двоичната черна кутия.