Как написать hello world на машинном коде

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

Assembler
1
2
3
4
5
6
7
8
9
10
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 адресация (все смещения считаются отнгосительно адреса следующей команды) то немного перепишем код с использованием меток:

Assembler
1
2
3
4
5
6
7
8
9
10
11
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). Опкод команды LEA0x8D. В качестве 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 заголовок:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
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. Т.к. у нас 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 — размер одной страницы памяти, а в качестве FileAlignment0x200 (стандартное значение для 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                          ........

Далее следует вставить описатель секции:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
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’. 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  ................

Теперь если попробовать запустить его, то у нас появится сообщение, как мы и ожидали ).
Название: preview.png
Просмотров: 5792

Размер: 6.1 Кб
Также можно поиграться с параметром FileAlignment дабы уменьшить размер файла.
Надеюсь вам было интересно, спасибо за внимание!
С уважением,
Кривоус Анатолий (The trick).

«Hello world» в машинных кодах

Дата публикации 29 мар 2019

| Редактировалось 17 май 2019

Всем привет. Как известно большинство из нас создают программы используя языки высокого уровня, некоторые также используют ассемблер. Сегодня мы с вами напишем программу используя только HEX редактор. Подразумевается что читатель знает строение исполняемых файлов хотя бы поверхностно, поэтому я не буду углубляться в детали, к тому же я уже приводил небольшой обзор загрузчика EXE файлов на VB6. Итак поехали…
Для начала определимся с функциональностью приложения и используемыми инструментами. Для простоты создадим 64-битное приложение которое показывает сообщение «Hello World!» и завершает свою работу. В качестве HEX — редактора будем использовать 010 Editor.
Для начала создадим схему чтобы определить все смещения и размеры внутри PE файла. Для начала определимся с количеством секций. Т.к. наше приложение будет вызывать внешние API функции, то нам нужна будет таблица импорта (вариант с получением через PEB я не рассматриваю). Для показа сообщения мы будем использовать функцию MessageBoxA, а для завершения приложения 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 формате (младший байт по младшему адресу)):

  1. +——————+———-+————————+———————————————+
  2. | метка           | смещение |         данные         |               описание                      |
  3. +——————+———-+————————+———————————————+
  4. | таблица импорта |   0x00   | 0x00001054             | OriginalFirstThunk ————————+ |
  5. |                 |   0x04   | 0x00000000             | TimeDateStamp                             | |
  6. |                 |   0x08   | 0x00000000             | ForwarderChain                            | |
  7. |                 |   0x0c   | 0x0000103c             | Name ——————+                  | |
  8. |                 |   0x10   | 0x00001074             | FirstThunk ————+—————+  | |
  9. |                 |   0x14   | 0x00001064             | OriginalFirstThunk —-+—————+|  | |
  10. |                 |   0x18   | 0x00000000             | TimeDateStamp          |              ||  | |
  11. |                 |   0x1c   | 0x00000000             | ForwarderChain         |              ||  | |
  12. |                 |   0x20   | 0x00001049             | Name —————-+ |              ||  | |
  13. |                 |   0x24   | 0x0000107c             | FirstThunk ———-+-+————+  ||  | |
  14. |                 |   0x28   | 0x00000000             | OriginalFirstThunk   | |           |  ||  | |
  15. |                 |   0x2c   | 0x00000000             | TimeDateStamp        | |           |  ||  | |
  16. |                 |   0x30   | 0x00000000             | ForwarderChain       | |           |  ||  | |
  17. |                 |   0x34   | 0x00000000             | Name                 | |           |  ||  | |
  18. |                 |   0x38   | 0x00000000             | FirstThunk           | |           |  ||  | |
  19. | имена библиотек |   0x3c   | kernel32.dll, 0        |                 <—-+-+           |  ||  | |
  20. |                 |   0x49   | user32.dll, 0          |                 <—-+             |  ||  | |
  21. | таблица имен 1  |   0x54   | 0x0000000000001084     | IMAGE_THUNK_DATA —————+  |  ||<-+ |
  22. |                 |   0x5c   | 0x0000000000000000     | IMAGE_THUNK_DATA завершающая    |  |  ||    |
  23. | таблица имен 2  |   0x64   | 0x0000000000001092     | IMAGE_THUNK_DATA ————+  |  |<-+|    |
  24. |                 |   0x6c   | 0x0000000000000000     | IMAGE_THUNK_DATA завершающая |  |  |   |    |
  25. | таблица адресов |   0x74   | 0x0000000000001084     | IMAGE_THUNK_DATA -+          |  |  |<—+    |
  26. |                 |   0x7c   | 0x0000000000001092     | IMAGE_THUNK_DATA  |—+       |  |<-+        |
  27. |      имя 1      |   0x84   | 0x0000, ExitProcess, 0 |                 <-+  |       |<-+           |
  28. |      имя 2      |   0x92   | 0x0000, MessageBoxA, 0 |                 <—-+     <-+              |
  29. +——————+———-+————————+———————————————+

Вставляем эти данные в новый файл — это будет у нас таблица импорта:

  1. 54 10 00 00 00 00 00 00 00 00 00 00 3C 10 00 00  T………..<…
  2. 74 10 00 00 64 10 00 00 00 00 00 00 00 00 00 00  t…d………..
  3. 49 10 00 00 7C 10 00 00 00 00 00 00 00 00 00 00  I…|………..
  4. 00 00 00 00 00 00 00 00 00 00 00 00 6B 65 72 6E  …………kern
  5. 65 6C 33 32 2E 64 6C 6C 00 75 73 65 72 33 32 2E  el32.dll.user32.
  6. 64 6C 6C 00 84 10 00 00 00 00 00 00 00 00 00 00  dll.„………..
  7. 00 00 00 00 92 10 00 00 00 00 00 00 00 00 00 00  ….’………..
  8. 00 00 00 00 84 10 00 00 00 00 00 00 92 10 00 00  ….„…….’…
  9. 00 00 00 00 00 00 45 78 69 74 50 72 6F 63 65 73  ……ExitProces
  10. 73 00 00 00 4D 65 73 73 61 67 65 42 6F 78 41 00  s…MessageBoxA.

Для отображения сообщения нужно само сообщение где-то хранить. Будем хранить его непосредственно за таблицей импорта т.е. по смещению 0xA0:

  1. 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 байтовую границу. Теперь используя относительное смещение напишем код на ассемблере, который далее мы переведем непосредственно в машинный код:

  1. sub RSP, 0x28           ; Резервируем теневую область
  2. mov R9, 0x00000040      ; MB_ICONINFORMATION
  3. xor R8, R8              ; lpCaption = NULL
  4. lea RDX, [MSG]          ; lpText = ‘Hello world!’
  5. xor RCX, RCX            ; HWND = NULL

Так как в x64 используется RIP адресация (все смещения считаются относительно адреса следующей команды) то немного перепишем код с использованием меток:

  1. sub RSP, 0x28                   ; Резервируем теневую область
  2. mov R9, 0x00000040              ; MB_ICONINFORMATION
  3. xor R8, R8                      ; lpCaption = NULL
  4. lea RDX, [RIP + (MSG L1)]     ; lpText = ‘Hello world!’
  5. L1: xor RCX, RCX                ; HWND = NULL
  6. Call [RIP + (MessageBoxA L2)]
  7. Call [RIP + (ExitProcess 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, 0x40 также имеет 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). Опкод команды LEA0x8D. В качестве 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. Все! Ничего сложного, только очень кропотливо. Давайте соберем все данные секции вместе:

  1. 0000h: 54 10 00 00 00 00 00 00 00 00 00 00 3C 10 00 00  T………..<…
  2. 0010h: 74 10 00 00 64 10 00 00 00 00 00 00 00 00 00 00  t…d………..
  3. 0020h: 49 10 00 00 7C 10 00 00 00 00 00 00 00 00 00 00  I…|………..
  4. 0030h: 00 00 00 00 00 00 00 00 00 00 00 00 6B 65 72 6E  …………kern
  5. 0040h: 65 6C 33 32 2E 64 6C 6C 00 75 73 65 72 33 32 2E  el32.dll.user32.
  6. 0050h: 64 6C 6C 00 84 10 00 00 00 00 00 00 00 00 00 00  dll.„………..
  7. 0060h: 00 00 00 00 92 10 00 00 00 00 00 00 00 00 00 00  ….’………..
  8. 0070h: 00 00 00 00 84 10 00 00 00 00 00 00 92 10 00 00  ….„…….’…
  9. 0080h: 00 00 00 00 00 00 45 78 69 74 50 72 6F 63 65 73  ……ExitProces
  10. 0090h: 73 00 00 00 4D 65 73 73 61 67 65 42 6F 78 41 00  s…MessageBoxA.
  11. 00A0h: 48 65 6C 6C 6F 20 77 6F 72 6C 64 21 00 48 83 EC  Hello world!.Hƒì
  12. 00B0h: 28 49 C7 C1 40 00 00 00 4D 31 C0 48 8D 15 DE FF  (IÇÁ@…M1ÀH
  13. 00C0h: FF FF 48 31 C9 FF 15 B1 FF FF FF 48 31 C9 FF 15  ÿÿH1Éÿ.±ÿÿÿH1Éÿ.

Итоговый размер секции у нас занимает 0xD4 байт. Точка входа у нас равна 0x10AD.
Теперь приступим к непосредственному созданию EXE файла. В самом начале любого PE файла располагается IMAGE_DOS_HEADER заголовок:

  1. typedef struct _IMAGE_DOS_HEADER
  2. } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

В этой структуре нас интересуют только поля e_magic и e_lfanew, находящихся по смещениям 0x00 и 0x3C соответственно. Первое поле содержит сигнатуру MZ, а второе смещение на NT заголовки. Так как мы не используем заглушку DOS, мы расположим NT заголовки сразу за ней, то есть смещение будет равно 0x40. Это очень удобно поскольку NT заголовки должны быть выровнены на 8 байтовую границу, а структура IMAGE_DOS_HEADER имеет размер 0x40 байт. Итак создаем новый файл и вписываем наши данные:

  1. 0000h: 4D 5A 00 00 00 00 00 00 00 00 00 00 00 00 00 00  MZ…………..
  2. 0010h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  …………….
  3. 0020h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  …………….
  4. 0030h: 00 00 00 00 00 00 00 00 00 00 00 00 40 00 00 00  …………@…

Далее вставляем структуру
IMAGE_NT_HEADERS:

  1. typedef struct _IMAGE_NT_HEADERS
  2.      IMAGE_FILE_HEADER FileHeader;
  3.      IMAGE_OPTIONAL_HEADER64 OptionalHeader;
  4. typedef struct _IMAGE_FILE_HEADER
  5.      DWORD PointerToSymbolTable;
  6.      WORD SizeOfOptionalHeader;
  7. typedef struct _IMAGE_OPTIONAL_HEADER64
  8.      UCHAR MajorLinkerVersion;
  9.      UCHAR MinorLinkerVersion;
  10.      DWORD SizeOfInitializedData;
  11.      DWORD SizeOfUninitializedData;
  12.      DWORD AddressOfEntryPoint;
  13.      WORD MajorOperatingSystemVersion;
  14.      WORD MinorOperatingSystemVersion;
  15.      WORD MajorSubsystemVersion;
  16.      WORD MinorSubsystemVersion;
  17.      DWORD64 SizeOfStackReserve;
  18.      DWORD64 SizeOfStackCommit;
  19.      DWORD64 SizeOfHeapReserve;
  20.      DWORD64 SizeOfHeapCommit;
  21.      DWORD NumberOfRvaAndSizes;
  22.      IMAGE_DATA_DIRECTORY DataDirectory[16];
  23. } IMAGE_OPTIONAL_HEADER64;

В качестве Signature вставляем строку из 4-х символов PE. Т.к. у нас 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 — размер одной страницы памяти, а в качестве FileAlignment0x200 (стандартное значение для 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. Вот что у нас получилось:

  1. 0000h: 4D 5A 00 00 00 00 00 00 00 00 00 00 00 00 00 00  MZ…………..
  2. 0010h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  …………….
  3. 0020h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  …………….
  4. 0030h: 00 00 00 00 00 00 00 00 00 00 00 00 40 00 00 00  …………@…
  5. 0040h: 50 45 00 00 64 86 01 00 00 00 00 00 00 00 00 00  PE..d†……….
  6. 0050h: 00 00 00 00 F0 00 22 00 0B 02 00 00 00 10 00 00  ….ð.»………
  7. 0060h: 00 00 00 00 00 00 00 00 AD 10 00 00 00 10 00 00  ……………
  8. 0070h: 00 00 40 00 00 00 00 00 00 10 00 00 00 02 00 00  ..@………….
  9. 0080h: 00 00 00 00 00 00 00 00 05 00 02 00 00 00 00 00  …………….
  10. 0090h: 00 20 00 00 00 02 00 00 00 00 00 00 02 00 00 0C  . …………..
  11. 00A0h: 00 00 10 00 00 00 00 00 00 10 00 00 00 00 00 00  …………….
  12. 00B0h: 00 00 10 00 00 00 00 00 00 10 00 00 00 00 00 00  …………….
  13. 00C0h: 00 00 00 00 10 00 00 00 00 00 00 00 00 00 00 00  …………….
  14. 00D0h: 00 10 00 00 A0 00 00 00 00 00 00 00 00 00 00 00  …. ………..
  15. 00E0h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  …………….
  16. 00F0h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  …………….
  17. 0100h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  …………….
  18. 0110h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  …………….
  19. 0120h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  …………….
  20. 0130h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  …………….
  21. 0140h: 00 00 00 00 00 00 00 00                          ……..

Далее следует вставить описатель секции:

  1. typedef struct _IMAGE_SECTION_HEADER
  2.      DWORD PointerToRelocations;
  3.      DWORD PointerToLinenumbers;
  4.      WORD NumberOfRelocations;
  5.      WORD NumberOfLinenumbers;

В качестве имени забиваем стандартное ‘.text’. 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 байт нулями. В итоге у нас получается вот такой файл:

  1. 0000h: 4D 5A 00 00 00 00 00 00 00 00 00 00 00 00 00 00  MZ…………..
  2. 0010h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  …………….
  3. 0020h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  …………….
  4. 0030h: 00 00 00 00 00 00 00 00 00 00 00 00 40 00 00 00  …………@…
  5. 0040h: 50 45 00 00 64 86 01 00 00 00 00 00 00 00 00 00  PE..d†……….
  6. 0050h: 00 00 00 00 F0 00 22 00 0B 02 00 00 00 10 00 00  ….ð.»………
  7. 0060h: 00 00 00 00 00 00 00 00 AD 10 00 00 00 10 00 00  ……………
  8. 0070h: 00 00 40 00 00 00 00 00 00 10 00 00 00 02 00 00  ..@………….
  9. 0080h: 00 00 00 00 00 00 00 00 05 00 02 00 00 00 00 00  …………….
  10. 0090h: 00 20 00 00 00 02 00 00 00 00 00 00 02 00 00 0C  . …………..
  11. 00A0h: 00 00 10 00 00 00 00 00 00 10 00 00 00 00 00 00  …………….
  12. 00B0h: 00 00 10 00 00 00 00 00 00 10 00 00 00 00 00 00  …………….
  13. 00C0h: 00 00 00 00 10 00 00 00 00 00 00 00 00 00 00 00  …………….
  14. 00D0h: 00 10 00 00 A0 00 00 00 00 00 00 00 00 00 00 00  …. ………..
  15. 00E0h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  …………….
  16. 00F0h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  …………….
  17. 0100h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  …………….
  18. 0110h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  …………….
  19. 0120h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  …………….
  20. 0130h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  …………….
  21. 0140h: 00 00 00 00 00 00 00 00 2E 74 65 78 74 00 00 00  ………text…
  22. 0150h: 00 10 00 00 00 10 00 00 00 02 00 00 00 02 00 00  …………….
  23. 0160h: 00 00 00 00 00 00 00 00 00 00 00 00 20 00 00 60  ………… ..`
  24. 0170h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  …………….
  25. 0180h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  …………….
  26. 0190h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  …………….
  27. 01A0h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  …………….
  28. 01B0h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  …………….
  29. 01C0h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  …………….
  30. 01D0h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  …………….
  31. 01E0h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  …………….
  32. 01F0h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  …………….
  33. 0200h: 54 10 00 00 00 00 00 00 00 00 00 00 3C 10 00 00  T………..<…
  34. 0210h: 74 10 00 00 64 10 00 00 00 00 00 00 00 00 00 00  t…d………..
  35. 0220h: 49 10 00 00 7C 10 00 00 00 00 00 00 00 00 00 00  I…|………..
  36. 0230h: 00 00 00 00 00 00 00 00 00 00 00 00 6B 65 72 6E  …………kern
  37. 0240h: 65 6C 33 32 2E 64 6C 6C 00 75 73 65 72 33 32 2E  el32.dll.user32.
  38. 0250h: 64 6C 6C 00 84 10 00 00 00 00 00 00 00 00 00 00  dll.„………..
  39. 0260h: 00 00 00 00 92 10 00 00 00 00 00 00 00 00 00 00  ….’………..
  40. 0270h: 00 00 00 00 84 10 00 00 00 00 00 00 92 10 00 00  ….„…….’…
  41. 0280h: 00 00 00 00 00 00 45 78 69 74 50 72 6F 63 65 73  ……ExitProces
  42. 0290h: 73 00 00 00 4D 65 73 73 61 67 65 42 6F 78 41 00  s…MessageBoxA.
  43. 02A0h: 48 65 6C 6C 6F 20 77 6F 72 6C 64 21 00 48 83 EC  Hello world!.Hƒì
  44. 02B0h: 28 49 C7 C1 40 00 00 00 4D 31 C0 48 8D 15 DE FF  (IÇÁ@…M1ÀH
  45. 02C0h: FF FF 48 31 C9 FF 15 B1 FF FF FF 48 31 C9 FF 15  ÿÿH1Éÿ.±ÿÿÿH1Éÿ.
  46. 02D0h: A0 FF FF FF 00 00 00 00 00 00 00 00 00 00 00 00   ÿÿÿ…………
  47. 02E0h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  …………….
  48. 02F0h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  …………….
  49. 0300h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  …………….
  50. 0310h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  …………….
  51. 0320h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  …………….
  52. 0330h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  …………….
  53. 0340h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  …………….
  54. 0350h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  …………….
  55. 0360h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  …………….
  56. 0370h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  …………….
  57. 0380h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  …………….
  58. 0390h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  …………….
  59. 03A0h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  …………….
  60. 03B0h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  …………….
  61. 03C0h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  …………….
  62. 03D0h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  …………….
  63. 03E0h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  …………….
  64. 03F0h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  …………….

Теперь если попробовать запустить его, то у нас появится сообщение, как мы и ожидали :) . Также можно поиграться с параметром FileAlignment дабы уменьшить размер файла.
[​IMG]


Надеюсь вам было интересно, спасибо за внимание!
С уважением,
Кривоус Анатолий (The trick).


Thetrik

Thetrik
UA6527P

Регистрация:
25 июл 2011
Публикаций:
0


WASM

Real Machine Code

What you need to run the test: Linux x86 or x64 (in my case I am using Ubuntu x64)

Let’s Start

This Assembly (x86) moves the value 666 into the eax register:

movl $666, %eax
ret

Let’s make the binary representation of it:

Opcode movl (movl is a mov with operand size 32) in binary is = 1011

Instruction width in binary is = 1

Register eax in binary is = 000

Number 666 in signed 32 bits binary is = 00000000 00000000 00000010 10011010

666 converted to little endian is = 10011010 00000010 00000000 00000000

Instruction ret (return) in binary is = 11000011

So finally our pure binary instructions will look like this:

1011(movl)1(width)000(eax)10011010000000100000000000000000(666)
11000011(ret)

Putting it all together:

1011100010011010000000100000000000000000
11000011

For executing it the binary code has to be placed in a memory page with execution privileges, we can do that using the following C code:

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>

/* Allocate size bytes of executable memory. */
unsigned char *alloc_exec_mem(size_t size)
{
    void *ptr;

    ptr = mmap(0, size, PROT_READ | PROT_WRITE | PROT_EXEC,
               MAP_PRIVATE | MAP_ANON, -1, 0);

    if (ptr == MAP_FAILED) {
            perror("mmap");
            exit(1);
    }

    return ptr;
}

/* Read up to buffer_size bytes, encoded as 1's and 0's, into buffer. */
void read_ones_and_zeros(unsigned char *buffer, size_t buffer_size)
{
    unsigned char byte = 0;
    int bit_index = 0;
    int c;

    while ((c = getchar()) != EOF) {
            if (isspace(c)) {
                    continue;
            } else if (c != '0' && c != '1') {
                    fprintf(stderr, "error: expected 1 or 0!n");
                    exit(1);
            }

            byte = (byte << 1) | (c == '1');
            bit_index++;

            if (bit_index == 8) {
                    if (buffer_size == 0) {
                            fprintf(stderr, "error: buffer full!n");
                            exit(1);
                    }
                    *buffer++ = byte;
                    --buffer_size;
                    byte = 0;
                    bit_index = 0;
            }
    }

    if (bit_index != 0) {
            fprintf(stderr, "error: left-over bits!n");
            exit(1);
    }
}

int main()
{
    typedef int (*func_ptr_t)(void);

    func_ptr_t func;
    unsigned char *mem;
    int x;

    mem = alloc_exec_mem(1024);
    func = (func_ptr_t) mem;

    read_ones_and_zeros(mem, 1024);

    x = (*func)();

    printf("function returned %dn", x);

    return 0;
}

Source: https://www.hanshq.net/files/ones-and-zeros_42.c

We can compile it using:

gcc source.c -o binaryexec

To execute it:

./binaryexec

Then we pass the first sets of instructions:

1011100010011010000000100000000000000000

press enter

and pass the return instruction:

11000011

press enter

finally ctrl+d to end the program and get the output:

function returned 666

  • Hello World in BASIC
  • Hello World in Machine Code
  • Hello World in FORTH
  • Hello World in Assembler

How to write a simple Hello World program in
BASIC, machine code, FORTH and assembler on a KC85/3.

Hello World in BASIC

The KC85/3 and KC85/4 came with a built-in BASIC in ROM, for the
KC85/2, a BASIC ROM expansion module was offered.

First start the BASIC interpreter by typing BASIC into the command
prompt (if you’re currently on the KC85/2, first reboot into KC85/3 or
KC85/4). On start, the BASIC interpreter asks for the ‘MEMORY END’, simply
hit Enter there. After the interpreter has figured out how much memory is
available, you should see the BASIC prompt:

basic_prompt

For the BASIC Hello World, we’ll go fancy and print HELLO WORLD in
a loop, changing the foreground color.

Simply type the following, at the end of each line, press Enter. Note that
the BASIC interpreter doesn’t support backspace to delete the character
before the cursor, you’ll have to move the cursor left and overwrite.

AUTO
CLS
FOR I=0 TO 15
COLOR I
PRINT "HELLO WORLD!"
NEXT
[press Escape key]

The command AUTO goes into editor mode. Each line is started with a number
at 10-increments. To leave editor mode, press the Escape key.

You should now see this:

basic_hello

Enter the RUN command to execute the current program, the result should look
like this:

basic_hello_result

Other useful commands are LIST to print the current program, and
EDIT [line-nr] to start editor mode at a line number.

When done, leave the BASIC interpreter with the BYE command.

Hello World in Machine Code

Most performance-critical KC games were written directly in machine code, at
least before the assembler ROM module was available (or if one couldn’t get
ahold of one). The process was a bit tricky since it involved a manual
translation step from assembler code to machine code:

  • write down the assembler statements (called mnemonics) on paper
  • manually translate those to machine code using a lookup table
  • type the machine code into memory using the MODIFY command
  • test and repeat

There was no way to step through the instructions or directly inspect the CPU
state, step-debugging happened in the programmer’s head and on paper.

Let’s dive right in, type the following into the KC85 emu, at the
end of each line, press Enter:

MODIFY 200
7F 7F ,H ,E ,L ,L ,O 01
CD 03 F0
23
,H ,E ,L ,L ,O 20
,W ,O ,R ,L ,D ,!
0D 0A
00
C9
.

After entering the dot and pressing Enter, you should be back at the
command prompt.

Now enter MENU (and Enter), there should be a new command called ‘HELLO’
either at the top or bottom of the usual menu commands.

Type HELLO (+Enter), and a ‘HELLO WORLD!’ should be printed on the screen.

hello_mc

Here’s a line-by-line explanation:

MODIFY 200

This starts memory editing at address 0x200 which is the typical start
address for user code. The area below 0x200 is used mostly by the operating
system.

7F 7F ,H ,E ,L ,L ,O 01

This is the ‘magic header’ with two 7F lead-bytes which identifies the command
to the operating system. The ‘comma plus character’ is a special input
mode of the MODIFY command to simplify the input of ASCII characters. What
will be written to memory is the ASCII character code. The final 01 terminates
the header meaning the command will be visible in the command list displayed
by the MENU command, the other possible value is the rarely used 00 which
is used for ‘hidden’ commands.

CD 03 F0

CD is the opcode for the CALL nnnn instruction, which calls a subroutine
at address nnnn (in this case: 0xF003, which is one of the system call entry
points). The 0xF003 entry point expects the system call number after
the CALL instruction:

23

0x23 is the system call number for the OSTR (Output String) function
which outputs a string on the screen. The string must be embedded in
the instruction stream and is terminated by a 0-byte:

,H ,E ,L ,L ,O 20
,W ,O ,R ,L ,D ,!
0D 0A
00

This is the 0-terminated string. 0x20 is the ASCII code of the space-
character, and 0D 0A are the ASCII codes for ‘Enter’ and ‘Cursor Down’,
basically ‘new line’ and ‘carriage return’.

C9

This is the Z80 RET instruction (return from subroutine), this returns
control back to the operating system.

And that’s it for the machine-code Hello World. The only thing to keep in
mind for low-level programming on the KC85 systems is to leave the
IX register alone (at least while interrupts are enabled), since IX
is used as base-pointer to operating system variables located at 0x01F0.

Hello World in FORTH

FORTH is a stack-based language with very low resource requirements
and a good balance between performance and productivity. On the KC85,
FORTH was available as a ROM module, so first thing is to insert
and activate the FORTH module:

Open the Expansion Slot window, and insert the FORTH module into slot 0x08:

forth_module2

Now the FORTH module must be switched active to address C000, and the
BASIC ROM must be switched off, since this is also mapped at C000 and
has higher priority.

To switch on the FORTH module in slot 8 and map it to address C000, type
SWITCH 8 C1, and to switch off the built-in BASIC ROM type SWITCH 2 0:

forth_activate

Typing MENU should now show 2 new entries (FORTH and REFORTH):

forth_cmds

Ok, now start FORTH, the screen should clear, a message KC — FORTH 3.1
should show up and the FORTH system is waiting for input.

FORTH is a stack-based language, this means that data items are pushed on
a stack, and operations work on items on the stack (often removing input data
items from the stack and pushing result data items back on the stack). It is
basically like a reverse LISP, for instance, adding the numbers 3 and 2
and writing the result to the console would look like this in LISP:

(write (+ 3 2))[press Enter]
5

In FORTH it looks like this:

3 2 + . [press Enter] 5 OK

What happens here is that first, the numbers 3 and 2
are pushed on the stack, then the operation + (add) takes the two top-most
numbers from the stack, adds them together and puts the result back on the
stack. Then the dot-operation . takes the top-most number from the stack
and prints it on the console.

Now back to the HELLO WORLD sample in FORTH. We want the same colorful
output as in the BASIC sample, so some sort of loop and setting the text
color is involved.

Enter the following in the FORTH console, at the end of each line, press Enter,
please pay special attention to the space-characters, the spaces between
character sequences are important, because FORTH only has a very primitive
parser (everything is a word, separated by spaces).

: HELLO
CR
16 0 DO
I 1 COLOR 
." HELLO WORLD!" CR
LOOP ;

What we’ve done here is extend FORTH by a new command (or ‘word’ in FORTH lingo)
called HELLO (a new word is introduced with : (colon), and finished
with ; (semicolon), both : and ; are FORTH words themselves.

CR prints a newline to the console.

16 0 DO starts a loop with the loop counter I going from 0 to 15.

On the next line I 1 COLOR the foreground and background color for text
output is set. I puts the current loop counter on the stack, and 1
is the standard blue background color.

The next line starting with the ‘dot-quote’ word .” prints all
following characters until the next quote character to screen.

Finally the LOOP word finishes the do-loop block, and the ; (semicolon)
finishes the new word definition.

We have now created a new word HELLO in the FORTH dictionary. You can
print the entire dictionary with VLIST (press Escape to stop early).

Time to test the new word. Type HELLO and Enter, and you should
see this:

forth_hello

Voila :)

To leave the FORTH system and go back to CAOS, type BYE.

Hello World in Assembler

Let’s write the fancy Hello World program in assembly.

First insert the module ‘M027 DEVELOPMENT’ into slot 0x08:

edas_module

Just like the FORTH module, the DEVELOPMENT module must be activated
at address C000, and the built-in BASIC ROM must be switched off:

edas_activate

Type MENU to see what the DEVELOPMENT module has to offer:

edas_menu

Quite a number of new commands! The only important one for now is EDAS
however, this enters the assembler development system.

Type EDAS to start the assembler system, when the assembler asks for
the memory end, simply press Enter.

The assembler system is actually a whole IDE, with its own set of
commands:

edas_system

Start the editor with the EDIT command, and enter the following assembler
source code.

NOTE: Use F1 to set the cursor to the next Tab position (the KC85
didn’t have a Tab key), at the end of each line, press Enter, and when
done press Escape to leave the editor.

        DEFW 7F7FH
        DEFM 'HELLO'
        DEFB 01
        LD A,01
        LD (0B781H),A
        XOR A
LOOP:   PUSH AF
        LD L,A
        CALL 0F003H
        DEFB 0FH
        CALL 0F003H
        DEFB 23H
        DEFM 'HELLO WORLD!'
        DEFW 0D0AH
        DEFB 00
        POP AF
        INC A
        CP 10H
        JR NZ,LOOP
        RET
[press Escape]

Let’s go through the code:

        DEFW 7F7FH
        DEFM 'HELLO'
        DEFB 01

This defines the magic command header so that the operating system finds
the new command called ‘HELLO’.

        LD A,01
        LD (0B781H),A

This loads the value 01 into the special operating system address 0B781H
(called ARGN), this is an input parameter for the call to the system function
0F COLOR further down and tells the function that we only want to
set the foreground color, but not the background color.

        XOR A
LOOP:   PUSH AF

This clears the register A to zero, and saves it on the stack. A will become
the foreground color value and loop counter. The LOOP: label marks the
beginning of the loop.

        LD L,A
        CALL 0F003H
        DEFB 0FH

This calls the system call 0F COLOR which is used to set the foreground,
and optionally background color. We told the function to only set the foreground
color before when writing the value 01 to address 0B781H, and the foreground
color value is expected in L, so load L with the current loop counter A.

        CALL 0F003H
        DEFB 23H
        DEFM 'HELLO WORLD!'
        DEFW 0D0A
        DEFB 0

This calls the system function 23 OSTR which outputs the following
zero-terminated string to the screen.

        POP AF
        INC A
        CP 10H
        JR NZ,LOOP
        RET

This is the end of the loop. First the loop counter is popped back from the
stack into A, incremented by one and compared against 10H. As long as A hasn’t
reached the value of 10H yet, the loop will repeat. Otherwise the RET
will be executed which returns control back to the operating system.

Now that the source code has been entered, it must be translated into
machine code and tested.

In the editor, press [Escape] to go back into the assembler IDE menu.

Now type ASM to ‘assemble’ the code, when asked for options, press
O (meaning Output):

edas_asm

To test, leave the assembler IDE with EXIT to return to the CAOS command
prompt, and type MENU to show the system menu. There should be a new
command HELLO, which when executed produces the following output:

edas_output

And that’s it :)

  • Главная
  • Блоги
  • The trick
  • «Hello world» в машинных кодах.

Важная информация

  1. iForum — форум начинающих и профессиональных программистов на языках — Assembler, C/C++, Basic, Visual Basic, Pascal, Delphi, вебмастеров, которым интересны технологии HTML, CSS, PHP, mySQL, JavaScript, Flash, системы управления контентом, хостинг и домены, а также системных администраторов работающих с Windows и UNIX подобными операционными системами.

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

    Вы можете быстро войти через социальные сети:



RSS лента

The trick

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

09.12.2016 в 05:53 (16961 Просмотров)

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

Assembler Code:

  1. MSG db "Hello world!", 0

  2. sub RSP, 0x28           ; Резервируем теневую область

  3. mov R9, 0x00000040      ; MB_ICONINFORMATION

  4. xor R8, R8              ; lpCaption = NULL

  5. lea RDX, [MSG]          ; lpText = 'Hello world!'

  6. xor RCX, RCX            ; HWND = NULL

  7. Call MessageBoxA

  8. xor RCX, RCX

  9. Call ExitProcess

Т.к. в x64 используется RIP адресация (все смещения считаются отнгосительно адреса следующей команды) то немного перепишем код с использованием меток:

Assembler Code:

  1. MSG db "Hello world!", 0

  2. sub RSP, 0x28                   ; Резервируем теневую область

  3. mov R9, 0x00000040              ; MB_ICONINFORMATION

  4. xor R8, R8                      ; lpCaption = NULL

  5. lea RDX, [RIP + (MSG - L1)]     ; lpText = 'Hello world!'

  6. L1: xor RCX, RCX                ; HWND = NULL

  7. Call [RIP + (MessageBoxA - L2)]

  8. L2: xor RCX, RCX

  9. Call [RIP + (ExitProcess - L3)]

  10. 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). Опкод команды LEA0x8D. В качестве 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 заголовок:

C++ Code:

  1. typedef struct _IMAGE_DOS_HEADER

  2. {

  3.      WORD e_magic;

  4.      WORD e_cblp;

  5.      WORD e_cp;

  6.      WORD e_crlc;

  7.      WORD e_cparhdr;

  8.      WORD e_minalloc;

  9.      WORD e_maxalloc;

  10.      WORD e_ss;

  11.      WORD e_sp;

  12.      WORD e_csum;

  13.      WORD e_ip;

  14.      WORD e_cs;

  15.      WORD e_lfarlc;

  16.      WORD e_ovno;

  17.      WORD e_res[4];

  18.      WORD e_oemid;

  19.      WORD e_oeminfo;

  20.      WORD e_res2[10];

  21.      DWORD e_lfanew;

  22. } 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:

C++ Code:

  1. typedef struct _IMAGE_NT_HEADERS

  2. {

  3.      DWORD Signature;

  4.      IMAGE_FILE_HEADER FileHeader;

  5.      IMAGE_OPTIONAL_HEADER64 OptionalHeader;

  6. } IMAGE_NT_HEADERS;

  7. typedef struct _IMAGE_FILE_HEADER

  8. {

  9.      WORD Machine;

  10.      WORD NumberOfSections;

  11.      DWORD TimeDateStamp;

  12.      DWORD PointerToSymbolTable;

  13.      DWORD NumberOfSymbols;

  14.      WORD SizeOfOptionalHeader;

  15.      WORD Characteristics;

  16. } IMAGE_FILE_HEADER;

  17. typedef struct _IMAGE_OPTIONAL_HEADER64

  18. {

  19.      WORD Magic;

  20.      UCHAR MajorLinkerVersion;

  21.      UCHAR MinorLinkerVersion;

  22.      DWORD SizeOfCode;

  23.      DWORD SizeOfInitializedData;

  24.      DWORD SizeOfUninitializedData;

  25.      DWORD AddressOfEntryPoint;

  26.      DWORD BaseOfCode;

  27.      DWORD64 ImageBase;

  28.      DWORD SectionAlignment;

  29.      DWORD FileAlignment;

  30.      WORD MajorOperatingSystemVersion;

  31.      WORD MinorOperatingSystemVersion;

  32.      WORD MajorImageVersion;

  33.      WORD MinorImageVersion;

  34.      WORD MajorSubsystemVersion;

  35.      WORD MinorSubsystemVersion;

  36.      DWORD Win32VersionValue;

  37.      DWORD SizeOfImage;

  38.      DWORD SizeOfHeaders;

  39.      DWORD CheckSum;

  40.      WORD Subsystem;

  41.      WORD DllCharacteristics;

  42.      DWORD64 SizeOfStackReserve;

  43.      DWORD64 SizeOfStackCommit;

  44.      DWORD64 SizeOfHeapReserve;

  45.      DWORD64 SizeOfHeapCommit;

  46.      DWORD LoaderFlags;

  47.      DWORD NumberOfRvaAndSizes;

  48.      IMAGE_DATA_DIRECTORY DataDirectory[16];

  49. } IMAGE_OPTIONAL_HEADER64;

В качестве Signature вставляем строку из 4-х символов PE. Т.к. у нас 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 — размер одной страницы памяти, а в качестве FileAlignment0x200 (стандартное значение для 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                          ........

Далее следует вставить описатель секции:

C++ Code:

  1. typedef struct _IMAGE_SECTION_HEADER

  2. {

  3.      BYTE Name[8];

  4.      DWORD VirtualSize;

  5.      DWORD VirtualAddress;

  6.      DWORD SizeOfRawData;

  7.      DWORD PointerToRawData;

  8.      DWORD PointerToRelocations;

  9.      DWORD PointerToLinenumbers;

  10.      WORD NumberOfRelocations;

  11.      WORD NumberOfLinenumbers;

  12.      DWORD Characteristics;

  13. } IMAGE_SECTION_HEADER;

В качестве имени забиваем стандартное ‘.text’. 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).

Сообщение форума

Машинный кодилимашинный язык– это язык, на котором записываются
программы для данной машины или данного
семейства машин. Этот код определяется
набором кодов операций и форматом команд
процессора, типом адресации ячеек,
форматом представления данных в памяти
и пр.

Для примера рассмотрим программу,
которая выводит на консоль текст «Hello,world!», для процессора
архитектуры x86 фирмыIntelc16-разрядной адресацией.
В этой архитектуре адресуемой ячейкой
памяти является каждый 8‑разрядный
байт. Поле КОП команды занимает первый
байт (т.е. допускается не более 28= 256 различных команд), поле адреса – 2
байта (т.е. можно адресовать 216 байтов
= 65536 байтов = 64 КБ – длинный адрес) или
1 байт (можно адресовать только 256 байт
– короткий адрес).

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

10111011

00010001

00000001

Для удобства чтения программ программисты
записывают их в шестнадцатеричном(16-ричном) коде, заменяя каждую четвёрку
битов одной 16-ричной цифрой (всего этих
цифр 16), как показано в следующей таблице:

16-ричная цифра

Двоичное число

Десятичное
число

0

0000

0

1

0001

1

2

0010

2

3

0011

3

4

0100

4

5

0101

5

6

0110

6

7

0111

7

8

1000

8

9

1001

9

A

1010

10

B

1011

11

C

1100

12

D

1101

13

E

1110

14

F

1111

15

10

10000

16

В любой системе счисления число, следующее
за последней цифрой, равно 10, т.е.
изображается двумя цифрами (см. в таблице
102, 1010и 1016).

Таким образом, эта команда в 16-ричной
системе имеет вид ВВ 11 01. Байты 11 01
образуют здесь двухбайтовое поле адреса.
Адрес, указанный в этом поле, – 011116,
а не 110116. Чтобы записать правильный
адрес, нужно поменять местами байты
адресного поля! В десятичной системе
этот адрес равен 273.

Вся программа в 16-ричном коде выглядит
следующим образом (за программой
последует её комментарий):

Адрес
первого
байта

Команды и
данные25

1

25610

010016

BB
11 01

2

25910

010316

B9
0D
00

3

26210

010616

B4
0E

4

26410

010816

8A
07

5

26610

010А16

CD
10

6

26710

010B16

43

7

26910

010D16

E2
F9

8

27110

010F16

CD
20

9

27310

011116

48
65 6C
6C
6F
2C
20 57 6F 72 6C 64 21

Программа размещена, начиная с ячейки
с адресом 010016, и состоит из 8 команд,
занимающих первые 17 байтов. После команд
в неё включена выдаваемая строка текста
«Hello,world!»
– 13 символов (включая пробел). Каждый
символ представлен своим 8‑разряднымASCII-кодом (см. любую таблицу
символов). Вся строка занимает 1310=D16байтов:

Символы

H

e

l

l

o

,

w

o

r

l

d

!

Их коды

48

65

6C

6C

6F

2C

20

57

6F

72

6C

64

21

Таким образом, вся программа с данными
занимает 30 байтов.

Команды с номерами 4 – 7 (выделены жирным
шрифтом) повторяются столько раз, сколько
символов в выводимой строке (13 раз). Это
цикл.

Комментарии к программе

В программе для передачи данных
используются регистры: 8-разряд­ные
– AL,AH(это
байты 16-разрядного регистра АХ),
16‑разрядные –BX,CX.
РегистрыCXиIP(адрес следующей команды) используются
для организации цикла. Команды «знают»,
какие регистры они используют.

Вывод символа на консоль осуществляется
операционной системой (BIOS)
с помощью прерывания с номером 1016.
Останов программы (возврат управления
операционной системеMSDOSпосле завершения
программы) осуществляется с помощью
прерывания 2016. Команда останова
необходима, иначе процессор начнёт
следующий байт (4816– символ «Н»)
рассматривать как код операции, и это
может привести к тяжёлым последствиям.

Адрес

Команды и
данные

Комментарии

010016

BB
11 01

Поместить в BX
адрес строки (байт с символом «Н»
становится текущим)

010316

B9
0D
00

Поместить в CX
длину строки (000D)

010616

B4
0E

Поместить в AH
номер функции 0E16
прерывания 1016

010816

8A
07

Поместить в
AL значение ячейки памяти, адрес которой
находится в BX (т.е. текущий символ)

010А16

CD
10

Вызов прерывания
10
16,
которое с помощью регистра АХ выведет
текущий символ

010B16

43

Увеличить
значение ВХ на 1 (сделать текущим
следующий байт)

010D16

E2
F9

Если CX≠0, то
уменьшить CX на 1 и увеличить
IP
(равный 010
F)
на
F9
(равносильно уменьшению на 7, т.к.
F9
– дополнительный код числа -7), т.е.
перейти по адресу 0108
16
– на начало цикла

010F16

CD
20

Вызвать
прерывание
2016:
завершить программу

011116

48
65 6C
6C
6F
2C
20 57 6F
72 6C
64 21

«Hello,
world!»

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

Поэтому уже в начале 1950-х годов стали
появляться языки программирования,
позволявшие записывать машинные команды
и данные не в цифровом виде, в символическом,
используя мнемонические имена команд,
регистров, ячеек памяти. Эти языки
называют языками символического
кодирования.
Для них создавались
программы-трансляторы, преобразующие
команды из символической записи в
цифровую. Эти трансляторы, а вместе с
ними и языки символического кодирования,
назывались сначалаавтокодами, а
позже ­ассемблерами(по-русски –
«сборщиками»). Появление таких языков
и трансляторов стало возможным лишь на
машинах с архитектурой фон Неймана
благодаря принципу хранимой программы
(они и были созданы для первых таких
машин26).

Приведём код той же программы на языке
ассемблера:

mov bx, HW ;
move
— переместить

mov cx, 000Dh

mov ah, 0Eh

L: mov al, [bx]

int 10h ;
interruption — прерывание

inc bx ;
increment – увеличить
на
1

loop L ;
loop — цикл

int 20h

HW: db ‘Hello,
World!’

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

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

write(‘Hello,
World!’)

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]

  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #
 


Первые шаги в машинных кодах — Самый – самый первый шаг.

1. Самый – самый первый шаг…

Многие любители не испытывают серьезных трудностей в
овладении БЕЙСИКом. Для этого достаточно немного практики. Но рано или поздно
они приходят к барьеру «машинного кода». Как это ни печально, но некоторые так
перед ним и останавливаются. Это ни в коей мере не связано с отсутствием
желания или способностей, просто многие не знают, с чего начать. Если в БЕЙСИКе
можно начинать с чего угодно (при ошибке компьютер сам Вас поправит), то здесь
Вы оказываетесь с процессором один на один, и такой метод проб и ошибок не
срабатывает.

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

Итак, давайте напишем первую программу в машинном коде.
Прежде всего, выделим для нее область памяти. Если Вы читали нашу книгу
«Большие возможности Вашего «ZX-Spectrum`а», то знаете, что для БЕЙСИКа в
оперативной памяти компьютера отведена область памяти, начинающаяся с адреса,
на который указывает системная переменная PROG и заканчивается адресом, на
который указывает системная переменная RAMTOP. Предположим, что Вы хотите
записать программу в машинных кодах, начиная с адреса 30000. Дайте команду
CLEAR 29999. Эта команда установит RAMTOP в 29999 и Ваша программа будет
защищена от возможной порчи из БЕЙСИКа. Даже если Вы дадите команду NEW,
области памяти, находящиеся выше RAMTOP, не будут поражены.

Теперь дайте две прямые команды одну за другой:

POKE 30000,0

POKE 30001,201

Мы сейчас записали два числа в нужные нам адреса. Они
образуют простейшую программу. Выполнить ее можно командой RANDOMIZE USR 30000.
Попробуйте сами… Вам покажется, что ничего не произошло, но это не так.
Сначала процессор обратился по адресу 30000 и нашел там число 0, которое
обозначает машинный код операции NOP. Операция NOP (no operation
— нет операции) дает команду процессору, что ничего делать не надо. В течение
0,0000014 сек. он действительно ничего не делает, а затем переходит к
следующему адресу, где находит число 201.

Это команда RET (return
возврат). Она дает процессору указание прекратить в этом месте программу в
машинных кодах и вернуться туда, откуда она вызывалась, т.е. в нашем случае — в
БЕЙСИК. Это самое процессор и сделал, о чем Вы получили сообщение БЕЙСИКа
«О.К.».

Если все, что Вы здесь прочитали, Вам понятно, то Вы уже
поняли, как составляются программы в машинных кодах. Можно, конечно, возразить,
что пользы от такой программы не очень много, но сейчас не в этом суть. Важно,
чтобы Вы поняли, что некая последовательность чисел может быть
последовательностью команд для процессора Z-80.

К сожалению, для нас мало, что говорит простая
последовательность чисел вроде таких, как 0 и 201. Держать в памяти коды всех
команд процессора (а их около семисот) непросто, но дело упрощается тем, что
есть промежуточный язык между процессором и человеком — язык Ассемблера. У
каждого кода есть своя мнемоника Ассемблера. Мнемоника — это набор букв,
являющихся сокращением английских слов. Для нашего примера программа на Ассемблере
выглядит так:

NOP

RET

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

И тех программ и других достаточно много. Часто они
объединяются в пакеты. Широко распространены пакеты GENS3/MONS3 фирмы HISOFT и EDITAS/MONITOR 16/48 фирмы PICTURESQUE. Здесь
GENS3 и EDITAS — Ассемблеры, а MONS3, MONITOR 16 и MONITOR
48 — Дизассемблеры.

Теперь давайте вернемся к нашей первой программе и
попробуем ее несколько развить, чтобы она все же что-то делала. Процессор Z-80
имеет несколько регистров, у которых есть имена – «А», «В», «С»
и т.д. Каждый из них может содержать одно какое-либо целое число от 0 до 255
(т.е. один байт).

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

Так, например, команда Ассемблера LD B,A (машинный
код — 71) означает «загрузить содержимое регистра А в регистр В».
LD — это сокращение от LOAD (загрузка).

Точно так же LD C,B (машинный код 72) означает «загрузить
в регистр С содержимое регистра В». Можно загружать в регистры и
целые числа. Например, LD A,n — означает
«загрузить в регистр А целое число n»,
где n может быть числом от 0 до 255. До этого
все команды  были однобайтными. Эта же команда — двухбайтная. Сначала идет
машинный код — 62, а за ним само число — n. Так,
например, LD A, 77 (загрузить в регистр А число 77) будет
выглядеть так: 62,77. Здесь 62 — код операции, — он сообщает процессору, что
надо сделать, а 77 — это операнд. Заметим здесь же, что бывают операции и
трехбайтные и четырехбайтные. Первый байт, как правило, — код операции, а
следующие за ним — операнды. Мы говорим «как правило» потому, что есть
некоторые операции, код которых записывается двумя байтами [прим.1].

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

Ассемблер

Адрес

Машинный код

NOP

30000

0

LD A, 77

30001

62

30002

77

LD B, A

30003

71

LD C, B

30004

72

RET

30005

201

Гораздо интереснее было бы организовать обмен между
процессором и оперативной памятью компьютера. Команд, которые позволяют это
сделать, тоже очень много. Например, это команда LD (nn),A. Ее машинный код — 50. Она означает следующее: «Загрузить
в ячейку памяти, адрес которой задан двухбайтным числом nn,
содержимое регистра A». Эта команда — трехбайтная. Первым идет код
операции (50), а за ним — двухбайтный операнд nn,
который задает нужный Вам адрес. Например, если Вам нужен адрес 30008, то тогда
операнд nn будет иметь вид: 56, 117 потому, что 117*256
+ 56 = 30008.

Этот двухбайтный операнд имеет старшую часть и младшую.
Запомните, что двухбайтные числа (как правило, это адреса) хранятся в памяти
всегда в обратном порядке, т.е. сначала младший байт, а потом старший [прим.2].

Таким образом, команда Ассемблера LD (30008),A в
машинных кодах запишется так: 50, 56, 117.

Существует и противоположная команда LD A,(nn) – «Загрузить в регистр А содержимое ячейки
памяти, заданной адресом nn». Ее код — 58, за
которым следуют два байта, указывающие на адрес: сначала младший, а потом —
старший. Тогда LD A,(30007) запишется так: 58, 55, 117.

Теперь мы уже можем перемещать содержимое ячеек памяти из
одной в другую, копируя его через регистр А процессора.

До
операции

A

ПРОЦЕССОР

После
операции

Ассемблер

Адрес

Машинный код

LD A,(30007)

30000

58

30001

55

30002

117

LD (30008),A

30003

50

30004

56

30005

117

RET

30006

201

Можно еще несколько усложнить задачу и попытаться
выполнить сложение содержимого двух каких-либо ячеек памяти и отправить
результат на хранение в какую-либо ячейку. Для выполнения арифметических
действий, например сложения, процессор Z-80 также имеет несколько команд.
Рассмотрим команду ADD A,B (ее машинный код — 128). ADD addition (сложение).

ADD A,B означает следующее: «Прибавить содержимое
регистра B процессора к содержимому регистра А и результат
оставить в регистре А».

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

Предположим, что Вы хотите сложить содержимое ячеек 30013
и 30014, а результат поместить, а ячейку 30015. Тогда программа на Ассемблере и
в машинных кодах будет иметь вид:

Ассемблер

Адрес

Машинный код

Комментарий

LD A,(30013)

30000

58

Загрузить
в регистр А число, содержащееся в адресе 30013.

30001

61

30002

117

LD B,A

30003

71

Загрузить
в регистр В содержимое регистра А

LD A,(30014)

30004

58

Загрузить
в регистр А число, содержащееся в адресе 30014.

30005

62

30006

117

ADD A,B

30007

128

Прибавить
к содержимому А содержимое регистра В.

LD (30015),A

30008

50

Выгрузить
содержимое регистра А в адрес 30015.

30009

63

30010

117

NOP

30011

0

Нет
операции. Пауза.

RET

30012

201

Возврат
туда, откуда эта программа вызывалась.

Попробуйте эту программу в работе. Пусть Вы хотите
сложить два числа, скажем 50 и 70. Сначала выделим память для этой программы в
машинных кодах.

10 CLEAR 29999

Теперь введем эту программу в память, начиная с адреса 30000.

20 FOR i=30000 TO 30012: READ q: POKE
i,q: NEXT i

30 DATA 58, 61, 117, 71, 58, 62, 117, 128, 50, 63, 117,
0, 201

Запишем в ячейки 30013 и 30014 те два числа, которые мы желаем
сложить:

40 POKE 30013, 50: POKE 30014, 70

Введем команду  на исполнение  нашей программы  в
машинных кодах.

50 RANDOMIZE USR 30000

И, наконец, стартуем нашу БЕЙСИК-программу — RUN.

Через долю секунды она отработает и на экране появится
сообщение О.К.

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

PRINT PEEK 30015 даст Вам: 120 О.К.

Итак, мы уже познакомились с несколькими командами
процессора Z-80. Теперь нам придется прервать такое популярное изложение,
поскольку система команд этого процессора довольно обширна и при таком подходе
пришлось бы отвести ей около тысячи страниц. Мы надеемся, что психологический
барьер Вы уже преодолели и далее мы переходим к систематическому и постепенному
освоению системы команд процессора.

Вы, по-видимому, поняли, что всякой команде в машинных
кодах соответствует своя мнемоника Ассемблера, в которой в нескольких буквах
зашифрована  суть операции. К  сожалению, обычно литературу пишут профессионалы
для профессионалов и они не утруждают себя переводом мнемоник на русский язык,
тем более, что по Z-80 книг на русском языке почти нет (есть несколько  ведомственных
переводов). Поэтому «Инфорком» в своем «Справочнике…» (часть 3) уделил место
словарю для перевода мнемоник Ассемблера в нормальный английский, а через него
и в русский язык.

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

Даже для одного только процессора Z-80 мнемоники до сих
пор не стандартизированы. Многие ассемблирующие программы, например, EDITAS, ZEUS, GENS1…GENS3 и др. имеют отклонения в форме записи мнемоник, но они
незначительны и всегда оговариваются в сопроводительной инструкции к программе.

Понравилась статья? Поделить с друзьями:
  • Как написать hello world на pascal
  • Как написать hello world на javascript
  • Как написать hello world на java
  • Как написать hello world на html
  • Как написать hello world на c visual studio code