Sunday, October 25, 2026

Spice86 for reverse engineering and rewriting real mode DOS binaries

Spice86 for reverse engineering and rewriting real mode DOS binaries

Spice86 is a cross-platform PC emulator that executes real mode DOS programs, records their control flow, and generates equivalent C# code for authorized reverse engineering workflows.

ToolOpenRakis/Spice86 — a real mode DOS emulator that reverse engineers binaries and generates runnable C# rewrites
CategoryEmulation-assisted reverse engineering and code translation
Primary UseAnalyzing legacy DOS executables without source: emulating them, building a CFG, and generating C# override projects for gradual rewriting
Safe UseAuthorized analysis of software you own or are licensed to audit: legacy game preservation, in-house binary modernization, and educational study of x86 real mode programs in lab environments
Telemetry NotePurely local tool: writes dumps (spice86dumpMemoryDump.bin, spice86dumpExecutionFlow.json, GeneratedProject/) keyed by program SHA-256 to a local directory; defenders observing it see only local file artifacts, no network egress beyond an optional local GDB/MCP port

Spice86, hosted at OpenRakis/Spice86, is a C#/.NET PC emulator built around a very specific mission: executing, understanding, and ultimately rewriting real mode DOS programs for which no source code exists. It is a port and continuation of an earlier Java project (kevinferrare/spice86), now rewritten on .NET 10 and distributed via NuGet, with pre-releases on the GitHub release page. It runs on Windows, macOS, and Linux under the Apache-2.0 license, which makes it approachable for both commercial legacy-migration work and hobbyist preservation projects. The repository currently sits at 665 stars, respectable for a tool in the niche of DOS binary decompilation.

What distinguishes Spice86 from a general-purpose emulator like DOSBox is its divide-and-conquer methodology. The workflow starts by simply running the target: Spice86 -e file.exe for MZ executables, -e file.com for COM files, -e file.bat for batch files, and even -e file.bin for raw BIOS images. As the program executes, the emulator instruments every taken branch and incrementally builds a Control Flow Graph (CFG) of the code paths actually exercised. At the end of each run it dumps runtime artifacts — a full memory snapshot, the execution flow, the block graph, and derived function partitions — which then feed automatic C# code generation.

The killer feature is the last step of that pipeline: from the explored CFG, Spice86 directly generates a self-contained, runnable C# project (GeneratedProject/ in the dump directory, plus spice86dumpCfgGeneratedOverrides.cs) that overrides the original assembly with mechanically translated C# equivalents. You end up with a fully working version of the program where each emulated function has a one-to-one C# counterpart, and you then rewrite those counterparts function by function, replacing mechanical translation with readable, intentional code. The README even notes that an LLM can assist at this stage, since short assembly sequences are generally easy to lift into a higher-level language.

The engineering rationale behind this approach is sound and worth appreciating. Because you always operate on a working program, regressions are caught immediately rather than after a big-bang rewrite. Because rewriting proceeds function by function, you recover the original author's intent incrementally instead of trying to absorb the whole binary at once. And because the CFG records only executed paths, the generated code reflects actual program behavior rather than a speculative static guess — a pragmatic hedge against the classic incompleteness of static analysis on obfuscated or data-driven code.

Dump management is deliberately deterministic. The base dump location is controlled either by the SPICE86_DUMPS_FOLDER environment variable or the --RecordedDataDirectory flag, defaulting to the current directory. Regardless of where dumps go, they are always placed in a subdirectory named after the program's SHA-256 hash, so multiple executables from the same title (the README gives SETUP.EXE and GAME.EXE as examples) keep isolated, mergeable state. On subsequent runs the emulator loads prior dumps first and complements them, and the CFG instruction graph (spice86dumpCfgReload.json) is reloaded at startup by default (--ReloadCfgGraph false to disable) so exploration is cumulative across sessions.

The dump set is well thought out for downstream tooling. spice86dumpMemoryDump.bin snapshots the real mode address space; spice86dumpExecutionFlow.json carries function addresses, labels, and executed instructions; spice86dumpCfgBlocks.json and spice86dumpCfgPartitions.json encode the block graph and recovered function partitions; and spice86dumpGhidraSymbols.txt provides Ghidra-compatible symbols. There is a dedicated companion project, OpenRakis/spice86-ghidra-plugin, that imports the memory dump into Ghidra for deeper static analysis — though the README is careful to stress this is now optional and simply an extra analysis aid, since C# override generation no longer depends on it.

For dynamic debugging, Spice86 ships a GDB server (-g/--GdbPort, default 10000, 0 disables it) plus an MCP HTTP server on port 8081 (--mcp-http-port), the latter suggesting the project is positioning itself for LLM-assisted analysis workflows. There is also a CPU heavy logging mode (--CpuHeavyLog) that records every executed instruction as SegmentedAddress InstructionString lines to a configurable file. The warning about significant performance impact is honest — this is a per-instruction trace — but the --CpuHeavyLogExpressions option is genuinely clever: named expressions such as life=AX+1 or myvalue=word ptr DS:[0x456] are appended to every log line, giving you a time-series view of arbitrary program state as execution proceeds. That is effectively a poor man's domain-specific taint view, ideal for reverse engineering game variables.

Hardware fidelity is broader than the average hobby emulator. The command line exposes --Xms (15 MB of XMS memory, default on), --Ems (8 MB via the EMM Page Frame), --A20Gate for programs relying on HMA address wraparound, -p/--ProgramEntryPointSegment with proper DOS PSP and MCB construction, and an extensive Sound Blaster and OPL/Adlib Gold configuration surface (SbType spanning SB1 through Sb16, GameBlaster, AdlibGold, plus SbIrq, SbSma, SbHdma lines). -m/--Mt32RomsPath accepts MT-32 ROM sets. There is even -c/--CDrive to mount a host directory as the emulated C drive, -x/--ExpectedChecksum to pin the exact binary under analysis, and -f/--FailOnUnhandledPort to detect gaps in hardware emulation rather than silently continuing.

Determinism support is a differentiator that serious reverse engineers will notice. --ClockStartTime fixes the emulated UTC start time, --ClockJitterSeed introduces controlled ±0.01 ms clock jitter, -i/--InstructionTimeScale switches the timer from wall-clock to instruction-count based, -t/--TimeMultiplier accelerates or slows execution, and --RenderingMode Sync fires VGA events on the emulation thread for reproducible runs. Combined with --JitMode (InterpretedThenCompiled by default), these options indicate a codebase designed for repeatable experiments, not just nostalgic play. The --StructureFile option, accepting a C header describing application structures (best exported from IDA or Ghidra), rounds out a toolchain that interoperates with mainstream RE suites rather than competing with them.

One subtle behavior deserves attention when planning a workflow: when code overrides are active (--UseCodeOverride true with an --OverrideSupplierClassName), the execution flow and CFG dumps are not written, because overrides replace the emulated assembly and the recorded graph would jump between native code and overrides. This is the correct design decision — a mixed graph would be misleading — but it means you should complete your exploration passes before wiring in overrides, and the README's cumulative reload mechanism makes that iterative loop natural.

In terms of authorized application, Spice86 is squarely a defensive and educational instrument: software archaeology on binaries you own or are licensed to examine, preservation of abandonware where rights permit, modernization of legacy in-house DOS tooling into maintainable C#, and structured study of x86 real mode execution. It attacks no systems, touches no network beyond local debug ports, and its entire output artifact set lives in hash-keyed local directories. For anyone facing a 1990s MZ executable with no source and a mandate to keep it alive, Spice86 currently offers the most methodical path from black-box binary to readable high-level code — install via NuGet (dotnet tool install Spice86 style consumption or the package page) and start with a simple Spice86 -e program.exe run to see the first CFG take shape.

Official project repository for OpenRakis/Spice86.
Download Tool

Educational analysis for authorized security professionals. Use only in controlled, authorized environments.

Share articleFacebookXLinkedIn

Continue exploring

Browse all articles →

0 comentários:

Post a Comment

Note: Only a member of this blog may post a comment.