Можно ли создать работающий исполняемый файл PE без текстового кода или секций данных

Обновлено: 21.11.2024

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

Во время многих криминалистических расследований Trustwave SpiderLabs мы часто натыкаемся на вредоносные образцы, которые были «упакованы». Эта техника/концепция может быть незнакома начинающим специалистам по реверсированию вредоносных программ или специалистам по цифровой криминалистике, поэтому я подумал, что было бы интересно использовать эту возможность, чтобы поговорить о переносимых исполняемых (PE) упаковщиках на высоком уровне. Если вы уже знаете, что такое PE-упаковщики и как они работают, вы можете продолжить чтение, однако вполне возможно, что вы не узнаете что-то новое. Думайте об этом как о записи в блоге 101.

Так что же такое упаковщики PE? Как они работают? Как вы можете победить их? Я постараюсь ответить на эти вопросы.

Обзор

По сути, упаковщики — это инструменты, которые используются для сжатия PE-файла. В первую очередь это позволяет пользователю, запускающему инструмент, уменьшить размер файла. Дополнительным преимуществом является то, что, поскольку файл сжат, это также обычно мешает многим обратным инженерам анализировать код статически (без его запуска). Упаковщики изначально создавались для законных целей, чтобы уменьшить общий размер файла. Позднее авторы вредоносного ПО осознали упомянутые выше преимущества и тоже стали их использовать. Конечно, простое сжатие PE-файла само по себе не сработает. Если вы попытаетесь запустить его, файл будет жаловаться и завершится ошибкой, и в итоге у вас будет ужасный день.

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

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

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

Знаете вы это или нет, почти наверняка вы сталкивались с PE-файлами раньше. Если вы использовали Microsoft Windows, вы использовали PE-файлы. Это формат файла, разработанный Microsoft, который используется для большого количества типов файлов. Наиболее распространенными из них являются файлы .exe и .dll (исполняемые файлы и динамически подключаемые библиотеки соответственно). PE-файлы состоят из двух частей — заголовка и разделов. Заголовок содержит сведения о самом PE-файле, а разделы содержат содержимое исполняемого файла.

Заголовок разбит на несколько частей: заголовок DOS, заголовок PE, необязательные заголовки и таблицу разделов. В PE-файле может быть любое количество разделов, и они могут называться так, как пожелает автор. Однако почти всегда должны быть разделы, содержащие фактический код файла, таблицу импорта и данные, используемые кодом. В итоге это выглядит примерно так:

Заголовки PE

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

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

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

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

Чтобы объяснить импорт, давайте представим гипотетическую ситуацию. У нас есть пять исполняемых файлов, каждый из которых имеет общий код. На самом деле не имеет большого смысла хранить этот код во всех этих исполняемых файлах. Вместо этого имеет смысл выделить этот код в отдельную библиотеку и просто загружать эту библиотеку во время выполнения каждым исполняемым файлом.По сути, это и есть таблица импорта — список библиотек и связанных с ними функций, которые исполняемый файл хочет загрузить во время выполнения. Эта таблица функций и библиотек заменяется в упакованном файле и генерируется при выполнении. Это означает, что если мы хотим распаковать уже упакованный двоичный файл, нам придется реконструировать эту информацию, чтобы распакованный PE-файл был действительным и работал должным образом. Как только этот заголовок проанализирован, поток выполнения переходит к таблице Sections.

Таблица разделов

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

Секции PE

Как упоминалось ранее, разделы PE обычно содержат как минимум раздел для кода, раздел для данных и раздел для импорта. Раздел импорта содержит фактические адреса для всех функций, необходимых для PE-файла. Эти адреса заполняются во время выполнения, так как каждая система Windows может отличаться. Таким образом, возможно, что функция не может быть расположена в одной и той же ячейке памяти между версиями Windows. Заполняя таблицу импорта этими адресами во время выполнения, мы можем использовать этот PE-файл на нескольких компьютерах с Microsoft Windows.

Разбираем пример

Теперь, когда мы хорошо разобрались со структурой PE-файла, давайте воспользуемся полученными знаниями для ручной распаковки настоящего файла. В этом примере я буду использовать calc.exe с упаковщиком MPRESS. MPRESS — это популярный упаковщик, который существует уже некоторое время. Он поддерживает большое количество типов файлов и работает со всеми текущими версиями Microsoft Windows. Итак, прежде чем мы упакуем наш файл, давайте взглянем на то, как выглядит calc.exe в исходном состоянии.

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

Теперь давайте упакуем образец с помощью MPRESS и посмотрим, как изменился файл. Как вы можете видеть на следующих снимках экрана, MPRESS изменил разделы, присутствующие в PE-файле. Разделы «.text» и «.data» заменены на «.MPRESS1» и «.MPRESS2». Раздел ".MPRESS1" содержит сжатые данные исходного файла calc.exe, а раздел ".MPRESS2" содержит ряд функций, используемых для распаковки этих данных и восстановления таблицы импорта.

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

В этом примере я собираюсь переключаться между IDA Pro и OllyDbg. Вы можете использовать любые инструменты, которые пожелаете, но мой личный стиль приводит к тому, что я часто переключаюсь между этими инструментами, поэтому здесь я собираюсь использовать их оба. Я считаю, что IDA лучше визуализирует происходящее, но OllyDbg, как правило, имеет больше возможностей и лучшие результаты в среде динамического анализа.

Одна из первых вещей, которую мы видим при отладке примера, — это вызов этой довольно сложной функции. На самом деле эта функция распаковывает сжатые данные, найденные в '.MPRESS1'. Для этого используется интересный метод под названием «декомпрессия на месте». Другими словами, MPRESS может распаковывать данные без создания нового раздела памяти и сброса в него распакованных данных. Вместо этого он просто перезаписывает сжатые данные распакованными данными.

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

В конце концов мы достигли исходной точки входа (на данный момент) распакованного calc.exe. Именно здесь мы хотим сбросить память процесса на диск. Для этой задачи я предпочитаю подключаемый модуль для OllyDbg под названием OllyDump. Он не только позволит вам создать дамп процесса, но также имеет возможность, среди прочего, перестроить таблицу импорта (помните, что процесс упаковки уничтожает оригинал).

Как и во многих других вещах в этой отрасли, к этому можно подойти по-разному. Если вы не большой поклонник OllyDbg, хорошей альтернативой только что упомянутому плагину является инструмент под названием ChimpRec. На главной странице этого инструмента есть полный список функций, но, по сути, он позволяет вам сделать дамп запущенного в данный момент процесса и перестроить его импортированные данные. . Я уверен, что есть много других, которые сделают то же самое.Эти две утилиты просто мои любимые.

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

давным-давно, в далекой галактике, я написал одну из своих первых статей для Microsoft Systems Journal (теперь MSDN® Magazine ). Статья «Взгляд внутрь PE: знакомство с форматом переносимых исполняемых файлов Win32» оказалась более популярной, чем я ожидал. По сей день я все еще слышу от людей (даже в Microsoft), которые используют эту статью, которая все еще доступна в библиотеке MSDN. К сожалению, проблема со статьями в том, что они статичны. За прошедшие годы мир Win32® сильно изменился, и статья сильно устарела. Я исправлю эту ситуацию в статье, состоящей из двух частей, начиная с этого месяца.
Вам может быть интересно, почему вы должны заботиться о формате исполняемого файла. Ответ сейчас тот же, что и тогда: исполняемый формат операционной системы и структуры данных довольно много говорят о базовой операционной системе. Поняв, что находится в ваших EXE и DLL, вы обнаружите, что стали лучшим программистом во всех отношениях.
Конечно, вы могли бы узнать многое из того, что я вам расскажу, прочитав спецификацию Microsoft. Однако, как и в большинстве спецификаций, она жертвует читабельностью ради полноты. В этой статье я сосредоточусь на объяснении наиболее важных частей этой истории, а также на том, как и почему, которые не вписываются в формальную спецификацию. Кроме того, в этой статье у меня есть кое-что полезное, чего нет ни в одной официальной документации Microsoft.

Преодоление разрыва

Обзор формата файла PE

Microsoft представила формат PE-файла, более известный как формат PE, как часть исходных спецификаций Win32. Однако файлы PE являются производными от более раннего формата Common Object File Format (COFF), используемого в VAX/VMS. В этом есть смысл, поскольку большая часть первоначальной команды разработчиков Windows NT пришла из Digital Equipment Corporation. Для этих разработчиков было естественным использовать существующий код для быстрой загрузки новой платформы Windows NT.
Термин «Переносимый исполняемый файл» был выбран потому, что целью было иметь общий формат файлов для всех разновидностей Windows на всех поддерживаемых процессорах. В значительной степени эта цель была достигнута с помощью того же формата, который используется в Windows NT и ее потомках, Windows 95 и ее потомках, а также в Windows CE.
Файлы OBJ, создаваемые компиляторами Microsoft, используют формат COFF. Вы можете получить представление о том, сколько лет формату COFF, взглянув на некоторые из его полей, которые используют восьмеричное кодирование! Файлы COFF OBJ имеют много общих структур данных и перечислений с PE-файлами, и я буду упоминать некоторые из них по ходу дела.
Добавление 64-битной версии Windows потребовало лишь нескольких модификаций формата PE. Этот новый формат называется PE32+. Никаких новых полей не добавлялось, и только одно поле в формате PE было удалено. Остальные изменения — это просто расширение некоторых полей с 32 до 64 бит. В большинстве таких случаев вы можете написать код, который просто работает как с 32-битными, так и с 64-битными PE-файлами. В заголовочных файлах Windows есть волшебная пыльца, которая делает различия невидимыми для большей части кода на C++.
Разница между файлами EXE и DLL полностью связана с семантикой. Оба они используют один и тот же формат PE. Единственным отличием является один бит, который указывает, следует ли рассматривать файл как EXE или как DLL. Даже расширение файла DLL является искусственным. У вас могут быть библиотеки DLL с совершенно разными расширениями — например, элементы управления .OCX и апплеты панели управления (файлы .CPL) являются библиотеками DLL.
Очень удобный аспект PE-файлов заключается в том, что структуры данных на диске — это те же структуры данных, которые используются в памяти. Загрузка исполняемого файла в память (например, вызовом LoadLibrary) — это прежде всего вопрос сопоставления определенных диапазонов PE-файла с адресным пространством. Таким образом, структура данных, подобная IMAGE_NT_HEADERS (которую я рассмотрю позже), идентична на диске и в памяти. Ключевым моментом является то, что если вы знаете, как найти что-то в PE-файле, вы почти наверняка сможете найти ту же информацию при загрузке файла в память.
Важно отметить, что PE-файлы не просто отображаются в память как один файл, отображаемый в память. Вместо этого загрузчик Windows просматривает PE-файл и решает, какие части файла отображать. Это сопоставление является последовательным, поскольку более высокие смещения в файле соответствуют более высоким адресам памяти при отображении в память. Смещение элемента в файле на диске может отличаться от его смещения после загрузки в память. Тем не менее, вся информация присутствует, чтобы вы могли выполнить преобразование смещения диска в смещение памяти (см. рис. 1).


Рисунок 1. Смещения

Разделы PE-файла

приводит к тому, что все данные, создаваемые Visual C++, помещаются в раздел с именем MY_DATA, а не в раздел по умолчанию .data. Большинство программ прекрасно работают с разделами по умолчанию, созданными компилятором, но иногда у вас могут возникнуть странные требования, которые требуют помещения кода или данных в отдельный раздел.
Секции не полностью формируются компоновщиком; скорее, они начинаются в файлах OBJ, обычно помещаемых туда компилятором. Задача компоновщика состоит в том, чтобы объединить все необходимые разделы из OBJ-файлов и библиотек в соответствующий конечный раздел в PE-файле. Например, каждый файл OBJ в вашем проекте, вероятно, имеет как минимум раздел .text, содержащий код. Компоновщик берет все разделы с именами .text из различных файлов OBJ и объединяет их в один раздел .text в PE-файле. Точно так же все разделы с именами .data из различных OBJ объединяются в один раздел .data в PE-файле. Код и данные из файлов .LIB также обычно включаются в исполняемый файл, но эта тема выходит за рамки данной статьи.
Существует довольно полный набор правил, которым следуют компоновщики, чтобы решить, какие разделы комбинировать и как. Я представил введение в алгоритмы компоновщика в колонке Under The Hood за июль 1997 года в MSJ. Раздел в файле OBJ может быть предназначен для использования компоновщиком и не включаться в окончательный исполняемый файл. Подобный раздел предназначен для того, чтобы компилятор передал информацию компоновщику.
Секции имеют два значения выравнивания: одно в файле на диске, а другое в памяти. Заголовок PE-файла указывает оба этих значения, которые могут различаться. Каждый раздел начинается со смещения, которое несколько кратно значению выравнивания. Например, в файле PE типичным выравниванием будет 0x200. Таким образом, каждый раздел начинается со смещения файла, кратного 0x200.
После сопоставления с памятью разделы всегда начинаются как минимум с границы страницы. То есть, когда раздел PE отображается в память, первый байт каждого раздела соответствует странице памяти. На процессорах x86 страницы выровнены по 4 КБ, а на IA-64 — по 8 КБ. В следующем коде показан фрагмент вывода PEDUMP для разделов .text и .data библиотеки Windows XP KERNEL32.DLL.

Относительные виртуальные адреса

В исполняемом файле есть много мест, где необходимо указать адрес в памяти. Например, адрес глобальной переменной необходим при обращении к ней. PE-файлы могут загружаться практически в любом месте адресного пространства процесса. Хотя у них есть предпочтительный адрес загрузки, вы не можете полагаться на то, что исполняемый файл действительно загружается туда. По этой причине важно иметь какой-то способ указания адресов, которые не зависят от того, где загружается исполняемый файл.
Чтобы избежать жестко заданных адресов памяти в PE-файлах, используются RVA. RVA — это просто смещение в памяти относительно места загрузки PE-файла. Например, рассмотрим EXE-файл, загруженный по адресу 0x400000, с разделом кода по адресу 0x401000. RVA раздела кода будет следующим:

Чтобы преобразовать RVA в фактический адрес, просто выполните обратный процесс: добавьте RVA к фактическому адресу загрузки, чтобы найти фактический адрес памяти. Между прочим, фактический адрес памяти называется виртуальным адресом (VA) на языке PE. Другой способ думать о VA состоит в том, что это RVA с добавленным предпочтительным адресом загрузки. Не забывайте ранее сделанное мною замечание о том, что адрес загрузки совпадает с HMODULE.
Хотите порыться в произвольной структуре данных DLL в памяти? Вот как. Вызовите GetModuleHandle с именем библиотеки DLL. Возвращаемый HMODULE — это просто адрес загрузки; вы можете применить свои знания о файловых структурах PE, чтобы найти в модуле все, что захотите.

Каталог данных

Импорт функций

Если вы не знакомы с языком ассемблера x86, это вызов через указатель функции. Каким бы ни было значение размера DWORD по адресу 0x405030, инструкция CALL отправит управление. В предыдущем примере адрес 0x405030 находится в IAT.
Менее эффективный вызов импортированного API выглядит следующим образом:

В этой ситуации CALL передает управление небольшой заглушке. Заглушка представляет собой JMP для адреса, значение которого равно 0x405030. Опять же, помните, что 0x405030 — это запись в IAT. Короче говоря, менее эффективный импортированный вызов API использует пять байтов дополнительного кода и занимает больше времени для выполнения из-за дополнительного JMP.
Вы, вероятно, задаетесь вопросом, почему когда-либо использовался менее эффективный метод. Есть хорошее объяснение. Предоставленный самому себе, компилятор не может отличить импортированные вызовы API от обычных функций в одном модуле. Таким образом, компилятор выдает инструкцию CALL в форме

где XXXXXXX – фактический кодовый адрес, который позже будет заполнен компоновщиком. Обратите внимание, что эта последняя инструкция CALL выполняется не через указатель функции. Скорее, это фактический кодовый адрес.Чтобы сохранить космическую карму в равновесии, компоновщик должен иметь кусок кода для замены XXXXXXXX. Самый простой способ сделать это — сделать так, чтобы вызов указывал на заглушку JMP, как вы только что видели.
Откуда берется заглушка JMP? Удивительно, но это происходит из библиотеки импорта для импортированной функции. Если бы вы исследовали библиотеку импорта и изучили код, связанный с именем импортированного API, вы бы увидели, что это заглушка JMP, подобная только что показанной. Это означает, что по умолчанию, при отсутствии какого-либо вмешательства, импортированные вызовы API будут использовать менее эффективную форму.
По логике вещей, следующий вопрос, который нужно задать, это как получить оптимизированную форму. Ответ приходит в виде подсказки, которую вы даете компилятору. Модификатор функции __declspec(dllimport) сообщает компилятору, что функция находится в другой DLL и что компилятор должен сгенерировать эту инструкцию

а не этот:

Кроме того, компилятор выдает информацию, указывающую компоновщику преобразовать часть указателя функции в инструкцию к символу с именем __imp_functionname. Например, если вы вызываете MyFunction, имя символа будет __imp_MyFunction. Заглянув в библиотеку импорта, вы увидите, что в дополнение к обычному имени символа есть также символ с префиксом __imp__. Этот символ __imp__ разрешается непосредственно в запись IAT, а не в заглушку JMP.
Так что это значит в вашей повседневной жизни? Если вы пишете экспортируемые функции и предоставляете для них файл .H, не забудьте использовать модификатор __declspec(dllimport) с функцией:

Если вы посмотрите на системные заголовочные файлы Windows, вы обнаружите, что они используют __declspec(dllimport) для Windows API. Это нелегко увидеть, но если вы выполните поиск макроса DECLSPEC_IMPORT, определенного в WINNT.H и используемого в таких файлах, как WinBase.H, вы увидите, как __declspec(dllimport) добавляется к объявлениям системного API.

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

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

Заголовок IMAGE_NT_HEADERS

Структура IMAGE_NT_HEADERS — это основное место, где хранятся особенности PE-файла. Его смещение задается полем e_lfanew в IMAGE_DOS_HEADER в начале файла. На самом деле существует две версии структуры IMAGE_NT_HEADER: одна для 32-битных исполняемых файлов, а другая для 64-битных версий. Различия настолько незначительны, что я буду считать их одинаковыми для целей этого обсуждения. Единственный правильный, одобренный Microsoft способ различения этих двух форматов — через значение поля Magic в IMAGE_OPTIONAL_HEADER (описано вкратце).
IMAGE_NT_HEADER состоит из трех полей:

Таблица разделов

Подведение итогов

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

Дополнительную информацию см. в разделе
Общий формат объектных файлов (COFF)

.

Я хочу отредактировать секцию .text PE-файла и вставить машинный код или изменить машинный код.

В конце концов .text окажется больше или меньше исходного размера.

Чтобы файл .exe продолжал работать, нужно ли изменять какие-либо заголовки?

На что мне нужно обратить внимание в PE-файле, чтобы убедиться, что он по-прежнему работает должным образом после операции с .text?

1 Ответ 1

Я бы посоветовал, если раздел .text окажется меньше, чем оригинал, просто дополнить его, чтобы он был того же размера, что и оригинал, что избавит вас от необходимости делать слишком многие другие изменения. Если он окажется больше, чем оригинал, все может стать довольно сложно.

Раздел .text обычно является первым разделом в исполняемом файле, и поскольку разделы выравниваются в памяти на основе записи "выравнивание раздела" в заголовке PE, следующий раздел обычно помещается в память в следующем целочисленном кратном выравнивания раздела после окончания раздела .text. Обычное выравнивание раздела составляет 4 КБ (0x1000 байт), поэтому в зависимости от исходного размера раздела .text у вас может быть от 0 до 4095 байт свободного места для расширения раздела .text, прежде чем вы столкнетесь со следующим разделом. в памяти.

Если в памяти достаточно свободного места для внесения изменений, вам также придется иметь дело с выравниванием разделов в самом исполняемом файле, которое определяется записью «выравнивание файла» в заголовке PE. Объем свободного места в исполняемом файле для ваших изменений будет равен разнице между необработанным размером раздела .text и его виртуальным размером, и, поскольку обычное выравнивание файла кажется равным 512 (0x200) байтам, поэтому будет между 0 и 511. байт.Если в файле недостаточно свободного места (но достаточно места в памяти), вам придется переместить следующие разделы на целое число, кратное выравниванию файла, и соответствующим образом настроить указатели файлов каждого раздела. Вам также потребуется обновить значение "size of code" в заголовке PE, чтобы учесть увеличенный размер раздела .text.

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

RSS
канал

Темы блога

    (15) (16) (24) (132) (106) (45) (29) (170) (15) (22) (2) (18) (2) (36) (5) (1) (33) ) (3) (71)

Архивы

Самодельный исполняемый файл

Создайте программу для Windows, поместив байты в буфер и записав их на диск: без компилятора, без ассемблера, без компоновщика, без ничего! Это был очевидный результат моих недавних попыток получить больший контроль над тем, что входит в мои исполняемые файлы, и на этот раз я мог установить каждый бит именно так, как хотел. Да, я все еще помешан на контроле.

Я начал с простой программы на C под названием ExeBuilder для создания буфера и записи его на диск в файл с именем handmade.exe. ExeBuilder выглядит так:

Вся интересная работа выполняется в BuildExe(). Эта функция вручную создает допустимый заголовок Windows PE, заполняя обязательные поля заголовка и оставляя необязательные поля обнуленными, затем создает один раздел .text и заполняет его несколькими байтами программного кода. Программа в этом случае мало что делает — она просто возвращает число 44.

Разбор сведений в заголовке PE и определение того, какие поля на самом деле необходимы, было непростой задачей. Все мои тесты проводились под 64-битной версией Windows 7. Если вы попробуете эти примеры на своем ПК, окажется, что более ранние версии Windows были более разрешительными для PE-заголовков, в то время как Windows 8 и 10 могут быть более строгими в отношении пустых PE-полей.

Вот моя первая реализация BuildExe(), которая создает хороший стандартный исполняемый файл с одним разделом .text, содержащим 4 байта кода.

Результирующий файл имеет размер 516 байт. Убедитесь, что это работает:

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

Заголовок DOS. Единственными полями, которые должны быть заполнены, являются подпись «MZ» в начале и параметр e_lfanew в конце (если вы на самом деле не пишете программу для DOS). e_lfanew дает смещение PE-заголовка, который в данном случае следует сразу за ним.

Заголовок PE. Настоящий заголовок PE не содержит многого, потому что все самое полезное содержится в дополнительном заголовке. Заголовок PE указывает 1 раздел (один раздел .text с кодом возврата 44) и 208 байтов в сумме для следующих двух разделов.

Необязательный заголовок. Необязательный заголовок является необязательным только в том случае, если вас не волнует, работает ли программа. Некоторые заслуживающие внимания значения:

  • SectionAlignment — каждый раздел исполняемого файла (.text, .data и т. д.) должен быть выровнен по этой границе в памяти во время выполнения. Стандартным является 4096 или 4 КБ, размер одной страницы виртуальной памяти.
  • AddressOfEntryPoint — выполнение программы начнется с этого смещения в памяти от базового адреса. Так как выравнивание раздела равно 4096, единственный раздел .text программы будет загружен со смещением 4096, и выполнение должно начинаться с первого байта этого раздела.
  • FileAlignment — аналогично выравниванию разделов, но для файла на диске, а не для программы в памяти. Стандартный размер — 512 байт, размер одного сектора диска.
  • SizeOfHeaders. На самом деле это не общий размер всех заголовков, а скорее смещение файла относительно данных первого раздела. Обычно это то же самое, что и общий размер всех заголовков плюс все необходимые отступы.

Каталоги данных. Типичный исполняемый файл будет хранить смещения и размеры для своих каталогов данных здесь, количество которых указано в необязательном заголовке. Каталоги данных используются для указания импорта и экспорта программы, ссылок на символы отладки и других полезных вещей. Создавать каталог данных для импорта вручную немного сложно, поэтому я этого не делал. Вот почему программа просто возвращает 44 вместо того, чтобы делать что-то более интересное, для чего потребовался бы импорт Win32 DLL. Handmade.exe вообще не имеет каталогов данных.

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

Таблица разделов. Для каждого раздела в таблице разделов есть запись. Handmade.exe имеет только один раздел .text, поэтому в таблице есть только одна запись. Он дает размер раздела в 4 байта, и это все, что нужно для кода «возврат 44». Раздел будет загружен в память по адресу RVA 4096, который также является точкой входа в программу.

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

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

Иногда картинка стоит 1000 слов, поэтому я также сделал шестнадцатеричный дамп исполняемого файла с цветовой кодировкой:


Уменьшение

После всего этого, конечно же, моей первой мыслью было попытаться сделать его меньше. Между таблицей разделов и данными раздела много пустого заполнения из-за выравнивания разделов в файле по 512 байт. Должен же быть какой-то способ уменьшить или убрать этот отступ, верно? Я попытался уменьшить Opt.FileAlignment до 4, уменьшить данные раздела .TEXT до 336 и соответствующим образом настроить sectHdr.PointerToRawData. Все, что я получил за свои усилия, это ошибка с жалобой «handmade.exe не является допустимым приложением Win32». Я не уверен, почему это не сработало. Возможно, ОС не нравятся разделы, которые не выровнены по 512 байтам в файле, независимо от того, что говорит заголовок PE.

Тогда я подумал, что, возможно, я мог бы повторно использовать заголовок в качестве данных раздела. Изменив sectHdr.PointerToRawData на 0, я мог заставить загрузчик Windows использовать копию исполняемого заголовка в качестве данных раздела .TEXT. 0 выровнен по 512 байтам, так что проблем с выравниванием быть не должно. Это казалось странным, так как исполняемый заголовок не является кодом x86, но, запихнув 4 байта кода в неиспользуемую область заголовка и настроив Opt.AddressOfEntryPoint, я теоретически мог все исправить. О чудо, это сработало! Размер нового исполняемого файла составлял всего 340 байт.

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

Это тоже сработало, но не без хлопот. После установки для PE.NumberOfSections значения 0, для параметров PE.SizeOfOptionalHeader и Opt.SizeOfHeaders необходимо было установить нулевые значения. Они оба, по сути, являются смещениями к структурам разделов, и без разделов, по-видимому, требуется смещение 0. Opt.SectionAlignment также пришлось уменьшить до 2048, и я, честно говоря, понятия не имею, почему. С этими изменениями модифицированная программа заработала.

С устранением таблицы разделов этого должно было быть достаточно, чтобы уменьшить исполняемый файл до 300 байт, но я обнаружил, что все, что меньше 328 байт, не будет работать. Оказалось, что ОС предполагает минимальный размер для необязательного заголовка или каталогов данных, независимо от размеров, указанных в заголовке. Таким образом, в конце файла handmade.exe требуется 28 байт заполнения. Здесь показана 328-байтовая версия BuildExe() с выделенными изменениями по сравнению с предыдущей версией:

Вот еще одна красивая картинка, показывающая исполняемый файл размером 328 байт:


Максимальное сжатие

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

Заголовок PE можно переместить вниз до смещения 4 в файле. Он должен быть выровнен по 4 байтам и не может быть со смещением 0, потому что тогда он перезапишет требуемую подпись «MZ» в начале файла. Сделать это просто: просто переместите все, кроме заголовка DOS, на 60 байт вниз.

Единственная сложность, связанная с таким перекрытием заголовков DOS и PE, связана с DWORD по смещению файла 60. Это значение является параметром e_lfanew, который задает смещение файла относительно заголовка PE, так что теперь должно быть равно 4. Но из-за перекрытия этот параметр также является параметром Opt.SectionAlignment, который указывает выравнивание между разделами в памяти во время выполнения. Будем надеяться, что в Windows все в порядке с выравниванием 4-байтового раздела! Оказывается, это нормально, но только если Opt.FileAlignment тоже равно 4. Не знаю, почему.

Этих изменений должно было быть достаточно, чтобы уменьшить размер файла до 240 байт, но ОС снова требует 28 байтов заполнения в конце файла. Вот обновленная 268-байтовая версия BuildExe():

И еще одна красивая картинка, на которой происходит смешивание цветов в местах наложения структур данных:

Согласно нескольким источникам, 268 байт — это абсолютный минимальный размер рабочего исполняемого файла в 64-разрядной версии Windows 7. Есть и другие трюки, которые еще больше уменьшат заголовок, но тогда мне просто нужно добавить больше отступов. Я не могу идти дальше!

15 комментариев

Не в 64-битной, но в DOS до XP 32-битной, моему наименьшему «Hello World» нужно 8 байт.

C:\Temp>Привет, мир$
Привет, мир
C:\Temp>

@data_miner, я думал, что для исполняемых файлов DOS по-прежнему требуется 64-байтовый заголовок DOS с подписью «MZ»? Я никогда не экспериментировал с разработкой для DOS.

Исполняемый файл занимает 304 байта, но я потратил 46 байтов на строку версии, поэтому, по сути, это 258 байтов.

@Jochen, вы не знаете, работает ли этот 688-байтовый EXE-файл под W7-64? В инструкциях сказано установить для /ALIGN значение 16, но когда я попытался установить для него значение меньше 512, EXE не удалось загрузить. Самый маленький рабочий EXE-файл W7-64, который мне удалось создать с помощью стандартного компилятора и компоновщика, имел размер 1024 байта: это с одним разделом и выравниванием раздела по умолчанию в 512 байт. Вы можете вручную обрезать результирующий EXE-файл до 516 байт, и он все еще работает, но компоновщик Microsoft, похоже, дополняет последний раздел до границы 512 байт, независимо от того, насколько он мал на самом деле.

@Steve: Да, для x64 вам нужно удалить флаг /ALIGN:32; потом работает... но размер увеличивается до 1024 байт...

C:\temp>dir
Том в Laufwerk C: hat keine Bezeichnung.
Серийный номер тома: 1CA6-4682

Verzeichnis von C:\temp

Забыл упомянуть: отрывок был немного изменен, чтобы представить автономный исполняемый файл DOS «MiniMZ». Чтобы быть частью заголовка PE, заголовок MZ, конечно же, должен иметь поле NewPEMarker, установленное на 0x40.

[Хм, ну мой первый пост пока не показывается, а добавление сразу появилось. Если первое сообщение потерялось, то вот оно снова (разбито на 2 части), пожалуйста, удалите, если все-таки окажется дубликат.]

Самый маленький DOS-файл .exe, который мне удалось создать, имеет длину 28 байт. Единственный код, который он использует, — это вызов функции для завершения самого себя (mov ah,4ch ; int 21h). Я переместил его в поля инициализации стека и установил в поле размера заголовка нулевое значение, чтобы заголовок загружался в начале сегмента кода загрузчиком DOS. Осталось только настроить CS:IP так, чтобы он точно указывал на поле инициализации сегмента стека.

Чтобы проиллюстрировать, вот отрывок из моего проекта, который на самом деле использует это (это «сокращатель PE без сжатия», который также использует трюк «align 4». Работает с относительно простыми программами на языке ассемблера, скомпилированными с помощью TASM или GoAsm, поэтому правда, они работают только на 32-битных системах после сжатия, там нужно было сделать много странных вещей. *g*):

Вот шестнадцатеричный дамп «Мини-МЗ»:
4D 5A 1C 00 01 00 00 00 00 00 00 00 FF FF B4 4C CD 21 00 00 0E 00 00 00 00 00 00 00

Вы можете минимизировать код для выхода из программы DOS, используя «CD 20» (int 20h), а не «B4 4C CD 21» (mov ah, 4Ch : int 21h).

таким образом, вы можете сократить еще 96 байт.

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

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

Введение

Каждый исполняемый файл имеет общий формат, называемый Common Object File Format (COFF), формат для исполняемых файлов, объектного кода, компьютерных файлов с общей библиотекой, используемых в системах Unix. И формат PE (Portable Executable) является одним из таких форматов COFF, доступных сегодня для исполняемых файлов, объектного кода, библиотек DLL, файлов шрифтов FON и дампов ядра в 32-разрядных и 64-разрядных версиях операционных систем Windows. А если вы меня спросите, что там на планшете для линукса то? Что ж, у нас есть формат Executable Link File (ELF) для Linux. Поскольку я посвятил этот пост заголовкам Windows PE, я рассмотрю формат ELF в некоторых других постах позже.

Формат PE – это структура данных, которая сообщает загрузчику ОС Windows, какая информация требуется для управления обернутым исполняемым кодом. Сюда входят ссылки на динамические библиотеки для связывания, таблицы экспорта и импорта API, данные управления ресурсами и данные TLS. Структуры данных на диске — это те же структуры данных, которые используются в памяти, и если вы знаете, как найти что-то в PE-файле, вы почти наверняка сможете найти ту же информацию после загрузки файла в память. Важно отметить, что PE-файлы не просто отображаются в память как один файл, отображаемый в память. Вместо этого загрузчик Win32 просматривает PE-файл и решает, какие части файла отображать.

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

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

Позвольте мне объяснить эти структуры данных с помощью примера. Итак, я беру здесь пример калькулятора (calc.exe), который я буду открывать в шестнадцатеричном редакторе (HxD). Вы можете взять этот удобный инструмент отсюда.

Заголовок DOS

Заголовок DOS занимает первые 64 байта файла. то есть первые 4 строки шестнадцатеричного редактора, как показано на изображении ниже. Если вы заметили, вы увидите строки ASCII «MZ», упомянутые в начале файла. Этот MZ занимает первые два байта (шестнадцатеричное: 4D 5A или 0x54AD) заголовка DOS, который читается как 5Ah 4Dh.

MZ — это инициалы Марка Збиковски, одного из разработчиков MS-DOS. Это поле называется e_magic или магическим числом, которое является одним из таких важных полей для идентификации типа файла, совместимого с MS-DOS. Все исполняемые файлы, совместимые с MS-DOS, устанавливают это значение равным 0x5A4D, т. е. «MZ» в ASCII.

Последнее поле, e_lfanew, представляет собой 4-байтовое смещение (F0 00 00 00) и сообщает, где находится заголовок PE. Проверьте раздел заголовка PE ниже, чтобы узнать об этом смещении.

Программа-заглушка DOS

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

В этом случае программа-заглушка реального режима запускается MS-DOS при загрузке исполняемого файла.Когда загрузчик Windows сопоставляет PE-файл с памятью, первый байт сопоставляемого файла соответствует первому байту заглушки MS-Dos.

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

Заголовок PE находится в поле e_lfanew заголовка MS-DOS. Поле e_lfanew дает смещение местоположения заголовка PE. Поле e_lfanew обозначает заголовок файла нового заголовка .exe. Основной PE-заголовок представляет собой структуру типа IMAGE_NT_HEADERS и в основном содержит SIGNATURE, IMAGE_FILE_HEADER и IMAGE_OPTIONAL_HEADER.

IMAGE_FILE_HEADER: заголовок файла представляет собой следующие 20 байтов PE-файла и содержит только самую основную информацию о структуре файла.

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

Поля заголовка файла изображения следующие:-

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

  • Magic: поле Magic указывает, является ли исполняемый образ 32-битным или 64-битным. В поле Magic установлено значение IMAGE_NT_OPTIONAL_HDR_MAGIC, а значение определяется как IMAGE_NT_OPTIONAL_HDR32_MAGIC (0x10b) в 32-разрядном приложении и как IMAGE_NT_OPTIONAL_HDR64_MAGIC (0x20b) в 64-разрядном приложении.
  • AddressOfEntryPoint: это адрес, с которого загрузчик Windows начнет выполнение. Он содержит RVA (относительный виртуальный адрес) точки входа (EP) модуля и обычно находится в разделе .text. Для исполняемого файла это начальный адрес. Для драйверов устройств это адрес инициализируемой функции. Функция точки входа не является обязательной для библиотек DLL, и если точка входа отсутствует, этот член равен нулю.
  • Члены BaseOfCode и BaseOfData содержат RVA начала разделов кода и данных соответственно.
  • ImageBase: это адрес, по которому исполняемый файл будет отображаться в памяти в определенное место в памяти. В Windows NT база образа по умолчанию для исполняемого файла — 0x10000, а для DLL — 0x400000. Имейте в виду, что в случае Windows 95адрес 0x10000 нельзя использовать для загрузки 32-разрядных EXE-файлов, поскольку он находится в области линейных адресов, общей для всех процессов. И из-за этого Microsoft решила изменить базовый адрес по умолчанию для исполняемого файла Win32 на 0x400000.Итак, по умолчанию это 0x400000 для приложений и 0x10000000 для библиотек DLL.
  • SectionAlignment и FileAlignment: оба элемента указывают выравнивание разделов PE в памяти и в файле соответственно. Когда исполняемый файл отображается в память, каждый раздел этого исполняемого файла начинается с виртуального адреса, который на самом деле кратен этому значению.
  • SizeOfImage: Элемент SizeOfImage указывает размер памяти, занимаемый PE-файлом во время выполнения. Оно должно быть кратным значениям SectionAlignment.
  • Подсистема: это поле определяет целевую подсистему для исполняемого файла, т. е. тип подсистемы, которую исполняемый файл использует для пользовательского интерфейса. Все возможные значения подсистем определены в файле WINNT.H:
< td>Выполняется в символьной подсистеме Posix
ЗначениеИдентификаторЗначение
НАТУРАЛЬНЫЙ1Не требует подсистемы
WINDOWS_GUI2Работает в подсистеме Windows GUI
WINDOWS_CLI3Работает в символьной подсистеме Windows
OS2_CUI 5Выполняется в символьной подсистеме OS/2
POSIX_CUI7

Наконец, в конце структуры IMAGE_OPTIONAL_HEADER следует так называемый массив Data Directory структур IMAGE_DATA_DIRECTORY. Элемент Data Directory — это указатель на первую структуру IMAGE_DATA_DIRECTORY.

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

Ниже приведены некоторые каталоги данных:

Несколько важных из них: ExportTableAddress (таблица экспортируемых функций), ImportTableAddress (таблица импортированных функций), ResourcesTable (таблица ресурсов, таких как изображения, встроенные в PE) и ImportAddressTable (IAT), в которой хранится адреса времени выполнения импортированных функций.

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

Таблица заголовков разделов

Таблица заголовков разделов представляет собой массив структур IMAGE_SECTION_HEADER и содержит информацию, относящуюся к различным разделам, доступным в образе исполняемого файла. Разделы на изображении отсортированы по RVA, а не по алфавиту. Таблица заголовков разделов содержит следующие важные поля:

  • SizeOfRawData: указывает реальный размер раздела в файле. Если это последний раздел и мы суммируем это значение со значением PointerToRawData, то результатом будет размер самого значения.
  • VirtualSize: этот раздел указывает размер раздела в памяти.
  • PointerToRawData: это смещение, с которого в файле начинается раздел необработанных данных. Таким образом, добавив это значение к приведенному выше значению и предполагая, что для свойства выравнивания файла задано значение по умолчанию, мы можем получить смещение начала следующего раздела в файле.
  • VirtualAddress: RVA раздела в памяти. Использование информации VirtualAddress и VirtualSize. мы можем получить RVA следующего раздела, предполагая, что свойство выравнивания памяти установлено по умолчанию.
  • Характеристики: это говорит о правах доступа к памяти для этого раздела памяти, обозначенного флагами (R, RW, RWE и т. д.). Эти флаги определяют, является ли раздел исполняемым, доступным для чтения, записи или их комбинацией.

Разделы

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

  • .text: Обычно это первый раздел, содержащий исполняемый код приложения. Внутри этого раздела также находится точка входа приложения: адрес первой инструкции приложения, которая будет выполнена. Приложение может иметь более одного раздела с исполняемым кодом.
  • .data: этот раздел содержит инициализированные данные приложения, такие как строки.
  • .rdata или .idata: Обычно эти имена разделов используются для разделов, в которых расположена таблица импорта. Это таблица, в которой перечислены API Windows, используемые приложением (вместе с именами связанных с ними библиотек DLL). Используя это, загрузчик Windows знает, какой API нужно найти, в какой системной DLL, чтобы получить ее адрес.
  • .reloc: содержит информацию о перемещении.
  • .rsrc: это обычное название раздела контейнера ресурсов, который содержит такие вещи, как изображения, используемые для пользовательского интерфейса приложения.
  • .debug: содержит отладочную информацию.

Обратите внимание, что автор может изменять названия этих разделов. Однако в общем случае упомянутые выше являются некоторыми из распространенных имен конкретных разделов, но не следует подразумевать, что они всегда будут использоваться с одним и тем же именем или для одной и той же цели. А поскольку длина массива составляет всего 8 байт, длина имен PE-разделов ограничена 8 символами. Их максимальная длина составляет 8 символов ASCII, и каждая секция имеет свои характеристики (права доступа к памяти).

Например, раздел .text обычно имеет доступ для чтения/выполнения, раздел .data — для чтения/записи, раздел .rsrc — только для чтения и т. д.

Разделы .edata и .data

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

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

Читайте также: