Як зазирнути в двійкові файли з командного рядка 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, і він знає, що make-файл є сценарієм.

  Як грати в Battlefield 1 на Linux

Використання файлу з двійковими файлами

Двійкові файли є більше «чорним ящиком», ніж інші. За допомогою відповідного програмного пакета можна переглядати файли зображень, відтворювати звукові файли та відкривати файли документів. Однак двійкові файли є більш складним завданням.

Наприклад, файли «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), розміщені за випадковою адресою, долають цю сприйнятливість.

  5 найкращих дистрибутивів Linux для ігор

Якщо ми зібрати нашу програму за допомогою компілятора gcc і надати параметр -no-pie, ми згенеруємо звичайний виконуваний файл.

Параметр -o (вихідний файл) дозволяє нам надати ім’я для нашого виконуваного файлу:

gcc -o hello -no-pie hello.c

Ми використаємо файл у новому виконуваному файлі та подивимося, що змінилося:

file hello

Розмір виконуваного файлу такий же, як і раніше (17 КБ):

ls -hl hello

Двійковий файл тепер ідентифікується як стандартний виконуваний файл. Ми робимо це лише з демонстраційними цілями. Якщо ви компілюєте програми таким чином, ви втратите всі переваги ASMR.

Чому виконуваний файл такий великий?

Наш приклад програми hello має розмір 17 КБ, тому її важко назвати великою, але тоді все відносно. Вихідний код становить 120 байт:

cat hello.c

Що збільшує двійковий файл, якщо все, що він робить, це друкує один рядок у вікні терміналу? Ми знаємо, що є заголовок ELF, але для 64-розрядного двійкового файлу це лише 64 байти. Очевидно, це має бути щось інше:

ls -hl hello

Давайте скануйте двійковий файл за допомогою strings як простий перший крок, щоб дізнатися, що всередині нього. Ми переведемо це на менше:

strings hello | less

У двійковому файлі є багато рядків, окрім «Hello, Geek world!» з нашого вихідного коду. Більшість із них є мітками для регіонів у двійковому файлі, а також іменами та інформацією про зв’язки спільних об’єктів. Сюди входять бібліотеки та функції в тих бібліотеках, від яких залежить двійковий файл.

The команда ldd показує нам залежності від спільних об’єктів двійкового файлу:

ldd hello

У вихідних даних є три записи, і два з них містять шлях до каталогу (перший не містить):

linux-vdso.so: Віртуальний динамічний спільний об’єкт (VDSO) є механізмом ядра, який дозволяє отримати доступ до набору підпрограм простору ядра за допомогою двійкового файлу простору користувача. Це дозволяє уникнути зайвих витрат на перемикання контексту з режиму ядра користувача. Спільні об’єкти VDSO дотримуються формату Executable and Linkable Format (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

Заголовок інтерпретується для нас.

  Як зробити Linux схожим на Windows XP

Перший байт усіх двійкових файлів ELF має шістнадцяткове значення 0x7F. Наступні три байти мають значення 0x45, 0x4C і 0x46. Перший байт є прапором, який ідентифікує файл як двійковий файл ELF. Щоб зробити це кристально зрозумілим, у наступних трьох байтах вказано «ELF». ASCII:

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

Інші записи – це розміри та кількість регіонів і розділів у двійковому файлі, щоб можна було розрахувати їх розташування.

Швидкий погляд на перші вісім байтів двійкового файлу з hexdump покаже байт підпису та рядок «ELF» у перших чотирьох байтах файлу. Параметр -C (канонічний) дає нам представлення ASCII байтів разом із їх шістнадцятковими значеннями, а параметр -n (число) дозволяє нам вказати, скільки байтів ми хочемо бачити:

hexdump -C -n 8 hello

objdump і детальний перегляд

Якщо ви хочете побачити деталі, ви можете скористатися командою objdump з параметром -d (дизассембле):

objdump -d hello | less

Це розбирає виконуваний машинний код і відображає його в шістнадцяткових байтах разом з еквівалентом на мові асемблера. Розташування адреси першого до побачення в кожному рядку показано в крайньому лівому куті.

Це корисно, лише якщо ви вмієте читати асемблеру або вам цікаво, що відбувається за завісою. Випуску багато, тому ми зменшили його.

Компіляція та зв’язування

Існує багато способів зібрати двійковий файл. Наприклад, розробник вибирає, чи включати інформацію про налагодження. Спосіб зв’язування двійкового файлу також відіграє певну роль у його вмісті та розмірі. Якщо двійкові посилання мають спільні об’єкти як зовнішні залежності, вони будуть меншими, ніж ті, з якими залежності статично зв’язуються.

Більшість розробників уже знають команди, які ми розглянули тут. Для інших, однак, вони пропонують кілька простих способів покопатися і подивитися, що знаходиться всередині бінарного чорного ящика.