Всем привет. Как известно большинство из нас создают программы используя языки высокого уровня, некоторые также используют ассемблер. Сегодня мы с вами напишем программу используя только HEX редактор. Подразумевается что читатель знает строение исполняемых файлов хотя бы поверхностно, поэтому я не буду углубляться в детали, к тому же я уже приводил небольшой обзор загрузчика EXE файлов на VB6. Итак поехали... Для начала определимся с функциональностью приложения и используемыми инструментами. Для простоты создадим 64-битное приложение которое показывает сообщение "Hello World!" и завершает свою работу. В качестве HEX - редактора будем использовать 010 Editor. Для начала создадим схему чтобы определить все смещения и размеры внутри PE файла. Для начала определимся с количеством секций. Т.к. наше приложение будет вызывать внешние API функции, то нам нужна будет таблица импорта (вариант с получением через PEB я не рассматриваю). Для показа сообщения мы будем использовать функцию MassageBoxA, а для завершения приложения ExitProcess, т.е. нам уже нужно 2 библиотеки - kernel32.dll и user32.dll. Давайте подсчитаем размер таблицы импорта. Для 2-х библиотек нужно разместить 3 структуры IMAGE_IMPORT_DESCRIPTOR (две с данными и одну забитую нулями), получаем 0x14 * 3 = 0x3C. Также нужно место для размещения имен библиотек в формате ASCIIZ: 0x3C + sizeof("kernel32.dll") + sizeof("user32.dll") = 0x54. Далее нужно расчитать размеры таблиц имен и таблиц адресов, по одной функции из каждой библиотеки получается 0x54 + sizeof(IMAGE_THUNK_DATA) * 4 + sizeof(IMAGE_THUNK_DATA) * 2 = 0x84. Теперь прибавляем размер имен функций: 0x84 + sizeof("MessageBoxA") + 2 + sizeof("ExitProcess") + 2 = 0xA0. Итак таблица импорта занимает у нас 0xA0 байт. Первую секцию разместим по первому доступному RVA выровненному на размер страницы, т.е. 0x1000. Таблицу импорта разместим в самом начале секции (не забывая что данные должны быть в little-endian формате (младший байт по младшему адресу)): Код (Text): +-----------------+----------+------------------------+---------------------------------------------+ | метка | смещение | данные | описание | +-----------------+----------+------------------------+---------------------------------------------+ | таблица импорта | 0x00 | 0x00001054 | OriginalFirstThunk -----------------------+ | | | 0x04 | 0x00000000 | TimeDateStamp | | | | 0x08 | 0x00000000 | ForwarderChain | | | | 0x0c | 0x0000103c | Name ------------------+ | | | | 0x10 | 0x00001074 | FirstThunk ------------+---------------+ | | | | 0x14 | 0x00001064 | OriginalFirstThunk ----+--------------+| | | | | 0x18 | 0x00000000 | TimeDateStamp | || | | | | 0x1c | 0x00000000 | ForwarderChain | || | | | | 0x20 | 0x00001049 | Name ----------------+ | || | | | | 0x24 | 0x0000107c | FirstThunk ----------+-+-----------+ || | | | | 0x28 | 0x00000000 | OriginalFirstThunk | | | || | | | | 0x2c | 0x00000000 | TimeDateStamp | | | || | | | | 0x30 | 0x00000000 | ForwarderChain | | | || | | | | 0x34 | 0x00000000 | Name | | | || | | | | 0x38 | 0x00000000 | FirstThunk | | | || | | | имена библиотек | 0x3c | kernel32.dll, 0 | <----+-+ | || | | | | 0x49 | user32.dll, 0 | <----+ | || | | | таблица имен 1 | 0x54 | 0x0000000000001084 | IMAGE_THUNK_DATA ---------------+ | ||<-+ | | | 0x5c | 0x0000000000000000 | IMAGE_THUNK_DATA завершающая | | || | | таблица имен 2 | 0x64 | 0x0000000000001092 | IMAGE_THUNK_DATA ------------+ | |<-+| | | | 0x6c | 0x0000000000000000 | IMAGE_THUNK_DATA завершающая | | | | | | таблица адресов | 0x74 | 0x0000000000001084 | IMAGE_THUNK_DATA -+ | | |<--+ | | | 0x7c | 0x0000000000001092 | IMAGE_THUNK_DATA |--+ | |<-+ | | имя 1 | 0x84 | 0x0000, ExitProcess, 0 | <-+ | |<-+ | | имя 2 | 0x92 | 0x0000, MessageBoxA, 0 | <----+ <-+ | +-----------------+----------+------------------------+---------------------------------------------+ Вставляем эти данные в новый файл - это будет у нас таблица импорта: Код (Text): 54 10 00 00 00 00 00 00 00 00 00 00 3C 10 00 00 T...........<... 74 10 00 00 64 10 00 00 00 00 00 00 00 00 00 00 t...d........... 49 10 00 00 7C 10 00 00 00 00 00 00 00 00 00 00 I...|........... 00 00 00 00 00 00 00 00 00 00 00 00 6B 65 72 6E ............kern 65 6C 33 32 2E 64 6C 6C 00 75 73 65 72 33 32 2E el32.dll.user32. 64 6C 6C 00 84 10 00 00 00 00 00 00 00 00 00 00 dll.„........... 00 00 00 00 92 10 00 00 00 00 00 00 00 00 00 00 ....’........... 00 00 00 00 84 10 00 00 00 00 00 00 92 10 00 00 ....„.......’... 00 00 00 00 00 00 45 78 69 74 50 72 6F 63 65 73 ......ExitProces 73 00 00 00 4D 65 73 73 61 67 65 42 6F 78 41 00 s...MessageBoxA. Для отображения сообщения нужно само сообщение где-то хранить. Будем хранить его непосредственно за таблицей импорта т.е. по смещению 0xA0: Код (Text): 00A0h: 48 65 6C 6C 6F 20 77 6F 72 6C 64 21 00 Hello world!. Сам код у нас будет начинаться сразу после данного сообщения, т.е. по смещению 0xA0 + sizeof("Hello world!") = 0xAD. Все API функции в x64 используют одноименное соглашение: первые 4 параметра передаются в регистрах RCX, RDX, R8, R9, остальные в стеке, также в стеке выделяется 32 байта теневой области. Также стек должен быть выровнен на 16 байтовую границу. Теперь используя относительное смещение напишем код на ассемблере, который далее мы переведем непосредственно в машинный код: Код (ASM): MSG db "Hello world!", 0 sub RSP, 0x28 ; Резервируем теневую область mov R9, 0x00000040 ; MB_ICONINFORMATION xor R8, R8 ; lpCaption = NULL lea RDX, [MSG] ; lpText = 'Hello world!' xor RCX, RCX ; HWND = NULL Call MessageBoxA xor RCX, RCX Call ExitProcess Т.к. в x64 используется RIP адресация (все смещения считаются относительно адреса следующей команды) то немного перепишем код с использованием меток: Код (ASM): MSG db "Hello world!", 0 sub RSP, 0x28 ; Резервируем теневую область mov R9, 0x00000040 ; MB_ICONINFORMATION xor R8, R8 ; lpCaption = NULL lea RDX, [RIP + (MSG - L1)] ; lpText = 'Hello world!' L1: xor RCX, RCX ; HWND = NULL Call [RIP + (MessageBoxA - L2)] L2: xor RCX, RCX Call [RIP + (ExitProcess - L3)] L3:
Теперь приступим непосредственно к трансляции в машинный код. Для этого я буду использовать вот эту таблицу. Первая инструкция sub RSP, 0x28 оперирует с 64 битным регистром RSP поэтому опкод должен содержать префикс REX.W(0x48), далее смотрим по списку инструкцию SUB чтобы первым операндом был 64 битный регистр, а вторым непосредственное однобайтовое значение и это - 0x83. теперь нужно определится с mod/rm байтом. Т.к. мы используем непосредственно регистр RSP то поле mod будет равно 0b11, а поле r/m будет равно 0b100 что соответствует регистрам AH/SP/ESP/RSP. В таблице указано что для нашей команды поле Register/ Opcode Field должно быть равно 5 (0b101 в двоичной форме). Собираем все вместе, и получаем 0b11-101-100 = 0xEC. Непосредственный операнд идет сразу же после mod/rm байта, в итоге полный код команды 48 83 EC 28. Следующая инструкция mov R9, 0x00000040 также имеет REX префикс, поскольку использует регистр недоступный в 32 битном режиме, а именно комбинацию REX.W и REX.B = 0x49. Префикс REX.B говорит о том что наша инструкция имеет расширенное поле rm. В 32 битном режиме мы могли бы использовать однобайтовую 0xB8 + r, в 64-битном нам придется использовать 0xC7. Также определяем поле mod/rm, т.к. у нас операнд непосредственный регистр, то mod = 0b11, а rm = 0b001 что соответствует регистру R9. По таблице поле Register/ Opcode Field должно быть равно 0, собирая все вместе получим 0b11-000-001 = 0xC1. Непосредственный операнд размещается за mod/rm полем. В итоге получаем полный код команды = 49 C7 C1 40 00 00 00. Следующая инструкция также работает с расширенными регистрами (двумя) поэтому она также содержит расширенный префикс с комбинацией REX.W, REX.B и REX.R = 0x4D. Префикс REX.R говорит о том что поле reg байта mod/rm также является расширенным. Далее ищем опкод команды XOR, здесь мы можем выбрать любой из двух либо 0x31 либо 0x33, я возьму первое. Также определяемся с полем mod/rm. Опять-таки т.к. мы используем непосредственно регистры то поле mod будет равно 0b11, по таблице регистров смотрим что расширеное поле для регистра R8 = 0b000. Собираем все вместе - 0b11-000-000 = 0xC0, а полный код команды - 4D 31 C0. Следующая инструкция - lea RDX, [RIP + (MSG - L1)], второй операнд у нас является непосредственным значением, т.к. мы работаем в 64 битном режиме и адресация у нас идет относительно адреса следующей команды. Т.е. нам нужна инструкция вида lea reg64, imm32, но сначала определимся с префиксом. Т.к. команда работает с 64 битным регистром то префикс будет REX.W(0x48). Опкод команды LEA - 0x8D. В качестве mod/rm у нас должно быть mod = 0b000, а rm = 0b101 что соответствует [RIP + disp32]. Для регистра RDX номер равен 0b010. Компонуем вместе - 0b00-010-101 = 0x15. После идет 4-байтное смещение. Теперь давайте посчитаем смещение до нашей строки относительно следующей команды: disp = -(sizeof("Hello world!") + sizeof({48 83 EC 28}) + sizeof({49 C7 C1 40 00 00 00}) + sizeof({4D 31 C0}) + sizeof({48 8D 15 00 00 00 00})) = 0xFFFFFFDE Т.е. полный код будет тогда = 48 8D 15 DE FF FF FF. Следующий XOR расчитывается также как и предыдущий: REX.W + 0x31 + 0b11-001-001 = 48 31 C9. Дальше у нас идет вызов из таблицы импорта, поэтому нужно посчитать смещение относительно следующей команды до 2-го элемента таблицы адресов (там у нас содержится адрес функции MessageBoxA), которое равно в данном случае -79 (0xFFFFFFB1). Теперь нам нужно найти опкод инструкции CALL которая позволяет вызывать по адресу расположенному в памяти. По таблице находим FF, Register/ Opcode Field должно быть равно 2. Теперь также посчитаем mod/rm байт. 0b00-010-101 = 0x15. Полный код команды = FF 15 B1 FF FF FF. Код следующей команды нам уже известен, поэтому переходим к последнему опкоду - CALL. Опять считаем смещение, оно равно -96 0xFFFFFFA0, подставляем и получаем опкод команды FF 15 A0 FF FF FF. Все! Ничего сложного, только очень кропотливо. Давайте соберем все данные секции вместе: Код (Text): 0000h: 54 10 00 00 00 00 00 00 00 00 00 00 3C 10 00 00 T...........<... 0010h: 74 10 00 00 64 10 00 00 00 00 00 00 00 00 00 00 t...d........... 0020h: 49 10 00 00 7C 10 00 00 00 00 00 00 00 00 00 00 I...|........... 0030h: 00 00 00 00 00 00 00 00 00 00 00 00 6B 65 72 6E ............kern 0040h: 65 6C 33 32 2E 64 6C 6C 00 75 73 65 72 33 32 2E el32.dll.user32. 0050h: 64 6C 6C 00 84 10 00 00 00 00 00 00 00 00 00 00 dll.„........... 0060h: 00 00 00 00 92 10 00 00 00 00 00 00 00 00 00 00 ....’........... 0070h: 00 00 00 00 84 10 00 00 00 00 00 00 92 10 00 00 ....„.......’... 0080h: 00 00 00 00 00 00 45 78 69 74 50 72 6F 63 65 73 ......ExitProces 0090h: 73 00 00 00 4D 65 73 73 61 67 65 42 6F 78 41 00 s...MessageBoxA. 00A0h: 48 65 6C 6C 6F 20 77 6F 72 6C 64 21 00 48 83 EC Hello world!.Hƒì 00B0h: 28 49 C7 C1 40 00 00 00 4D 31 C0 48 8D 15 DE FF (IÇÁ@...M1ÀH 00C0h: FF FF 48 31 C9 FF 15 B1 FF FF FF 48 31 C9 FF 15 ÿÿH1Éÿ.±ÿÿÿH1Éÿ. 00D0h: A0 FF FF FF ÿÿÿ Итоговый размер секции у нас занимает 0xD4 байт. Точка входа у нас равна 0x10AD.
Теперь приступим к непосредственному созданию EXE файла. В самом начале любого PE файла располагается IMAGE_DOS_HEADER заголовок: Код (C++): typedef struct _IMAGE_DOS_HEADER { WORD e_magic; WORD e_cblp; WORD e_cp; WORD e_crlc; WORD e_cparhdr; WORD e_minalloc; WORD e_maxalloc; WORD e_ss; WORD e_sp; WORD e_csum; WORD e_ip; WORD e_cs; WORD e_lfarlc; WORD e_ovno; WORD e_res[4]; WORD e_oemid; WORD e_oeminfo; WORD e_res2[10]; DWORD e_lfanew; } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER; В этой структуре нас интересуют только поля e_magic и e_lfanew, находящихся по смещениям 0x00 и 0x3C соответственно. Первое поле содержит сигнатуру MZ, а второе смещение на NT заголовки. Т.к. мы не используем заглушку DOS, мы расположим NT заголовки сразу за ней, т.е. смещение будет равно 0x40. Это очень удобно поскольку NT заголовки должны быть выровнены на 8 байтовую границу, а структура IMAGE_DOS_HEADER имеет размер 0x40 байт. Итак создаем новый файл и вписываем наши данные: Код (Text): 0000h: 4D 5A 00 00 00 00 00 00 00 00 00 00 00 00 00 00 MZ.............. 0010h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0020h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0030h: 00 00 00 00 00 00 00 00 00 00 00 00 40 00 00 00 ............@... Далее вставляем структуру IMAGE_NT_HEADERS: Код (C++): typedef struct _IMAGE_NT_HEADERS { DWORD Signature; IMAGE_FILE_HEADER FileHeader; IMAGE_OPTIONAL_HEADER64 OptionalHeader; } IMAGE_NT_HEADERS; typedef struct _IMAGE_FILE_HEADER { WORD Machine; WORD NumberOfSections; DWORD TimeDateStamp; DWORD PointerToSymbolTable; DWORD NumberOfSymbols; WORD SizeOfOptionalHeader; WORD Characteristics; } IMAGE_FILE_HEADER; typedef struct _IMAGE_OPTIONAL_HEADER64 { WORD Magic; UCHAR MajorLinkerVersion; UCHAR MinorLinkerVersion; DWORD SizeOfCode; DWORD SizeOfInitializedData; DWORD SizeOfUninitializedData; DWORD AddressOfEntryPoint; DWORD BaseOfCode; DWORD64 ImageBase; DWORD SectionAlignment; DWORD FileAlignment; WORD MajorOperatingSystemVersion; WORD MinorOperatingSystemVersion; WORD MajorImageVersion; WORD MinorImageVersion; WORD MajorSubsystemVersion; WORD MinorSubsystemVersion; DWORD Win32VersionValue; DWORD SizeOfImage; DWORD SizeOfHeaders; DWORD CheckSum; WORD Subsystem; WORD DllCharacteristics; DWORD64 SizeOfStackReserve; DWORD64 SizeOfStackCommit; DWORD64 SizeOfHeapReserve; DWORD64 SizeOfHeapCommit; DWORD LoaderFlags; DWORD NumberOfRvaAndSizes; IMAGE_DATA_DIRECTORY DataDirectory[16]; } IMAGE_OPTIONAL_HEADER64; В качестве Signature вставляем строку из 4-х символов PE\0\0. Т.к. у нас 64 битное приложение то в качестве Machine устанавливаем значение IMAGE_FILE_MACHINE_AMD64 равное 0x8664. В качестве NumberOfSections укажем 1, т.к. у нас одна секция. Три следующих поля нам не нужны, поэтому забиваем их нулями. Размер необязательного заголовка установим в IMAGE_SIZEOF_NT_OPTIONAL64_HEADER (0x00F0). Для Characteristics зададим комбинацию флагов IMAGE_FILE_EXECUTABLE_IMAGE и IMAGE_FILE_LARGE_ADDRESS_AWARE (0x0022). Далее начнем заполнять необязательный заголовок. В качестве Magic указываем IMAGE_NT_OPTIONAL_HDR64_MAGIC (0x020B). Версия линкера нам не нужна, поэтому забиваем туда нули. Размер кода указываем равным 0x1000, потому что тут указывается выровненный размер данных на размер одной страницы. Размер инициализированных и неинициализированных данных забиваем нулями. Как мы выше вычислили, точка входа у нас равна 0x10AD, в BaseOfCode забиваем RVA нашей секции, т.к. она содержит код. В качестве ImageBase задаем 0x0000000000400000 - это наш базовый адрес, тут можно в принципе указать любое значение, т.к. наш модуль не содержит абсолютных ссылок. В качестве SectionAlignment указываем 0x1000 - размер одной страницы памяти, а в качестве FileAlignment - 0x200 (стандартное значение для PE файлов). Версии операционной системы и образа мы не используем, а вот в качестве MajorSubsystemVersion и MinorSubsystemVersion укажем 0x0005 и 0x0002 (аналогично /SUBSYSTEM[,major[.minor]] ключу линкера). В качестве SizeOfImage укажем значение 0x2000, поскольку наш файл будет располагаться на двух страницах памяти (заголовки и одна секция). Значение SizeOfHeaders нужно посчитать сложением всех заголовков и выравниванием на границу FileAlignment: align(sizeof(IMAGE_DOS_HEADER) + sizeof(IMAGE_NT_HEADERS) + sizeof(IMAGE_SECTION_HEADER), 0x200) = 0x200 Контрольную сумму также оставляем без внимания, а вот в качестве Subsystem вбиваем IMAGE_SUBSYSTEM_WINDOWS_GUI (0x0002). В поле DllCharacteristics забиваем комбинацию флагов IMAGE_DLLCHARACTERISTICS_NO_SEH и IMAGE_DLLCHARACTERISTICS_NO_BIND = 0x0C00. Размер резервируемой памяти стека оставим по умолчанию 0x100000 байт, тоже самое и с начальным размером - 0x1000 байт. Теже самые значения забъем и для кучи. LoaderFlags - устаревшее поле и нас не интересует. NumberOfRvaAndSizes - забиваем 0x10. В каталоге директорий нам понадобится только таблица импорта под индексом 1. Забиваем туда 0x1000 в качестве виртуального адреса, а размер равен (как мы ранее вычислили) 0xA0. Вот что у нас получилось: Код (Text): 0000h: 4D 5A 00 00 00 00 00 00 00 00 00 00 00 00 00 00 MZ.............. 0010h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0020h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0030h: 00 00 00 00 00 00 00 00 00 00 00 00 40 00 00 00 ............@... 0040h: 50 45 00 00 64 86 01 00 00 00 00 00 00 00 00 00 PE..d†.......... 0050h: 00 00 00 00 F0 00 22 00 0B 02 00 00 00 10 00 00 ....ð."......... 0060h: 00 00 00 00 00 00 00 00 AD 10 00 00 00 10 00 00 ............... 0070h: 00 00 40 00 00 00 00 00 00 10 00 00 00 02 00 00 ..@............. 0080h: 00 00 00 00 00 00 00 00 05 00 02 00 00 00 00 00 ................ 0090h: 00 20 00 00 00 02 00 00 00 00 00 00 02 00 00 0C . .............. 00A0h: 00 00 10 00 00 00 00 00 00 10 00 00 00 00 00 00 ................ 00B0h: 00 00 10 00 00 00 00 00 00 10 00 00 00 00 00 00 ................ 00C0h: 00 00 00 00 10 00 00 00 00 00 00 00 00 00 00 00 ................ 00D0h: 00 10 00 00 A0 00 00 00 00 00 00 00 00 00 00 00 .... ........... 00E0h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00F0h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0100h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0110h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0120h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0130h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0140h: 00 00 00 00 00 00 00 00 ........
Далее следует вставить описатель секции: Код (C++): typedef struct _IMAGE_SECTION_HEADER { BYTE Name[8]; DWORD VirtualSize; DWORD VirtualAddress; DWORD SizeOfRawData; DWORD PointerToRawData; DWORD PointerToRelocations; DWORD PointerToLinenumbers; WORD NumberOfRelocations; WORD NumberOfLinenumbers; DWORD Characteristics; } IMAGE_SECTION_HEADER; В качестве имени забиваем стандартное '.text\0\0\0'. VirtualSize устанавливаем равным 0x1000 (округляем на границу выравнивания секций). VirtualAdress как мы в самом начале определили как 0x1000. Поле SizeOfRawData устанавливаем равным 0x200 байт поскольку размер данных секции равен 0xD4 байт, но его нужно округлить на границу FileAlignment, а в оставшееся место секции забить нули или произвольные данные. Поле PointerToRawData у нас равно значению из IMAGE_OPTIONAL_HEADER64.SizeOfHeaders, т.е. 0x200. Поля до поля Characteristics забиваем нулями, а вот это поле будет равно комбинации флагов IMAGE_SCN_CNT_CODE, IMAGE_SCN_MEM_EXECUTE и IMAGE_SCN_MEM_READ, т.е. 0x60000020. Все, добиваем нулями до границы 512 байт и прикрепляем секцию которую тоже добиваем до 512 байт нулями. В итоге у нас получается вот такой файл: Код (Text): 0000h: 4D 5A 00 00 00 00 00 00 00 00 00 00 00 00 00 00 MZ.............. 0010h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0020h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0030h: 00 00 00 00 00 00 00 00 00 00 00 00 40 00 00 00 ............@... 0040h: 50 45 00 00 64 86 01 00 00 00 00 00 00 00 00 00 PE..d†.......... 0050h: 00 00 00 00 F0 00 22 00 0B 02 00 00 00 10 00 00 ....ð."......... 0060h: 00 00 00 00 00 00 00 00 AD 10 00 00 00 10 00 00 ............... 0070h: 00 00 40 00 00 00 00 00 00 10 00 00 00 02 00 00 ..@............. 0080h: 00 00 00 00 00 00 00 00 05 00 02 00 00 00 00 00 ................ 0090h: 00 20 00 00 00 02 00 00 00 00 00 00 02 00 00 0C . .............. 00A0h: 00 00 10 00 00 00 00 00 00 10 00 00 00 00 00 00 ................ 00B0h: 00 00 10 00 00 00 00 00 00 10 00 00 00 00 00 00 ................ 00C0h: 00 00 00 00 10 00 00 00 00 00 00 00 00 00 00 00 ................ 00D0h: 00 10 00 00 A0 00 00 00 00 00 00 00 00 00 00 00 .... ........... 00E0h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00F0h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0100h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0110h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0120h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0130h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0140h: 00 00 00 00 00 00 00 00 2E 74 65 78 74 00 00 00 .........text... 0150h: 00 10 00 00 00 10 00 00 00 02 00 00 00 02 00 00 ................ 0160h: 00 00 00 00 00 00 00 00 00 00 00 00 20 00 00 60 ............ ..` 0170h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0180h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0190h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 01A0h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 01B0h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 01C0h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 01D0h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 01E0h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 01F0h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0200h: 54 10 00 00 00 00 00 00 00 00 00 00 3C 10 00 00 T...........<... 0210h: 74 10 00 00 64 10 00 00 00 00 00 00 00 00 00 00 t...d........... 0220h: 49 10 00 00 7C 10 00 00 00 00 00 00 00 00 00 00 I...|........... 0230h: 00 00 00 00 00 00 00 00 00 00 00 00 6B 65 72 6E ............kern 0240h: 65 6C 33 32 2E 64 6C 6C 00 75 73 65 72 33 32 2E el32.dll.user32. 0250h: 64 6C 6C 00 84 10 00 00 00 00 00 00 00 00 00 00 dll.„........... 0260h: 00 00 00 00 92 10 00 00 00 00 00 00 00 00 00 00 ....’........... 0270h: 00 00 00 00 84 10 00 00 00 00 00 00 92 10 00 00 ....„.......’... 0280h: 00 00 00 00 00 00 45 78 69 74 50 72 6F 63 65 73 ......ExitProces 0290h: 73 00 00 00 4D 65 73 73 61 67 65 42 6F 78 41 00 s...MessageBoxA. 02A0h: 48 65 6C 6C 6F 20 77 6F 72 6C 64 21 00 48 83 EC Hello world!.Hƒì 02B0h: 28 49 C7 C1 40 00 00 00 4D 31 C0 48 8D 15 DE FF (IÇÁ@...M1ÀH 02C0h: FF FF 48 31 C9 FF 15 B1 FF FF FF 48 31 C9 FF 15 ÿÿH1Éÿ.±ÿÿÿH1Éÿ. 02D0h: A0 FF FF FF 00 00 00 00 00 00 00 00 00 00 00 00 ÿÿÿ............ 02E0h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 02F0h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0300h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0310h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0320h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0330h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0340h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0350h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0360h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0370h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0380h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0390h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 03A0h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 03B0h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 03C0h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 03D0h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 03E0h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 03F0h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ Теперь если попробовать запустить его, то у нас появится сообщение, как мы и ожидали ). Также можно поиграться с параметром FileAlignment дабы уменьшить размер файла. Надеюсь вам было интересно, спасибо за внимание! С уважением, Кривоус Анатолий (The trick).