Исследование и дизассемблирование файлов

Формат COFF-файла

Последнее обновление: 27.07.2023

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

Существуют различные форматы объектных файлов, но один из наиболее распространенных и популярных - формат COFF (Common Object File Format)

Формат объектного файла COFF

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

В дополнение к машинному коду и двоичным данным исполняемые файлы содержат другие метаданные, в том числе информацию об отладке, информацию о компоновке динамически подключаемых библиотек и сведения о том, как операционная система должна загружать различные разделы файла в память. В зависимости от процессора и ОС исполняемые файлы могут также содержать информацию о перемещениях/релокациях, чтобы ОС могла исправлять абсолютные адреса при загрузке файла в память. Обычно объектные и исполнимые файлы содержат одинаковую информацию и поэтому их форматы часто во многом совпадают. Например, Linux использует формат ELF (Executable and Linkable Format), Windows - формат PE, который во многом аналогичен формату COFF.

Заголовок файла COFF

Вначале каждого файла COFF идет заголовок файла COFF. На ОС Windows и Linux он имеет следующую структуру:

// версия для Windows в winnt.h
typedef struct _IMAGE_FILE_HEADER {
    WORD Machine;
    WORD NumberOfSections;
    DWORD TimeDateStamp;
    DWORD PointerToSymbolTable;
    DWORD NumberOfSymbols;
    WORD SizeOfOptionalHeader;
    WORD Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

// версия для Linux в coff.h
struct COFF_filehdr {
    char f_magic[2]; /* число magic number */
    char f_nscns[2]; /* количество разделов */
    char f_timdat[4]; /* время и дата */
    char f_symptr[4]; /* указатель на таблицу символов symtab */
    char f_nsyms[4]; /* число записей в таблице символов symtab*/
    char f_opthdr[2]; /* размер дополнительного заголовка - sizeof(optional hdr) */
    char f_flags[2]; /* флаги */
};

Основные элементы заголовка файла:

  • f_magic/Machine

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

    Значение

    Описание

    0x14c

    Intel 386

    0x8664

    x86-64 / AMD AMD64

    0x162

    MIPS R3000

    0x168

    MIPS R10000

    0x169

    MIPS little endian WCI v2

    0x183

    old Alpha AXP

    0x184

    Alpha AXP

    0x1a2

    Hitachi SH3

    0x1a3

    Hitachi SH3 DSP

    0x1a6

    Hitachi SH4

    0x1a8

    Hitachi SH5

    0x1c0

    ARM little endian

    0x1c2

    Thumb

    0x1c4

    ARMv7

    0x1d3

    Matsushita AM33

    0x1f0

    PowerPC little endian

    0x1f1

    PowerPC with floating-point support

    0x200

    Intel IA64

    0x266

    MIPS16

    0x268

    Motorola 68000 series

    0x284

    Alpha AXP 64-bit

    0x366

    MIPS with FPU

    0x466

    MIPS16 with FPU

    0xebc

    EFI bytecode

    0x9041

    Mitsubishi M32R little endian

    0xaa64

    ARM64 little endian

    0xc0ee

    CLR pure MSIL

  • f_nscns/NumberOfSections

    Количество разделов в файле

  • f_timdat/TimeDateStamp

    Содержит метку времени в стиле Unix (количество секунд с 1 января 1970 г.), которая указывает на дату и время создания файла.

  • f_symptr/PointerToSymbolTable

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

  • f_nsyms/NumberOfSymbols

    Количество записей в таблице символов.

  • f_opthdr/SizeOfOptionalHeader

    Указывает на размер необязательного заголовка, который следует сразу за заголовком файла (то есть первый байт необязательного заголовка следует сразу за полем f_flags/Characteristics в структуре заголовка файла). Компоновщик или другая программа может использовать это значение, чтобы определить, где в файле заканчивается необязательный заголовок и начинаются заголовки разделов. Заголовки разделов следуют сразу за необязательным заголовком, но размер необязательного заголовка не является фиксированным. Различные реализации файла COFF могут иметь разные необязательные структуры заголовков. Если необязательный заголовок отсутствует в файле COFF, поле f_opthdr/SizeOfOptionalHeader будет содержать ноль, а заголовок первого раздела будет следовать сразу за заголовком файла.

  • f_flags/Characteristics

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

Дополнительный заголовок

Необязательный заголовок COFF содержит информацию, относящуюся к исполняемым файлам. Этот заголовок может отсутствовать, если файл содержит объектный код, который не является исполняемым. Данный необязательный заголовок всегда присутствует в файлах Linux COFF и Microsoft PE/COFF, даже если файл не является исполняемым. Структуры Windows и Linux для этого необязательного заголовка файла принимают следующие формы в C:

// Microsoft PE/COFF Optional Header (winnt.h)
typedef struct _IMAGE_OPTIONAL_HEADER {
 //
 // Стандартные поля
 //
 WORD Magic;
 BYTE MajorLinkerVersion;
 BYTE MinorLinkerVersion;
 DWORD SizeOfCode;
 DWORD SizeOfInitializedData;
 DWORD SizeOfUninitializedData;
 DWORD AddressOfEntryPoint;
 DWORD BaseOfCode;
 DWORD BaseOfData;
 //
 // Дополнительные поля для NT
 //
 DWORD ImageBase;
 DWORD SectionAlignment;
 DWORD FileAlignment;
 WORD MajorOperatingSystemVersion;
 WORD MinorOperatingSystemVersion;
 WORD MajorImageVersion;
 WORD MinorImageVersion;
 WORD MajorSubsystemVersion;
 WORD MinorSubsystemVersion;
 DWORD Win32VersionValue;
 DWORD SizeOfImage;
 DWORD SizeOfHeaders;
 DWORD CheckSum;
 WORD Subsystem;
 WORD DllCharacteristics;
 DWORD SizeOfStackReserve;
 DWORD SizeOfStackCommit;
 DWORD SizeOfHeapReserve;
 DWORD SizeOfHeapCommit;
 DWORD LoaderFlags;
 DWORD NumberOfRvaAndSizes;
 IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

// Linux/COFF Optional Header format (from coff.h)
typedef struct 
{
 char magic[2]; /* тип файла */
 char vstamp[2]; /* версия */
 char tsize[4]; /* размер раздела .text/.code */
 char dsize[4]; /* размер раздела .data с инициализированными данными */
 char bsize[4]; /* размер раздела .bss с неинициализированными данными */
 char entry[4]; /* точка вхожа */
 char text_start[4]; /* начало раздела .text */
 char data_start[4]; /* начало раздела .data */
} COFF_AOUTHDR;

Первое, на что следует обратить внимание, что эти структуры не идентичны. Версия для Microsoft содержит значительно больше информации, чем версия для Linux. Поле f_opthdr/SizeOfOptionalHeader существует в заголовке файла для определения фактического размера необязательного заголовка. Основные компоненты заголовка:

  • magic/Magic

    Предоставляет еще одно значение подписи для файла COFF. Это значение подписи определяет тип файла (то есть COFF), а не систему, в которой он был создан. Линкеры используют значение этого поля, чтобы определить, действительно ли они работают с COFF-файлом (а не с каким-то произвольным файлом, который может запутать компоновщика).

  • vstamp/MajorLinkerVersion/MinorLinkerVersion

    Указывает номер версии формата COFF, чтобы компоновщик, написанный для более старой версии формата файла, не пытался обрабатывать файлы, предназначенные для более новых компоновщиков.

  • tsize/SizeOfCode

    Содержит размер раздела кода (.code/.text). Если файл COFF содержит более одного раздела кода, значение этого поля не определено, хотя обычно оно определяет размер первого раздела кода

  • dsize/SizeOfInitializedData

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

  • bsize/SizeOfUninitializedData

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

  • entry/AddressOfEntryPoint

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

  • text_start/BaseOfCode

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

  • data_start/BaseOfData

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

Для хранения адреса начала раздела .bss с неинициализированными данными отдельного поля не требуется. Формат файла COFF предполагает, что загрузчик операционной системы автоматически выделяет память для раздела BSS, когда программа загружается в память. А некоторые компиляторы фактически объединяют разделы BSS и DATA вместе из соображений производительности.

Заголовки разделов

Заголовки разделов следуют за необязательным заголовком в файле COFF. В отличие от файла и дополнительных заголовков, файл COFF может содержать несколько заголовков разделов. Поле f_nscns/NumberOfSections в заголовке файла указывает точное количество заголовков разделов (и, следовательно, разделов) в COFF-файле.

Поскольку размер необязательного заголовка является переменным (и, по сути, может быть даже равен 0, если он отсутствует), вам нужно добавить поле f_opthdr/SizeOfOptionalHeader в заголовок файла к размеру заголовка файла, чтобы получить начальное смещение заголовка первого раздела.

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

Определения структур на языке C для заголовков разделов Windows и Linux:

// определение для Windows  (из файла winnt.h)
typedef struct _IMAGE_SECTION_HEADER {
    BYTE Name[IMAGE_SIZEOF_SHORT_NAME];
    union {
        DWORD PhysicalAddress;
        DWORD VirtualSize;
    } Misc;
    DWORD VirtualAddress;
    DWORD SizeOfRawData;
    DWORD PointerToRawData;
    DWORD PointerToRelocations;
    DWORD PointerToLinenumbers;
    WORD NumberOfRelocations;
    WORD NumberOfLinenumbers;
    DWORD Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

// Определение для Linux (из файла coff.h)
struct COFF_scnhdr 
{
    char s_name[8]; /* название раздела */
    char s_paddr[4]; /* физический адрес */
    char s_vaddr[4]; /* виртуальный адрес */
    char s_size[4]; /* размер раздела */
    char s_scnptr[4]; /* указатель файла на непосредственные данные раздела */
    char s_relptr[4]; /* указатель файла на релокацию */
    char s_lnnoptr[4]; /* указатель файла на номера строк */
    char s_nreloc[2]; /* количество записей о релокации */
    char s_nlnno[2]; /* количество записей номеров строк */
    char s_flags[4]; /* флаги */
};

Компоненты заголовка раздела

  • s_name/Name

    хранит имя раздела. Как видно из определения Linux, это поле ограничено восемью символами, и, соответственно, имена разделов будут иметь длину не более восьми символов. (Обычно, если в исходном файле указано более длинное имя, компилятор/ассемблер усекает его до 8 символов при создании COFF-файла.) Если длина имени раздела составляет ровно восемь символов, эти восемь символов займут все 8 байтов этого поля, нулевой байт, который обычно служит в качестве завершения строки, не будет добавляться. Если имя раздела короче восьми символов, за именем будет следовать концевой нулевой байт.

    Значение этого поля часто имеет вид .text, CODE, .data или DATA. Но стоит учитывать, что имя не определяет тип раздела. Так, можно создать раздел .code/.text и назвать его "DATA", либо создать раздел данных (.data) и назвать его ".text" или "CODE". Поле s_flags/Characteristics определяет фактический тип этого раздела.

  • s_paddr/PhysicalAddress/VirtualSize

    Редко используется большинством инструментов. В Unix-подобных операционных системах (таких как Linux) для этого поля обычно устанавливается то же значение, что и для поля VirtualAddress. Различные инструменты Windows устанавливают для этого поля разные значения (включая ноль).

  • s_vaddr/VirtualAddress

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

  • s_size/SizeOfRawData

    Хранит размер раздела в байтах.

  • s_scnptr/PointerToRawData

    Хранит смещение файла до начала данных раздела

  • s_reptr/PointerToRelocations

    Хранит смещение файла до списка релокаций для этого раздела.

  • s_lnnoptr/PointerToLinenumbers

    Хранит смещение файла к записям номеров строк для текущего раздела.

  • s_nreloc/NumberOfRelocations

    Хранит количество записей перемещения для этого раздела.

  • s_nlnno/NumberOfLinenumbers

    Хранит количество записей номеров строк для раздела. Информация о номере строки используется отладчиками

  • s_flags/Characteristics

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

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850