Repository navigation
NE (16-bit Windows) executables through the Ghidra lane: I have a working fork prototype. Would you want it? #2231
CassieFox23
started this conversation in
Ideas
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
TL;DR: Today REA refuses 16-bit Windows (NE) programs, and the docs say that is on purpose. In my fork I made a small change that opens them through Ghidra, and it worked on one sample. I will not open a PR unless you want one, and a no is a fine answer.
Why I am asking
A user on REA's Discord wanted to look at a DOS-era program in NE format, the 16-bit Windows kind. REA turned it away. I filed the confusing error text as #1979, which is closed. The docs in
docs/ghidra-dos.mdsay "NE, LE, and LX declarations are explicitly unsupported", so I made no PR.Today versus the prototype
The sample is
binw/wzoom.exefrom Open Watcom v2 (Sybase Open Watcom Public License 1.0), a Windows 3.x program.open_binaryon wzoom.exetarget_unavailableneread_bytesat the first functiondosserv.exe)dos-mzdos-mzWhat it would cost you
The change adds a new
nevalue to several public format lists, including the tool output schemas. That is why I am asking first. NE addresses also differ from DOS. They read as selector then offset. So 1000:007c shows as 0x1000007c.Not run
The question
Would you take this as a PR? Would you want it shaped differently? Or would you rather leave NE refused? Any answer is fine with me, and I am happy to drop it.
Design and files changed
The branch is feat/ne-ghidra-lane in my fork. It holds two commits on base 83a4445 (#2034): 39c0a1a70 for code and tests (19 files) and 3155636e2 for docs (1 file). The branch is local only and not pushed.
How it works:
ne, with x86 architecture. Admission needs the full 64-byte NE header. A newparseNeRecordinsrc/domain/binaryTarget.tsdoes this. LE and LX still returnunsupported <sig> executable in MZ image.BinaryTargetResolver.tsreads 64 bytes at the NE header. So headers beyond the 4096-byte probe work. LE and LX are refused there too.neoption. It passes-loader NeLoader -processor "x86:LE:16:Protected Mode" -cspec default. It refuses NE together with PSP, MIPS soft-float, DOS, raw-binary or a language override.REA_GHIDRA_LANGUAGE_IDis rejected for NE. None of the DOS keys apply.docs/ghidra-dos.mdhas a new "NE executables" section.Files that gained the new
nevalue:src/domain/binaryTargetTypes.tssrc/domain/evidence.tssrc/domain/analysisSnapshot.tssrc/domain/artifactGraph.ts, in the target-format list onlysrc/contracts/toolOutputSchemaPrimitives.tsBinaryTargetResolver.tsTests added or changed:
src/domain/dosMz.test.tstests/boundary/filesystem/targetDiagnostics.test.tstests/boundary/filesystem/binaryTarget.test.tstests/boundary/providers/ghidra/ghidraLauncher.test.tssrc/ghidra/GhidraProvider.behavior.test.tssrc/hopper/HopperProvider.test.tstests/boundary/providers/hopper/hopperAnalysisProfile.test.tsLeft alone on purpose:
GhidraLoadImageAttest.tsandGhidraLoadImageValues.ts. NE falls through to the existingunsupportedresult.nativeLoadImage.ts.ReaGhidraBridge.java.unknownfor NE.GoPeImage.ts, which still refuses NE.verify-real-ghidra-dos.mjs.Calls and replies, trimmed
Over MCP stdio with
REA_ANALYSIS_PROVIDER=ghidra, on the branch:open_binary{"format":"ne","kind":"executable","architecture":"x86"}list_proceduresUSER::MESSAGEBOX.list_segmentsCode1at0x10000000to0x10004ff2procedure_pseudo_codeonFUN_1000_007cCREATEWINDOW(...),SHOWWINDOWandUPDATEWINDOWcallsread_bytesat0x10000000, 16 bytes5589e553515283ec1ac746e00000c746, the same 16 bytes I read from the file at offset 578address_to_file_offsetat0x10000000inspect_native_load_imageunsupported, with measured mappings. This is the intended result.The 217 local rows match the 217 functions that Ghidra headless found. Headless gave 217 with the forced NeLoader and with its own natural pick.
Two selectors appear among the local functions. The selector 0x1000 holds 131 functions. The selector 0x1078 holds the 86 thunks.
Refusals and the control:
unsupported LE executable in MZ image,category: unsupported_target,constraint: file_format_rejected.unsupported LX executable in MZ image.dosserv.exe: stilldos-mz, 94 procedures, as on main.Here is the refusal on main for the same file. I checked it on 2026-10-11, at main sha 1449e8e (#1910).
{"error":{"code":"target_unavailable","category":"unavailable","message":"Cannot open artifact: unsupported NE executable in MZ image. Check that the path exists, is readable, and points to a supported file.","retryable":false}}Checks with exit codes
Node 24.18.0, on the branch:
npm run typechecknpm run buildnpm run check:fastnpm run test:fastnpm run test:boundaryThe 2 failures are in files my diff does not touch. Both files sit under
tests/boundary.cliOptionAliases.test.tsfile fails. Two options oftrace-web-module-importshave no description. They are--import-map-pathand--import-map-base-url.referenceSourceReader.test.tsfile expectedignoredand thenkept. It got the other order. This may be directory listing order, but I did not verify that.The exit code of
npm ciwas not captured. The build ran fine afterwards.Ghidra headless on the same file exited 0. It printed "Using Loader: New Executable (NE)" and language
x86:LE:16:Protected Mode:default. It found 217 functions and 7,458 instructions.Test setup
🤖 Generated with Claude Code (Sonnet 5.5, verified by Opus 5.5) 🦊
All reactions