Путеводитель по написанию вирусов под Win32: 3. Заголовок PE

Дата публикации 23 окт 2002

Путеводитель по написанию вирусов под Win32: 3. Заголовок PE — Архив WASM.RU

Вероятно, это одна из самых важных глав данного пособия. Прочитайте его!

Введение

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

Давайте рассмотрим каждую из этих частей поподробнее. Вот диаграмма в стиле Micheal J. O'Leary.

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

IMAGE_FILE_HEADER

Я дам краткое описание (резюме того, что сказал в своем прекрасном документе о формате PE-файла Мэтт Питрек) полей IMAGE_FILE_HEADER.

¤ PE\0\0:

Это метка, которая есть у каждого PE-файла. Просто проверяйте ее существование при заражении файла. Если метки нет, то это не PE-файл, ок?

¤ Машина:

Так как компьютером, который мы используем может быть несовместим с PC (теоретически), а PE-файлы могут быть и на таких компьютерах, в этом поле указывается тип машины, для которой предназначается приложение. Это может быть одно из следующих значений:

Код (Text):
  1.  
  2.  IMAGE_FILE_MACHINE_I386    equ  14Ch    ; Intel 386.
  3.  IMAGE_FILE_MACHINE_R3000   equ  162h    ; MIPS little-endian,160h big-endian
  4.  IMAGE_FILE_MACHINE_R4000   equ  166h    ; MIPS little-endian
  5.  IMAGE_FILE_MACHINE_R10000  equ  168h    ; MIPS little-endian
  6.  IMAGE_FILE_MACHINE_ALPHA   equ  184h    ; Alpha_AXP
  7.  IMAGE_FILE_MACHINE_POWERPC equ  1F0h    ; IBM PowerPC Little-Endian

¤ Количество секций:

Это поле играет важную роль при заражении. Оно сообщает, сколько секций в файле.

¤ Временной штамп:

Содержит количество секунд, которое прошло с декабря 31-ого 1969 года с 4:00 до того момента, когда файл был слинкован.

¤ Указатель на таблицу символов

Не интересно, поскольку используется только в OBJ-файлах.

¤ Количество символов:

Не интересно, поскольку используется только в OBJ-файлах.

¤ Размер опционального заголовка:

Содержит количество байтов, которое занимает IMAGE_OPTIONAL_HEADER (смотри описание

¤ Характеристики:

Флаг дает нам еще кое-какую информацию о файле. Нам это не интересно.

IMAGE_OPTIONAL_HEADER

¤ Magic:

Так как это поле всегда равно 010Bh, похоже, что это какая-то сигнатура. Не интересно.

¤ Старшая и младшая версии линкера:

Версия линкера, который создал файл. Не интересно.

¤ Размер кода:

Количество байт (округленное) во всех секциях, содержащих исполняемый код.

¤ Размер инициализированных данных:

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

¤ Размер неинициализированных данных:

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

¤ Адрес точки входа:

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

¤ База кода:

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

¤ База данных

RVA, откуда начинаются секции с данными. Они обычно идут последними после PE-заголовка и секций с кодом.

¤ База образа:

Когда линкер создает экзешник, он предполагает, что файл будет промэппирован в определенное место в памяти. Этот адрес и хранится в данном поле, делая возможным определенную оптимизацию со стороны линкера. Если файл действительно промэппирован по этому адресу, код не нуждается в каком-либо патчении перед запуском. В экзешниках, компилируемых для Windows NT, база образа по умолчанию равна 0x10000, а для DLL он по умолчанию равен 0x400000. В Win9x адрес 0x10000 не может использоваться при загрузке 32-х битных EXE, так как он находится внутри региона, разделяемого всеми процессами. Из-за этого Микрософт изменил адрес базы по умолчанию на 0x400000.

¤ Выравнивание секций:

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

¤ Файловое выравнивание:

В PE-файле raw-данные, представляющие каждую из секций, начинаются со значения, кратного данному параметру. По умолчанию он равен 0x200, вероятно, потому что тогда секции будут начинаться с начала сектора диска (который также равен 0x200 байтам). Это поле эквивалентно размеру выравнивания сегментов/ресурсов в NE-файлах. В отличии от NE-файлов, PE-файлы не имеют сотен секций, поэтому на файловое выравнивание уходит очень мало место.

¤ Старшая и младшая версии операционной системы:

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

¤ Младшая и старшая версии образа:

Задаваемое пользователем поле. Оно позволяет вам иметь разные версии EXE или DLL. Вы можете установить значения этих полей с помощью опции линкера /VERSION. Например "LINK /VERSION:2.0 myobj.obj".

¤ Старшая и младшая версии подсистемы:

Содержат минимальную версию подсистемы, которая требуется для запуска данной программы. Типичное значение этого поля - 3.10 (что означает Windows NT 3.1).

¤ Зарезервировано1:

Похоже, что оно всегда равно 0 (идеально для метки заражения).

¤ Размер образа:

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

¤ Размер заголовков:

Размер PE-заголовка и таблицы секций (объектов). Raw-данные секций начинаются непосредственно после всех компонентов заголовка.

¤ Чексумма:

Предположительно CRC файла. Как и в других микрософтовских форматах исполняемых файлов, это поле игнорируется и устанавливается в 0. Есть одно исключение из этого правила: в доверенных (trusted) сервисах и EXE это поле должно содержать верную чексумму.

¤ Подсистема:

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

Код (Text):
  1.  
  2.  NATIVE         1       Не требует подсистемы (например драйвер устройства)
  3.  WINDOWS_GUI    2       Выполняется в подсистеме Windows GUI
  4.  WINDOWS_CUI    3       Выполняется в символьной подсистеме Windows
  5.                         (консольное приложение)
  6.  OS2_CUI        5       Выполняется в символьной подсистеме OS/2 (только OS/2
  7.                         1.x)
  8.  POSIX_CUI      7       Выполняется в символьной подсистеме Posix

¤ Характеристики DLL:

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

Код (Text):
  1.  
  2.  1      Вызывать инициализацию, когда DLL впервые загружается в адресное
  3.         пространство процесса
  4.  2      Вызывать инициализацию, когда тред завершает работу
  5.  4      Вызывать инициализацию, когда тред начинает работу
  6.  8      Вызывать инициализацию, когда DLL завершает свою работу

¤ Размер зарезервированного стека:

Количество виртуальной памяти, резервируемой для начального стека треда. Тем не менее, не вся эта память выделяется (смотри следующее поле). Это поле по умолчанию равно 0x100000. Если вы укажете 0 в качестве размера стека при создании треда функцией CreateThread, именно столько будет занимать стек нового треда.

¤ Размер выделенного стека:

Количество памяти, выделяемой для начального стека треда. Это поле по умолчанию равно 0x1000 (1 страница) у Microsoft Linker, в то время как TLINK32 делает это поле равным двум страницам.

¤ Размер зарезервированной кучи:

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

¤ Размер выделенной кучи:

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

¤ Флаги загрузчика:

Согласно WINNT.H эти поля относятся к поддержке отладки. Я никогда не видел экзешника с установленными битами этого поля, да и как заставить линкер их установить не совсем понятно.

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

¤ Number Of Rva And Sizes:

Количество элементов в массиве DataDirectory (ниже). Современные компиляторы всегда устанавливает это поле равным 16.

IMAGE_SECTION_HEADER

¤ Имя секции:

Это 8-ми байтовое имя в формате ANSI (UNICODE), которая задает имя секции. Большая часть имен секций начинается с "." (например ".text"), но это не обязательное требование как утверждают некоторые руководства по формату PE. Вы можете назвать вашу секцию как хотите с помощью специальной директивы ассемблера или с помощью "#pragma data_seg" и "#pragma code_seg" в Microsoft C/C++ компиляторе. Важно учитывать, что если имя секции занимает 8 байт, то в конце не будет NULL-байта. Вы можете использовать %.8s с функцией printf, чтобы скопировать строку в другой буфер и добавить NULL в конце.

¤ Виртуальный размер:

Значение этого файла отличается в EXE и OBJ. В EXE он содержит реальный размер код или данных. Это размер до округления до ближайшего числа, кратного файловому выравниванию. Поле SizeOfRawData 'размер raw-данных' (похоже, названное не совсем верно) содержит округленное значение. Линкер Borland'а меняет значения этих двух полей и похоже, что он прав. Для OBJ файлов этой поле означает физический адрес секции. Первая секция начинается с адреса 0. Чтобы найти физический адрес следующей секции в OBJ-файле, добавьте значение SizeOfRawData к физическому адресу текущих секции.

¤ Виртуальный адрес:

В EXE это поле содержит RVA на то место, куда загрузчику следует промэппировать секцию. Чтобы посчитать реальный стартовый адрес данной секции в памяти и добавьте базовый адрес образа к виртуальному адресу (поле VirtualAddress). Микрософтовские инструменты по умолчанию указывают на RVA 0x1000. В OBJ'ах это поле не имеет значения и установленно в 0.

¤ Размер raw-данных:

В EXE это поле содержит округленный до кратного файловому выравниванию числа размер секции. Например, предположим, что файловое выравнивание равно 0x200. Если поле VirtualSize содержит значение 0x35A, в этом поле будет находиться 0x400. В OBJ'ах это поле содержит точный размер секции, созданной компилятором или ассемблером. Другими словами для OBJ это поле играет ту же роль, что и виртуальный размер в EXE.

¤ Указатель на raw-данные:

Это смещение на raw-данные, которое меняется от файла к файлу. Если ваша программа самостоятельно загружает файл PE или COFF в память (вместо того, чтобы позволить сделать это операционной системе), это поле более важно, чем VirtualAddress - по этому смещению вы найдете данные секций, а не по RVA, указанном в поле виртуального адреса.

¤ Указатель на релокейшены:

В OBJ'ах это смещение на информацию о релокейшенах данной секции. Информация о релокейшенах каждой секции OBJ следует непосредственно за raw-данными этой секции. В EXE это поле не имеет значения (как и следующее поле) и установлено в ноль. Когда линкер создает EXE, он устанавливает большую часть адресных записей (fixups), и только релокейшены адреса базы и импортируемых функций устанавливаются во время загрузки. Информация о релокейшенах базы и импортируемых функций находится в специальных секциях, поэтому в EXE нет необходимости держать информацию о релокейшенах после каждой секции raw-данных.

¤ Указатель на номера строк:

Это смещение на таблице номеров строк. Эта таблица соотносит номера строк исходного кода со сгенерированным кодом для каждой конкретной строки. В современных отладочных форматах, таких как формат CodeView, информация о номерах строк хранится как часть отладочной информации. В отладочном формате COFF, тем не менее, информация о номерах строк хранится отдельно от символьной информации о именах/типах. Обычно только секциим кода (такие как .text) требуется данная информация. В EXE-файлах номера строк собираются ближе к концу файла после raw-данных секций. В OBJ-файлах таблица номеров строк для секций находится после секции данных и таблицы релокейшенов для этой секции.

¤ Количество релокейшенов:

Количество релокейшенов в соответствующей таблице для данной секции (поле PointerToRelocations - 'указатель на релокейшены'). Похоже, что данное поле содержит верные данные только в OBJ'ах.

¤ Количество номеров строк:

Количество номеров строк в соответствующей таблице для данной секции.

¤ Характеристики:

То, что большинство программистов называет флагами, формат COFF/PE называет характеристиками. Это поле является множеством флагов, которые задают аттрибуты секции (такие как код/данные, доступно ли для чтения или для записи). Чтобы получить полный список всех возможных аттрибутов секций, смотрите IMAGE_SCN_XXX_XXX #defin'ы в WINNT.H. Некоторые из важных флагов приведены ниже:

0x00000020 Эта секция содержит код. Обычно устанавливается вместе с флагом выполняемого кода (0x80000000).

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

0x00000080 Эта секция содержит неинициализированные данные (например секция .bss).

0x00000200 Эта секция содержит комментарии или другой тип информации. Типичное использование данной секции - это секция .drectve, добавляемая компилятором и содержащая команды для линкера.

0x00000800 Содержимое этой секции не должно помещаться в конечный EXE-файл. Эти секции используются компилятором/ассемблером, чтобы передать информацию линкеру.

0x02000000 Эту секция можно выгрузить из памяти после загрузки (например секция с релокейшенами - .reloc).

0x10000000 Эта секция является разделяемой. Если используется вместе с DLL, данные в этой секции будут разделяться всеми процессами, ее использующими. По умолчанию секции данных являются неразделяемыми, и это означает, что каждый процесс, использующий DLL получает свою собственную копию этой секции данныи. Если использовать техническую терминологию, флаг разделяемости говорит менеджеру загрузки, чтобы тот установил мэппинги страниц таким образом, чтобы все процессы, использующие DL ссылались на одну и ту же физическую страницу в памяти. Чтобы сделать секцию разделямой, используйте аттрибут SHARED во время линковки. Например:

LINK /SECTION:MYDATA,RWS ...

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

0x20000000 Эта секция является исполняемой. Этот флаг обычно устанавливается везде, где установлен флаг кода (0x00000020).

0x40000000 Эта секция доступня для чтения. Этот флаг установлен почти для всех секций EXE-файла.

0x80000000 Эта секция доступна для записи. Если этот флаг не установлен в секции EXE, загрузчик должен пометить промэппированные страницы как доступные только для чтения или выполнения. Обычно такой аттрибут есть у секций .data и .bss. Что интересно, у секции .idata этот аттрибут тоже установлен.

Необходимые изменения

Хорошо, здесь я объясню вам изменения, которые необходимо выполнить при заражении PE. Я предполагаю, что вы делаете вирус, который увеличивает размер последней секции PE-файла. Эта техника получила наибольшее распространение среди нас, да и она, между прочим, гораздо проще, чем добавление другой секции. Давайте посмотрим, как вирус может изменить заговок исполняемого файла. Для этого мы используем программу INFO-PE Lord'а Julus'а [SLAM].

-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·

+ DOS-информация +

Код (Text):
  1.  
  2.  Анализируемый файл: GOAT002.EXE
  3.  
  4.  DOS-отчеты:
  5.               ¦ Размер файла          - 2000H      (08192d)
  6.               ¦ Время создания файла  - 17:19:46   (hh:mm:ss)
  7.               ¦ Дата создания файла   - 11/06/1999 (dd/mm/yy)
  8.               ¦ Аттрибуты : архивный
  9.  
  10.  [...]

+ PE Header +

+ Опциональный заголовок PE +

+ Заголоки PE-секции +

-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·

Это был нормальный незараженный файл. Ниже идет тот же самый файл, но зараженный моим Aztec'ом (Вирус-пример для Ring-3, смотри ниже).

-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·

+ DOS INFORMATION +

Анализируемый файл: GOAT002.EXE

Код (Text):
  1.  
  2.  DOS-отчеты:
  3.              ¦ Размер файла  - 2600H      (09728d)
  4.              ¦ Время создания файла  - 23:20:58   (hh:mm:ss)
  5.              ¦ Дата создания файла  - 22/06/1999 (dd/mm/yy)
  6.              ¦ Аттрибуты : архивный
  7.  
  8.  [...]

+ Заголовок PE +

+ Опциональный PE-заголовок +

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

-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·

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

Код очень прост. Для тех, кто не понимает ничего без кода, взгляните на Win32.Aztec, который полностью объяснен в следующей главе. © Billy Belcebu, пер. Aquila


0 1.977
archive

archive
New Member

Регистрация:
27 фев 2017
Публикаций:
532