SPICE86 - это инструмент для выполнения, реверс -инженера и переписать программы DOS Real Mode, для которых исходный код недоступен.
Выпуск доступен на Nuget.
Предварительные передачи также доступны на странице выпуска
Примечание: это порт, и продолжение оригинального Java Spice86.
Требуется .NET 9 и работает на Windows, MacOS и Linux.
Переписывание программы только из бинарника - сложная задача.
Spice86 - это инструмент, который помогает вам сделать это с помощью методического разделения и подхода.
Общий процесс:
Это программа .NET, вы запускаете ее с помощью обычной командной строки или Dotnet Run. Пример с запуском программы с именем file.exe:
Spice86 -e file.exe
COM -файлы и файлы BIOS также поддерживаются.
Рекомендуется установить переменную среды spice86_dumps_folder, указывающая на то, где эмулятор должен сбрасывать данные времени выполнения. Если переменная установлена или если пройден параметр -если -зарегистрированный параметр, эмулятор сбросит кучу информации о запуске там. Если ничего не установлено, данные будут сброшены в текущий каталог. Если там уже есть данные, эмулятор будет загружать его первым и завершить, вам не нужно начинать с нуля каждый раз!
--Ems (Default: false) Enables EMS memory. EMS adds 8 MB of memory accessible to DOS programs through the EMM Page Frame.
--A20Gate (Default: false) Disables the 20th address line to support programs relying on the rollover of memory addresses above the HMA (slightly above 1 MB).
-m, --Mt32RomsPath Zip file or directory containing the MT-32 ROM files
-c, --CDrive Path to C drive, default is exe parent
-r, --RecordedDataDirectory Directory to dump data to when not specified otherwise. Working directory if blank
-e, --Exe Required. Path to executable
-a, --ExeArgs List of parameters to give to the emulated program
-x, --ExpectedChecksum Hexadecimal string representing the expected SHA256 checksum of the emulated program
-f, --FailOnUnhandledPort (Default: false) If true, will fail when encountering an unhandled IO port. Useful to check for unimplemented hardware. false by default.
-g, --GdbPort gdb port, if empty gdb server will not be created. If not empty, application will pause until gdb connects
-o, --OverrideSupplierClassName Name of a class that will generate the initial function information. See documentation for more information.
-p, --ProgramEntryPointSegment (Default: 4096) Segment where to load the program. DOS PSP and MCB will be created before it.
-u, --UseCodeOverride (Default: true) <true or false> if false it will use the names provided by overrideSupplierClassName but not the code
-i, --InstructionsPerSecond <number of instructions that have to be executed by the emulator to consider a second passed> if blank will use time based timer.
-t, --TimeMultiplier (Default: 1) <time multiplier> if >1 will go faster, if <1 will go slower.
-d, --DumpDataOnExit (Default: true) When true, records data at runtime and dumps them at exit time
-h, --HeadlessMode (Default: false) Headless mode. If true, no GUI is shown.
-l, --VerboseLogs (Default: false) Enable verbose level logs
-w, --WarningLogs (Default: false) Enable warning level logs
-s, --SilencedLogs (Default: false) Disable all logs
-i, --InitializeDOS (Default: true) Install DOS interrupt vectors or not.
--StructureFile Path to a C header file that describes the structures in the application. Works best with exports from IDA or Ghidra
--help Display this help screen.
--version Display version information.
Spice86 говорит о протоколе удаленного GDB:
Вы должны указать порт для Server GDB, чтобы запустить при запуске Spice86:
Spice86 --GdbPort=10000
Spice86 будет ждать, пока GDB подключится перед началом выполнения, чтобы вы могли настроить точки останова и так далее.
Вот как подключиться от клиента командной строки GDB и как установить архитектуру:
(gdb) target remote localhost:10000
(gdb) set architecture i8086
Вы можете добавить точки останова, шаг, просмотреть память и так далее.
Пример с точкой перерыва на VGA VRAM пишет:
(gdb) watch *0xA0000
Просмотр сборки:
(gdb) layout asm
Удаление точки останова:
(gdb) remove 1
Поиск последовательности байтов в памяти (начальный адрес 0, длина F0000, байты ASCII из строки Spice86 '):
(gdb) find /b 0x0, 0xF0000, 0x53, 0x70, 0x69, 0x63, 0x65, 0x38, 0x36
GDB не поддерживает x86 реального режима сегментированной адресации, поэтому указатели должны ссылаться на фактический физический адрес в памяти. VRAM по адресу A000: 0000 будет 0XA0000 в GDB.
Аналогичным образом, переменная $ PC в GDB будет выявлена Spice86 как физический адрес, указанный CS: IP.
Список пользовательских команд можно отобразить так:
(gdb) monitor help
(gdb) monitor dumpall
Сбрасывает все, что описано ниже, за один выстрел. Файлы создаются в папке дампа, как объяснено здесь несколько файлов:
Перерыв после x и эмулированных циклов процессора:
(gdb) monitor breakCycles 1000
Перерыв в конце эмулированной программы:
(gdb) monitor breakStop
#REFRESHING ECREE или BUFFERS во время отладки
(gdb) monitor vbuffer refresh
Для приятного и продуктивного опыта работы с GDB, клиент SEERGDB настоятельно рекомендуется.
Конкретный пример с Cryo Dune здесь.
Сначала запустите свою программу и убедитесь, что все работает нормально в Spice86. Если вы столкнетесь с проблемами, это может быть связано с невыполненными функциями оборудования / DOS / BIOS.
Когда Spice86 выходит, он должен сбрасывать данные в текущую папку или в папку, указанную переменной env
Откройте данные в Ghidra с помощью Spice86-Ghidra-Plugin и генерируйте код. Вы можете импортировать сгенерированные файлы в шаблонном проекте, который вы генерируете через Spice86-Dotnet-Templates:
dotnet new spice86.project
Вы можете предоставить свой собственный код C# для переопределения исходного кода сборки программы.
Spice86 может принять вход.
Для полного примера вы можете проверить исходный код криогенного.
Вот простой пример того, как это будет выглядеть:
namespace My . Program ;
// This class is responsible for providing the overrides to spice86.
// There is only one per program you reimplement.
public class MyProgramOverrideSupplier : IOverrideSupplier {
public IDictionary < SegmentedAddress , FunctionInformation > GenerateFunctionInformations ( int programStartSegment ,
Machine machine ) {
Dictionary < SegmentedAddress , FunctionInformation > res = new ( ) ;
// In more complex examples, overrides may call each other
new MyOverrides ( res , programStartSegment , machine ) ;
return res ;
}
public override string ToString ( ) {
return "Overrides My program exe. class is " + GetType ( ) . FullName ;
}
}
// This class contains the actual overrides. As the project grows, you will probably need to split the reverse engineered code in several classes.
public class MyOverrides : CSharpOverrideHelper {
private MyOverridesGlobalsOnDs globalsOnDs ;
public MyOverrides ( IDictionary < SegmentedAddress , FunctionInformation > functionInformations , int segment , Machine machine ) {
// "myOverides" is a prefix that will be appended to all the function names defined in this class
base ( functionInformations , "myOverides" , machine ) ;
globalsOnDs = new MyOverridesGlobalsOnDs ( machine ) ;
// incUnknown47A8_0x1ED_0xA1E8_0xC0B8 will get executed instead of the assembly code when a call to 1ED:A1E8 is performed.
// Also when dumping functions, the name myOverides.incUnknown47A8 or instead of unknown
// Note: the segment is provided in parameter as spice86 can load executables in different places depending on the configuration
DefineFunction ( segment , 0xA1E8 , "incDialogueCount47A8" , IncDialogueCount47A8_0x1ED_0xA1E8_0xC0B8 ) ;
DefineFunction ( segment , 0x0100 , "addOneToAX" , AddOneToAX_0x1ED_0x100_0x1FD0 ) ;
}
public Action IncDialogueCount47A8_0x1ED_0xA1E8_0xC0B8 ( ) {
// Accessing the memory via accessors
globalsOnDs . SetDialogueCount47A8 ( globalsOnDs . GetDialogueCount47A8 ( ) + 1 ) ;
// Depends on the actual return instruction performed by the function, needed to be called from the emulated code as
// some programs like to mess with the stack ...
return NearRet ( ) ;
}
private Action AddOneToAX_0x1ED_0x100_0x1FD0 ( ) {
// Assembly for this would be
// INC AX
// RETF
// Note that you can access the whole emulator to change the state in the overrides.
state . AX ++ ;
return NearRet ( ) ;
}
}
// Memory accesses can be encapsulated into classes like this to give names to addresses and make the code shorter.
public class MyOverridesGlobalsOnDs : MemoryBasedDataStructureWithDsBaseAddress {
public DialoguesGlobalsOnDs ( Machine machine ) {
base ( machine ) ;
}
public void SetDialogueCount47A8 ( int value ) {
this . SetUint8 ( 0x47A8 , value ) ;
}
public int GetDialogueCount47A8 ( ) {
return this . GetUint8 ( 0x47A8 ) ;
}
}Помните : вы должны сообщить Spice86 использовать переопределения кода сборки с помощью аргумента командной строки "-Usecodeoverride true" при отладке вашего проекта.
Наряду с обязательным путем к вашей программе DOS, прошел с аргументом -Exepath.
Spice86 поставляется со встроенным отладчиком, который можно использовать для отладки эмулированной программы. Это простой отладчик, который позволяет вам проверять память, разборку, регистры и стек.
Структура просмотра позволяет вам осматривать память структурированным образом. Полезно проверять память как структуру, такую как DOS PSP, DOS MCB, регистры VGA и т. Д.
Сначала вам нужен файл заголовка C, который описывает структуры в приложении. Вы можете генерировать один с Ghidra или IDA. Затем вы можете загрузить его с помощью аргумента командной строки --StructureFile . Это позволит кнопку «Просмотр структуры» на вкладке памяти отладчика.
Там вы вводите сегмент: Adfset Adder и выбираете структуру, которую хотите просмотреть. Структура будет отображаться в представлении дерева и памяти в шестнадцатеричном представлении.
Дисплей обновляется всякий раз, когда приложение приостанавливается, поэтому вы можете пройти через программу и посмотреть, как меняется структура. Экспорт нового файла заголовка C из Ghidra или IDA также обновит зрителя структуры новой информацией в режиме реального времени.
Вы также можете ввести представление структуры, выбрав диапазон байтов на вкладке памяти и щелкнув правой к ней правой кнопку.
Можно предоставить C: диск для эмулированных функций DOS с опцией -CDRIVE . По умолчанию текущая папка. Для некоторых игр вам может потребоваться установить диск C в папку игры.
Вы можете передать аргументы (макс 127 chars!) К эмулированной программе с опцией -Exeargs . По умолчанию пусто.
Эмулированное аппаратное обеспечение таймера ПК (Intel 8259) поддерживает измерение времени из любого:
Экран обновляется 30 раз в секунду, и каждый раз, когда обнаруживается проведенное ожидание VGA (см. Renderer.cs).
ПРОЦЕССОР:
Память:
Графика:
DOS:
Вход:
CD-ROM:
Звук:
На *NIX Systems вам нужно будет установить Libportaudio. Без этого звука не будет.
Список совместимости доступен здесь.
dotnet build Spice86 -e < path to executable >или используйте это там, где находится Spice86.csproj:
dotnet run -e < path to executable >Это использует Ghidra и Java 17.
Прежде чем использовать его, определите переменную среды с именем spice86_dumps_folder, указывающая на папку, где расположены дамбы Spice86. Они генерируются на выходе.
Общая процедура, в порядке:
1. Собственный сценарий Ghidra 'importSymbolscript.py' (используется входом "Spice86dumpghidrasymbols.txt")
2. Автоанализ Гидры (только включает в себя «точки входа»).
3. Теперь вы можете использовать плагин.
Помните: если Ghidra отображает подпрограммы, используйте ключ «F», чтобы преобразовать их в функции. Генератор кода работает только с функциями.
Кроме того, если у вас есть какое -либо странное поведение, убедитесь, что у вас есть Java 17 и только Java 17. Так это нравится Гидре.
Cryo Dune:
Пользовательский интерфейс оснащен Avalonia UI.
