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提供動力。
