decu emu
decu emu

Я остался под впечатлением от прочитанной первой или второй главы про хакерство в книге Криса Касперски "Техника отладки программ без исходных текстов".

Хоть я и знал о том, что раньше писали в машинных кодах, но когда Крис так подал интересно материал, что я замотивировался сделать небольшой компьютер эмулятор, который бы позволял обрабатывать мой машинный код. Тут дело проще чем кажется, так как я решил взять всего 16 инструкций для этого дела. Плюс ко всему я как-то давно мечтал сделать hex редактор на ncurses, но каждый раз не было смысла его делать просто так, а теперь сделал.

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

	* 0000 ADD - Команда сложения
	* 0001 SUB - вычитание
	* 0010 AND - битовое И
	* 0011 OR - битовое ИЛИ
	* 0100 XOR - ксор регистров или числа
	* 0101 SHL - сдвиг влево
	* 0110 SHR - сдвиг вправо
	* 0111 LD - запись в регистр аккумулятор
	* 1000 IN - входящие данные по порту
	* 1001 NOP - нет команды
	* 1010 OUT - исходящие данные по порту
	* 1011 PUSH - пуш байта
	* 1100 POP - поп байта
	* 1101 TEST - вычитание без записи в регистр A
	* 1110 JC - условный и безусловный переход
	* 1111 HALT - останова программы
	Эти команды формируют старший полубайт опкода. Младший полубайт опкода указывает на регистры или регистр число.

флаги

	Z - флаг нуля, то-есть если делается тест, вычитается значение, если результат равно нулю, то устанавливается этот флаг
	S - флаг отрицательного числа, если в результате стало равно меньше нуля, или то-есть установлен 0x80 бит.
	C - флаг переполнения

Условия у переходов

	0000 JC
	0001 JS
	0010 JZ
	0011 JMP

младший полубайт

	* A -           0b00
	* X -           0b01
	* Y -           0b10
	* число/адрес - 0b11

адреса

	0x00 in, out - таймер, можно задать время обновления в миллисекундах
	0x01 in, out - экран, 22x10. Значение заноситься из регистров. В регистре A находится символ, в регистре X координата по x, в регистре Y координата по y. Например [IN 0x01]; [1000 0000 0000 0001]; 0x80 0x01
	0x02 out - чтение в регистр A значения стрелок. Четыре стороны, 0b1000 - это только влево. 0b1100 - это влево и вверх. Как в vim [jkl;]
	0x03 out - чтение в регистр A нажатой клавиши, всего 4 клавиши.

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

Как можно работать с памятью. Например рассмотрим инструкцию LD, которая загружает в регистр какое-то число. Регистр может быть регистром данных или регистром адресом.

LD инструкция это 0111 - то-есть 0x7x. Например хотим загрузить в регистр A число 0x14, будет так. LD 0111; A 00; число 11; 0x14 0001 0100; Получается 0x73 0x14.

Команд мало и их можно запомнить, плюс потренировать свою память.

В этом эмуляторе есть стек, который начинается с адреса 0xffff, мы можем контролировать стек только с помощью PUSH и POP. С помощью LD мы можем загружать только байт, и адрес может быть только байт (хотя можно сделать и два байта). Пока адрес для LD есть как один байт, поэтому все переменные нужно хранить в первых 256 байтах.

Цель проекта почувствовать что такое программировать в машинных кодах на упрощенной архитектуре. Я бы хотел даже электронное устройство такое иметь, где можно было бы ходить с ним и в память вбивать опкоды, чтобы на экране потом что-нибудь выводилось.

Такое я смог написать только под вдохновением, когда как разработка моего эмулятора i386 простаивает.

В любом случае, можно теперь почувствовать ту боль или то восхищение, когда код писался с помощью чисел.

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

Если интерес не пропадет, то сделаю сборку для android или аврора.

Еще в планах сделать пошаговый отладчик.

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

Комментарии (12)


  1. andy_p
    04.01.2025 21:52

    книге Криса Касперского

    Он вроде как Крис Касперски.


    1. svs85
      04.01.2025 21:52

      Более того, интересна история придуманного псевдонима :)))


      1. themen2
        04.01.2025 21:52

        Ага, он приходил к настоящему Касперскому, но тот чё то психанул, если не ошибаюсь и куда то убежал на пердячей тяге


  1. longtolik
    04.01.2025 21:52

    Это здорово, когда мы чувствуем железо.

    Последний раз использовал машинные коды, когда для Raspberry Pi 3 делал звук/видео буфер на голом железе. FASM интерпретировал мнемонику в машинный код, а GCC - нет. Пришлось в текст программы вставить кусок данных. Заработало.

    А в общем, до сих пор помню многие коды из PDP-11.

    Можно купить БК-0010 и пробовать..

    Ещё делал дизассемблер на лету для IBM совместимых компьютеров . Надо было убрать кусок кода, который защиту на Com port проверял. Через T бит после каждой команды выполнялос прерывание, и дизассемблировалась команда. На 386SX работало с приемлемой скоростью.


    1. kpen
      04.01.2025 21:52

      Машинные коды для PDP-11... пультовый режим...
      1000:012737
      1002:000123
      1004:177566

      а теперь набираем 1000 G

      (это печатало символ S на алфавитно-цифровой экран)

      Трава тогда была зеленее... как алфавитно-цифровой монитор ДВК-2)

      Уважаемый Анатолий, не могли бы Вы вкратце поделиться Вашим методом по запихиванию и запуску машинных кодов в RaspberryPi? (если это не секрет).


      1. longtolik
        04.01.2025 21:52

        Еще там была проверка готовности через регистр состояния.
        Коллега делал проверку бита готовности через TSTB и потом ветвление.
        Позже я понял, что надо делать через BPL, так как бит готовности - он же старший в байте, поэтому соответствует знаку.
        Потом, когда RT-11 (ФОДОС) появился, фокус ему показывал
        MOV #123, R0
        EMT O341 // .TTYOUT R0
        EMT O350 // EXIT

        Встраивание данных в код

        Кусок программы от Peter Lemon на ассемблере
        Инициализация Frame Buffer
        ...
        FB_Init:
        mov w0,FB_STRUCT + MAIL_TAGS
        mov x1,MAIL_BASE
        orr x1,x1,PERIPHERAL_BASE
        str w0,[x1,MAIL_WRITE + MAIL_TAGS] ; Mail Box Write

        ldr w0,[FB_POINTER] ; W0 = Frame Buffer Pointer
        cbz w0,FB_Init ; IF (Frame Buffer Pointer == Zero) Re-Initialize Frame Buffer
        ...

        Он транслируется в FASM, но выдает ошибки в GCC aarch64-linux-gnu

        Поэтому сначала я вставлял машинные коды нетранслируемых команд как директивы .word ...
        а после преобразовал текст программы.
        ...
        FB_Init:
        mov w0, FB_STRUCT+MAIL_TAGS
        .word 0x5282a900 // альтеративно - такая команда

        mov	x1, MAIL_BASE
        orr	x1,x1, PERIPHERAL_BASE
        str	w0, [x1,MAIL_WRITE + MAIL_TAGS]	// Mail Box Write
        
        adr	x1, FB_POINTER		//.word 0x18000700 //ldr w0,[FB_POINTER] // W0 = Frame Buffer Pointer []
        ldr	w0, [x1]					// Store Frame Buffer Pointer Physical Address
        cbz	w0, FB_Init				// IF (Frame Buffer Pointer == Zero) Re-Initialize Frame Buffer
        
        and	w0, w0,0x3FFFFFFF	// Convert Mail Box Frame Buffer Pointer From BUS Address To Physical Address ($CXXXXXXX -> $3XXXXXXX)
        adr	x1, FB_POINTER		// already there
        str	w0, [x1]					// Store Frame Buffer Pointer Physical Address
        

        ...

        Дизассемблер:
        

        0000000000000094 :
        94: 5282a900 mov w0, #0x1548 // #5448
        98: 5282a900 .word 0x5282a900
        9c: d2971001 mov x1, #0xb880 // #47232
        a0: b2681421 orr x1, x1, #0x3f000000
        a4: b9002820 str w0, [x1, #40]
        a8: 1000a8a1 adr x1, 15bc
        ac: b9400020 ldr w0, [x1]
        b0: 34ffff20 cbz w0, 94
        b4: 12007400 and w0, w0, #0x3fffffff
        b8: 1000a821 adr x1, 15bc
        bc: b9000020 str w0, [x1]
        c0: b26a03ff mov sp, #0x400000 // #4194304
        c4: 14001682 b 5acc

        Две первые строки 
        mov	w0, FB_STRUCT+MAIL_TAGS
        .word 0x5282a900
        
        выдают один и тот же машинный код
        94:	5282a900 	...
        98:	5282a900 	...
        

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

        Тут нужно смотреть, чтобы изменения кода происходили, не мешая кешу команд и данных.

        Самомодифицирующиеся программы существуют уже давно.

        Про Raspberry Pi
        Пробовал на 3+A и B, 2W - все с процессором A53. Четыре ядра работают - это позволяет на каждом выполнять свою задачу без переключений. С Raspberry Pi 5 на голом железе пока не пробовал - там чип ввода-вывода поставили.


  1. jobless
    04.01.2025 21:52

    Это самый удобный отладчик в моей жизни. Система команд и адреса устройств в восьмеричном виде на ОДНОМ листе А4(фото бумага). Первая моя программа которую кто то использовал кроме меня тестировала перфокарты. Перфораторы старые и не все дырки качественно пробивались (помимо просто ошибок). Предыдущая не моя программа печатала номера плохих карт и их в ручную нужно было подсчитывать в колоде. А моя позволяла остановить чтение на ошибке проверить карту и вернуть на чтение в карман устройства.


  1. nixtonixto
    04.01.2025 21:52

    Начало нулевых, программировал AT89C2051 без компьютера (не было), самодельным программатором на базе двоичных счётчиков, генераторов-одновибраторов, 28С еепром и параллельной ОЗУ. Через ОЗУ переставлял куски программы в еепром, корректировал и вводил код через сдвиговой регистр и 2 кнопки (CLK и DATA). Вначале рисовал алгоритм, потом переводил в машинные коды и вводил в память программатора.


  1. alabamaa
    04.01.2025 21:52

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


    1. xverizex Автор
      04.01.2025 21:52

      Я её читаю, чтобы усвоить базу, узнать какая защита была раньше. Это же знания.


  1. 4m3l
    04.01.2025 21:52

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

    Вы не думали на FPGA реализовать свою архитектуру и играть с ней?


    1. xverizex Автор
      04.01.2025 21:52

      Вы не думали на FPGA реализовать свою архитектуру и играть с ней?

      Я думал об этом, я хочу электронику изучить, но мне сложно себя организовать, так как я не знаю как правильно учиться электронике. А так, да, можно было бы сделать такое. Может в будущем сделаю, если обрету эти священные знания по электронике.

      Спасибо, что своим вопросом вдохновляете.
      Да и все присутствующие в комментариях очень порадовали меня, мотивация очень сильная, я рад, что ностальгия есть у людей по тем временам.