Как писать и выполнять машинный код PURE вручную без контейнеров, таких как EXE или ELF?

Мне просто нужна демонстрационная версия hello world, чтобы увидеть, как работает машинный код.

Хотя окна EXE и linux ELF находятся рядом с машинным кодом, но не PURE

Как я могу написать/выполнить PURE машинный код?

Ответ 1

Все знают, что приложение, которое мы обычно пишем, выполняется в операционной системе. И управляется этим.

Это означает, что операционная система запущена на машине. Поэтому я думаю, что это ЧИСТЫЙ машинный код, который вы сказали.

Итак, вам нужно изучить, как работает операционная система.

Вот код сборки NASM для загрузочного сектора, который может печатать "Hello world" в PURE.

 org
   xor ax, ax
   mov ds, ax
   mov si, msg
boot_loop:lodsb
   or al, al 
   jz go_flag   
   mov ah, 0x0E
   int 0x10
   jmp boot_loop

go_flag:
   jmp go_flag

msg   db 'hello world', 13, 10, 0

   times 510-($-$$) db 0
   db 0x55
   db 0xAA

И вы можете найти больше ресурсов здесь: http://wiki.osdev.org/Main_Page.

END.

Если у вас установлен нос и установлена дискета, вы можете

nasm boot.asm -f bin -o boot.bin
dd if=boot.bin of=/dev/fd0

Затем вы можете загрузиться с этой дискеты, и вы увидите сообщение. (ПРИМЕЧАНИЕ: вы должны сделать первую загрузку вашего компьютера дискетой.)

На самом деле, я предлагаю вам запустить этот код на полной виртуальной машине, такой как: bochs, virtualbox и т.д. Потому что трудно найти машины с дискетой.

Итак, шаги Во-первых, вам необходимо установить полноценную виртуальную машину. Во-вторых, создайте визуальную дискету с помощью commend: bximage В-третьих, запишите файл bin на эту визуальную дискету. Наконец, запустите ваш визуальный компьютер с этой визуальной дискеты.

ПРИМЕЧАНИЕ. В https://wiki.osdev.org есть некоторая базовая информация по этой теме.

Ответ 2

Вы можете писать в PURE машинный код вручную БЕЗ СБОРКИ

Linux/ELF: https://github.com/XlogicX/m2elf. Это все еще в стадии разработки, я только начал работать над этим вчера.

Исходный файл для "Hello World" будет выглядеть следующим образом:

b8    21 0a 00 00   #moving "!\n" into eax
a3    0c 10 00 06   #moving eax into first memory location
b8    6f 72 6c 64   #moving "orld" into eax
a3    08 10 00 06   #moving eax into next memory location
b8    6f 2c 20 57   #moving "o, W" into eax
a3    04 10 00 06   #moving eax into next memory location
b8    48 65 6c 6c   #moving "Hell" into eax
a3    00 10 00 06   #moving eax into next memory location
b9    00 10 00 06   #moving pointer to start of memory location into ecx
ba    10 00 00 00   #moving string size into edx
bb    01 00 00 00   #moving "stdout" number to ebx
b8    04 00 00 00   #moving "print out" syscall number to eax
cd    80            #calling the linux kernel to execute our print to stdout
b8    01 00 00 00   #moving "sys_exit" call number to eax
cd    80            #executing it via linux sys_call

WIN/MZ/PE:

Расположение сценария shellcode2exe.py (принимает код оболочки asciihex и создает допустимый исполняемый файл MZ PE):

http://zeltser.com/reverse-malware/shellcode2exe.py.txt

Зависимость:

corelabs.coresecurity.com/index.php?module=Wiki&action=attachment&type=tool&page=InlineEgg&file=InlineEgg-1.08.tar.gz

экстракт

python setup.py build




sudo python setup.py install

Ответ 3

Похоже, вы ищете старый 16-разрядный формат DOS .COM. Байты файла .COM загружаются со смещением 100h в сегменте программы (ограничивая их максимальным размером 64k - 256 байт), а процессор просто запускается с шагом 100h. Нет никаких заголовков или любой требуемой информации любого рода, только исходные инструкции CPU.

Ответ 4

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

Если вам нужно что-то более простое, чем ELF, но * nix, посмотрите на формат a.out, который намного проще. Традиционно компиляторы * nix C (все еще) записывают свой исполняемый файл в файл с именем a.out, если не указано имя вывода.

Ответ 5

В Windows - не менее 32-битной Windows - вы можете выполнить RAW INSTRUCTIONS с использованием .com файла.

Например, если вы берете эту строку и сохраняете ее в блокноте с расширением .com:

X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*

Он напечатает строку и запустит антивирусное программное обеспечение.

Ответ 6

При настройке встроенной системы вы можете создать двоичный образ rom или ram, который строго соответствует инструкциям и связанным с ними данным из программы. И часто можно записать этот двоичный файл в flash/rom и запустить его.

Операционные системы хотят знать больше, и разработчики часто хотят оставить больше, чем в своем файле, чтобы они могли отлаживать или делать с ним другие вещи позже (разобрать с некоторыми узнаваемыми именами символов). Кроме того, встроенный или в операционной системе вам может потребоваться отделить .text от .data из .bss из .rodata и т.д. И форматов файлов, таких как .elf, предоставить механизм для этого, и предпочтительным вариантом использования является загрузка этого эльфа с некоторыми вроде загрузчика, будь то операционная система или что-то программирующее ром и плунжер микроконтроллера.

.exe также имеет информацию о заголовке. Как упоминалось .com не загружался по адресу 0x100h и разветвлен там.

чтобы создать исходный двоичный файл из исполняемого файла, с созданным gcc файлом эльфа, например, вы можете сделать что-то вроде

objcopy file.elf -O бинарный файл .bin

Если программа сегментирована (.text,.data и т.д.), и эти сегменты не возвращаются назад, бинарный файл может стать довольно большим. Опять же, используя встроенный в качестве примера, если rom находится в 0x00000000, а данные или bss - на 0x20000000, даже если ваша программа имеет всего 4 байта objcopy данных, создаст файл размером 0x20000004, заполняющий промежуток между .text и .data(как и должен потому что это то, что вы просили у него).

Что вы пытаетесь сделать? Чтение эльфа или файла hex hex или srec довольно тривиально, и из него вы можете увидеть все биты и байты двоичного файла. Или разобрать эльфа, или что-то еще покажет вам это в удобочитаемой форме. (objdump -D file.elf > file.list)

Ответ 7

С чистым машинным кодом вы можете использовать любой язык, на котором есть возможность записывать файлы. даже visual basic.net может писать 8,16,32,64 бит при перестановке между типами int во время записи.

Вы даже можете настроить, чтобы vb выписывал машинный код в цикле по мере необходимости для чего-то вроде setpixel, где x, y изменяется, и у вас есть ваши цвета argb.

или, регулярно создавайте свою программу vb.net в Windows и используйте NGEN.exe, чтобы создать собственный файл кода вашей программы. Он создает чистый машинный код, специфичный для ia-32, одним выстрелом, отбрасывающим отладчик JIT.

Ответ 8

Следующая программа - это программа Hello World, которую я написал в машинном коде 16 бит (intel 8086). Если вы хотите узнать машинный код, я предлагаю вам сначала изучить ассемблер, потому что каждая строка кода в ассемблере преобразуется в строку кода в Машинный код. Хорошо знаю, что я из немногих людей в мире, которые все еще программируют на машинном коде, а не на ассемблере.

Кстати, чтобы запустить его, сохраните файл с расширением ".com" и запустите на DOSBOX!

Итак, это программа Hello World.

Ответ 9

Это приятные ответы, но почему кто-то захочет это сделать, может лучше ответить на этот вопрос. Я думаю, что самая важная причина - получить полный контроль над своей машиной, особенно за ее кеш-запись, за максимальную производительность и не позволять любой ОС делиться процессором или виртуализировать ваш код (тем самым замедляя его) или особенно в эти дни, отслеживая ваш код также. Насколько я могу судить, ассемблер не справляется с этими проблемами, а M $/Intel и другие компании рассматривают это как нарушение или "для хакеров". Однако это очень неправильно. Если ваш код ассемблера передается ОС или проприетарному оборудованию, истинная оптимизация (возможно, на частотах ГГц) будет недосягаема. Это очень важная проблема в отношении науки и техники, поскольку наши компьютеры не могут использоваться в полной мере без аппаратной оптимизации и часто вычисляют на несколько порядков ниже нее. Вероятно, есть обходное решение или какое-то оборудование с открытым исходным кодом, которое позволяет это, но я еще не нашел его. Пенни для любых мыслей.

Ответ 10

Я не программист, и все, что я знаю о программировании, - это то, что я узнал, когда я закончил раздел JavaScript в Kahn Academy, но я даже не могу вспомнить, что такое цикл for. Я искал что-то о машинных кодах и нашел руководство по программированию машинного кода для 16-битной Windows, и первая программа должна была сгенерировать Hello, World!.

Код:

ba 0c 01 b4 09
cd 21
b8 00 4c
cd 21
48 65 6c 6c 6f 2c 20 57 6f 72 6c 64 21 0d 0a 24

В двоичном выражении это означает:

10111010 00001100 00000001
10110100 00001001
11001101 00100001
10111000 00000000 01001100
11001101 00100001
01001000 01100101 01101100 01101100 01101111 00101100 00100000 01010111 01101111 01110010 01101100 01100100 00100001 00100001 00001010 00100100

У меня есть куча всех-вы-вызовов, которые разделяют строки кода, но по какой-то причине это не появляется для меня. Возможно, это будет для вас.

Ответ 11

Код реальной машины

Что нужно для запуска теста: Linux x86 или x64 (в моем случае я использую Ubuntu x64)

Позвольте начать

Эта сборка (x86) перемещает значение 666 в регистр eax:

movl $666, %eax
ret

Позвольте сделать двоичное представление этого:

Код операции movl (movl - это mov с размером операнда 32) в двоичном виде = 1011

Инструкция ширина в двоичном виде = 1

Зарегистрируйте eax в двоичном виде = 000

Число 666 в двоичном формате со знаком 32 бита = 00000000 00000000 00000010 10011010

666 преобразовано в младший порядковый номер is = 10011010 00000010 00000000 00000000

Инструкция ret (возврат) в двоичном виде = 11000011

Итак, наконец наши чистые двоичные инструкции будут выглядеть так:

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

Собираем все вместе:

1011100010011010000000100000000000000000
11000011

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

#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 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 %d\n", x);

    return 0;
}

Источник: https://www.hanshq.net/files/ones-and-zeros_42.c

Мы можем скомпилировать его с помощью:

gcc source.c -o binaryexec

Чтобы выполнить это:

./binaryexec

Затем мы передаем первые наборы инструкций:

1011100010011010000000100000000000000000

нажмите ввод

и передайте инструкцию возврата:

11000011

нажмите ввод

наконец, Ctrl + D, чтобы завершить программу и получить вывод:

функция вернула 666