SPICE86は、ソースコードが利用できないリアルモードDOSプログラムを実行、リバースエンジニアリング、書き換えるツールです。
Nugetでリリースを利用できます。
リリース前のページもご利用いただけます
注:これはポートであり、元のJava Spice86からの継続です。
.NET 9が必要で、Windows、MacOS、Linuxで実行されます。
バイナリのみからプログラムを書き換えることは難しいタスクです。
SPICE86は、整然とした格差と征服のアプローチでそうするのに役立つツールです。
一般的なプロセス:
これは.NETプログラムであり、通常のコマンドラインまたはdotNet実行で実行します。 file.exeというプログラムを実行する例:
Spice86 -e file.exe
comファイルとBIOSファイルもサポートされています。
SPICE86_DUMPS_FOLDER環境変数を設定することをお勧めします。エミュレータがランタイムデータをダンプする場所を指します。変数が設定されている場合、または-RecordedDataDirectoryパラメーターが渡される場合、エミュレータはそこでの実行に関する多くの情報をダンプします。何も設定されていない場合、データは現在のディレクトリに捨てられます。すでにデータがある場合は、エミュレータが最初にロードして完了します。毎回ゼロから開始する必要はありません。
--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変数は、CS:IPで指摘されている物理アドレスとしてSPICE86によって公開されます。
カスタムコマンドのリストは、次のように表示できます。
(gdb) monitor help
(gdb) monitor dumpall
以下で説明したすべてを1発のショットで捨てます。ここで説明されているように、ファイルはダンプフォルダーに作成されます。いくつかのファイルが生成されます。
xエミュレートされたCPUサイクルの後に壊れます:
(gdb) monitor breakCycles 1000
エミュレートプログラムの終わりに壊れます:
(gdb) monitor breakStop
#デバッグ中の画面またはバッファーを再表現します
(gdb) monitor vbuffer refresh
GDBで心地よく生産的な体験をするには、SEERGDBクライアントを強くお勧めします。
Cryo Duneの具体的な例はこちら。
最初にプログラムを実行し、Spice86ですべてが正常に機能することを確認してください。問題が発生した場合、それは不実装のハードウェア / DOS / BIOS機能によるものである可能性があります。
SPICE86が終了すると、現在のフォルダーまたはenv変数で指定されたフォルダーにデータをダンプする必要があります
Spice86-Ghidra-Pluginを使用してGhidraのデータを開き、コードを生成します。 SPICE86-DOTNET-TEMPLATESを介して生成するテンプレートプロジェクトに生成されたファイルをインポートできます。
dotnet new spice86.project
独自のC#コードを提供して、プログラムの元のアセンブリコードをオーバーライドできます。
SPICE86は、関数のメモリアドレスとそのC#オーバーライドの間にマッピングを構築するSpice86.core.emulator.function.ioverridesupplierのインスタンスを入力できます。
完全な例については、極低温のソースコードを確認できます。
これがどのように見えるかの簡単な例です。
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ヘッダーファイルをエクスポートすると、新しい情報リアルタイムで構造ビューアーを更新します。
また、[メモリ]タブでバイトの範囲を選択して右クリックすることにより、構造ビューを入力することもできます。
オプション(CDRIVE)を使用して、エミュレートされたDOS関数用のC:ドライブを提供することができます。デフォルトは現在のフォルダーです。一部のゲームでは、Cドライブをゲームフォルダーに設定する必要がある場合があります。
引数(Max 127 Chars!)をオプション-exeargsを使用してエミュレートプログラムに渡すことができます。デフォルトは空です。
PCのエミュレートタイムハードウェア(Intel 8259)は、次のいずれかからの測定時間をサポートしています。
画面は1秒あたり30回更新され、VGAリトレースが待機するたびに検出されるたびに更新されます(renderer.csを参照)。
CPU:
メモリ:
グラフィック:
DOS:
入力:
CD-ROM:
音:
*nixシステムでは、Libportaudioをインストールする必要があります。それがなければ、音はありません。
互換性リストはこちら。
dotnet build Spice86 -e < path to executable >または、これを使用して、Spice86.csprojが配置されています。
dotnet run -e < path to executable >これはGhidraとJava 17を使用します。
使用する前に、Spice86ダンプがあるフォルダーを指すSpice86_dumps_folderという名前の環境変数を定義します。それらは出口で生成されます。
一般的な手順、順番:
1.Ghidra独自のスクリプト「Importsymbolscript.py」(使用される入力は「Spice86DumpGhidrasymbols.txt」です)
2.GhidraのAuto-analyze(「解散エントリポイント」のみを有効にする)
3.今、プラグインを使用できます。
覚えておいてください:Ghidraがサブルーチンを表示する場合、「F」キーを使用して機能に変換します。コードジェネレーターは関数でのみ動作します。
また、奇妙な動作がある場合は、Java 17とJava 17のみを持っていることを確認してください。それがGhidraがそれを好む方法です。
Cryo Dune:
UIはAvalonia UIを搭載しています。
