SPICE86 es una herramienta para ejecutar, ingeniería inversa y reescribir programas DOS de modo real para los cuales el código fuente no está disponible.
El lanzamiento está disponible en Nuget.
Los prelabastamientos también están disponibles en la página de lanzamiento
Nota: Este es un puerto y una continuación del Java Spice86 original.
Requiere .NET 9 y se ejecuta en Windows, MacOS y Linux.
Reescribir un programa de solo el binario es una tarea difícil.
SPICE86 es una herramienta que lo ayuda a hacerlo con un enfoque metódico de división y conquistar.
Proceso general:
Este es un programa .NET, lo ejecuta con la línea de comando regular o la ejecución de Dotnet. Ejemplo con la ejecución de un programa llamado File.exe:
Spice86 -e file.exe
Los archivos COM y los archivos BIOS también son compatibles.
Se recomienda establecer la variable de entorno SPICE86_DUMPS_Folder que apunte a dónde el emulador debe descargar los datos de tiempo de ejecución. Si se establece la variable o si se pasa el parámetro de registro de Datirectorio, el emulador descargará un montón de información sobre la ejecución allí. Si no se establece nada, los datos se descartarán en el directorio actual. Si ya hay datos allí, el emulador lo cargará primero y lo completará, ¡no necesita comenzar desde cero cada vez!
--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 habla el protocolo remoto GDB:
Debe especificar un puerto para que el servidor GDB comience al iniciar SPICE86:
Spice86 --GdbPort=10000
SPICE86 esperará a que GDB se conecte antes de comenzar la ejecución para que pueda configurar los puntos de interrupción, etc.
Aquí está cómo conectarse desde el cliente de línea de comandos GDB y cómo establecer la arquitectura:
(gdb) target remote localhost:10000
(gdb) set architecture i8086
Puede agregar puntos de interrupción, pasar, ver la memoria, etc.
Ejemplo con un punto de interrupción en VGA VRAM escribe:
(gdb) watch *0xA0000
Asamblea de visualización:
(gdb) layout asm
Eliminar un punto de interrupción:
(gdb) remove 1
Buscando una secuencia de bytes en la memoria (dirección de inicio 0, longitud F0000, bytes ASCII de la cadena 'Spice86'):
(gdb) find /b 0x0, 0xF0000, 0x53, 0x70, 0x69, 0x63, 0x65, 0x38, 0x36
GDB no admite el direccionamiento segmentado del modo real X86, por lo que los punteros deben referirse a la dirección física real en la memoria. VRAM en la dirección A000: 0000 sería 0xa0000 en GDB.
Del mismo modo, la variable de $ PC en GDB estará expuesta por SPICE86 como la dirección física señalada por CS: IP.
La lista de comandos personalizados se puede mostrar así:
(gdb) monitor help
(gdb) monitor dumpall
Dobla todo lo que se describe a continuación de una sola vez. Los archivos se crean en la carpeta de volcado como se explica aquí varios archivos se producen:
Romper después de x ciclos de CPU emulados:
(gdb) monitor breakCycles 1000
Romper al final del programa emulado:
(gdb) monitor breakStop
#Crefreshing screen o buffers mientras depuración
(gdb) monitor vbuffer refresh
Para una experiencia agradable y productiva con GDB, el cliente SEERGDB es muy recomendable.
Ejemplo concreto con crio dune aquí.
Primero ejecute su programa y asegúrese de que todo funcione bien en SPICE86. Si encuentra problemas, podría deberse a funciones de hardware / DOS / BIOS no implementadas.
Cuando SPICE86 sale, debe volcar los datos en la carpeta actual o en la carpeta especificada por la variable ENV
Abra los datos en Ghidra con el Spice86-Ghidra-Plugin y genere código. Puede importar los archivos generados en un proyecto de plantilla que genere a través de las placas de spice86-dotnet:
dotnet new spice86.project
Puede proporcionar su propio código C# para anular el código de ensamblaje original del programa.
Spice86 puede tomar una instancia de entrada de Spice86.core.emulator.function.ioverridesupplier que construye una asignación entre la dirección de memoria de las funciones y sus anulaciones de C#.
Para obtener un ejemplo completo, puede verificar el código fuente de criogénico.
Aquí hay un simple ejemplo de cómo se vería:
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 ) ;
}
}Recuerde : debe decirle a SPICE86 que use su código de ensamblaje anulaciones con el argumento de la línea de comandos "--useCodeOverride True" al depurar su proyecto.
Junto con el camino obligatorio a su programa DOS, aprobado con el argumento -Exepath.
SPICE86 viene con un depurador incorporado que se puede utilizar para depurar el programa emulado. Es un depurador simple que le permite inspeccionar la memoria, el desmontaje, los registros y la pila.
El visor de estructura le permite inspeccionar la memoria de manera estructurada. Es útil inspeccionar la memoria como una estructura, como DOS PSP, el DOS MCB, los registros VGA, etc.
Primero necesita un archivo de encabezado C que describa las estructuras en la aplicación. Puedes generar uno con Ghidra o Ida. Luego puede cargarlo con el argumento --StructureFile Commandline. Esto habilitará el botón "Vista de estructura" en la pestaña Memoria del depurador.
Allí ingresa a un segmento: Offset Dirección y elija la estructura que desea ver. La estructura se mostrará en una vista de árbol y la memoria en una vista hexadecimal.
La pantalla se actualiza cada vez que la aplicación se detiene, por lo que puede atravesar el programa y ver cómo cambia la estructura. Exportar un nuevo archivo de encabezado C de Ghidra o IDA también actualizará el visor de estructura con la nueva información en tiempo real.
También puede ingresar la vista de estructura seleccionando un rango de bytes en la pestaña de memoria y hacer clic con el botón derecho en ella.
Es posible proporcionar una unidad C: para las funciones de DOS emuladas con la opción - -cdrive . El valor predeterminado es la carpeta actual. Para algunos juegos, es posible que deba establecer la unidad C en la carpeta del juego.
Puede aprobar argumentos (¡MAX 127 Chars!) Al programa emulado con la opción : EXELETGS . El valor predeterminado está vacío.
El hardware de temporizador emulado de la PC (Intel 8259) admite el tiempo de medición desde cualquiera:
La pantalla se actualiza 30 veces por segundo y cada vez que se detecta una espera de retroceso VGA (ver Renderer.cs).
UPC:
Memoria:
Gráficos:
DOS:
Aporte:
CD-ROM:
Sonido:
En los sistemas *nix, necesitará tener instalado Libportudio. Sin ella, no habrá sonido.
Lista de compatibilidad disponible aquí.
dotnet build Spice86 -e < path to executable >o use esto donde se encuentra Spice86.csproj:
dotnet run -e < path to executable >Esto usa Ghidra y Java 17.
Antes de usarlo, defina una variable de entorno llamada SPICE86_DUMPS_Folder apuntando a una carpeta donde se encuentran los volcados SPICE86. Se generan en salida.
Procedimiento general, en orden:
1.Ghidra El propio script 'importsymbolscript.py' (la entrada utilizada es "Spice86dumpghidrasymbols.txt")
2.Ghidra's Auto-Analyze (solo habilite 'desasimar puntos de entrada')
3. Ahora, puede usar el complemento.
Recuerde: si Ghidra muestra subrutinas, use la tecla 'F' para convertirlas en funciones. El generador de código solo funciona con funciones.
Además, si tiene algún comportamiento extraño, asegúrese de tener Java 17 y solo Java 17. Así es como le gusta a Ghidra.
Crio dune:
La interfaz de usuario está impulsada por Avalonia UI.
