mirror of
https://github.com/morluto/rea.git
synced 2026-10-12 02:19:24 +00:00
193 lines
9.8 KiB
Markdown
193 lines
9.8 KiB
Markdown
# DOS MZ and COM analysis with Ghidra
|
|
|
|
REA can analyze DOS MZ executables and explicitly selected COM images through the bring-your-own Ghidra adapter.
|
|
The Linux x64 and macOS arm64 verification lanes exercise actual 16-bit disassembly and
|
|
decompilation through the CLI and stdio MCP. Provider admission accepts Ghidra
|
|
12.1.x and that installation's declared JDK range. The verification lane uses
|
|
Ghidra 12.1.4 and JDK 21; no DOS emulator or cross-compiler is required.
|
|
|
|
## Open and inspect
|
|
|
|
```bash
|
|
rea inspect /absolute/path/to/legacy.exe --provider ghidra --json
|
|
rea search /absolute/path/to/legacy.exe entry --kind procedures --provider ghidra --json
|
|
rea instructions /absolute/path/to/legacy.exe entry --provider ghidra --json
|
|
rea function /absolute/path/to/legacy.exe entry --provider ghidra --json
|
|
rea inspect-native-load-image /absolute/path/to/legacy.exe --provider ghidra --json
|
|
rea read-bytes /absolute/path/to/legacy.exe 0x10000 --length 16 --provider ghidra --json
|
|
rea address-to-file-offset /absolute/path/to/legacy.exe 0x10000 --provider ghidra --json
|
|
```
|
|
|
|
Use an observed procedure name or address from discovery when the target does
|
|
not name its entry function `entry`. MCP uses `open_binary`, `list_procedures`,
|
|
`procedure_info`, `read_function_instructions`, and `analyze_function` with the
|
|
same admitted target and provider semantics.
|
|
|
|
The classifier validates MZ page counts, initialized module length, header and
|
|
relocation-table bounds, relocation destinations, and the initialized entry
|
|
point. It distinguishes DOS load-module and relocation bytes from the Windows
|
|
new-header field. PE remains PE; NE, LE, and LX declarations are explicitly
|
|
unsupported. A damaged declared Windows header does not silently become DOS.
|
|
Inventory classification returns `unknown` when its bounded prefix cannot
|
|
establish the format; this is distinct from the complete target admission check.
|
|
|
|
Ghidra imports a private target snapshot with `MzLoader`, language
|
|
`x86:LE:16:Real Mode`, and compiler specification `default`. These settings,
|
|
the fixed Ghidra load segment `0x1000`, and the linear address convention are
|
|
committed in the analysis profile. The handshake rejects language or compiler
|
|
drift. Closing the session deletes the temporary project and snapshot.
|
|
|
|
The profile also commits `function_body_evidence: complete-inclusive-ranges-v1`,
|
|
so snapshots from older length-only Ghidra results do not satisfy the current
|
|
profile.
|
|
|
|
## Headerless COM images
|
|
|
|
COM has no identifying header. Select its interpretation explicitly; neither the
|
|
`.com` suffix nor arbitrary unrecognized bytes are enough to admit it.
|
|
|
|
```bash
|
|
rea inspect /absolute/path/to/legacy.com --target-format dos-com --provider ghidra --json
|
|
rea function /absolute/path/to/legacy.com 0x10100 --target-format dos-com --provider ghidra --json
|
|
rea inspect-native-load-image /absolute/path/to/legacy.com --target-format dos-com --provider ghidra --json
|
|
```
|
|
|
|
```json
|
|
{
|
|
"name": "open_binary",
|
|
"arguments": {
|
|
"path": "/absolute/path/to/legacy.com",
|
|
"format": "dos-com",
|
|
"provider_id": "ghidra"
|
|
}
|
|
}
|
|
```
|
|
|
|
The caller's interpretation applies to the complete file, even if its filename
|
|
suggests another format. Files must contain 1..65280 bytes: the 64 KiB COM segment
|
|
minus the 256-byte PSP prefix. Ghidra uses `BinaryLoader` with real-mode language
|
|
and maps file offset zero at `1000:0100` (linear `0x10100`). A packaged pre-analysis
|
|
script seeds the entry function and CS/DS/ES/SS context `0x1000`. These settings
|
|
and the preparation policy are committed in the analysis profile; a failed
|
|
preparation prevents the resident bridge from serving results.
|
|
|
|
Load-image verification checks all original and modified file bytes, complete
|
|
source mapping/memory digests, absence of relocations, entry and measured register
|
|
context. The context is imposed for analysis, not observed from execution. PSP
|
|
memory, initial stack contents/SP, DOS interrupts and PC-98 devices are not
|
|
modeled. COM has no MZ header, relocation table or appended-overlay model; all
|
|
selected bytes belong to its analysis module. The original file stays unchanged.
|
|
|
|
## Addresses and function extent
|
|
|
|
Returned default-space addresses are linear byte coordinates. A Ghidra
|
|
segment:offset address has linear value `segment * 16 + offset`; multiple
|
|
segment aliases can identify the same byte. REA accepts the returned linear
|
|
address for subsequent lookups and leaves instruction decoding context with
|
|
Ghidra. These coordinates are analysis load addresses, not file offsets or
|
|
observations of a running DOS system.
|
|
|
|
Offsets that Ghidra would truncate to a different address are rejected before
|
|
reading or annotating a function. This also applies to explicit or encoded
|
|
address-space names; leading zeros preserve the numeric offset.
|
|
|
|
`procedure_info` and function dossiers include `body` evidence:
|
|
|
|
```json
|
|
{
|
|
"available": true,
|
|
"provenance": "ghidra-function-body-address-set",
|
|
"ranges": [
|
|
{ "start": "0x10000", "end": "0x1000d" },
|
|
{ "start": "0x10060", "end": "0x10065" }
|
|
],
|
|
"total_bytes": 20,
|
|
"span_bytes": 102,
|
|
"non_contiguous": true,
|
|
"contains_entry": true
|
|
}
|
|
```
|
|
|
|
Each end is **inclusive**. `total_bytes` counts the complete observed
|
|
AddressSet; `span_bytes` measures its enclosing span and is null across address
|
|
spaces. Gaps and shared tails must not be treated as owned bytes. This is
|
|
Ghidra's database observation, not a proof of original source ownership or
|
|
exhaustive code discovery. Providers that do not report complete ranges return
|
|
`available: false` and an explicit reason.
|
|
|
|
## Loaded-image evidence
|
|
|
|
`inspect_native_load_image` independently checks the immutable session snapshot
|
|
against the loaded Ghidra Program. It measures complete original and modified
|
|
FileBytes digests, every source mapping and its memory digest, relocation records
|
|
(including multiplicity), and external entry points. It verifies MZ header and
|
|
module partitions, relocation fixups and linear coordinates rather than relying
|
|
on the imported executable SHA alone.
|
|
|
|
The result is `verified`, `mismatch`, or `unsupported`. Every check retains its
|
|
expected and observed values. A memory-digest mismatch identifies the source
|
|
range's file offset and analysis address; it does not locate the first differing
|
|
byte inside that range. Mapping ends are **inclusive**, unlike the exclusive ends
|
|
from `list_segments`. Appended overlays remain in source-file identity and are
|
|
excluded from initialized module coverage. Uninitialized allocations are reported;
|
|
their contents and DOS runtime semantics are not verified. Formats other than MZ and explicit COM return
|
|
`unsupported` with measured observations, not a successful MZ verification.
|
|
|
|
`read_bytes` returns initialized provider memory, including loader fixups; an
|
|
unmapped or uninitialized byte ends the read with an explicit returned length and
|
|
`complete: false`. `address_to_file_offset` uses the provider's source mapping;
|
|
unmapped, uninitialized and ambiguous mappings fail explicitly. A relocated word
|
|
can differ from the original file word even when its source offset is known.
|
|
|
|
## Coverage boundaries
|
|
|
|
Static decompilation does not execute the target or emulate BIOS, DOS
|
|
interrupts, device ports, or self-modifying code. PC-98 hardware behavior and
|
|
recovered calling conventions can remain uncertain; provider pseudocode and
|
|
diagnostics are retained for analyst review.
|
|
|
|
For a packed executable, importing the original can analyze its unpacking
|
|
stub. Analyze a separately prepared unpacked copy to inspect the original
|
|
program's recovered code. Retain both artifact digests and the transformation
|
|
evidence; REA never silently replaces the admitted target. Appended overlays
|
|
are separate from the initialized MZ module. Raw COM files and DOS extenders
|
|
without an admitted MZ real-mode entry are outside this import boundary.
|
|
|
|
DOS MZ is available through Ghidra, with no Hopper support claim. It is outside
|
|
the Windows native PE P0 boundary. macOS x64 DOS verification remains unverified.
|
|
|
|
## Real verification
|
|
|
|
```bash
|
|
npm run verify:ghidra:dos
|
|
```
|
|
|
|
The lane generates a small public fixture from source at runtime. It verifies
|
|
16-bit instruction bytes, a near call, a segment-relocated far call, actual
|
|
decompilation, disjoint function ranges, stable CLI/MCP observations, unchanged
|
|
input bytes, and session/process cleanup. It does not require external
|
|
executable fixtures, an emulator, or an existing analysis database.
|
|
|
|
Provider p-code representations can contain process-specific address-space selector
|
|
tokens. The lane preserves those reported values and compares stable dossier
|
|
observations rather than asserting cross-process identity of raw p-code.
|
|
|
|
## Function annotations
|
|
|
|
`annotate_native_function` changes the name and/or comments at one exact local function entry, verifies readback, and returns a refreshed dossier. This is useful when recording recovered DOS function roles; it does not claim original-source names. Name writes preserve the existing namespace; readback returns the fully qualified name used by function inventories. Regular comments map to Ghidra PRE comments and inline comments to EOL comments. Empty text clears a comment; omitted fields preserve it. The whole edit rolls back if a setter, readback or analysis fails.
|
|
|
|
```json
|
|
{
|
|
"procedure": "0x10100",
|
|
"name": "entry",
|
|
"comment": "Recovered startup role",
|
|
"inline_comment": "Inspect segment setup"
|
|
}
|
|
```
|
|
|
|
```bash
|
|
rea annotate-native-function /local/program.com 0x10100 --target-format dos-com --provider ghidra --name entry --comment 'Recovered startup role' --json
|
|
```
|
|
|
|
The CLI returns the updated analysis before closing its isolated session. MCP edits remain visible to later reads until `close_binary`. Original bytes remain unchanged, database edits are discarded on close, and immutable analysis snapshots are invalidated after edits. This operation does not save a Ghidra project, change ABI/types/body ranges, patch instructions, or emulate a DOS runtime. Windows P0 does not admit it.
|