Каталог расширений

Популярные теги

3gp       avi       fb2       jpg       mp3       pdf      

Как создать пустой exe файл


Создаем 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 файл похожим образом, интересна ли будет такая статья?)

Ссылки:

Как создать пустой файл в системе Windows для новичков

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

С файлом более-менее ясно, т. е. это любая текстовая, мультимедийная информация пользователя любого отрезка и размера. Понятно? Более чем. Но есть еще одно туманное понятие – некое расширение файла. Что это такое?

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

Значит, файл – сама информация, расширение файла – ее тип.

Необходимость создания информации без ее определенного типа в Windows: истоки

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

Для проверки создадим самый простой текстовый файл блокнота и назовем его «Как создать пустой файл»:

  • Для этого щелкаем в меню проводника правой кнопкой мыши.
  • В контекстном меню наводим курсор на строчку «Создать».
  • Выбираем из представленных файлов «Текстовый файл».

В окне проводника появляется файл блокнота с выделенным названием «Новый текстовый документ» - система предлагает переименовать его. Выполним это действие.

Назовем его, обратим внимание на то, что, кроме названия, с правой стороны больше нет никаких указаний. Это как раз и есть признак того, что в системе Windows информационный тип скрыт в настройках.

В меню проводника переходим в левый верхний угол, где есть строчка «Файл», нажимаем на нее, появляется еще одно контекстное меню, в котором ищем строчку «Параметры», кликаем по ней.

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

Таким образом, мы видим после названия текстового документа появившийся символ «.», а также три латинские буквы txt. Это и есть скрытое ранее обозначение расширения файла, в данном случае аббревиатура расширения текстового файла.

Двигаемся дальше в работе с файлом и его типом

Теперь решаем основную задачу - создать пустой файл без расширения в Windows:

  • Выделяем нужный файл левой клавишей мыши.
  • Жмем правую кнопку мыши, в появившемся контекстном меню выбираем строчку «Переименовать».
  • Название файла выделится, передвигаем курсор от названия файла к его расширению, в нашем случае к .txt, и клавишей клавиатуры Backspace стираем все символы до самого названия файла.
  • Появляется предупредительное окно информации, в котором смело жмем «Да».
  • Жмем клавишу Enter, чтобы сохранить наши действия.

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

Файловая информация и смена ее формата: заключение

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

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

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

Почитайте внимательней мат.часть на предмет развития и появления .NET. Эта технология развилась благодаря COM-технологии. А все эти Ко-Сервера, Ко-Клиенты ничто иное как исполнимые файлы прописанные в реестре.
Не связывался с COM, но хорошо представляю, как загружаются .net приложения. Система вместе с исполняемым файлом загружает mscoree.dll передаёт управление _CorExeMain, а не нативной точке входа в exe файле. На системах до Windows XP, где загрузчик не подозревает о существовании .net, исполняемый файл загружается обычным образом, и для того, чтобы .net приложения могли загружаться и в старых системах, в каждой сборке есть небольшой stub для Windows, состоящий из одной команды:
jmp _CorExeMain
Так вот что мешает написать вместо этой одной команды небольшую программу, которая бы определяла наличие .net, и при его отсутствии давала бы внятное сообщение об ошибке? А обучив загрузчик Windows XP распознавать и загружать .net сборки без участия этого jmp ничего не мешало также позаботиться о нормальном сообщении об ошибке.
Почему исполняемые файлы .net выводят внятную ошибку при попытке запуска под DOS, но под более новой Windows они не могут этого сделать? Для меня это загадка.
Это должен разработчик заботится. При написании инсталяционного скрипта для продукта он должен обнаружить нужное и выдать сообщение чего не хватает.
Да, так говорит Microsoft. Но пользователю никто не запрещает запустить уже установленную программу после переустановки операционной системы. Писали бы в ошибке тогда «Переустановите программу».

Как сделать установочный файл EXE

Что вы делаете, когда вы создали какое-то новое программное обеспечение Windows — что угодно из простого диагностического инструмента

в сложную компьютерную видеоигру

— а ты хочешь поделиться этим с миром? Ну, вы можете сжать файлы в один ZIP-файл и распространять его.

Или вы можете быть модным и вместо этого создать установщик EXE.

В этом посте мы рассмотрим три различных метода: самораспаковывающийся пакет, простой установщик, использующий встроенный IExpress, и расширенный установщик, использующий настраиваемую установку Inno.

Быстрый метод: использование 7-Zip

Вы, вероятно, уже используете 7-Zip для извлечения всех видов архивных файлов

и вы, вероятно, уже знаете, что 7-Zip может создавать архивные файлы, но знаете ли вы, что вы также можете использовать его для создания EXE-файла, который действует как установщик?

Это называется Архив SFX («Самораспаковывающийся») и работает, сжимая все ваши конечные файлы вместе, а затем встраивая специальный архив EXE в архив, который знает, как извлечь все. Другими словами, получатель может извлечь архив SFX (который выглядит как сам файл EXE), даже если у него нет подходящего программного обеспечения, что может случиться с такими форматами, как 7Z, RAR, TAR и ZIP.

Как создать свой собственный SFX-архив с 7-Zip:

  1. Подготовьте все свои файлы и каталоги в одном главном каталоге и назовите каталог по своему усмотрению.
  2. Нажмите правой кнопкой мыши на каталог и выберите 7-Zip> Добавить в архив…
  3. Под опциями включите Создать SFX архив и выберите следующие настройки …
    > Формат архива: 7z
    >
    Уровень сжатия: Нормальный
    >
    Метод сжатия: LZMA2
    >
    Размер словаря: 16 МБ

    он активно разрабатывался с 1997 года. Он был создан частично в ответ на актуальный неполный уровень InstallShield Express. С тех пор он стал создателем инсталляторов для тех, кто не хочет исследовать собственные опции.

    Его самая большая ничья — это гибкость. Inno Setup использует файлы сценариев с расширением ISS («Inno Setup Script») для настройки различных аспектов программы установки: какие файлы включены, где происходит установка, создавать ли ярлыки и т. Д. Файлы ISS используют форматирование, подобное формату файлов INI. и может быть создан с помощью мастера установки Inno.

    Как создать свой собственный установщик с помощью Inno Setup:

    1. Запустите приложение Inno Setup Compiler.
    2. В окне приветствия выберите Создайте новый файл сценария с помощью мастера сценариев.
    3. Введите имя приложения и версию приложения. При желании вы также можете указать сведения об издателе приложения и веб-сайте приложения. Нажмите следующий.
    4. Выберите Основная папка назначения, который по умолчанию для программных файлов. Введите Имя папки приложения, Это имя главного каталога, куда будут идти ваши установочные файлы. Нажмите следующий.
    5. За Основной исполняемый файл приложения, найдите и выберите основной EXE-файл, который запустит ваше приложение. Если вы не устанавливаете приложение, включите Приложение не имеет основного исполняемого файла. Затем добавьте файлы и папки в вашу установку с помощью Добавить файлы… а также Добавить папки … кнопок. Нажмите следующий.
    6. На странице «Ярлыки приложений» оставьте значения по умолчанию или измените их в соответствии со своими предпочтениями. Они все говорят сами за себя. Нажмите следующий.
    7. На странице документации приложения вы можете указать до трех TXT-файлов, которые будут отображаться в процессе установки конечного пользователя. Обычно это LICENSE.TXT, INSTALL.TXT и README.TXT, но они могут быть любыми. Нажмите следующий.
    8. На странице «Настройка языков» сохраните английский но не стесняйтесь добавлять столько языков, сколько пожелаете. Нажмите следующий.
    9. На странице «Настройки компилятора» вы можете настроить EXE-файл установщика:
      > Пользовательская папка вывода компилятора где будет находиться полученный установочный EXE-файл.
      > Имя выходного файла компилятора это то, что файл EXE будет называться. Значением по умолчанию является setup.exe.
      > Файл настроек пользовательской настройки это значок, который будет использоваться для установочного EXE-файла. Это должен быть файл ICO, который вы можете скачать или конвертировать из PNG

      ,

    10. Настроить пароль защитит неавторизованных пользователей от использования вашего установщика. Оставьте это поле пустым, чтобы отключить эту функцию.
    11. Нажмите Конец. Когда будет предложено скомпилировать новый скрипт, нажмите да. Когда будет предложено сохранить скрипт, выберите нет если это одноразовый установочный файл или выберите да если вы планируете изменить или обновить его позже. Готово!

    Скачать — Inno Setup (бесплатно)

    Какой метод подходит вам?

    Если ваше программное обеспечение очень простое или вы собираетесь распространять его среди ограниченного числа людей, используйте метод 7-Zip. Это просто, быстро и практически не требует технических знаний.

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

    Если у вас сложное программное обеспечение и у вас за плечами большой технический опыт, воспользуйтесь методом Inno Setup. Он самый гибкий, самый мощный и не страдает от проблем, которые мешают IExpress.

    Какой метод вы будете использовать? Есть ли другие методы, которые мы пропустили? Дайте нам знать в комментариях ниже!

    Изображение предоставлено: sdecoret, DaGa5 через Shutterstock.com

КАк создать пустой exe файл)

Для просмотра онлайн кликните на видео ⤵

Как создать обычный ехе файл без вреда.[teZowOw~] Подробнее

Как создать ярлык от .exe файла? [Видео уроки] Подробнее

Как создать свой .exe файл из пакетного файла Windows .bat. Подробнее

Как создать файл без расширения: самые простые способы | IT S.W.A.T.

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

Командная строка

Универсальный инструмент, посредством которого в Windows можно сделать совершенно всё, главное, знать, каким образом.

  1. Запускаем командную строку любым удобным способом, например, жмём Win + R, а в появившемся окошке выполняем команду «cmd».
  2. В открывшемся окне с черным фоном вводим и выполняем команду «copy con>file» либо «1 >> c:\file», где file – название нашего документа. Можно вводить полный путь к файлу, если пользоваться командной строкой не умеете. Это избавит от необходимости переходить в нужный каталог в текстовом режиме.

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

Это самый простой, универсальный и быстрый способ создать пустой файл без расширения в любой версии Windows 7. Все остальные на «Семёрке» и «Десятке» будут незначительно отличаться из-за различий в интерфейсе.

Проводник

Через стандартный менеджер файлов также можно сделать файл без расширения.

  1. Для этого открываем каталог, в котором он должен появиться.
  2. Кликаем правой клавишей мыши на пустом месте в окне и вызываем пункт «Новый».
  3. В выпавшем списке выбираем любой пункт кроме первого.

Появится файл, название которого следует ввести.

  1. Вводим имя нового документа без точек или удаляем точку и жмём «Enter».

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

На этом – всё готово.

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

На многих компьютерах опция редактирования расширения недоступна, исправить это очень просто.

Windows 7

  1. Открываем Проводник, например, при помощи сочетания горячих клавиш Win + E.
  2. Кликаем Alt для вызова главного меню, если оно невидимо.
  3. Из него вызываем пункт «Сервис», в выпавшем списке выбираем «Параметры папок».

  1. Идём во вкладку «Вид».
  2. В фрейме «Дополнительные параметры» пролистываем список пунктов до самого низа и убираем флажок возле опции «Скрывать расширения для…».
  3. Сохраняем новую конфигурацию.

Теперь возможность изменения расширения документов доступна.

Windows 10

В Windows 8 и 10 активация и отключение функции осуществляется проще и быстрее.

  1. В Проводнике вызываем главное меню при помощи Alt, если оно спрятано.
  2. Во вкладке «Вид» отмечаем галочкой опцию «Расширение имён файлов».

Вот и всё.

Для открытия и изменения содержимого документа его необходимо перетащить в окно любого текстового редактора или через контекстное меню вызвать команду «Открыть с помощью…» и выбрать нужное приложение в выпадающем списке.

Этих способов решения проблемы вполне достаточно для всех случаев, однако мы коснёмся ещё пары вариантов решения задачи для исчерпания темы.

Блокнот

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

Рассмотрим, как создать файл без расширения txt через любой более функциональный аналог Notepad’а. Им может быть AkelPad, Notepad++, Sublime Text и иной подобный текстовый редактор. Рассмотрим на примере последнего, с остальными всё будет точно так же.

  1. Скачиваем приложение с официального сайта, инсталлируем и запускаем его.
  2. Жмём Ctrl + S либо вызываем команду «Сохранить» через пункт главного меню «Файл».
  3. Вводим название документа, в поле «Тип…» указываем «AllFiles (*.*)» и кликаем «Сохранить».

Total Commander

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

  1. Открываем целевой каталог и вызываем контекстное меню папки правым кликом по пустому месту панели.
  2. Выбираем знакомый пункт «Новый» и указываем любой объект, кроме папки и ярлыка.

  1. В окошке для ввода имени пишем любое название без точки и кликаем «ОК» для создания нового объекта.

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

Мы рассмотрели основные способы создания файлов без расширения в операционных системах Windows при помощи инструментария самой ОС и посредством популярных приложений.

Как создать пустой файл

Обновлено: 05.01.2018, Computer Hope

Пользователи Windows

Наконечник

Чтобы создать пустой файл (0 байт), это должен быть текстовый файл. Другие типы документов, такие как пустой .doc (файл текстового редактора) или пустой Excel (электронная таблица), работать не будут. Причина в том, что они содержат дополнительные данные, которые структурируют и идентифицируют файл, даже если вы не ввели никаких данных в документ.

В Windows щелкните правой кнопкой мыши в области, в которой вы хотите создать файл.Z

Пользователи Linux и Unix

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

 коснитесь myexample 
.

Как в Windows создать пустой файл в командной строке?

Переполнение стека
  1. Около
  2. Товары
  3. Для команд
  1. Переполнение стека Общественные вопросы и ответы
  2. Переполнение стека для команд
.

emacs - Как с помощью elisp создать пустой файл?

Переполнение стека
  1. Около
  2. Товары
  3. Для команд
  1. Переполнение стека Общественные вопросы и ответы
  2. Переполнение стека для команд Где разработчики и технологи делятся частными знаниями с коллегами
.

linux - Как создать пустой файл из пустого diff с помощью утилиты patch?

Переполнение стека
  1. Около
  2. Товары
  3. Для команд
  1. Переполнение стека Общественные вопросы и ответы
  2. Стек более
.

angularjs - Создание файлов Exe с помощью Electron без папки ресурсов

Переполнение стека
  1. Около
  2. Товары
  3. Для команд
  1. Переполнение стека Общественные вопросы и ответы
  2. Переполнение стека для команд Где разработчики и технологи делятся частными знаниями с коллегами
.

c # - как создать exe-файл из моего созданного файла (файл .cs)?

Переполнение стека
  1. Около
  2. Товары
  3. Для команд
  1. Переполнение стека Общественные вопросы и ответы
  2. Переполнение стека для команд Где разработчики и технологи делятся частными знаниями с коллегами
  3. Вакансии Программирование и связанные с ним технические возможности карьерного роста
  4. Талант Нанимайте технических специалистов и создавайте свой бренд работодателя
  5. Реклама Свяжитесь с разработчиками и технологами по всему миру
.

Смотрите также