Spice86是执行,反向工程和重写实际模式程序的工具。
Nuget上的发布可用。
发布页面上也可以使用预释放
注意:这是一个端口,是原始Java Spice86的延续。
它需要.NET 9并在Windows,MacOS和Linux上运行。
仅从二进制中重写程序是一项艰巨的任务。
Spice86是一种通过有方法的鸿沟和征服方法来帮助您这样做的工具。
一般过程:
这是一个.NET程序,您可以使用常规命令行或dotnet运行运行。运行一个名为file.exe的程序的示例:
Spice86 -e file.exe
还支持COM文件和BIOS文件。
建议设置Spice86_dumps_folder环境变量,以指向模拟器应在何处转移运行时数据。如果已设置变量或传递 - 记录DatAdaDirectory参数,则模拟器将转换有关在此处运行的一堆信息。如果什么都没有设置,则数据将被丢弃在当前目录中。如果已经存在数据,则模拟器将首先加载并完成它,您无需每次从零开始!
--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远程协议:
您需要在启动Spice86时为GDB服务器指定端口:
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,'spice86'字符串的ASCII字节):
(gdb) find /b 0x0, 0xF0000, 0x53, 0x70, 0x69, 0x63, 0x65, 0x38, 0x36
GDB不支持X86真实模式分割的地址,因此指针需要参考内存中的实际物理地址。地址为A000:0000的VRAM在GDB中为0xa0000。
同样,GDB中的$ PC变量将被Spice86暴露为CS:IP指向的物理地址。
可以这样显示自定义命令的列表:
(gdb) monitor help
(gdb) monitor dumpall
将下面描述的所有内容一次丢弃。文件是在转储文件夹中创建的,如此处解释,该文件已产生几个文件:
X仿真CPU周期后破裂:
(gdb) monitor breakCycles 1000
在模拟程序结束时中断:
(gdb) monitor breakStop
调试时#refreshing屏幕或缓冲区
(gdb) monitor vbuffer refresh
为了在GDB上获得令人愉悦且富有成效的经验,强烈建议使用SEEGRDB客户。
混凝土示例与冷冻沙丘在这里。
首先运行您的程序,并确保在Spice86中一切正常。如果您遇到问题,可能是由于未完成的硬件 / DOS / BIOS功能所致。
当Spice86退出时,它应将数据转储到当前文件夹或Env变量指定的文件夹中
使用Spice86-Ghidra-Plugin打开Ghidra的数据并生成代码。您可以通过Spice86-Dotnet-Templates生成的模板项目中导入生成的文件:
dotnet new spice86.project
您可以提供自己的C#代码来覆盖程序原始装配代码。
Spice86可以输入Spice86.core.emulator.function.ioverridesupplier的输入实例,该实例在函数的内存地址与其C#覆盖之间构建映射。
为了完整的示例,您可以检查低温的源代码。
这是一个简单的示例,说明它的外观:
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命令行加载它。这将在调试器的“内存”选项卡中启用“结构视图”按钮。
在那里您输入一个段:偏移地址,然后选择要查看的结构。该结构将显示在树视图中,并在十六进制视图中显示内存。
每当暂停应用程序时,显示屏会更新,因此您可以逐步浏览程序并查看结构的变化。从Ghidra或IDA导出新的C标头文件还将通过实时更新结构查看器。
您还可以通过在“内存”选项卡中选择一系列字节并右键单击它来输入结构视图。
可以使用选项- 驱动器为模拟DOS函数提供c:驱动器。默认值是当前文件夹。对于某些游戏,您可能需要将C驱动器设置为游戏文件夹。
您可以将参数(最大127个字符!)传递给带有选项的模拟程序。默认值为空。
PC的模拟计时器硬件(Intel 8259)支持两者的测量时间:
屏幕每秒刷新30次,每次检测到VGA回溯等待(请参阅Renderer.cs)。
中央处理器:
记忆:
图形:
DOS:
输入:
CD-ROM:
声音:
在 *nix系统上,您需要安装libportaudio。没有它,就不会有声音。
兼容性列表可在此处提供。
dotnet build Spice86 -e < path to executable >或在spice86.csproj的位置使用此信息:
dotnet run -e < path to executable >这使用Ghidra和Java 17。
在使用它之前,定义一个名为Spice86_dumps_folder指向Spice86转储所在的文件夹的环境变量。它们是在退出时生成的。
一般程序,按顺序:
。
2.Ghidra的自动分析(仅启用“分解入口点”)
3.现在,您可以使用插件。
请记住:如果Ghidra显示子例程,请使用“ F”键将其转换为功能。代码生成器仅适用于函数。
另外,如果您有任何奇怪的行为,请确保您有Java 17和Java 17。这就是Ghidra喜欢的方式。
冷冻沙丘:
UI由Avalonia UI提供动力。
