Тарифы Услуги Сим-карты

Структура исполняемых файлов в ос. Cтруктура файловой системы Linux

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;
Я лишь сухо опишу данные поля, т.к. названия интуитивно понятные и представляют из себя непосредственные значения, а не VA, RVA, RAW и прочие страшные интригующие штуки, о которых пока, мы только слышали от старых пиратов. Хотя с RAW мы уже сталкивались - это как раз смещения относительно начала файла (их ещё называют сырыми указателями или file offset). То есть если мы имеем RAW адрес, это значит что нужно шагнуть от начала файла на RAW позиций (ptrFile + RAW). После можно начинать читать значения. Ярким примером данного вида является e_lfnew - что мы рассмотрели выше в Dos заголовке.

*Machine : WORD - это число (2 байта) задаёт архитектуру процессора, на которой данное приложение может выполняться.
NumberOfSections : DWORD - количество секций в файле. Секции (в дальнейшем будем называть таблицей секций) следуют сразу после заголовка (PE-Header). В документации сказано что количество секций ограничено числом 96.
TimeDateStamp : WORD - число хранящее дату и время создания файла.
PointerToSymbolTable : DWORD - смещение (RAW) до таблицы символов, а SizeOfOptionalHeader - это размер данной таблицы. Данная таблица призвана служить для хранения отладочной информации, но отряд не заметил потери бойца с самого начала службы. Чаще всего это поле зачищается нулями.
SIzeOfOptionHeader : WORD - размер опционального заголовка (что следует сразу за текущим) В документации указано, что для объектного файла он устанавливается в 0…
*Characteristics : WORD - характеристики файла.

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

Оставим этот остров! Нам нужно двигаться дальше. Ориентир - страна под названием Optional-Header.

“- Где карта, Билли? Мне нужна карта.”
(Остров сокровищ)

Optional-Header (IMAGE_OPTIONAL_HEADER)

Название сего материка заголовка не очень удачное. Этот заголовок является обязательным и имеет 2 формата PE32 и PE32+ (IMAGE_OPTIONAL_HEADER32 и IMAGE_OPTIONAL_HEADER64 соответственно). Формат хранится в поле Magic : WORD. Заголовок содержит необходимую информацию для загрузки файла. Как всегда :

IMAGE_OPTIONAL_HEADER

typedef struct _IMAGE_OPTIONAL_HEADER { WORD Magic; BYTE MajorLinkerVersion; BYTE MinorLinkerVersion; DWORD SizeOfCode; DWORD SizeOfInitializedData; DWORD SizeOfUninitializedData; DWORD AddressOfEntryPoint; DWORD BaseOfCode; DWORD BaseOfData; 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_OPTIONAL_HEADE R, *PIMAGE_OPTIONAL_HEADER;


* Как всегда, мы изучим только основные поля, которые имеют наибольшее влияние на представление о загрузке и того, как двигаться дальше по файлу. Давайте условимся - в полях данной структуры, содержаться значения с VA (Virtual address) и RVA (Relative virtual address) адресами. Это уже адреса не такие как RAW, и их нужно уметь читать (точнее считать). Мы непременно научимся это делать, но только для начала разберём структуры, которые идут друг за другом, чтобы не запутаться. Пока просто запомните - это адреса, которые после расчётов, указывают на определённое место в файле. Также встретится новое понятие - выравнивание. Его мы рассмотрим в купе с RVA адресами, т.к. эти они довольно тесно связаны.

AddressOfEntryPoint : DWORD - RVA адрес точки входа. Может указывать в любую точку адресного пространства. Для.exe файлов точка входа соответствует адресу, с которого программа начинает выполняться и не может равняться нулю!
BaseOfCode : DWORD - RVA начала кода программы (секции кода).
BaseOfData : DWORD - RVA начала кода программы (секции данных).
ImageBase : DWORD - предпочтительный базовый адрес загрузки программы. Должен быть кратен 64кб. В большистве случаев равен 0x00400000.
SectionAligment : DWORD - размер выравнивания (байты) секции при выгрузке в виртуальную память.
FileAligment : DWORD - размер выравнивания (байты) секции внутри файла.
SizeOfImage : DWORD - размер файла (в байтах) в памяти, включая все заголовки. Должен быть кратен SectionAligment.
SizeOfHeaders : DWORD - размер всех заголовков (DOS, DOS-Stub, PE, Section) выравненный на FileAligment.
NumberOfRvaAndSizes : DWORD - количество каталогов в таблице директорий (ниже сама таблица). На данный момент это поле всегда равно символической константе IMAGE_NUMBEROF_DIRECTORY_ENTRIES, которая равна 16-ти.
DataDirectory : IMAGE_DATA_DIRECTORY - каталог данных. Проще говоря это массив (размером 16), каждый элемент которого содержит структуру из 2-ух DWORD-ых значений.

Рассмотрим что из себя представляет структура IMAGE_DATA_DIRECTORY :

Typedef struct _IMAGE_DATA_DIRECTORY { DWORD VirtualAddress; DWORD Size; } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
Что мы имеем? Мы имеем массив из 16 элементов, каждый элемент которого, содержит адрес и размер (чего? как? зачем? всё через минуту). Встаёт вопрос чего именно это характеристики. Для этого, у microsoft имеется специальные константы для соответствия. Их можно увидеть в самом конце описания структуры. А пока:

// Directory Entries #define IMAGE_DIRECTORY_ENTRY_EXPORT 0 // Export Directory #define IMAGE_DIRECTORY_ENTRY_IMPORT 1 // Import Directory #define IMAGE_DIRECTORY_ENTRY_RESOURCE 2 // Resource Directory #define IMAGE_DIRECTORY_ENTRY_EXCEPTION 3 // Exception Directory #define IMAGE_DIRECTORY_ENTRY_SECURITY 4 // Security Directory #define IMAGE_DIRECTORY_ENTRY_BASERELOC 5 // Base Relocation Table #define IMAGE_DIRECTORY_ENTRY_DEBUG 6 // Debug Directory // IMAGE_DIRECTORY_ENTRY_COPYRIGHT 7 // (X86 usage) #define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE 7 // Architecture Specific Data #define IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8 // RVA of GP #define IMAGE_DIRECTORY_ENTRY_TLS 9 // TLS Directory #define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10 // Load Configuration Directory #define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 11 // Bound Import Directory in headers #define IMAGE_DIRECTORY_ENTRY_IAT 12 // Import Address Table #define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT 13 // Delay Load Import Descriptors #define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14 // COM Runtime descriptor
Ага! Мы видим, что каждый элемент массива, отвечает за прикреплённую к нему таблицу. Но увы и ах, пока эти берега недосягаемы для нас, т.к. мы не умеем работаться с VA и RVA адресами. А для того чтобы научиться, нам нужно изучить что такое секции. Именно они расскажут о своей структуре и работе, после чего станет понятно для чего нужны VA, RVA и выравнивания. В рамках данной статьи, мы затронем только экспорт и иморт. Предназначение остальных полей можно найти в оф. документации, либо в книжках. Так вот. Собственно поля:

VirtualAddress : DWORD - RVA на таблицу, которой соответствует элемент массива.
Size : DWORD - размер таблицы в байтах.

Итак! Чтобы добраться до таких экзотических берегов как таблицы импорта, экспорта, ресурсов и прочих, нам необходимо пройти квест с секциями. Ну что ж юнга, взглянем на общую карту, определим где мы сейчас находимся и будем двигаться дальше:

А находимся мы не посредственно перед широкими просторами секций. Нам нужно непременно выпытать что они таят и разобраться уже наконец с другим видом адресации. Нам хочется настоящих приключений! Мы хотим поскорее отправится к таким республикам как таблицы импорта и экспорта. Старые пираты говаривают, что не каждый смог до них добраться, а тот кто добрался вернулся -с золотом и женщинами со священными знаниями об океане. Отчаливаем и держим путь на Section header.

“- Ты низложен, Сильвер! Слезай с бочки!”
(Остров сокровищ)

Section-header (IMAGE_SECTION_HEADER)


Сразу за массивом DataDirectory друг за другом идут секции. Таблица секций представляет из себя суверенное государство, которое делится на NumberOfSections городов. Каждый город имеет своё ремесло, свои права, а также размер в 0x28 байт. Количество секций указано в поле NumberOfSections , что хранится в File-header-е. Итак, рассмотрим структуру :

Typedef struct _IMAGE_SECTION_HEADER { BYTE 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;
Name : BYTE - название секции. На данный момент имеет длину в 8 символов.
VirtualSize : DWORD - размер секции в виртуальной памяти.
SizeOfRawData : DWORD - размер секции в файле.
VirtualAddress : DWORD - RVA адрес секции.
SizeOfRawData : DWORD - размер секции в файле. Должен быть кратен FileAligment .
PointerToRawData : DWORD - RAW смещение до начала секции. Также должен быть кратен FileAligment
Characteristics : DWORD - атрибуты доступа к секции и правила для её загрузки в вирт. память. Например атрибут для определения содержимого секции (иниц. данные, не инициал. данные, код). Или атрибуты доступа - чтение, запись, исполнение. Это не весь их спектр. Характеристики задаются константами из того-же WINNT.h, которые начинаются с IMAGE_SCN_. Более подробно ознакомится с атрибутами секций можно . Также хорошо описаны атрибуты в книгах Криса Касперски - список литературы в конце статьи.

По поводу имени следует запомнить следующее - секция с ресурсам, всегда должна иметь имя.rsrc. В противном случае ресурсы не будут подгружены. Что касается остальных секций - то имя может быть любым. Обычно встречаются осмысленные имена, например.data, .src и т.д… Но бывает и такое:

Секции, это такая область, которая выгружается в виртуальную память и вся работа происходит непосредственно с этими данными. Адрес в виртуальной памяти, без всяких смещений называется Virtual address, сокращённо VA. Предпочитаемый адрес для загрузки приложения, задаётся в поле ImageBase . Это как точка, с которой начинается область приложения в виртуальной памяти. И относительно этой точки отсчитываются смещения RVA (Relative virtual address). То есть VA = ImageBase + RVA; ImageBase нам всегда известно и получив в своё распоряжение VA или RVA, мы можем выразить одно через другое.

Тут вроде освоились. Но это же виртуальная память! А мы то находимся в физической. Виртуальная память для нас сейчас это как путешествие в другие галактики, которые мы пока можем лишь только представлять. Так что в виртуальную память нам на данный момент не попасть, но мы можем узнать что там будет, ведь это взято из нашего файла.

Выравнивание


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

Как можно заметить, секция выгружается в память не по своему размеру. Здесь используются выравнивания. Это значение, которому должны быть кратен размер секции в памяти. Если посмотреть на схему, то мы увидим, что размер секции 0x28, а выгружается в размере 0x50. Это происходит из-за размера выравнивания. 0x28 “не дотягивает” до 0x50 и как следствие, будет выгружена секция, а остальное пространство в размере 0x50-0x28 занулится. А если размер секции был бы больше размера выравнивания, то что? Например sectionSize = 0x78, а sectionAligment = 0x50, т.е. остался без изменений. В таком случае, секция занимала бы в памяти 0xA0 (0xA0 = 0x28 * 0x04) байт. То есть значение которое кратно sectionAligment и полностью кроет sectionSize . Следует отметить, что секции в файле выравниваются аналогичным образом, только на размер FileAligment . Получив необходимую базу, мы можем разобраться с тем, как конвертировать из RVA в RAW.

“Здесь вам не равнина, здесь климат иной.”
(В.С. Высоцкий)

Небольшой урок арифметики


Перед тем как начать выполнение, какая то часть программы должна быть отправлена в адресное пространство процессора. Адресное пространство - это объём физически адресуемой процессором оперативной памяти. “Кусок” в адресном пространстве, куда выгружается программа называется виртуальным образом (virtual image). Образ характеризуется адресом базовой загрузки (Image base) и размером (Image size). Так вот VA (Virtual address) - это адрес относительно начала виртуальной памяти, а RVA (Relative Virtual Address) относительно места, куда была выгружена программа. Как узнать базовый адрес загрузки приложения? Для этого существует отдельное поле в опциональном заголовке под названием ImageBase . Это была небольшая прелюдия чтобы освежить в памяти. Теперь рассмотрим схематичное представление разных адресаций:

Дак как же всё таки прочитать информацию из файла, не выгружая его в виртуальную память? Для этого нужно конвертировать адреса в RAW формат. Тогда мы сможем внутри файла шагнуть на нужный нам участок и прочитать необходимые данные. Так как RVA - это адрес в виртуальной памяти, данные по которому были спроецированы из файла, то мы можем произвести обратный процесс. Для этого нам понадобится ключ девять на шестнадцать простая арифметика. Вот несколько формул:

VA = ImageBase + RVA; RAW = RVA - sectionRVA + rawSection; // rawSection - смещение до секции от начала файла // sectionRVA - RVA секции (это поле хранится внутри секции)
Как видно, чтобы высчитать RAW, нам нужно определить секцию, которой принадлежит RVA. Для этого нужно пройти по всем секциям и проверить следующие условие:

RVA >= sectionVitualAddress && RVA < ALIGN_UP(sectionVirtualSize, sectionAligment) // sectionAligment - выравнивание для секции. Значение можно узнать в Optional-header. // sectionVitualAddress - RVA секции - хранится непосредственно в секции // ALIGN_UP() - функция, определяющая сколько занимает секция в памяти, учитывая выравнивание
Сложив все пазлы, получим вот такой листинг:

Typedef uint32_t DWORD; typedef uint16_t WORD; typedef uint8_t BYTE; #define ALIGN_DOWN(x, align) (x & ~(align-1)) #define ALIGN_UP(x, align) ((x & (align-1))?ALIGN_DOWN(x,align)+align:x) // IMAGE_SECTION_HEADER sections; // init array sections int defSection(DWORD rva) { for (int i = 0; i < numberOfSection; ++i) { DWORD start = sections[i].VirtualAddress; DWORD end = start + ALIGN_UP(sections[i].VirtualSize, sectionAligment); if(rva >= start && rva < end) return i; } return -1; } DWORD rvaToOff(DWORD rva) { int indexSection = defSection(rva); if(indexSection != -1) return rva - sections.VirtualAddress + sections.PointerToRawData; else return 0; }
*Я не стал включать в код объявление типа, и инициализацию массива, а лишь предоставил функции, которые помогут при расчёте адресов. Как видите, код получился не очень сложным. Разве что малость запутанным. Это проходит… если уделить ещё немного времени колупанию в.exe через дизассемблер.

УРА! Разобрались. Теперь мы можем отправится в края ресурсов, библиотек импорта и экспорта и вообще куда душа желает. Мы ведь только что научились работать с новым видом адресации. В путь!

“-Неплохо, неплохо! Всё же они получили свой паёк на сегодня!”
(Остров сокровищ)

Export table


В самом первом элементе массива DataDirectory хранится RVA на таблицу экспорта, которая представлена структурой IMAGE_EXPORT_DIRECTORY. Эта таблица свойственна файлам динамических библиотек (.dll). Основной задачей таблицы является связь экспортируемых функций с их RVA. Описание представлено в оф. спецификикации :

Typedef struct _IMAGE_EXPORT_DIRECTORY { DWORD Characteristics; DWORD TimeDateStamp; WORD MajorVersion; WORD MinorVersion; DWORD Name; DWORD Base; DWORD NumberOfFunctions; DWORD NumberOfNames; DWORD AddressOfFunctions; DWORD AddressOfNames; DWORD AddressOfNameOrdinals; } IMAGE_EXPORT_DIRECTORY,*PIMAGE_EXPORT_DIRECTORY;
Эта структура содержит три указателя на три разные таблицы. Это таблица имён (функций) (AddressOfNames ), ординалов(AddressOfNamesOrdinals ), адресов(AddressOfFunctions ). В поле Name хранится RVA имени динамической библиотеки. Ординал - это как посредник, между таблицей имён и таблицей адресов, и представляет из себя массив индексов (размер индекса равен 2 байта). Для большей наглядности рассмотрим схему:

Рассмотрим пример. Допустим i-ый элемент массива имён указывает на название функции. Тогда адрес этой функции можно получить обратившись к i-му элементу в массиве адресов. Т.е. i - это ординал.

Внимание! Если вы взяли к примеру 2-ой элемент в таблице ординалов, это не значит 2 - это ординал для таблиц имён и адресов. Индексом является значение, хранящееся во втором элементе массива ординалов.

Количество значений в таблицах имён (NumberOfNames ) и ординалов равны и не всегда совпадают с количеством элементов в таблице адресов (NumberOfFunctions ).

“За мной пришли. Спасибо за внимание. Сейчас, должно быть, будут убивать!”
(Остров сокровищ)

Import table


Таблица импорта неотъемлемая часть любого приложения, которая использует динамические библиотеки. Данная таблица помогает соотнести вызовы функций динамических библиотек с соответствующими адресами. Импорт может происходить в трёх разных режимах: стандартный, связывающем (bound import) и отложенном (delay import). Т.к. тема иморта достаточно многогранна и тянет на отдельную статью, я опишу только стандартный механизм, а остальные опишу только «скелетом».

Стандартный импорт - в DataDirectory под индексом IMAGE_DIRECTORY_ENTRY_IMPORT(=1) хранится таблица импорта. Она представляет собой массив из элементов типа IMAGE_IMPORT_DESCRIPTOR. Таблица импорта хранит (массивом) имена функций/ординалов и в какое место загрузчик должен записать эффективный адрес этой функций. Этот механизм не очень эффективен, т.к. откровенно говоря всё сводится к перебору всей таблицы экспорта для каждой необходимой функции.

Bound import - при данной схеме работы в поля (в первом элементе стандартной таблицы импорта) TimeDateStamp и ForwardChain заносится -1 и информация о связывании хранится в ячейке DataDirectory с индексом IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT(=11). То есть это своего рода флаг загрузчику о том что нужно использовать bound import. Так же для «цепочки bound импорта» фигурируют свои структуры. Алгоритм работы заключается в следующем - в виртуальную память приложения выгружается необходимая библиотека и все необходимые адреса «биндятся» ещё на этапе компиляции. Из недостатоков можно отметить то, что при перекомпиляции dll, нужно будет перекомпилировать само приложение, т.к. адреса функций будут изменены.

Delay import - при данном методе подразумевается что.dll файл прикреплён к исполняемому, но в память выгружается не сразу (как в предыдущих двух методах), а только при первом обращении приложения к символу (так называют выгружаемые элементы из динамических библиотек). То есть программа выполняется в памяти и как только процесс дошёл до вызова функции из динамической библиотеки, то вызывается специальный обработчик, который подгружает dll и разносит эффективные адреса её функций. За отложенным импортом загрузчик обращается к DataDirectory (элемент с номером 15).

Малость осветив методы импорта, перейдём непосредственно к таблице импорта.

“-Это моряк! Одежда у него была морская. - Да ну? А ты думал найти здесь епископа?”
(Остров сокровищ - Джон Сильвер)

Import-descriptor (IMAGE_IMPORT_DESCRIPTOR)


Для того чтобы узнать координаты таблицы импорта, нам нужно обратиться к массиву DataDirectory . А именно к элементу IMAGE_DIRECTORY_ENTRY_IMPORT (=1). И прочитать RVA адрес таблицы. Вот общая схема пути, который требуется проделать:

Затем из RVA получаем RAW, в соответствии с формулами приведёнными выше, и затем “шагаем” по файлу. Теперь мы впритык перед массивом структур под названием IMAGE_IMPORT_DESCRIPTOR. Признаком конца массива служит “нулевая” структура.

Typedef struct _IMAGE_IMPORT_DESCRIPTOR { union { DWORD Characteristics; DWORD OriginalFirstThunk; } DUMMYUNIONNAME; DWORD TimeDateStamp; DWORD ForwarderChain; DWORD Name; DWORD FirstThunk; } IMAGE_IMPORT_DESCRIPTOR,*PIMAGE_IMPORT_DESCRIPTOR;
Я не смог выудить на msdn ссылку на описание структуры, но вы можете наблюдать её в файле WINNT.h. Начнём разбираться.

OriginalFirstThunk : DWORD - RVA таблицы имён импорта (INT).
TimeDateStamp : DWORD - дата и время.
ForwarderChain : DWORD - индекс первого переправленного символа.
Name : DWORD - RVA строки с именем библиотеки.
FirstThunk : DWORD - RVA таблицы адресов импорта (IAT).

Тут всё несколько похоже на экспорт. Также таблица имён (INT) и и тоже рубище на нём адресов (IAT). Также RVA имени библиотеки. Только вот INT и IAT ссылаются на массив структур IMAGE_THUNK_DATA. Она представлена в двух формах - для 64- и для 32-ый систем и различаются только размером полей. Рассмотрим на примере x86:

Typedef struct _IMAGE_THUNK_DATA32 { union { DWORD ForwarderString; DWORD Function; DWORD Ordinal; DWORD AddressOfData; } u1; } IMAGE_THUNK_DATA32,*PIMAGE_THUNK_DATA32;
Важно ответить, что дальнейшие действия зависят от старшего бита структуры. Если он установлен, то оставшиеся биты представляют из себя номер импортируемого символа (импорт по номеру). В противном случае (старший бит сброшен) оставшиеся биты задают RVA импортируемого символа (импорт по имени). Если мы имеем импорт по имени, то указатель хранит адрес на следующую структуру:

Typedef struct _IMAGE_IMPORT_BY_NAME { WORD Hint; BYTE Name; } IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
Здесь Hint - это номер функции, а Name - имя.

Для чего это всё? Все эти массивы, структуры… Рассмотрим для наглядности замечательную схему с

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

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

В форме файлов могут хранятся исходные тексты программ, готовые к выполнению программы, документы, графические изображения и любые другие данные. По типу организации и содержанию файлы разделяются на две категории – текстовые и двоичные (бинарные). Текстовые файлы в соответствии с назначением хранят строки символов интерпретируемые как тексты. Исполняемые файлы состоят из программных кодов готовых к исполнению программ.

Уникальные имена обеспечивают возможность упорядочивания файлов и обеспечения доступа к ним операционной систем и других программ. Имя файла состоит из двух частей, разделенных точкой: собственно имя файла и расширение ,определяющееего тип (программа, данные и т. д.).Имяфайлу присваивает пользователь (иногда система по умолчанию). Тип файла обычно задается программой автоматически при егосоздании, что позволяет в большинстве случаев автоматизировать запуск программ. Например, .com, .exe – исполняемые файлы (программы), .txt, .rtf . doc – текстовые файлы, .pas – исходный текст программы, написанной на языке Pascal .

Для упорядочения размещения файлов на дисках их имена регистрируются в специальных файлах – каталогах (в современных ОС эти файлы называют папками) . Каталог это файл–таблица (хранящаяся на том же диске, где и файлы), в которой хранятся имена файлов, сведения об их размере, времени последнего обновления, атрибуты (свойства) файла и т.д. Если в каталоге хранится имя файла, то нередко говорят, что файл «находится» в данном каталоге. В действительности файл располагается (сохраняется) в некоторой области памяти на диске компьютера, зачастую в виде нескольких частей, фрагментов на разных дорожках и дисках пакета (на свободных участках носителя). Соответствующая информация содержатся в каталоге.

На каждом диске может быть много каталогов – их число определяется целесообразностью и ограничивается только емкостью диска. Это касается и количества файлов в каталоге. Все современные дисковые операционные системы обеспечивают создание файловой системы, предназначенной для упорядочения хранения данных и обеспечения доступа к ним. Принцип организации файловой системы – табличный. Поверхность жесткого диска рассматривается как трехмерная матрица, измерениями которой являются номера поверхности, цилиндра и сектора. Под цилиндром понимается совокупность всех дорожек, принадлежащих разным поверхностям и находящихся на равном удалении от оси вращения. Данные о том, в каком месте диска записан тот или иной файл, хранятся в системной области диска в специальных таблицах размещения файлов (FAT-таблицах).

Порядок хранения файлов на диске определяется организацией файловой системы (организации каталогов и способа описания в них размещения и атрибутов файлов).

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

Начальный, корневой каталог содержит вложенные каталоги 1-го уровня, в свою очередь в каждом из них бывают вложенные каталоги 2-го уровня и т. д. Каждый каталог имеет имя (без расширения), и он может быть зарегистрирован в другом, родительском каталоге. Необходимо отметить, что в каталогах всех уровней могут храниться не только каталоги, но и файлы.

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

К функциям обслуживания файловой системы относятся следующие операции, выполняемые под управлением операционной системы:

    создание файлов и присвоение им имен;

    создание каталогов и присвоение им имен;

    переименование файлов и каталогов;

    копирование и перемещение файлов между дисками компьютера и между каталогами одного диска;

    удаление файлов и каталогов;

    навигация по файловой структуре с целью доступа к заданному файлу или каталогу;

    управление атрибутами файла.

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

Исполняемый файл на диске и модуль, получаемый после загрузки, очень похожи. Загрузчик попросту использует отображенные в память файлы Win32, чтобы загрузить соответствующие части РЕ-файла в адресное пространство программы. Так же просто загружается и DLL. После того как ЕХЕ или.DLL модуль загружены, Windows обращается с ними так же, как и с другими отображенными в память файлами.

В Win32, напротив, память, используемая под программы, данные, ресурсы, таблицы ввода, таблицы вывода и другие элементы, представляет собой один сплошной линейный массив адресного пространства. Все, что достаточно знать в этом случае, – это адрес, в который загрузчик отобразил в памяти исполняемый файл. Тогда для того, чтобы найти лю¬бой элемент модуля, достаточно следовать указателям, которые хранятся как часть отображения.

Заголовок MS-DOS

Заголовок MS-DOS занимает первые 64 байта PE файла. Структура, представляющая содержание MS-DOS-заголовка следующая:


typedef struct _IMAGE_DOS_HEADER { //DOS .EXE заголовок
USHORT e_magic; //MZ
USHORT e_cblp; //Байты на последней
//странице файла
USHORT e_cp; //Страницы в файле
USHORT e_crlc; //Настройки
USHORT e_cparhdr; //Размер заголовка в
//параграфах
USHORT e_minalloc; //Минимальная выделенная память
USHORT e_maxalloc; //Максимальная выделенная память
USHORT e_ss; //Начальное (относительное)
//значение SS
USHORT e_sp; //Начальное значение SP
USHORT e_csum; //Контрольная сумма
USHORT e_ip; //Начальное значение IP
USHORT e_cs; //Начальное (относительное)
//значение CS
USHORT e_lfarlc; //адрес Файла таблицы настройки
USHORT e_ovno; //Оверлейный номер
USHORT e_res ; //Зарезервированные слова
USHORT e_oemid; //OEM идентификатор (для
//e_oeminfo)
USHORT e_oeminfo; //OEM информация; e_oemid
//специфический
USHORT e_res2 ; //Зарезервированные слова
LONG e_lfanew; //адрес смещения PE-заголовка
} IMAGE_DOS_HEADER, * PIMAGE_DOS_HEADER;

Основной заголовок РЕ-файла представляет структуру типа IMAGE_NT_НEADERS, определенную в файле WINNT.H. Структура IMAGE_NT_HEADERS в памяти – это то, что Windows использует в качестве своей базы данных модуля в памяти. Каждый загруженный ЕХЕ-файл или DLL представлены в Windows структурой IMAGE_NT_HEADERS. Эта структура состоит из двойного слова и двух подструктур, как показано ниже:

DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER OptionalHeader;

PE File Signature

Поле Signature (сигнатура – подпись), представленное как ASCII код, – это РЕ\0\0 (два нулевых байта после РЕ). Если поле e_lfanew в заголовке DOS указало вместо обозначения РЕ обозначение NE в этом месте, значит, вы работаете с файлом Win16 NE. Аналогично, если указано обозначение LE в поле Signature, то это файл VxD (VirtualDeviceDriver – драйвер виртуального устройства). Обозначение LX указывает на файл старой соперницы Windows 95 – OS/2.

PE File Header

За двойным словом – сигнатурой РЕ, в заголовке РЕ-файла следует структура типа IMAGE_FILE_HEADER. Поля этой структуры содержат только самую общую информацию о файле.
Далее приводятся поля IMAGE_FILE_HEADER:

typedef struct _IMAGE_FILE_HEADER
{
USHORT Machine;
USHORT NumberOfSections;
ULONG TimeDateStamp;
ULONG PointerToSymbolTable;
ULONG NumberOfSymbols;
USHORT SizeOfOptionalHeader;
USHORT Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER

Machine – это центральный процессор, для которого предназначен файл. Определены следующие идентификаторы процессоров:

Intel I386 0xl4C
Intel I860 0xl4D
MIPS R3000 0х162
MIPS R4000 0х166
DEC Alpha AXP 0х184
Power PC 0x1F0 (little endian)
Motorola 68000 0х268
PA RISC 0х290 (Precision Architecture)

NumberOfSections – количество секций в ЕХЕ- или OBJ-файле.

TimeDateStamp – время, когда файл был создан компоновщиком (или компилятором, если это OBJ-файл). В этом поле указано количество секунд, истекших с 16:00 31.12.1969

PointerToSymbolTable – файловое смещение COFF-таблицы символов. Это поле используется только в OBJ- и РЕ-файлах с информацией COFF-отладчика. РЕ-файлы поддерживают разнообразные отладочные форматы, так что отладчики должны ссылаться ко входу IMAGE_DIRECTORY_ENTRY_DEBUG в каталоге данных.

NumberOfSymbols – количество символов в COFF-таблицс символов.

SizeOfOplionalHeader – размер необязательного заголовка, который может следован, за этой структурой. В исполняемых файлах – это размер структуры IMAGE_OPTIONAL_HEADER, которая следует за этой структурой.

Characteristics – флаги, содержащие информацию о файле. Здесь описываются некоторые важные поля.
0х0001 – файл не содержит перемещений
0х0002 – файл представляет исполняемое отображение (т.е. это не OBJ- или LIB-файл)
0х2000 – файл является библиотекой динамической компоновки (DLL), а не программой

PE File Optional Header

Третьим компонентом заголовка РЕ-файла является структура типа IMAGE_OPTIONAL_HEADER. Для РЕ-файлов эта часть является обязательной. Наиболее важными полями являются поля ImageBase и Subsystem.

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

Subsystem – тип подсистемы, которую данный исполняемый файл использует для своего пользовательского интерфейса. WINNT.H определяет следующие значения:
NATIVE = 1 – подсистема не требуется (например, для драйвера устройства)
WINDOWS_GUI = 2 – запускается в подсистеме Windows GUI
WINDOWS_GUI = 3 – запускается в подсистеме Windows character (терминальное приложение)
OS2_GUI = 5 – запускается в подсистеме OS/2 (только приложения OS/2 IJC)
POSIX_GUI = 7 – запускается в подсистеме Posix

Таблица секций

Сразу после заголовка РЕ-файла в памяти следует массив из 1MAGE_SECT10N_HEADER. Эта таблица. содержит информацию о каждой секции отображения. Количество элементов этого массива задается в заголовке РЕ-файла (поле IMAGE_NT_HEADER.FileHeader.NumberOfSections). Секции в отображении упорядочены по их стартовому адресу, а не в алфавитном порядке.
Каждый IMAGE_SECTION_HEADER представляет собой полную базу данных об одной секции файла ЕХЕ или OBJ \ и имеет следующий формат.

#define IMAGE_SIZEOF_SHORT_NAME 8
typedef struct _IMAGE_SECTION_HEADER
{
UCHAR Name;
union {
ULONG PhysicalAddress;
ULONG VirtualSize;
} Misc;
ULONG VirtualAddress;
ULONG SizeOfRawData;
ULONG PointerToRawData;
ULONG PointerToRelocations;
ULONG PointerToLinenumbers;
USHORT NumberOfRelocations;
USHORT NumberOfLinenumbers;
ULONG Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

Name – 8-байтовое имя в стандарте ANSI (не Unicode), которое именует секцию.

Misc – это поле имеет различные назначения в зависимости от того, встречается ли оно в ЕХЕ- или OBJ-файле. В ЕХЕ-файле оно содержит виртуальный размер секции программного кода или данных. В случае OBJ-файлов это поле указывает физический адрес секции.

VirtualAddress – в случае ЕХЕ-файлов это поле содержит RVA, куда загрузчик должен отобразить секцию. Средства Microsoft устанавливают по умолчанию RVA первой секции равным 0х101. Для объектных файлов это поле устанавливается в 0.

SizeOfRawData – в ЕХЕ-файлах это поле содержит размер секции, выровненный па ближайшую верхнюю границу размера файла.
PointerToRawData – файловое смещение участка, где находятся исходные данные для секции. Если пользователь сам отображает в мять РЕ- или COFF-файл (вместо того, чтобы доверить загрузку операционной системе), это поле важнее, чем в VirtualAddress.

PointerToRelocations – в объектных файлах это файловое смещение информации о поправках, которая следует за исходными данными для данной секции. В ЕХЕ-файлах это поле устанавливается в 0.

PointerToLinenumhers – файловое смещение таблицы номеров строк. Таблица номеров строк ставит в соответствие номера строк исходного файла адресам, по которым можно найти код, сгенерированный для данной строки. Обычно только секции с программным кодом (например, .text или CODE) имеют номера строк. В ЕХЕ-файлах номера строк собраны в конце файла после исходных данных для секций. В объектных файлах таблица номеров строк для секции следует за исходными данными секции и таблицей перемещений для этой секции.

NumberOfRelocations – количество перемещений в таблице поправок для данной секции (используется только в объектных файлах).
NumberOfLinenumbers – количество номеров строк в таблице номеров строк для данной секции.
Characteristics – набор флагов, которые указывают на атрибуты секции (программа/данные, предназначен для чтения, предназначен для записи и т.н.).

Часто встречающиеся секции

Секция.text (или CODE

В этой секции собран весь программный код общего назначения, генерируемый компилятором или ассемблером. Компоновщик объединяет все секции.text из различных объектных файлов в одну большую секцию.text в ЕХЕ-файле.

Секция.data (или DATA , если PE-файл создан Borland C++)

Инициализированные данные попадают в секцию.data. Инициализированные данные состоят из тех глобальных и статических переменных, которые были проинициализированы во время компиляции. Они также включают строковые литералы (например, строку "Hello World" в программе C/C++). Компоновщик объединяет все секции.data из разных объектных и LIB-файлов в одну секцию.data в ЕХЕ-файле. Локальные переменные расположены в стеке цепочки и не занимают места в секциях.data и.bss.

Секция.bss

В секции.bss хранятся неинициализированные статические и глобальные переменные. Компоновщик объединяет все сек¬ции.bss из разных объектных и LIB-файлов в одну секцию.bss в ЕХЕ-файле.

Секция.CRT

Еще одна секция для инициализированных данных, используемая библиотеками поддержки выполнения программы Microsoft C/C++. Данные из этой секции используются для таких це¬лей, как вызов конструкторов статических классов C++ перед вызовом main или WinMain.

Секция.rsrc

Секция.rsrc содержит ресурсы модуля.

Секция.idata

Секция.idata (или таблица импорта) содержит информацию о функциях (и данных), которые модуль импортирует из других DLL. Таблица импорта начинается с массива, состоящего из IMAGE_IMPORT_DESCRIPTOR. Каждый элемент (IMAGE_IMPORT_DESCRIPTOR) соответствует одной из DLL, с кото¬рой неявно связан данный РЕ-файл. Количество элементов в массиве нигде не учитывается. Вместо этого последняя структу¬ра массива IMAGE_IMPORT_DESCRIPTOR имеет поля, содержащие NULL.
Структура IMAGE_IMPORT_DESCRIPTOR имеет следующий формат

typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics;
DWORD OriginalFirstThunk;
};
DWORD TimeDateStamp;

DWORD ForwarderChain;
DWORD Name;
DWORD FirstThunk;
} IMAGE_IMPORT_DESCRIPTOR

Characteristics/OriginalFirstThunk – в этом поле содержится смещение (RVA) массива двойных слов. Каждое из этих двойных слов в действительности является объединением IMAGE_THUNK_DATA. Каждое двойное слово IMAGE_THUNK_DATA соответствует одной функции, импортируемой данным ЕХЕ-файлом или DLL.

TimeDateStamp – отметка о времени и дате, указывающая, когда был создан данный файл.
ForwarderChain – это поле имеет отношение к передаче, когда одна DLL передает ссылку на какую-то свою функцию другой DLL.
Name – это RVA строки символов ASCII, оканчивающейся нулем и содержащей имена импортируемых DLL.

FirstThunk – RVA-смещение массива двойных слов IMAGE_THUNK_DATA. В большинстве случаев двойное слово рассматривает¬ся как указатель на структуру IMAGE_IMPORT_BY_NAME. Это структура выглядит следующим образом:

typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint;
BYTE Name;
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;

Hint – номер экспорта у функции импорта.
Name – Строка ASCIIZ с именем импортируемой функции.

Фрагмент программы читающей из РЕ файла список импортируемых программой функций ОС.

Приведенный ниже фрагмент программы зарисывает в файл FunctionList.txt список библиотек с импортируемыми программой функциями.

  1. void ShowImportFunction()

    BYTE *pImage = (BYTE*) GetModuleHandle(NULL ) ;

    IMAGE_DOS_HEADER *idh;

    IMAGE_OPTIONAL_HEADER *ioh;

    IMAGE_SECTION_HEADER *ish;

    IMAGE_IMPORT_DESCRIPTOR *iid;

    IMAGE_IMPORT_BY_NAME *ibn;

    IMAGE_THUNK_DATA *thunk;

    int i = 0 ;

    DWORD j = 0 ;

    char lib = "Импортируемая библиотека: " ;

  2. HANDLE file = CreateFile(TEXT("FunctionList.txt" ) ,GENERIC_READ|GENERIC_WRITE,

    0 ,0 ,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL|FILE_FLAG_RANDOM_ACCESS,0 ) ;

    idh = (IMAGE_DOS_HEADER*) pImage;

    ioh = (IMAGE_OPTIONAL_HEADER*)

    (pImage + idh->e_lfanew + 4 +

    sizeof (IMAGE_FILE_HEADER) ) ;

    ish = (IMAGE_SECTION_HEADER*) ((DWORD) ioh + sizeof (IMAGE_OPTIONAL_HEADER) ) ;

    for (i = 0 ; i < 16 ; i++)

Структура PE файла

Общее описание PE файла

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

Во всех 32-разрядных ветках ОС Windows объектные (.OBJ), библиотечные (.LIB) и исполняемые (.EXE и.DLL) файлы хранятся в едином формате COFF (Common Object File Format), который используется некоторыми системами семейства Unix и ОС VMS.

Формат PE (Portable Executable) является специализацией COFF для хранения исполняемых модулей. Он был стандартизован Tool Interface Standard Committee (Microsoft, Intel, IBM, Borland, Watcom и др.) в 1993 г., а затем понемногу обновлялся (последнее известное мне обновление было проведено в феврале 1999 г., но оно не учитывает поддержки метаданных для.NET, добавленной в 2000 г.). Название Portable Executable связано с тем, что данный формат не зависит от архитектуры процессора, для которого построен исполняемый файл.

На сегодняшний день существует два формата PE-файлов: PE32 и PE32+. Оба они ограничивают адресное пространство программы размеров в 4 Гб (0xFFFFFFFF), но PE32 использует 32-битовые адреса (архитектура Win32), а PE32+ - 64-битовые адреса (архитектура Win64).

Большинство описанных ниже структур и констант содержатся в стандартном заголовочном файле Windows WINNT.H.

Любой PE-файл состоит из нескольких заголовков и нескольких (от 1 до 96) секций. Заголовки содержат служебную информацию, описывающую различные свойства исполняемого файла и его структуру. Секции содержат данные, которые размещаются в адресном пространстве процесса во время загрузки исполняемого файла в память.

PE-файлы являются файлами с относительной загрузкой, т.е. теоретически могут размещаться в пространстве адресов 0x00000000 - 0xFFFFFFFF с любого адреса, называемого базовым адресом. Поскольку базовый адрес заранее неизвестен, структура PE-файлов основана на понятия RVA (relative virtual address, относительный виртуальный адрес). RVA представляет собой смещение от базового адреса исполняемого файла до данного адреса. Иными словами, для получения линейного адреса в виртуальной памяти процесса нужно сложить RVA с базовым адресом.

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

Общая структура PE-файла представлена в таблице 2.1:

Таблица 2.1 - Структура ре файла

Подробно каждая из этих структур описана ниже.

Общее описание заголовка и заглушки DOS

Поскольку и приложения DOS, и приложения Windows имеют расширение.EXE, все исполняемые файлы Windows используют схему двойной загрузки. Она состоит в том, что файл начинается с заголовка DOS, за которым следует заглушка (stub), т.е. небольшой EXE-файл формата DOS. При попытке загрузить файл из DOS"а исполняется заглушка, а при загрузке файла из Windows загрузчик анализирует заголовок DOS и извлекает из него смещение до настоящего заголовка исполняемого файла.

ь Структура заголовка DOS называется IMAGE_DOS_HEADER. Я не буду полностью описывать заголовок DOS, т.к. нас интересуют в нем только два поля, а именно:

ь 2-байтовая (WORD) сигнатура, находящаяся по смещению 0 (e_magic) и равная «MZ» (IMAGE_DOS_SIGNATURE);

ь 4-байтовое (DWORD) смещение от начала файла до заголовка PE, находяшееся по смещению 0x3C (e_lfanew).

При загрузке PE-файла сначала нужно проверить сигнатуру DOS, затем найти смещение до заголовка PE, а затем проверить сигнатуру PE, расположенную в начале его заголовка. Эта сигнатура состоит из 4 байтов и равна «PE» (обозначение IMAGE_NT_SIGNATURE).

Такую же схему двойной загрузки используют и другие файлы (исполняемые файлы Win16 и OS/2 и VxD-драйверы Windows 9x), поэтому проверка правильности сигнатуры PE обязательна.

Обычно заглушка DOS выводит на экран сообщение типа «Эта программа требует Microsoft Windows» и заканчивает работу. Однако при сборке программы мы можем указать в командной строке сборщика любой EXE-файл DOS в качестве заглушки. Это позволяет создавать «дуальные» программы, работающие и в DOS, и в Windows.

Сруктура DOS заголовка

typedef struct _IMAGE_DOS_HEADER { // DOS.EXE заголовок

USHORT e_magic; // Магическое число

USHORT e_cblp; // Количество байт на последней странице файла

USHORT e_cp; // Количество страниц в файле

USHORT e_crlc; // Relocations

USHORT e_cparhdr; // Размер заголовка в параграфах

USHORT e_minalloc; // Minimum extra paragraphs needed

USHORT e_maxalloc; // Maximum extra paragraphs needed

USHORT e_ss; // Начальное (относительное) значение регистра SS

USHORT e_sp; // Начальное значение регистра SP

USHORT e_csum; // Контрольная сумма

USHORT e_ip; // Начальное значение регистра IP

USHORT e_cs; // Начальное (относительное) значение регистра CS

USHORT e_lfarlc; // Адрес в файле на таблицу переадресации

USHORT e_ovno; // Количество оверлеев

USHORT e_res; // Зарезервировано

USHORT e_oemid; // OEM identifier (for e_oeminfo)

USHORT e_oeminfo; // OEM information; e_oemid specific

USHORT e_res2 ; // Зарезервировано

LONG e_lfanew; // Адрес в файле нового.exe-заголовка

} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

Самым важным здесь является поле e_lfanew, которое содержит 4-байтовое смещение от начала файла до PE-заголовка. Первое поле структуры e_magic содержит сигнатуру исполняемого файла. Все MS-DOS-совместимые исполняемые файлы имеют сигнатуру 0x54AD, которая в ASCII-символах представлена двумя символами MZ. По этой причине заголовок DOS часто называют MZ-заголовком.

Структура PE заголовка

Заголовок PE (IMAGE_NT_HEADERS) состоит из трех частей (см. Таблицу 2.2):

Таблица 2.2 - Структура заголовка

struct IMAGE_NT_HEADERS {

DWORD Signature;

IMAGE_FILE_HEADER FileHeader;

IMAGE_OPTIONAL_HEADER OptionalHeader;

Заголовок PE всегда начинается с 4-байтовой сигнатуры «PE» (IMAGE_NT_SIGNATURE). За ней следуют два заголовка: заголовок файла (IMAGE_FILE_HEADER) и необязательный заголовок (IMAGE_OPTIONAL_HEADER). Несмотря на свое название, IMAGE_OPTIONAL_HEADER присутствует в PE-файле всегда (необязательным он является с точки зрения общего формата COFF, поскольку не используется в объектных и библиотечных файлах).

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

Заголовок файла состоит из 0x14 байтов (определение IMAGE_SIZEOF_FILE_HEADER), размещается сразу после сигнатуры и содержит общее описание файла. Он состоит из следующих полей:

Таблица 2.3 - Структура заголовка файла

Описание назначения полей.

Поле Machine

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

Поле TimeDateStamp

32-битовое число, которое содержит дату и время создания данного файла. Формат этого поля недокументирован, однако сборщики Microsoft заносят сюда время как количество секунд от полуночи 01.01.1970 в UTC (т.е. используют юниксовский формат времени, возвращаемого функцией time языка C). Между прочим, это означает, что текущее состояние формата PE действительно только до 18.01.2038 г.

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

Поля PointerToSymbolTable и NumberOfSymbols

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

Поле SizeOfOptionalHeader

16-битовое число, задающее размер необязательного заголовка. Оно равно 0xE0 для файлов PE32 (IMAGE_SIZEOF_NT_OPTIONAL32_HEADER) и 0xF0 для файлов PE32+ (IMAGE_SIZEOF_NT_OPTIONAL64_HEADER).

Поле Characteristics

16-битовое поле флагов, содержащее COFF-атрибуты файла.

Необязательный заголовок

Необязательный заголовок имеет два различных формата: для PE32 и для PE32+. Соответствующие структуры называются IMAGE_OPTIONAL_HEADER32 и IMAGE_OPTIONAL_HEADER64. Их поля сведены в Таблице 2.4:

Таблица 2.4 - Структура необязательного заголовка

Смещение (hex, PE32/PE32+)

Размер (PE32/PE32+)

Тип (PE32/PE32+)

Название

Описание

Сигнатура заголовка.

MajorLinkerVersion

Старшая цифра номера версии сборщика. Загрузчиком не используется.

MinorLinkerVersion

Младшая цифра номера версии сборщика. Загрузчиком не используется.

Сумма размеров всех секций, содержащих програмный код.

SizeOfInitializedData

Сумма размеров всех секций, содержащих инициализированные данные.

SizeOfUninitializedData

Сумма размеров всех секций, содержащих неинициализированные данные.

AddressOfEntryPoint

RVA точки запуска программы. Для драйвера - это адрес DriverEntry, для DLL - адрес DllMain или нуль.

RVA начала кода программы.

RVA начала данных программы. Ненадежное поле, загрузчиком не используется. В PE32+ отсутствует!

Предпочтительный базовый адрес программы в памяти, кратный 64 Кб. По умолчанию равен 0x00400000 для EXE-файлов в Windows 9x/NT, 0x00010000 для EXE-файлов в Windows CE и 0x10000000 для DLL. Загрузка программы с этого адреса позволяет обойтись без настройки адресов.

SectionAlignment

Выравнивание в байтах для секций при загрузке в память, большее или равное FileAlignment. По умолчанию равно размеру страницы виртуальной памяти для данного процессора.

Выравнивание в байтах для секций внутри файла. Должно быть степенью 2 от 512 до 64 Кб включительно. По умолчанию равно 512. Если SectionAlignment меньше размера страницы виртуальной памяти, то FileAlignment должно с ним совпадать.

MajorOperatingSystemVersion

Старшая цифра номера версии операционной системы. Загрузчиком не используется.

MinorOperatingSystemVersion

Младшая цифра номера версии операционной системы. Загрузчиком не используется.

MajorImageVersion

Старшая цифра номера версии данного файла. Загрузчиком не используется.

MinorImageVersion

Младшая цифра номера версии данного файла. Загрузчиком не используется.

MajorSubsystemVersion

Старшая цифра номера версии подсистемы.

MinorSubsystemVersion

Младшая цифра номера версии подсистемы.

Win32VersionValue

Зарезервировано, всегда равно 0.

Размер файла в памяти, включая все заголовки. Должен быть кратен SectionAlignment.

Суммарный размер заголовка и заглушки DOS, заголовка PE и заголовков секций, выравненный на границу FileAlignment. Задает смещение от начала файла до данных первой секции.

Контрольная сумма файла.

Исполняющая подсистема Windows для данного файла.

DllCharacteristics

Дополнительные атрибуты файла.

SizeOfStackReserve

Размер стека стартового потока программы в байтах виртуальной памяти. При загрузке в физическую память отображается только SizeOfStackCommit байт, в дальнейшем отображается по одной странице виртуальной памяти. По умолчанию равен 1 Мб.

SizeOfStackCommit

Начальный размер стека программы в байтах. По умолчанию равен 4 Кб.

SizeOfHeapReserve

Размер кучи программы в байтах. При загрузке в физическую память отображается только SizeOfHeapCommit байт, в дальнейшем отображается по одной странице виртуальной памяти. По умолчанию равен 1 Мб. Во всех 32-разрядных версиях Windows куча ограничена только размером виртуальной памяти и это поле, по-видимому, игнорируется.

SizeOfHeapCommit

Начальный размер кучи программы в байтах. По умолчанию равен 4 Кб.

Устаревшее поле, не используется.

NumberOfRvaAndSizes

Количество описателей каталогов данных. На текущий момент всегда равно 16.

IMAGE_DATA_DIRECTORY

Описатели каталогов данных.

16-битовое поле, содержащее сигнатуру заголовка. Может принимать значения (см. Таблицу 2.4):

Таблица 2.4 - Допустимые значения поля Magic

Поля MajorSubsystemVersion и MinorSubsystemVersion

16-битовые числа, указывающее ожидаемую версию Windows. В отличие от всех остальных полей с номерами версий это поле анализировалось при загрузке программ в NT 4.0 и 95. Если программа была графическим приложением и это поле не содержало версии 4.0, то считалось, что программа разработана для NT 3.51 и моделировалось поведение этой ОС (в частности, отсутствие трехмерных стилей диалогов и пр.). В настоящее время не используется и практически всегда равно 4.0.

Поле CheckSum

32-битовая контрольная сумма файла. Проверяется только в Windows NT при загрузке драйверов ядра и нескольких системных DLL. Алгоритм вычисления контрольной суммы недокументирован, но для ее вычисления можно использовать системную функцию CheckSumMappedFile из библиотеки IMAGEHLP.DLL.

Поле Subsystem

16-битовое число, указывающее в какой подсистеме Windows API должен исполняться данный файл. Его возможные значения представлены в таблице 2.5:

Таблица 2.5 - Допустимые значения поля Subsystem

Название

Значение

Подсистема

IMAGE_SUBSYSTEM_UNKNOWN

Неизвестная подсистема.

IMAGE_SUBSYSTEM_NATIVE

Подсистема не требуется, используется драйверами и «родными» приложениями NT.

IMAGE_SUBSYSTEM_WINDOWS_GUI

Графическая подсистема Windows.

IMAGE_SUBSYSTEM_WINDOWS_CUI

Консольная подсистема Windows.

IMAGE_SUBSYSTEM_OS2_CUI

Консольная подсистема OS/2.

IMAGE_SUBSYSTEM_POSIX_CUI

Консольная подсистема POSIX.

IMAGE_SUBSYSTEM_NATIVE_WINDOWS

«Родной» драйвер Win 9x.

IMAGE_SUBSYSTEM_WINDOWS_CE_GUI

Графическая подсистема Windows CE.

IMAGE_SUBSYSTEM_EFI_APPLICATION

Программа EFI.

IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER

Драйвер EFI, обеспечивающий загрузочный сервис.

IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER

Драйвер EFI времени выполнения.

IMAGE_SUBSYSTEM_EFI_ROM

Прошивка ПЗУ для EFI.

IMAGE_SUBSYSTEM_XBOX

Подсистема Xbox.

Поле DLLCharacteristics

16-битовое поле флагов, задающие дополнительные атрибуты файла. Возможные значения флагов представлены в таблице 2.6.

Таблица 2.6 - Возможные значения флагов поля DLLCharacteristics

Поля NumberOfRvaAndSizes и DataDirectory

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

struct IMAGE_DATA_DIRECTORY {

DWORD VirtualAddress;

В настоящее время поле NumberOfRvaAndSizes всегда содержит число 16 (символическое имя IMAGE_NUMBEROF_DIRECTORY_ENTRIES). Соответственно массив DataDirectory состоит также из 16 описателей. Каждый описатель содержит RVA и размер для одного каталога данных. Если какого-либо каталога в файле нет, то оба поля его описателя равны нулю.

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

Заголовки секций

Сразу после заголовка PE в файле располагается массив заголовков секций. Его размер задается полем NumberOfSections заголовка файла. Каждый заголовок секции состоит из 0x28 байт и имеет следующую структуру (см. Таблицу 2.7):

Таблица 2.7 - Струкура заголовка секции

Смещение (hex)

Название

Описание

Название секции.

Misc. VirtualSize

Размер секции в памяти. Если это значение больше SizeOfRawData, то секция дополняется в памяти нулевыми байтами.

RVA секции в памяти.

Размер секции в файле. Всегда кратен FileAlignment из необязательного заголовка. Если секция содержит только неинициализированные данные, то это поле равно нулю.

PointerToRawData

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

PointerToRelocations

PointerToLinenumbers

В исполняемых файлах это поле всегда равно нулю.

NumberOfRelocations

В исполняемых файлах это поле всегда равно нулю.

NumberOfLinenumbers

В исполняемых файлах это поле всегда равно нулю.

Characteristics

Атрибуты секции.

Название секции

Название секции содержит от 0 до 8 ASCII-символов. Вместо константы 8 можно использовать определение IMAGE_SIZEOF_SHORT_NAME. Если длина названия меньше 8 символов, то оно дополняется нулевыми байтами. Если оно состоит ровно из 8 символов, то завершающего нулевого байта нет. Важно отметить, что название секции, вообще говоря, никак не соотносится с ее содержимым. Каждый компилятор использует свое собственное соглашение о именовании секций, поэтому полагаться на название секции при ее анализе нельзя. Единственно надежным способом определить, что содержит данная секция, является анализ ее атрибутов и содержащихся в ней каталогов данных.

Атрибуты секции

32-битовое поле Characteristics содержит набор флагов, описывающих содержимое данной секции. Секции исполняемого файла могут иметь следующие атрибуты (см. Таблицу 2.8):

Таблица 2.8 - Атрибуты секции

Название

Значение

Описание

IMAGE_SCN_CNT_CODE

Секция содержит исполняемый код.

IMAGE_SCN_CNT_INITIALIZED_DATA

Секция содержит инициализированные данные.

IMAGE_SCN_CNT_UNINITIALIZED_DATA

Секция содержит неинициализированные данные.

IMAGE_SCN_MEM_DISCARDABLE

Секция может быть удалена из памяти после создания процесса.

IMAGE_SCN_MEM_NOT_CACHED

Секция не может кэшироваться.

IMAGE_SCN_MEM_NOT_PAGED

Секция не может выгружаться в файл подкачки.

IMAGE_SCN_MEM_SHARED

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

IMAGE_SCN_MEM_EXECUTE

Секцию можно исполнять как программный код.

IMAGE_SCN_MEM_READ

IMAGE_SCN_MEM_WRITE

В секцию можно писать.

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

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

2.1 Файлы

Требования к хранению информации:

    возможность хранения больших объемов данных

    информация должна сохраняться после прекращения работы процесса

    несколько процессов должны иметь одновременный доступ к информации

2.1.1 Именование файлов

Длина имени файла зависит от ОС, может быть от 8 (MS-DOS) до 255 (Windows, LINUX) символов.

ОС могут различать прописные и строчные символы. Например, WINDOWS и windows для MS-DOS одно и тоже, но для UNIX это разные файлы.

Во многих ОС имя файла состоит из двух частей, разделенных точкой, например windows.exe. Часть после точки называют расширением файла . По нему система различает тип файла.

У MS-DOS расширение составляет 3 символа. По нему система различает тип файла, а также можно его исполнять или нет.

У UNIX расширение ограничено размером имени файла в 255 символов, также у UNIX может быть несколько расширений, но расширениями пользуются больше прикладные программы, а не ОС. По расширению UNIX не может определить исполняемый это файл или нет.

2.1.2 Структура файла

Три основные структуры файлов:

    Последовательность байтов - ОС не интересуется содержимым файла, она видит только байты. Основное преимущество такой системы, это гибкость использования. Используются в Windows и UNIX.

    Последовательность записей - записей фиксированной длины (например, перфокарта), считываются последовательно. Сейчас не используются.

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

Три типа структур файла.

2.1.3 Типы файлов

Основные типы файлов:

    Регулярные - содержат информацию пользователя. Используются в Windows и UNIX.

    Каталоги - системные файлы, обеспечивающие поддержку структуры файловой системы. Используются в Windows и UNIX.

    Символьные - для моделирования ввода-вывода. Используются только в UNIX.

    Блочные - для моделирования дисков. Используются только в UNIX.

Основные типы регулярных файлов:

    ASCII файлы - состоят из текстовых строк. Каждая строка завершается возвратом каретки (Windows), символом перевода строки (UNIX) и используются оба варианта (MS-DOS). Поэтому если открыть текстовый файл, написанный в UNIX, в Windows, то все строки сольются в одну большую строку, но под MS-DOS они не сольются (это достаточно частая ситуация ). Основные преимущества ASCII файлов:
    - могут отображаться на экране, и выводится на принтер без преобразований
    - могут редактироваться почти любым редактором

    Двоичные файлы - остальные файлы (не ASCII). Как правило, имеют внутреннею структуру.

Основные типы двоичных файлов:

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

    Неисполняемые - все остальные.

Примеры исполняемого и не исполняемого файла

«Магическое число» - идентифицирующее файл как исполняющий.

2.1.4 Доступ к файлам

Основные виды доступа к файлам:

    Последовательный - байты читаются по порядку. Использовались, когда были магнитные ленты.

2.1.5 Атрибуты файла

Основные атрибуты файла:

    Защита - кто, и каким образом может получить доступ к файлу (пользователи, группы, чтение/запись). Используются в Windows и UNIX.

    Пароль - пароль к файлу

    Создатель - кто создал файл

    Владелец - текущий владелец файла

    Флаг "только чтение" - 0 - для чтения/записи, 1 - только для чтения. Используются в Windows.

    Флаг "скрытый" - 0 - виден, 1 - невиден в перечне файлов каталога (по умолчанию). Используются в Windows.

    Флаг "системный" - 0 - нормальный, 1 - системный. Используются в Windows.

    Флаг "архивный" - готов или нет для архивации (не путать сжатием). Используются в Windows.

    Флаг "сжатый" - файл сжимается (подобие zip архивов). Используются в Windows.

    Флаг "шифрованный" - используется алгоритм шифрования. Если кто-то попытается прочесть файл, не имеющий на это прав, он не сможет его прочесть. Используются в Windows.

    Флаг ASCII/двоичный - 0 - ASCII, 1 - двоичный

    Флаг произвольного доступа - 0 - только последовательный, 1 - произвольный доступ

    Флаг "временный" - 0 - нормальный, 1 - для удаления файла по окончании работы процесса

    Флаг блокировки - блокировка доступа к файлу. Если он занят для редактирования.

    Время создания - дата и время создания. Используются UNIX.

    Время последнего доступа - дата и время последнего доступа

    Время последнего изменения - дата и время последнего изменения. Используются в Windows и UNIX.

    Текущий размер - размер файла. Используются в Windows и UNIX.

2.1.6 Операции с файлами

Основные системные вызовы для работы с файлами:

    Create - создание файла без данных.

    Delete - удаление файла.

    Open - открытие файла.

    Close - закрытие файла.

    Read - чтение из файла, с текущей позиции файла.

    Write - запись в файл, в текущею позицию файла.

    Append - добавление в конец файла.

    Seek - устанавливает файловый указатель в определенную позицию в файле.

    Get attributes - получение атрибутов файла.

    Set attributes - установить атрибутов файла.

    Rename - переименование файла.

2.1.7 Файлы, отображаемые на адресное пространство памяти

Иногда удобно файл отобразить в памяти (не надо использовать системные вызовы ввода-вывода для работы с файлом), и работать с памятью, а потом записать измененный файл на диск.

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

При использовании сегментной организации памяти, файл загружают в отдельный сегмент.

Пример копирования файла через отображение в памяти.

Алгоритм:

    Создается сегмент для файла 1

    Файл отображается в памяти

    Создается сегмент для файла 2

    Сегмент 1 копируется в сегмент 2

    Сегмент 2 сохраняется на диске

Недостатки этого метода:

    Тяжело определить длину выходного файла

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

    Файл может оказаться большим, больше сегмента или виртуального пространства.

2.2 Каталоги

2.2.1 Одноуровневые каталоговые системы

В этой системе все файлы содержатся в одном каталоге.

Однокаталоговая система, содержащая четыре файла, файлов А два, но разных владельцев

Преимущества системы:

    Простота

    Возможность быстро найти файл, не надо лазить по каталогам

Недостатки системы:

    Различные пользователи могут создать файлы с одинаковыми именами.

2.2.2 Двухуровневые каталоговые системы

Для каждого пользователя создается свой собственный каталог.

Двухуровневая каталоговая система

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

Эту проблему можно решить созданием системного каталога, с общим доступом.

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

2.2.3 Иерархические каталоговые системы

Каждый пользователь может создавать столько каталогов, сколько ему нужно.

Иерархическая каталоговая система

Почти все современные универсальные ОС, организованы таким образом. Специализированным ОС это может быть не нужным.

2.2.4 Имя пути

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

Два основных метода указания файла:

    абсолютное имя пути - указывает путь от корневого каталога, например:
    - для Windows \usr\ast\mailbox
    - для UNIX /usr/ast/mailbox
    - для MULTICS >usr>ast>mailbox

    относительное имя пути - путь указывается от текущего каталога (рабочего каталога), например:
    - если текущий каталог /usr/, то абсолютный путь /usr/ast/mailbox перепишется в ast/mailbox
    - если текущий каталог /usr/ast/, то абсолютный путь /usr/ast/mailbox перепишется в mailbox
    - если текущий каталог /var/log/, то абсолютный путь /usr/ast/mailbox перепишется в../../usr/ast/mailbox

./ - означает текущий каталог

../ - означает родительский каталог

2.2.5 Операции с каталогами

Основные системные вызовы для работы с каталогами:

    Create - создать каталог

    Delete - удалить каталог

    OpenDir - закрыть каталог

    CloseDir - закрыть каталог

    Rename - переименование каталога