Как заглянуть внутрь двоичных файлов из командной строки Linux

5 февраля 2021 |

Как заглянуть внутрь двоичных файлов из командной строки Linux

A stylized Linux terminal with lines of green text on a laptop. fatmawati achmad zaenuri / Shutterstock

Есть загадочный файл? Команда Linux file быстро сообщит вам, какой это тип файла. Однако если это двоичный файл, вы можете узнать о нем еще больше. В файле есть множество товарищей по конюшне, которые помогут вам его проанализировать. Мы покажем вам, как использовать некоторые из этих инструментов.

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

Файлы обычно имеют характеристики, позволяющие программным пакетам определять, какой это тип файла, а также то, что данные в нем представляют. Нет смысла пытаться открыть файл PNG в музыкальном проигрывателе MP3, поэтому полезно и прагматично, чтобы файл содержал какую-либо форму идентификатора.

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

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

Описанные здесь инструменты уже были установлены в дистрибутивах Manjaro 20, Fedora 21 и Ubuntu 20.04, которые мы использовали для исследования этой статьи. Давайте начнем наше расследование с помощью команды file.

Использование команды file

У нас есть коллекция файлов разных типов в нашем текущем каталоге. Они представляют собой смесь документов, исходного кода, исполняемых и текстовых файлов.

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

ls -hl

Давайте попробуем применить файл к некоторым из них и посмотрим, что мы получим:

файл build_instructions.odt файл build_instructions.pdf файл COBOL_Report_Apr60.djvu

Три формата файлов правильно определены. Где возможно, файл дает нам немного больше информации. Сообщается, что файл PDF имеет формат версии 1.5.

Даже если мы переименуем файл ODT, чтобы он имел расширение с произвольным значением XYZ, файл все равно будет правильно идентифицирован, как в браузере файлов файлов и в командной строке с помощью file.

OpenDocument file correctly identified within the Files file browser, even though its extension is XYZ.

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

file build_instructions.xyz

Использование файла на носителе, например изображений и музыкальных файлов, обычно дает информацию об их формате, кодировке, разрешении и т. д.:

file screenshot.png файл screenshot.jpg файл Pachelbel_Canon_In_D.mp3

Интересно, что даже с текстовыми файлами файл не оценивает файл по его расширению. Например, если у вас есть файл с расширением «.c ”, содержащее стандартный текст, но не исходный код, файл не принимает его за подлинный файл исходного кода C:

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

файл правильно определяет файл заголовка («.h») как часть коллекции файлов исходного кода C и знает, что make-файл является сценарием.

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

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

Например, файлы «hello» и «wd» являются двоичными исполняемыми файлами. Это программы. Файл с именем «wd.o» является объектным файлом. Когда исходный код компилируется компилятором, создаются один или несколько объектных файлов. Они содержат машинный код, который компьютер в конечном итоге выполнит при запуске законченной программы, вместе с информацией для компоновщика. Компоновщик проверяет каждый объектный файл на наличие вызовов функций в библиотеки. Он связывает их с любыми библиотеками, которые использует программа. Результатом этого процесса является исполняемый файл.

Файл «watch.exe» — это двоичный исполняемый файл, который был кросс-скомпилирован для работы в Windows:

файл wd файл wd.o файл привет файл часы.exe

Если взять последний в первую очередь, файл говорит нам, что файл «watch.exe» является исполняемым файлом PE32 +, консольной программой для семейства процессоров x86 в Microsoft Windows. PE — это переносимый исполняемый формат, который имеет 32- и 64-разрядные версии. PE32 — это 32-разрядная версия, а PE32 + — 64-разрядная версия.

Все три других файла определены как файлы в исполняемом и связываемом формате (ELF). Это стандарт для исполняемых файлов и файлов общих объектов, таких как библиотеки. Вскоре мы рассмотрим формат заголовка ELF.

Что может броситься в глаза, так это то, что два исполняемых файла («wd» и «hello») идентифицированы как общие объекты Linux Standard Base (LSB), а объектный файл «wd.o» идентифицируется как перемещаемый младший бит. Слово «исполняемый файл» очевидно в его отсутствии.

Объектные файлы можно перемещать, то есть код внутри них может быть загружен в память в любом месте. Исполняемые файлы перечислены как общие объекты, поскольку они были созданы компоновщиком из объектных файлов таким образом, что они унаследовали эту возможность.

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

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

Если мы скомпилируем нашу программу с помощью компилятора gcc и предоставим параметр -no-pie, мы сгенерируем обычный исполняемый файл.

Параметр -o (выходной файл) позволяет нам предоставить имя для нашего исполняемого файла:

gcc -o hello -no-pie hello.c

Мы будем использовать файл в новом исполняемом файле и посмотреть, что изменилось:

file hello

Размер исполняемого файла то же самое, что и раньше (17 КБ):

ls -hl hello

Теперь двоичный файл идентифицируется как стандартный исполняемый файл. Мы делаем это только в демонстрационных целях. Если вы компилируете приложения таким образом, вы потеряете все преимущества ASMR.

Почему исполняемый файл такой большой?

Наш пример приветственной программы имеет размер 17 КБ, поэтому ее сложно назвать большой, но все относительно. Исходный код составляет 120 байт:

cat hello.c

Что заставляет двоичный файл увеличиваться в объеме, если все, что он делает, — это выводит одну строку в окно терминала? Мы знаем, что есть заголовок ELF, но для 64-битного двоичного файла это всего 64 байта. Ясно, что это должно быть что-то еще:

ls -hl hello

Давайте просканируем двоичный файл с помощью команды strings в качестве простого первого шага, чтобы обнаружить, что внутри него. Мы разделим это на less:

strings hello | less

В двоичном файле есть много строк, помимо «Hello, Geek world!» из нашего исходного кода. Большинство из них являются метками для областей в двоичном файле, а также именами и связующей информацией общих объектов. К ним относятся библиотеки и функции в этих библиотеках, от которых зависит двоичный файл.

Команда ldd показывает нам зависимости общих объектов двоичного файла:

ldd hello

В выходных данных три записи, две из которых включают путь к каталогу (первая не содержит):

  • linux-vdso.so: Virtual Dynamic Shared Object (VDSO ) — это механизм ядра, который позволяет двоичному файлу пространства пользователя получать доступ к набору процедур пространства ядра. Это позволяет избежать накладных расходов на переключение контекста из пользовательского режима ядра. Общие объекты VDSO придерживаются формата исполняемых и связываемых файлов (ELF), что позволяет им динамически связываться с двоичным файлом во время выполнения. VDSO выделяется динамически и использует ASMR. Возможность VDSO предоставляется стандартной библиотекой GNU C, если ядро ​​поддерживает схему ASMR.
  • libc.so.6: общий объект библиотеки GNU C.
  • / lib64 / ld -linux-x86-64.so.2: Это динамический компоновщик, который хочет использовать двоичный файл. Динамический компоновщик опрашивает двоичный файл, чтобы определить, какие у него зависимости. Он запускает эти общие объекты в память. Он подготавливает двоичный файл к запуску и может найти и получить доступ к зависимостям в памяти. Затем он запускает программу.

Заголовок ELF

Мы можем исследовать и декодировать заголовок ELF, используя утилиту readelf и параметр -h (заголовок файла):

readelf -h hello

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

Первый байт всех двоичных файлов ELF установлен в шестнадцатеричное значение 0x7F. Следующие три байта устанавливаются в 0x45, 0x4C и 0x46. Первый байт — это флаг, который идентифицирует файл как двоичный файл ELF. Чтобы сделать это кристально ясным, следующие три байта обозначают «ELF» в ASCII:

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

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

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

hexdump -C -n 8 hello

objdump и подробное представление

Если вы хотите увидеть мельчайшие детали, вы можете использовать команду objdump с параметром -d (дизассемблировать):

objdump -d привет | less

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

Это полезно только в том случае, если вы умеете читать на ассемблере или вам интересно, что происходит за кулисами. Выходных данных много, поэтому мы сократили его по конвейеру.

Компиляция и компоновка

Есть много способов скомпилировать двоичный файл. Например, разработчик решает, включать ли отладочную информацию. Способ связывания двоичного файла также играет роль в его содержимом и размере. Если двоичные ссылки совместно используют объекты как внешние зависимости, он будет меньше, чем тот, на который зависимости статически связываются.

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

Как заглянуть внутрь двоичных файлов из командной строки Linux

Tags:

Напишите пару строк: