Разное

Структура exe файла: Пошаговое руководство к исполняемым файлам (EXE) Windows / Хабр

Структура COM и EXE файлов

Структура COM и EXE файлов

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

Наверное всем известны файлы с расширением COM. Главным COM файлом на ПК является вездесущий command.com (командный файл DOS) . Что же такое COM файл, как он работает и запускается ?
Структура COM — файла проста . В файлах данного типа,обычно не имеющими даже заголовка файла,содержатся только машинный код и данные программы.
Размер COM — файла ограничен 64 кб, т.е. размером одного сегмента памяти

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

Пример небольшого COM файла выводящего на экран сообщение:

.286 ; Устанавливаем тип процессора
mov ah,09h
;Функцию DOS (прерывание 21h) 09h
mov dx,offset message
; Заносим в dx значение переменной message
int 21h
;Устанавливаем прерывание которое должно обработать функцию 09h

mov ah,4Ch ;Функцию DOS (прерывание 21h) 4Ch
int 21h
;Устанавливаем прерывание которое должно обработать функцию 00h

message db «My first COM programms»,13,10,»$» ;Придаем значение переменной message

Использую определение прерывания можно сказать как действует эта программа.

mov ah,09h
int 21h

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

Ниже приведены два варианта кода COM — файла до «инфицирования» вирусом и после:

До инфицирования:

.286 ;Задаем тип процессора
code segmen
; Начало сегмента кода программы
org 100h
;Все COM программы начинаются с адреса 100h

jmp coob ; Тело программы начинается с метки coob

mov ah,4Ch
int 21h

coob: ;Начало тела программы
mov ah,09h
; Заносим значение 09h в регистр ah
mov dx,offset message
; В регистре dx указываем адрес строки с текстом
int 21h
; Указываем, что это прерывание DOS 21h

message db «Файл не инфицирован»,13,10,»$» ; Строка с текстом для вывода на экран

mov ah,4Ch ;Выходим из программы
int 21h
; Прерывание 21h

ends code ; Конец сегмента кода программы

После инфицирования

.286 ;Задаем тип процессора
code segmen
; Начало сегмента кода программы
org 100h
;Все COM программы начинаются с адреса 100h

jmp virus ; JMP изменены таками образом, чтобы вирус получил управление

mov ah,4Ch
int 21h
coob:

mov ah,09h

mov dx,offset message

int 21h

message db «Файл не инфицирован»,13,10,»$» ; Строка с текстом для вывода на экран
mov ah,4Ch
int 21h

virus:

mov …….
……………
} Вирус размножается и выполняет свои разрушительные действия
… int 21h

jmp coob

ends code ; Конец сегмента кода программы

 

Ниже приведена структура инфицированного файла

Команда перехода

Хвост COM программы

Тело вируса

Оригинальное начало
COM программы

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

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

Примерная структура EXE файла:

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

Тело программы

Конец программы

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

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

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

  • OVERWRITE — вирусы, замещающие программный код

  • COMPANION — вирусы-спутники

  • PARASITIC — вирусы, внедряющиеся в программу

  • метод переименования EXE файла

  • внедрение способом переноса

  • внедрение способом сдвига

? Как компилировать программу написанную на Ассемблере в COM файл
Для компилирования необходимо иметь соответствующее программное обеспечение на ПК. Для этого достаточно набора программ Turbo Assembler. В этом наборе находятся файлы tasm.exe и tlink.exe необходимые для компиляции в COM файл .
Написанная Вами программка на Ассемблере должна храниться в обычном текстовом файле, далее:
— копируем ваш файл в каталог с файлами tasm и tlink
— пишем команду tasm.exe leo.asm ( leo.asm — название файла с вашей программой)
если все прошло хорошо, то появятся еще файлы с таким же именем но с расширением obj, если нет, то на экране появятся сообщения с указанием типа ошибки и номером строки с ошибкой.
tlink leo.obj/t — И вот ваш COM файл готов, можете его запустить. Но будьте осторожней, не попадите в капкан установленный вами.

 

Стандартный вирус

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

Интересно, а кто создал первый вирус ?

С момента появления вычислительной техники программистов и электронщиков интересовала одна тема: самовопроизводяющиеся и самораспространяющиеся механизмы. Первым кто попробовал осуществить эту идею в 1951 году был Дж. фон Нейман. Но Нейман и не думал использовать эти материалы в каких либо разрушительных целях, но другие люди, воспользовавшись его материалами и знаниями в этой области начали создавать различных вредоносные программы — компьютерные вирусы. Но что же побудило этих людей пойти на это. Ученые считают, что людей побуждают создавать компьютерные вирусы некоторые факторы:

— озорство и одновременное непонимание всех последствий распространения вируса
— стремление навредить кому-либо
— неестественная потребность в совершение преступлений
— желание самоутвердиться
— невозможночть использовать свои знания в правильном русле

Как и у биологических, у компьютерных вирусов есть определенные стадии «развития»:

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

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

 

ФАЙЛОВЫЕ

Вирусы могут внедряться в следующие компоненты системы:

— файлы с компонентами DOS
— исполняемые файлы COM
— исполняемые файлы EXE
— внешние драйвера устройств (SYS- и BIN-файлы)
— объектные модули (OBJ-файлы)
— файлы программы до их
компиляции, в надежде на то, что их когда нибуть компилируют и запустят
— командные файлы (
BAT-файлы)
— файлы-библиотеки (LIB, DLL и др.файлы)
— оверлейные файлы (PIF, OV? и др. файлы)
— файлы текстовых процессоров поддерживающих макроcы (DOC, XLS и др.файлы)

С каждым днем этот список растет.

Чаще всего вирусы внедряются в файлы COM, EXE и DOC

ЗАГРУЗОЧНЫЕ

загрузояные вирусы распространяются в BOOT секторах дисков и дискет

— BR — на дискетах
— MBR — на жестком диске

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

ФАЙЛОВО-ЗАГРУЗОЧНЫЕ ВИРУСЫ

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

СПОСОБЫ ЗАРАЖЕНИЯ СРЕДЫ ОБИТАНИЯ

Вирусы могут «имплантироваться» в следующие места файлов:

— конец файла
— начало файла
— середина файлов
— хвостовой части файлов (свободной)

Создаем EXE / Хабр

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

Сейчас он способен собрать Hello World, но в этой статье я хочу рассказать не про парсинг и внутреннее устройство компилятора, а про такую важную часть как побайтовая сборка exe файла.

Начало

Хотите спойлер? Наша программа будет занимать 2048 байт.

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

Но сейчас мы с вами попробуем это исправить!

Для сборки нашей программы нам потребуется любой HEX редактор (лично я использовал HxD).

Для старта возьмем псевдокод:

Исходный код

func MessageBoxA(u32 handle, PChar text, PChar caption, u32 type) i32 ['user32.dll']
func ExitProcess(u32 code) ['kernel32.dll']

func main()
{
	MessageBoxA(0, 'Hello World!', 'MyApp', 64)
	ExitProcess(0)
}

Первые две строки указывают на функции импортируемые из библиотек WinAPI. Функция MessageBoxA выводит диалоговое окно с нашим текстом, а ExitProcess сообщает системе о завершении программы.
Рассматривать отдельно функцию main нет смысла, так как в ней используются функции, описанные выше.

DOS Header

Для начала нам нужно сформировать корректный DOS Header, это заголовок для DOS программ и влиять на запуск exe под Windows не должен.

Более-менее важные поля я отметил, остальные заполнены нулями.

Стуктура IMAGE_DOS_HEADER

Struct IMAGE_DOS_HEADER
{
     u16 e_magic	// 0x5A4D	"MZ"
     u16 e_cblp		// 0x0080	128
     u16 e_cp		// 0x0001	1
     u16 e_crlc
     u16 e_cparhdr	// 0x0004	4
     u16 e_minalloc	// 0x0010	16
     u16 e_maxalloc	// 0xFFFF	65535
     u16 e_ss
     u16 e_sp		// 0x0140	320
     u16 e_csum		
     u16 e_ip
     u16 e_cs
     u16 e_lfarlc	// 0x0040	64
     u16 e_ovno
     u16[4] e_res
     u16 e_oemid
     u16 e_oeminfo
     u16[10] e_res2
     u32 e_lfanew	// 0x0080	128
}

Самое главное, что этот заголовок содержит поле e_magic означающее, что это исполняемый файл, и e_lfanew — указывающее на смещение PE-заголовка от начала файла (в нашем файле это смещение равно 0x80 = 128 байт).

Отлично, теперь, когда нам известна структура заголовка DOS Header запишем ее в наш файл.

(1) RAW DOS Header (Offset 0x00000000)

4D 5A 80 00 01 00 00 00  04 00 10 00 FF FF 00 00
40 01 00 00 00 00 00 00  40 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00  00 00 00 00 80 00 00 00

Уточнение

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

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

Например, первый блок мы вставляем по смещению 0x00000000, и он займет 64 байта (0x40 в 16-ричной системе), следующий блок мы будем вставлять уже по этому смещению 0x00000040 и т.д.

Готово, первые 64 байта записали. Теперь нужно добавить еще 64, это так называемый DOS Stub (Заглушка). Во время запуска из-под DOS, она должна уведомить пользователя что программа не предназначена для работы в этом режиме.

Но в целом, это маленькая программа под DOS которая выводит строку и выходит из программы.

Запишем наш Stub в файл и рассмотрим его детальнее.

(2) RAW DOS Stub (Offset 0x00000040)

0E 1F BA 0E 00 B4 09 CD  21 B8 01 4C CD 21 54 68
69 73 20 70 72 6F 67 72  61 6D 20 63 61 6E 6E 6F
74 20 62 65 20 72 75 6E  20 69 6E 20 44 4F 53 20
6D 6F 64 65 2E 0D 0A 24  00 00 00 00 00 00 00 00

А теперь этот же код, но уже в дизассемблированном виде
Asm DOS Stub

0000	push cs			; Запоминаем Code Segment(CS) (где мы находимся в памяти)
0001	pop ds			; Указываем что Data Segment(DS) = CS
0002	mov dx, 0x0E	; Указываем адрес начала строки DS+DX, которая будет выводиться до символа $(Конец строки) 
0005	mov ah, 0x09	; Номер инструкции (Вывод строки)
0007	int 0x21		; Вызов системного прерывания 0x21
0009	mov ax, 0x4C01	; Номер инструкции 0x4C (Выход из программы) 
						; Код выхода из программы 0x01 (Неудача)
000c	int 0x21		; Вызов системного прерывания 0x21
000e	"This program cannot be run in DOS mode.\x0D\x0A$" ; Выводимая строка

Это работает так: сначала заглушка выводит строку о том, что программа не может быть запущена, а затем выходит из программы с кодом 1. Что отличается от нормального завершения (Код 0).

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

А еще забавно, что строка заглушки заканчивается как \x0D\x0D\x0A$. Скорее всего причина такого поведения в том, что c++ по умолчанию открывает файл в текстовом режиме. В результате символ \x0A заменяется на последовательность \x0D\x0A. В результате получаем 3 байта: 2 байта возврата каретки Carriage Return (0x0D) что бессмысленно, и 1 на перевод строки Line Feed (0x0A). В бинарном режиме записи (std::ios::binary) такой подмены не происходит.

Для проверки корректности записи значений я буду использовать Far с плагином ImpEx:

NT Header

Спустя 128 (0x80) байт мы добрались до NT заголовка (IMAGE_NT_HEADERS64), который содержит в себе и PE заголовок (IMAGE_OPTIONAL_HEADER64). Несмотря на название IMAGE_OPTIONAL_HEADER64 является обязательным, но различным для архитектур x64 и x86.
Структура IMAGE_NT_HEADERS64

Struct IMAGE_NT_HEADERS64
{
	u32 Signature	// 0x4550 "PE"
	
	Struct IMAGE_FILE_HEADER 
	{
		u16 Machine	// 0x8664 архитектура x86-64
		u16 NumberOfSections	// 0x03 Количество секций в файле 
		u32 TimeDateStamp		// Дата создания файла
		u32 PointerToSymbolTable
		u32 NumberOfSymbols
		u16 SizeOfOptionalHeader // Размер IMAGE_OPTIONAL_HEADER64 (Ниже)
		u16 Characteristics	// 0x2F 
	}
	
	Struct IMAGE_OPTIONAL_HEADER64
	{
		u16 Magic	// 0x020B Указывает что наш заголовок для PE64
		u8 MajorLinkerVersion
		u8 MinorLinkerVersion
		u32 SizeOfCode
		u32 SizeOfInitializedData
		u32 SizeOfUninitializedData	
		u32 AddressOfEntryPoint	// 0x1000 
		u32 BaseOfCode	// 0x1000 
		u64 ImageBase	// 0x400000 
		u32 SectionAlignment	// 0x1000 (4096 байт)
		u32 FileAlignment	// 0x200
		u16 MajorOperatingSystemVersion	// 0x05	Windows XP
		u16 MinorOperatingSystemVersion	// 0x02	Windows XP
		u16 MajorImageVersion
		u16 MinorImageVersion
		u16 MajorSubsystemVersion	// 0x05	Windows XP
		u16 MinorSubsystemVersion	// 0x02	Windows XP
		u32 Win32VersionValue
		u32 SizeOfImage	// 0x4000
		u32 SizeOfHeaders // 0x200 (512 байт)
		u32 CheckSum
		u16 Subsystem	// 0x02 (GUI) или 0x03 (Console)
		u16 DllCharacteristics
		u64 SizeOfStackReserve	// 0x100000
		u64 SizeOfStackCommit	// 0x1000
		u64 SizeOfHeapReserve	// 0x100000
		u64 SizeOfHeapCommit	// 0x1000
		u32 LoaderFlags
		u32 NumberOfRvaAndSizes // 0x16 
		
		Struct IMAGE_DATA_DIRECTORY [16] 
		{
			u32 VirtualAddress
			u32 Size
		}
	}
}

Разберемся что хранится в этой структуре:
Описание IMAGE_NT_HEADERS64
Signature — Указывает на начало структуры PE заголовка

Далее идет заголовок IMAGE_FILE_HEADER общий для архитектур x86 и x64.

Machine — Указывает для какой архитектуры предназначен код в нашем случае для x64

NumberOfSections — Количество секции в файле (О секциях чуть ниже)

TimeDateStamp — Дата создания файла

SizeOfOptionalHeader — Указывает размер следующего заголовка IMAGE_OPTIONAL_HEADER64, ведь он может быть заголовком IMAGE_OPTIONAL_HEADER32.

Characteristics — Здесь мы указываем некоторые атрибуты нашего приложения, например, что оно является исполняемым (EXECUTABLE_IMAGE) и может работать более чем с 2 Гб RAM (LARGE_ADDRESS_AWARE), а также что некоторая информация была удалена (на самом деле даже не была добавлена) в файл (RELOCS_STRIPPED | LINE_NUMS_STRIPPED | LOCAL_SYMS_STRIPPED).

SizeOfCode — Размер исполняемого кода в байтах (секция .text)

SizeOfInitializedData — Размер инициализированных данных (секция .rodata)

SizeOfUninitializedData — Размер не инициализированных данных (секция .bss)

BaseOfCode — указывает на начало секции кода блок

SectionAlignment — Размер по которому нужно выровнять секции в памяти

FileAlignment — Размер по которому нужно выровнять секции внутри файла

SizeOfImage — Размер всех секций программы

SizeOfHeaders — Размер всех заголовков вместе (IMAGE_DOS_HEADER, DOS Stub, IMAGE_NT_HEADERS64, IMAGE_SECTION_HEADER[IMAGE_FILE_HEADER.NumberOfSections]) выровненный по FileAlignment

Subsystem — Указывает тип нашей программы GUI или Console

MajorOperatingSystemVersion, MinorOperatingSystemVersion, MajorSubsystemVersion, MinorSubsystemVersion — Говорят о том на какой системе можно запускать данный exe, и что он может поддерживать. В нашем случае мы берем значение 5.2 от Windows XP (x64).

SizeOfStackReserve — Указывает сколько приложению нужно зарезервировать памяти под стек. Этот параметр по умолчанию составляет 1 Мб, максимально можно указать 1Гб. Вроде как умные программы на Rust умеют считать необходимый размер стека, в отличии от программ на C++ где этот размер нужно править вручную.

SizeOfStackCommit — Размер по умолчанию составляет 4 Кб. Как должен работать данный параметр пока не разобрался.

SizeOfHeapReserve — Указывает сколько резервировать памяти под кучу. Равен 1 Мб по умолчанию.

SizeOfHeapCommit — Размер по умолчанию равен 4 Кб. Подозреваю что работает аналогично SizeOfStackCommit, то есть пока неизвестно как.

IMAGE_DATA_DIRECTORY — массив записей о каталогах. В теории его можно уменьшить, сэкономив пару байт, но вроде как все описывают все 16 полей даже если они не нужны. А теперь чуть подробнее.

У каждого каталога есть свой номер, который описывает, где хранится его содержимое. Пример:

Export(0) — Содержит ссылку на сегмент который хранит экспортируемые функции. Для нас это было бы актуально если бы мы создавали DLL. Как это примерно должно работать можно посмотреть на примере следующего каталога.

Import(1) — Этот каталог указывает на сегмент с импортируемыми функциями из других DLL. В нашем случае значения VirtualAddress = 0x3000 и Size = 0xB8. Это единственный каталог, который мы опишем.

Resource(2) — Каталог с ресурсами программы (Изображения, Текст, Файлы и т.д.)

Значения других каталогов можно посмотреть в документации.

Теперь, когда мы посмотрели из чего состоит NT-заголовок, запишем и его в файл по аналогии с остальными по адресу 0x80.
(3) RAW NT-Header (Offset 0x00000080)

50 45 00 00 64 86 03 00  F4 70 E8 5E 00 00 00 00
00 00 00 00 F0 00 2F 00  0B 02 00 00 3D 00 00 00
13 00 00 00 00 00 00 00  00 10 00 00 00 10 00 00
00 00 40 00 00 00 00 00  00 10 00 00 00 02 00 00
05 00 02 00 00 00 00 00  05 00 02 00 00 00 00 00
00 40 00 00 00 02 00 00  00 00 00 00 02 00 00 00
00 00 10 00 00 00 00 00  00 10 00 00 00 00 00 00
00 00 10 00 00 00 00 00  00 10 00 00 00 00 00 00
00 00 00 00 10 00 00 00  00 00 00 00 00 00 00 00
00 30 00 00 B8 00 00 00  00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00

В результате получаем вот такой вид IMAGE_FILE_HEADER, IMAGE_OPTIONAL_HEADER64 и IMAGE_DATA_DIRECTORY заголовков:

Далее описываем все секции нашего приложения согласно структуре IMAGE_SECTION_HEADER

Структура IMAGE_SECTION_HEADER

Struct IMAGE_SECTION_HEADER
{
	i8[8] Name
	u32 VirtualSize
	u32 VirtualAddress
	u32 SizeOfRawData
	u32 PointerToRawData
	u32 PointerToRelocations
	u32 PointerToLinenumbers
	u16 NumberOfRelocations
	u16 NumberOfLinenumbers
	u32 Characteristics
}

Описание IMAGE_SECTION_HEADER

Name — имя секции из 8 байт, может быть любым

VirtualSize — сколько байт копировать из файла в память

VirtualAddress — адрес секции в памяти выровненный по SectionAlignment

SizeOfRawData — размер сырых данных выровненных по FileAlignment

PointerToRawData — адрес секции в файле выровненный по FileAlignment

Characteristics — Указывает какие данные хранит секция (Код, инициализированные или нет данные, для чтения, для записи, для исполнения и др.)

В нашем случае у нaс будет 3 секции.

Почему Virtual Address (VA) начинается с 1000, а не с нуля я не знаю, но так делают все компиляторы, которые я рассматривал. В результате 1000 + 3 секции * 1000 (SectionAlignment) = 4000 что мы и записали в SizeOfImage. Это полный размер нашей программы в виртуальной памяти. Вероятно, используется для выделения места под программу в памяти.

 Name	| RAW Addr	| RAW Size	| VA	| VA Size | Attr
--------+---------------+---------------+-------+---------+--------
.text	| 200		| 200		| 1000	| 3D	  |   CER
.rdata	| 400		| 200		| 2000	| 13	  | I   R
.idata	| 600		| 200		| 3000	| B8	  | I   R

Расшифровка атрибутов:

I — Initialized data, инициализированные данные

U — Uninitialized data, не инициализированные данные

C — Code, содержит исполняемый код

E — Execute, позволяет исполнять код

R — Read, позволяет читать данные из секции

W — Write, позволяет записывать данные в секцию

.text (.code) — хранит в себе исполняемый код (саму программу), атрибуты CE

.rdata (.rodata) — хранит в себе данные только для чтения, например константы, строки и т.п., атрибуты IR

.data — хранит данные которые можно читать и записывать, такие как статические или глобальные переменные. Атрибуты IRW

.bss — хранит не инициализированные данные, такие как статические или глобальные переменные. Кроме того, данная секция обычно имеет нулевой RAW размер и ненулевой VA Size, благодаря чему не занимает места в файле. Атрибуты URW

.idata — секция содержащая в себе импортируемые из других библиотек функции. Атрибуты IR

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

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

(4) RAW Sections (Offset 0x00000188)

                         2E 74 65 78 74 00 00 00
3D 00 00 00 00 10 00 00  00 02 00 00 00 02 00 00
00 00 00 00 00 00 00 00  00 00 00 00 20 00 00 60
2E 72 64 61 74 61 00 00  13 00 00 00 00 20 00 00
00 02 00 00 00 04 00 00  00 00 00 00 00 00 00 00
00 00 00 00 40 00 00 40  2E 69 64 61 74 61 00 00
B8 00 00 00 00 30 00 00  00 02 00 00 00 06 00 00
00 00 00 00 00 00 00 00  00 00 00 00 40 00 00 40

Следующий адрес для записи будет 00000200 что соответствует полю SizeOfHeaders PE-Заголовка. Если бы мы добавили еще одну секцию, а это плюс 40 байт, то наши заголовки не уложились бы в 512 (0x200) байт и пришлось бы использовать уже 512+40 = 552 байта выровненные по FileAlignment, то есть 1024 (0x400) байта. А все что останется от 0x228 (552) до адреса 0x400 нужно чем-то заполнить, лучше конечно нулями.

Взглянем как выглядит блок секций в Far:

Далее мы запишем в наш файл сами секции, но тут есть один нюанс.

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

Поэтому программы компилируются в несколько проходов. Например секция .rdata идет после секции .text, при этом мы не можем узнать виртуальный адрес переменной в .rdata, ведь если секция .text разрастется больше чем на 0x1000 (SectionAlignment) байт, она займет адреса 0x2000 диапазона. И соответственно секция .rdata будет находиться уже не в адресе 0x2000, а в адресе 0x3000. И нам будет необходимо вернуться и пересчитать адреса всех переменных в секции .text которая идет перед .rdata.

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

Секция .text

Asm segment .text

0000	push rbp
0001	mov rbp, rsp
0004	sub rsp, 0x20
0008	mov rcx, 0x0
000F	mov rdx, 0x402000
0016	mov r8, 0x40200D
001D	mov r9, 0x40
0024	call QWORD PTR [rip + 0x203E]
002A	mov rcx, 0x0
0031	call QWORD PTR [rip + 0x2061]
0037	add rsp, 0x20
003B	pop rbp
003C	ret

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

Но скажем так, если бы это была не функция main, а подфункция следовало бы сделать именно так.

А вот первые 3 в данном случае хоть и не обязательны, но желательны. Например, если бы мы использовали не MessageBoxA, а printf то без этих строк получили бы ошибку.

Согласно соглашению о вызовах для 64-разрядных систем MSDN, первые 4 параметра передаются в регистрах RCX, RDX, R8, R9. Если они туда помещаются и не являются, например числом с плавающей точкой. А остальные передаются через стек.

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

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

Поэтому если не хотите, чтобы программа себя странно вела, всегда резервируйте как минимум 8 байт * 4 аргумента = 32(0x20) байт, если передаете функции хотя бы 1 аргумент.

Рассмотрим блок кода с вызовами функций

MessageBoxA(0, 'Hello World!', 'MyApp', 64)
ExitProcess(0)

Сначала мы передаем наши аргументы:

rcx = 0

rdx = абсолютный адрес строки в памяти ImageBase + Sections[«.rdata»].VirtualAddress + Смещение строки от начала секции, строка читается до нулевого байта

r8 = аналогично предыдущему

r9 = 64(0x40) MB_ICONINFORMATION, значок информации

А далее идет вызов функции MessageBoxA, с которым не все так просто. Дело в том, что компиляторы стараются использовать как можно более короткие команды. Чем меньше размер команды, тем больше таких команд влезет в кэш процессора, соответственно, будет меньше промахов кэша, подзагрузок и выше скорость работы программы. Для более подробной информации по командам и внутренней работе процессора можно обратиться к документации Intel 64 and IA-32 Architectures Software Developer’s Manuals.

Мы могли бы вызвать функцию по полному адресу, но это заняло бы как минимум (1 опкод + 8 адрес = 9 байт), а с относительным адресом команда call занимает всего 6 байт.

Давайте взглянем на эту магию поближе: rip + 0x203E, это ни что иное, как вызов функции по адресу, указанному нашим смещением.

Я подсмотрел немного вперед и узнал адреса нужных нам смещений. Для MessageBoxA это 0x3068, а для ExitProcess это 0x3098.

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

Для первого call смещение будет указывать на конец команды call это 002A не забываем что в памяти этот адрес будет по смещению Sections[«.text»].VirtualAddress, т.е. 0x1000. Следовательно, RIP для нашего call будет равен 102A. Нужный нам адрес для MessageBoxA находится по адресу 0x3068. Считаем 0x3068 — 0x102A = 0x203E. Для второго адреса все аналогично 0x1000 + 0x0037 = 0x1037, 0x3098 — 0x1037 = 0x2061.

Именно эти смещения мы и видели в командах ассемблера.

0024	call QWORD PTR [rip + 0x203E]
002A	mov rcx, 0x0
0031	call QWORD PTR [rip + 0x2061]
0037	add rsp, 0x20

Запишем в наш файл секцию .text, дополнив нулями до адреса 0x400:
(5) RAW .text section (Offset 0x00000200-0x00000400)

55 48 89 E5 48 83 EC 20  48 C7 C1 00 00 00 00 48
C7 C2 00 20 40 00 49 C7  C0 0D 20 40 00 49 C7 C1
40 00 00 00 FF 15 3E 20  00 00 48 C7 C1 00 00 00
00 FF 15 61 20 00 00 48  83 C4 20 5D C3 00 00 00
........
00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00

Хочется отметить что всего лишь 4 строки реального кода содержат весь наш код на ассемблере. А все остальное нули что бы набрать FileAlignment. Последней строкой заполненной нулями будет 0x000003F0, после идет 0x00000400, но это будет уже следующий блок. Итого в файле уже 1024 байта, наша программа весит уже целый Килобайт! Осталось совсем немного и ее можно будет запустить.

Секция .rdata

Это, пожалуй, самая простая секция. Мы просто положим сюда две строки добив нулями до 512 байт.
.rdata

0400	"Hello World!\0"
040D	"MyApp\0"

(6) RAW .rdata section (Offset 0x00000400-0x00000600)

48 65 6C 6C 6F 20 57 6F  72 6C 64 21 00 4D 79 41
70 70 00 00 00 00 00 00  00 00 00 00 00 00 00 00
........
00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00

Секция .idata

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

Первое что нас ждет новая структура IMAGE_IMPORT_DESCRIPTOR

Структура IMAGE_IMPORT_DESCRIPTOR

Struct IMAGE_IMPORT_DESCRIPTOR
{
	u32 OriginalFirstThunk (INT)
	u32 TimeDateStamp
	u32 ForwarderChain
	u32 Name
	u32 FirstThunk (IAT)
}

Описание IMAGE_IMPORT_DESCRIPTOR

OriginalFirstThunk — Адрес указывает на список имен импортируемых функций, он же Import Name Table (INT)

Name — Адрес, указывающий на название библиотеки

FirstThunk — Адрес указывает на список адресов импортируемых функций, он же Import Address Table (IAT)

Для начала нам нужно добавить 2 импортируемых библиотеки. Напомним:

func MessageBoxA(u32 handle, PChar text, PChar caption, u32 type) i32 ['user32.dll']
func ExitProcess(u32 code) ['kernel32.dll']

(7) RAW IMAGE_IMPORT_DESCRIPTOR (Offset 0x00000600)

58 30 00 00 00 00 00 00  00 00 00 00 3C 30 00 00
68 30 00 00 88 30 00 00  00 00 00 00 00 00 00 00
48 30 00 00 98 30 00 00  00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00  00 00 00 00

У нас используется 2 библиотеки, а что бы сказать что мы закончили их перечислять. Последняя структура заполняется нулями.

 INT	| Time	 | Forward  | Name   | IAT
--------+--------+----------+--------+--------
0x3058	| 0x0    | 0x0      | 0x303C | 0x3068
0x3088	| 0x0    | 0x0      | 0x3048 | 0x3098
0x0000	| 0x0    | 0x0      | 0x0000 | 0x0000

Теперь добавим имена самих библиотек:
Имена библиотек

063С	"user32.dll\0"
0648	"kernel32.dll\0"

(8) RAW имена библиотек (Offset 0x0000063С)

                                     75 73 65 72
33 32 2E 64 6C 6C 00 00  6B 65 72 6E 65 6C 33 32
2E 64 6C 6C 00 00 00 00

Далее опишем библиотеку user32:
(9) RAW user32.dll (Offset 0x00000658)

                         78 30 00 00 00 00 00 00 
00 00 00 00 00 00 00 00  78 30 00 00 00 00 00 00
00 00 00 00 00 00 00 00  00 00 4D 65 73 73 61 67 
65 42 6F 78 41 00 00 00

Поле Name первой библиотеки указывает на 0x303C если мы посмотрим чуть выше, то увидим что по адресу 0x063C находится библиотека «user32.dll\0».

Подсказка, вспомните что секция .idata соответствует смещению в файле 0x0600, а в памяти 0x3000. Для первой библиотеки INT равен 3058, значит в файле это будет смещение 0x0658. По этому адресу видим запись 0x3078 и вторую нулевую. Означающую конец списка. 3078 ссылается на 0x0678 это RAW-строка

«00 00 4D 65 73 73 61 67 65 42 6F 78 41 00 00 00»

Первые 2 байта нас не интересуют и равны нулю. А вот дальше идет строка с названием функции, заканчивающаяся нулем. То есть мы можем представить её как «\0\0MessageBoxA\0».

При этом IAT ссылается на аналогичную таблице IAT структуру, но только в нее при запуске программы будут загружены адреса функций. Например, для первой записи 0x3068 в памяти будет значение отличное от значения 0x0668 в файле. Там будет адрес функции MessageBoxA загруженный системой к которому мы и будем обращаться через вызов call в коде программы.

И последний кусочек пазла, библиотека kernel32. И не забываем добить нулями до SectionAlignment.

(10) RAW kernel32.dll (Offset 0x00000688-0x00000800)

                         A8 30 00 00 00 00 00 00 
00 00 00 00 00 00 00 00  A8 30 00 00 00 00 00 00 
00 00 00 00 00 00 00 00  00 00 45 78 69 74 50 72 
6F 63 65 73 73 00 00 00  00 00 00 00 00 00 00 00 
........
00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00

Проверяем что Far смог корректно определить какие функции мы импортировали:

Отлично! Все нормально определилось, значит теперь наш файл готов к запуску.

Барабанная дробь…

Финал

Поздравляю, мы справились!

Файл занимает 2 Кб = Заголовки 512 байт + 3 секции по 512 байт.

Число 512(0x200) ни что иное, как FileAlignment, который мы указали в заголовке нашей программы.

Дополнительно:

Если хочется вникнуть чуть глубже, можно заменить надпись «Hello World!» на что-нибудь другое, только не забудьте изменить адрес строки в коде программы (секция .text). Адрес в памяти 0x00402000, но в файле будет обратный порядок байт 00 20 40 00.

Или квест чуть сложнее. Добавить в код вызов ещё одного MessageBox. Для этого придется скопировать предыдущий вызов, и пересчитать в нем относительный адрес (0x3068 — RIP).

Заключение

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

Если кто-то собрал свой exe значит мой труд был не напрасен.

Думаю в скором времени создать ELF файл похожим образом, интересна ли будет такая статья?)

Ссылки:

Структура и выполнение EXE-файла. — Мегаобучалка

EXE-модуль, созданный компоновщиком, состоит из следующих двух частей: 1) заголовок — запись, содержащая информацию по управлению и настройке программы и 2) собственно загрузочный модуль.

В заголовке находится информация о размере выполняемого модуля, области загрузки в памяти, адресе стека и относительных смещениях, которые должны заполнить машинные адреса в соответствии с относительными шестнадцатеричными позициями. Для EXE-файла все несколько сложнее чем COM-файл. Общеизвестно что EXE файл отличается от COM файла тем что состоит из двух частей — заголовка, содержащего управляющую информацию для загрузки и самого загружаемого модуля — программы. Программа загружается в память, затем производится настройка адресов в соответствии с ТHА, потом из заголовка берутся значения SS:SP и CS:IP. В ES и DS заносится сегментный адрес PSP. Рассмотрим структуру заголовка EXE файла:

ТАБЛИЦА EXE – ФАЙЛА

Смещение относительно начала(hex) Содержание Комментарий
00-01 4D5A — подпись компоновщика (признак EXE файла) Компоновщик устанавливает этот код для идентификации правильного EXE-файла
02-03 Длина последнего блока Число байтов в последнем блоке EXE-файла
04-05 Длина файла в блоках по 512 байт Число 512 байтовых блоков EXE-файла, включая заголовок
06-07 Количество элементов таблицы настройки адресов (Relocation table) Число настраиваемых элементов
08-09 Длина заголовка в параграфах Число 16-тибайтовых блоков (параграфов) в заголовке, (необходимо для локализации начала выполняемого модуля, следующего после заголовка)
0A-0B Минимальный объем памяти который надо выделить после конца программы ( в параграфах) Минимальное число параграфов, которые должны находится после загруженной программы
0C-0D Максимальный объем памяти… Переключатель загрузки в младшие или старшие адреса. При компоновке программист должен решить, должна ли его программа загружаться для выполнения в младшие адреса памяти или в старшие. Обычным является загрузка в младшие адреса. Значение шест.0000 указывает на загрузку в старшие адреса, а шест. FFFF — в младшие. Иные значения определяют максимальное число параграфов, которые должны находиться после загруженной программы
0E-0F Сегментный адрес стека относительно начала программы (SS) Относительный адрес сегмента стека в выполняемом модуле
10-11 Значение SP при запуске Адрес, который загрузчик должен поместить в регистр SP перед передачей управления в выполнимый модуль
12-13 Контрольная сумма — результат сложения без переноса всех слов файла Контрольная сумма — сумма всех слов в файле (без учета переполнений) используется для проверки потери данных
14-15 Значение IP Относительный адрес, который загрузчик должен поместить в регистр IP до передачи управления в выполняемый модуль
16-17 Значение CS Относительный адрес кодового сегмента в выполняемом модуле. Этот адрес загрузчик заносит в регистр CS
18-19 Адрес первого элемента ТHА Смещение первого настраиваемого элемента в файле.
1A-1B Номер сегмента перекрытия Номер оверлейного фрагмента: нуль обозначает, что заго ловок относится к резидентной части EXE-файла
Номер сегмента перекрытия Таблица настройки, содержащая переменное число настраиваемых элементов, соответствующее значению по смещению 06

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

Система строит префикс программного сегмента следом за резидентной частью COMMAND.COM (DOS), которая выполняет операцию загрузки. Затем COMMAND.COM выполняет следующие действия:

— Считывает форматированную часть заголовка в память.

— Вычисляет размер выполнимого модуля (общий размер файла в позиции 04 минус размер заголовка в позиции 08) и загружает модуль в память с начала сегмента.

— Считывает элементы таблицы настройки в рабочую область

и прибавляет значения каждого элемента таблицы к началу

сегмента (позиция OE).

— Устанавливает в регистрах SS и SP значения из заголовка

и прибавляет адрес начала сегмента.

— Устанавливает в регистрах DS и ES сегментный адрес

префикса программного сегмента.

— Устанавливает в регистре CS адрес PSP и прибавляет вели

чину смещения в заголовке (позиция 16) к регистру CS.

Если сегмент кода непосредственно следует за PSP, то смещение в заголовке равно 256 (шест.100). Регистровая пара CS:IP содержит стартовый адрес в кодовом сегменте, т.е. начальный адрес программы.

После инициализации регистры CS и SS содержат правильные адреса, а регистр DS (и ES) должны быть установлены в программе для их собственных сегментов данных:

1. PUSH DS ;Занести адрес PSP в стек

2. SUB AX,AX ;Занести нулевое значение в стек

3. PUSH AX ; для обеспечения выхода из программы

4. MOV AX,datasegname ;Установка в регистре DX

5. MOV DS,AX ; адреса сегмента данных

При завершении программы команда RET заносит в регистр IP нулевое значение, которое было помещено в стек в начале выполнения программы. В регистровой паре CS:IP в этом случае получается адрес, который является адресом первого байта PSP, где расположена команда INT 20H. Когда эта команда будет выполнена, управление перейдет в DOS.

ПРИМЕР EXE-ПРОГРАММЫ

Рассмотрим следующую таблицу компоновки (MAP) программы:
Start Stop Length Name 00000H 0003AH 003BH CSEG 00040H 0005AH 001BH DSEG 00060H 0007FH 0020H STACK
Program entry point at 0000:0000
Class
CODE
DATA
STACK

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

В соответствии с таблицей MAP кодовый сегмент CSEG находится по адресу 00000 — этот относительный адрес является началом выполняемого модуля. Длина кодового сегмента составляет шест.003B байтов. Следующий сегмент по имени DSEG начинается по адресу шест.00040 и имеет длину шест.001B. Адрес шест.00040 является первым после CSEG адресом, выровненным на границу параграфа (т.е. это значение кратно шест.10). Последний сегмент, STACK, начинается по адресу шест.00060 — первому после DSEG, адресу выровненному на границу параграфа.

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

00 Шест.4D5A.

02 Число байтов в последнем блоке: 5B00.

04 Число 512 байтовых блоков в файле, включая заголовок: 0200 (шест.0002х512=1024).

06 Число элементов в таблице настройки, находящейся после форматированной части заголовка: 0100, т.е. 0001.

08 Число 16 байтовых элементов в заголовке: 2000 (шест.0020=32 и 32х16=512).

0C Загрузка в младшие адреса: шест. FFFF.

0E Относительный адрес стекового сегмента: 6000 или шест.

60.

10 Адрес для загрузки в SP: 2000 или шест.20.

14 Смещение для IP: 0000.

16 Смещение для CS: 0000.

18 Смещение для первого настраиваемого элемента: 1E00 или шест.1E.

После загрузки программы под управлением отладчика DEBUG регистры получают следующие значения:

SP = 0020 DS = 138F ES = 138F

SS = 13A5 CS = 139F IP = 0000

Для EXE-модулей загрузчик устанавливает в регистрах DS и ES адрес префикса программного сегмента, помещенного в доступной области памяти, а в регистрах IP, SS и SP — значения из заголовка программы.

Регистр SP

Загрузчик использует шест.20 из заголовка для инициализации указателя стека значением длины стека. В данном примере стек был определен, как 16 DUP (?), т.е. 16 двухбайтовых полей общей длиной 32 (шест.20) байта. Регистр SP указывает на текущую вершину стека.

Регистр CS

В соответствии со значением в регистре DS после загрузки программы, адрес PSP равен шест.138F(0). Так как PSP имеет длину шест.100 байтов, то выполняемый модуль, следующий непосредственно после PSP, находится по адресу шест.138F0+100=139F0. Это значение устанавливается загрузчиком в регистре CS. Таким образом, регистр CS определяет начальный адрес кодовой части программы (CSEG). С помощью команды D CS:0000 в отладчике DEBUG можно просмотреть в режиме дампа машинный код в памяти. Обратим внимание на идентичность дампа и шестнадцатеричной части ассемблерного LST файла кроме операндов, отмеченных символом R.

Регистр SS

Для установки значения в регистре SS загрузчик также использует информацию из заголовка:

Начальный адрес PSP 138F0

Длина PSP 100

Относительный адрес стека 60

Адрес стека 13A50

Регистр DS

Загрузчик использует регистр DS для установки начального адреса PSP. Так как заголовок не содержит стартового адреса, то регистр DS необходимо инициализировать в программе следующим образом:

0004 B8 —- R MOV AX,DSEG

0007 8E D8 MOV DS,AX

Ассемблер оставляет незаполненным машинный адрес сегмента DSEG, который становится элементом таблицы настройки в заголовке. С помощью отладчика DEBUG можно просмотреть завершенную команду в следующем виде:

B8 A313

Значение A313 загружается в регистр DS в виде 13A3. В результате имеем

Регистр Адрес Смещение

CS 139F0 00

DS 13A30 40

SS 13A50 60

Попробуем выполнить трассировку любой скомпонованной программы под управлением отладчика DEBUG (DOS) и обратим внимание на изменяющиеся значения в регистрах:

Команда Изменяющиеся регистры

PUSH DS IP и SP

SUB AX,AX IP и AX (если был не нуль)

PUSH AX IP и SP

MOV AX,DSEG IP и AX

MOV DS,AX IP и DS

Регистр DS содержит теперь правильный адрес сегмента данных. Можно использовать теперь команду D DS:00 для просмотра содержимого сегмента данных DSEG и команду D SS:00 для просмотра содержимого стека.

Структура и процесс загрузки EXE-программы. Создаем вирус и антивирус

Структура и процесс загрузки EXE-программы

В отличие от COM-программ, EXE-программы могут состоять из нескольких сегментов (кодов, данных, стека). Они могут занимать больше 64Кбайт.

EXE-файл имеет заголовок, который используется при его загрузке. Заголовок состоит из форматированной части, содержащей сигнатуру и данные, необходимые для загрузки EXE-файла, и таблицы для настройки адресов (Relocation Table). Таблица состоит из значений в формате сегмент: смещение. К смещениям в загрузочном модуле, на которые указывают значения в таблице, после загрузки программы в память должен быть прибавлен сегментный адрес, с которого загружена программа.

При запуске EXE-программы системным загрузчиком (вызовом функции DOS 4Bh) выполняются следующие действия:

1. Определяется сегментный адрес свободного участка памяти, размер которого достаточен для размещения программы.

2. Создается и заполняется блок памяти для переменных среды.

3. Создается блок памяти для PSP и программы (сегмент:0000h – PSP; сегмент+0010h:0000h – программа). В поля PSP заносятся соответствующие значения.

4. Адрес DTA устанавливается равным PSP:0080h.

5. В рабочую область загрузчика считывается форматированная часть заголовка EXE-файла.

6. Вычисляется длина загрузочного модуля по формуле: Size=((PageCnt*512)–(HdrSize*16))–PartPag.

7. Определяется смещение загрузочного модуля в файле, равное HdrSize*16.

8. Вычисляется сегментный адрес (START_SEG) для загрузки – обычно это PSP+10h.

9. Считывается в память загрузочный модуль (начиная с адреса START_SEG:0000).

10. Для каждого входа таблицы настройки:

a) читаются слова I_OFF и I_SEG;

b) вычисляется RELO_SEG=START_SEG+I_SEG;

c) читается слово по адресу RELO_SEG:I_OFF;

d) к прочитанному слову прибавляется START_SEG;

e) результат запоминается по тому же адресу (RELO_SEG:I_OFF).

11. Распределяется память для программы в соответствии с MaxMem и MinMem.

12. Инициализируются регистры, выполняется программа:

a) ES=DS=PSP;

b) АХ=результат проверки правильности идентификаторов драйверов, указанных в командной строке;

c) SS=START_SEG+ReloSS, SP=ExeSP;

d) CS=START_SEG+ReloCS, IP=ExeIP.

Данный текст является ознакомительным фрагментом.

Читать книгу целиком

Поделитесь на страничке

НОУ ИНТУИТ | Лекция | Структура программных компонентов

Аннотация: Управление памятью в Windows. Обзор структуры исполняемых файлов в формате Portable Executable (PE). Пример генерации PE-файла.

Формат исполняемых файлов

Исполняемый файл (executable file) — это файл, который может быть загружен в память загрузчиком операционной системы и затем исполнен. В операционной системе Windows исполняемые файлы, как правило, имеют расширения «.exe» и «.dll». Расширение «.exe» имеют программы, которые могут быть непосредственно запущены пользователем. Расширение «.dll» имеют так называемые динамически связываемые библиотеки (dynamic link libraries). Эти библиотеки экспортируют функции, используемые другими программами.

Для того чтобы загрузчик операционной системы мог правильно загрузить исполняемый файл в память, содержимое этого файла должно соответствовать принятому в данной операционной системе формату исполняемых файлов. В разных операционных системах в разное время существовало и до сих пор существует множество различных форматов. В этой главе мы рассмотрим формат Portable Executable (PE). Формат PE — это основной формат для хранения исполняемых файлов в операционной системе Windows. Сборки .NET тоже хранятся в этом формате.

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

А теперь — немного истории. Формат PE был создан разработчиками Windows NT. До этого в операционной системе Windows использовались форматы New Executable (NE) и Linear Executable (LE) для представления исполняемых файлов, а для хранения объектных файлов использовался Object Module Format (OMF). Формат NE предназначался для 16-разрядных приложений Windows, а формат LE, изначально разработанный для OS/2, был уже 32-разрядным. Возникает вопрос: почему разработчики Windows NT решили отказаться от существующих форматов? Ответ становится очевидным, если обратить внимание на то, что большая часть команды, работавшей над созданием Windows NT, ранее работала в Digital Equipment Corporation. Они занимались в DEC разработкой инструментария для операционной системы VAX/VMS, и у них уже были навыки и готовый код для работы с исполняемыми файлами, представленными в формате Common Object File Format (COFF). Соответственно, формат COFF в слегка модифицированном виде был перенесен в Windows NT и получил название PE.

В «.NET Framework Glossary» сказано, что PE — это реализация Microsoft формата COFF. В то же время в [5] утверждается, что PE — это формат исполняемых файлов, а COFF — это формат объектных файлов. Вообще, мы можем наблюдать путаницу в документации Microsoft относительно названия формата. В некоторых местах они называют его COFF, а в некоторых — PE. Правда, можно заметить, что в новых текстах название COFF используется все меньше и меньше. Более того, формат PE постоянно эволюционирует. Например, несколько лет назад в Microsoft отказались от хранения отладочной информации внутри исполняемого файла, и поэтому теперь многие поля в структурах формата COFF просто не используются. Кроме того, формат COFF — 32-разрядный, а последняя редакция формата PE (она называется PE32+) может использоваться на 64-разрядных аппаратных платформах. Поэтому, видимо, дело идет к тому, что название COFF вообще перестанут использовать.

Интересно отметить, что исполняемые файлы в устаревших форматах NE и LE до сих пор поддерживаются Windows. Исполняемые файлы в формате NE можно запускать под управлением NTVDM (NT Virtual DOS Machine), а формат LE используется для виртуальных драйверов устройств (VxD).

Почему в названии формата PE присутствует слово «portable» («переносимый»)? Дело в том, что Windows NT была реализована не только для платформы Intel x86, но и для платформ MIPS R4000, DEC Alpha и PowerPC. И во всех реализациях для хранения исполняемых файлов использовался формат PE. При этом речь не шла о достижении двоичной совместимости между этими платформами, то есть exe-файл, предназначенный для выполнения на платформе Intel x86, нельзя было запустить на PowerPC. Важно понимать, что переносимость формата еще не означает переносимость исполняемых файлов, записанных в этом формате. Формат PE переносим в том смысле, что он слабо зависит от типа процессора и поэтому подходит для разных платформ (в том числе и для платформы .NET).

Далее в этой главе мы не будем затрагивать 64-разрядный вариант формата PE, потому что в настоящее время сборки .NET хранятся в прежнем 32-разрядном формате. Однако отметим, что 64-разрядный PE очень слабо отличается от 32-разрядного. Основное отличие касается разрядности полей структур PE-файла.

Компьютеры и Интернет

EXE файл – это специальный файл, который заканчивается расширением «.exe», иначе его называют исполняемый файл. И когда вы делаете клик на таком exe файле, встроенная процедура автоматически начинает выполнять код, который может привести в движение несколько функций. Файлы exe применяются для установки и запуска ПО и различных подпрограмм. EXE файл – это всего лишь один из нескольких типов форматов файлов, которые могут работать с различными ОС. Текстовые файлы, являются файлами, которые не могут генерировать код, и просто отображают текст, заканчиваются на txt. Другим распространенным типом файлов является сжатый или зашифрованный файл, который использует в конце расширение zip.
Файл exe является из самых полезных типов файлов именно потому, что он запускает программы; но он также делает его потенциально опасным. Он может использоваться как система доставки для вирусов или других вредоносных процедур. Внешне зараженный файл exe может показаться довольно правильным. Нажатие на него может показаться запущенным не более, чем анимированный мультфильм или простая аркадная игра. Но невидимый код может работать в фоновом режиме, заражая компьютер или компрометируя его.

Если вы нажмёте на такой вредоносный файл exe, в течение следующих может быть нескольких часов или даже дней, компьютер может начать нестабильно работать. Часто пользователь не связывает проблему с причиной и приписывает её другим факторам, таким как необходимость дефрагментации или замены диска. Личные данные или введение паролей когда вы нажимаете клавиш для его ввода, могут быть переданы на неизвестный веб-сайт и пользователь даже не будет знать об этом, или ПК может удалить свои собственные файлы и вообще привести к сбою ОС. Любой из этих сценариев и многие другое можно привести в движение, просто щёлкнув файл «bad» exe.

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

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

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

извлекаем ресурсы или запускаем файлы под Windows, Linux и Mac OS

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

Где используется формат EXE

Такие файлы использовались ранее и существуют сейчас в таких ОС, как MS-DOS, OS/2, Windows, Symbian и OpenVMS. Такие файлы задействуются в 16-, 32- и 64-разрядных ОС.

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

Программы для работы с форматом EXE

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

При помощи Resource Hacker можно открыть и вытянуть информацию и ресурсы из EXE файла — иконки, версии, и другие

Resourse Hacker (сокращенно — Reshack). Это приложение бесплатное, и его размер всего 545 кб. В этой программке можно менять такие элементы, как курсоры, и конки и проч., но доступа к программному коду она не дает. То же самое можно сделать в программе Resource Tuner.

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

  1. Файл сжат упаковщиком или поврежден. Действительно, файлы часто бывают сжаты, так как разработчики стремятся уменьшить их объем. Resource Tuner может осуществить распаковку только наиболее популярного упаковщика – UPX. Другие упаковщики программа не поддерживает, поэтому файл вам придется распаковывать самостоятельно.
  2. Файл — 16-битный NE Executable. Такие файлы не поддерживаются, и открыть их не удастся.
  3. Файл не является исполняемым. Даже если у файла, который не является исполняемым, будет расширение exe, программа выдаст данную ошибку. Ведь расширение могли поменять специально.

Файлы exe используются для распространения вирусов, в частности, троянов. Поэтому при открытии таких файлов соблюдайте осторожность и не забывайте проводить сканирование системы, например, с помощью бесплатной лечащей утилиты Dr.Web CureIt.

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

Какие файлы не стоит открывать в редакторах ресурсов

  1. EXE более гигабайта величиной. В программе установлены ограничения – образ открываемого файла должен разместиться в пределах первого гигабайта памяти.
  2. Файлы, которые создавались в Visual Basic. Секция ресурсов таких файлов содержит только иконку и номер версии. Сам код на VB находится в специальном формате, который не откроется в редакторе ресурсов.
  3. Установщики других программ. В ресурсах хранятся только номер версии и иконка. Внутри таких файлов находится контейнер, который содержит другой, сжатый файл EXE, и программу-распаковщик. Кроме того, для сохранения данных в таких программах используются разные технологии.
  4. Самораспаковывающиеся архивы в виде exe-шников. Это просто архивированная информация и программа для ее распаковки.

Какие еще существуют программы для открытия exe

Другие распространенные программы, позволяющие открыть exe под Windows:

  • VMware ThinApp;
  • Microsoft Windows. Для открытия и работы с установочными файлами EXE ОС Windows пользуется программой под названием Windows Installer. Скачивать и устанавливать данный компонент вручную не придется — изначально он присутствует в операционной системе, а при необходимости обновить его это производится автоматически через центральный сервер обновлений Microsoft — WSUS;
  • IcoFX;
  • Microsoft Visual Studio. Среда для разработчиков с широким функционалом для написания приложений под Windows.

Если ваш ПК является «обладателем» Mac OS, то подойдут такие приложения:

  • Parallels Desktop 7, VMware Fusion 4, Oracle VM VirtualBox. Для использования функционала по работе с файлами формата EXE на любой из этих 3-х программ должна быть установлена Microsoft Windows. Т.е. фактически каждая такая программа — среда виртуализации, которая будет открывать и работать с exe файлами.
  • Darwine;
  • CrossOver;
  • Kronenberg WineBottler.

В Linux работают Cedega, Wine и DataFlex.

Если под операционную систему Linux при выборе, чем открывать exe файлы, вы остановитесь на Wine, то рекомендуем обратить внимание на версию от Ethersoft. В нее включен ряд модулей, которые ориентированы на запуск и работу с приложениями для бизнеса — 1С Предприятие и продукты Microsoft. Также хочется отметить, что Wine уже длительное время умеет стабильно работать с MS Office, включая текстовый редактор Word.

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

EXE Расширение файла — Что такое EXE-файл и как его открыть?

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

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

На платформах, отличных от Windows, таких как macOS и Linux, EXE-файлы не используются для исполняемых файлов. Например, macOS использует файлы .APP для запуска приложений. Однако, если вы хотите запустить EXE-файл на платформе, отличной от Windows, вы можете использовать виртуальную машину, такую ​​как Parallels Desktop или VM VirtualBox, которая позволяет запускать Windows в среде, отличной от Windows.

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

На компьютерах Mac вы можете использовать виртуальную машину Windows, созданную Parallels Desktop или VMware Fusion, для запуска программы, содержащейся в EXE-файле.

В Linux вы можете использовать виртуальную машину Windows, созданную Oracle VM VirtualBox, для запуска программы, содержащейся в EXE-файле, или вы можете использовать Wine, приложение, специально разработанное для того, чтобы пользователи могли запускать приложения Windows без установки Windows.

Файлы

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

.

Файловые системы в операционной системе: структура, атрибуты, тип

  • Home
  • Testing

      • Back
      • Agile Testing
      • BugZilla
      • Cucumber
      • Database Testing
      • Testing
      • Database Testing
      • Назад
      • JUnit
      • LoadRunner
      • Ручное тестирование
      • Мобильное тестирование
      • Mantis
      • Почтальон
      • QTP
      • Назад
      • 00030003 Центр контроля качества
      • 0003 Центр контроля качества
      • SoapUI
      • Управление тестированием
      • TestLink
  • SAP

      • Назад 9000 4
      • ABAP
      • APO
      • Начинающий
      • Basis
      • BODS
      • BI
      • BPC
      • CO
      • Назад
      • CRM
      • Crystal Reports
      • H0003 Crystal Reports

      • QM
      • Заработная плата
      • Назад
      • PI / PO
      • PP
      • SD
      • SAPUI5
      • Безопасность
      • Менеджер решений
      • Successfactors
      • SAP Tutorials
    • 000
    • 9000
    • 9000
    • 9000 9000 Web Назад

    • Apache
    • AngularJS
    • ASP.Net
    • C
    • C #
    • C ++
    • CodeIgniter
    • СУБД
    • JavaScript
    • Назад
    • Java
    • JSP
    • Kotlin
    • Linux
    • Linux
    • Kotlin
    • Linux
    • js

    • Perl
    • Назад
    • PHP
    • PL / SQL
    • PostgreSQL
    • Python
    • ReactJS
    • Ruby & Rails
    • Scala
    • SQL
    • 000

    • SQL
    • 000

      0003 SQL

      000

      0003 SQL

      000

    • UML
    • VB.Net
    • VBScript
    • Веб-службы
    • WPF
  • Обязательно учите!

      • Назад
      • Бухгалтерский учет
      • Алгоритмы
      • Android
      • Блокчейн
      • Business Analyst
      • Создание веб-сайта
      • CCNA
      • Облачные вычисления
      • 00030003 COBOL 9000 Compiler
          9000 Встроенные системы

        • 00030002 9000 Compiler 9000
        • Ethical Hacking
        • Учебники по Excel
        • Программирование на Go
        • IoT
        • ITIL
        • Jenkins
        • MIS
        • Сеть
        • Операционная система
        • Назад
        • Управление проектами Обзоры
        • Salesforce
        • SEO
        • Разработка программного обеспечения
        • VB A
    • Big Data

        • Назад
        • AWS
        • BigData
        • Cassandra
        • Cognos
        • Хранилище данных
        • 0003

        • HBOps
        • 0003

        • HBOps
        • MicroStrategy

    .

  • Добавить комментарий

    Ваш адрес email не будет опубликован. Обязательные поля помечены *