"Hello world" в машинных кодах.

Автор обещает много интересных штучек.

Модератор: The trick

The trick
Постоялец
Постоялец
 
Сообщения: 781
Зарегистрирован: 26.06.2010 (Сб) 23:08

"Hello world" в машинных кодах.

Сообщение The trick » 09.12.2016 (Пт) 4:40

Всем привет. Как известно большинство из нас создают программы используя языки высокого уровня, некоторые также используют ассемблер. Сегодня мы с вами напишем программу используя только 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 формате (младший байт по младшему адресу)):
Код: Выделить всё
+-----------------+----------+------------------------+---------------------------------------------+
| метка           | смещение |         данные         |               описание                      |
+-----------------+----------+------------------------+---------------------------------------------+
| таблица импорта |   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 |                 <----+     <-+              |
+-----------------+----------+------------------------+---------------------------------------------+

Вставляем эти данные в новый файл - это будет у нас таблица импорта:
Код: Выделить всё
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:
Код: Выделить всё
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 байтовую границу. Теперь используя относительное смещение напишем код на ассемблере, который далее мы переведем непосредственно в машинный код:
Код: Выделить всё
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 адресация (все смещения считаются отнгосительно адреса следующей команды) то немного перепишем код с использованием меток:
Код: Выделить всё
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. Все! Ничего сложного, только очень кропотливо. Давайте соберем все данные секции вместе:
Код: Выделить всё
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 заголовок:
Код: Выделить всё
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 байт. Итак создаем новый файл и вписываем наши данные:
Код: Выделить всё
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:
Код: Выделить всё
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. Вот что у нас получилось:
Код: Выделить всё
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                          ........

Далее следует вставить описатель секции:
Код: Выделить всё
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 байт нулями. В итоге у нас получается вот такой файл:
Код: Выделить всё
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).
Вложения
HEX_exe_by_The_trick.zip
(350 байт) Скачиваний: 210
UA6527P

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16478
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Re: "Hello world" в машинных кодах.

Сообщение Хакер » 09.12.2016 (Пт) 7:20

Debugger, если читает это, наверное вспомнит весёлые времена :wink:

Бегло прочитал. У тебя есть терминологическая ошибка:
The trick писал(а): подставляем и получаем опкод команды FF 15 A0 FF FF FF.


Опкод — это, всё-таки, не вся закодированная инструкция, а та её часть, которая определяет саму операцию.
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

The trick
Постоялец
Постоялец
 
Сообщения: 781
Зарегистрирован: 26.06.2010 (Сб) 23:08

Re: "Hello world" в машинных кодах.

Сообщение The trick » 09.12.2016 (Пт) 11:16

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

Debugger
Продвинутый гуру
Продвинутый гуру
Аватара пользователя
 
Сообщения: 1667
Зарегистрирован: 17.06.2006 (Сб) 15:11

Re: "Hello world" в машинных кодах.

Сообщение Debugger » 09.12.2016 (Пт) 22:20

(на правах оффтопика)
Да, весело было! :D

Потратил на аналогичную задачу три дня, потому что не обращал внимания на выравнивания.

ger_kar
Продвинутый гуру
Продвинутый гуру
Аватара пользователя
 
Сообщения: 1957
Зарегистрирован: 19.05.2011 (Чт) 19:23
Откуда: Кыргызстан, Иссык-Куль, г. Каракол

Re: "Hello world" в машинных кодах.

Сообщение ger_kar » 05.01.2017 (Чт) 15:35

The trick писал(а): также в стеке выделяется 32 байта теневой области. Также стек должен быть выровнен на 16 байтовую границу.
А можно о этом подробнее, что еще за теневая область и понты с выравниванием? Зачем оно вообще надо?
Бороться и искать, найти и перепрятать

The trick
Постоялец
Постоялец
 
Сообщения: 781
Зарегистрирован: 26.06.2010 (Сб) 23:08

Re: "Hello world" в машинных кодах.

Сообщение The trick » 05.01.2017 (Чт) 18:35

ger_kar писал(а):
The trick писал(а): также в стеке выделяется 32 байта теневой области. Также стек должен быть выровнен на 16 байтовую границу.
А можно о этом подробнее, что еще за теневая область и понты с выравниванием? Зачем оно вообще надо?

Потому что первые 4 аргумента передаются в регистрах и для них резервируется место в стеке (к примеру вызываемая процедура может сохранить туда значения), это требует соглашение вызова x64. Для любых внутренних функций ты можешь не резервировать это место. Стек должен быть выровнен т.к. в x64 используются регистры SSE которые требуют такого выравнивания.
UA6527P

Teranas
Бывалый
Бывалый
Аватара пользователя
 
Сообщения: 224
Зарегистрирован: 13.12.2008 (Сб) 4:26
Откуда: Новосибирск

Re: "Hello world" в машинных кодах.

Сообщение Teranas » 05.01.2017 (Чт) 20:39

Что-то подобное я уже читал лет 10 назад, там тоже описывался метод создания программы "Hello world" в HEX редакторе. Нет, нет, я без претензий, но мне кажется, это устарело и мало кто будет использовать, если такая, же программа пишется в одну текстовую строчку.
С уважением, Андрей.

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16478
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Re: "Hello world" в машинных кодах.

Сообщение Хакер » 05.01.2017 (Чт) 20:56

Teranas писал(а):это устарело и мало кто будет использовать

Бред. Смысл статьи в формировании в голове понимания устройства PE-формата и процессов, которые происходят при формировании EXE силами линкера и его начинки силами компилятора.
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

ger_kar
Продвинутый гуру
Продвинутый гуру
Аватара пользователя
 
Сообщения: 1957
Зарегистрирован: 19.05.2011 (Чт) 19:23
Откуда: Кыргызстан, Иссык-Куль, г. Каракол

Re: "Hello world" в машинных кодах.

Сообщение ger_kar » 06.01.2017 (Пт) 18:07

Мне статья очень понравилась, тем более, что разобран формат x64, в котором, как оказалось есть свои нюансы. Плохо, что для x64 нет такого же удобного отладчика как OllyDbg для 32-х разрядных приложений.
Бороться и искать, найти и перепрятать

The trick
Постоялец
Постоялец
 
Сообщения: 781
Зарегистрирован: 26.06.2010 (Сб) 23:08

Re: "Hello world" в машинных кодах.

Сообщение The trick » 06.01.2017 (Пт) 18:14

ger_kar писал(а):Мне статья очень понравилась, тем более, что разобран формат x64, в котором, как оказалось есть свои нюансы. Плохо, что для x64 нет такого же удобного отладчика как OllyDbg для 32-х разрядных приложений.

x64dbg как отдаленный аналог.
UA6527P


Вернуться в The trick

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 4

    TopList