NE¶
Level: 201 · for anyone with a hex editor open
One line: A New Executable is reached through the MZ header's e_lfanew, stores its segment offsets in sectors whose size is a shift count in the header — so a 16-bit field reaches past 64 KB — and names its module and its exports as Pascal strings: a length byte, the bytes, and an ordinal, with no terminator anywhere.
What the loader reads¶
NE is the executable format of 16-bit Windows and OS/2 1.x, from 1985 to Windows 3.11, and of every .dll, .drv and .fon of that era. It sits behind an MZ header exactly as a PE does: e_lfanew at 0x3C points at the letters NE, and a 64-byte information block follows, all little-endian:
| offset | field | what it says |
|---|---|---|
| 0 | ne_magic |
4e 45, 0x454E little-endian |
| 2 | ne_ver, ne_rev |
linker version |
| 4 | ne_enttab, ne_cbenttab |
the entry table, offset and size |
| 12 | ne_flags |
program and application flags |
| 14 | ne_autodata, ne_heap, ne_stack |
the data segment, and how much heap and stack |
| 20 | ne_csip, ne_sssp |
the entry point and stack, as segment:offset |
| 28 | ne_cseg, ne_cmod, ne_cbnrestab |
counts: segments, imported modules, non-resident names |
| 34 | ne_segtab, ne_rsrctab, ne_restab, ne_modtab, ne_imptab |
offsets of five tables, from the NE header |
| 44 | ne_nrestab |
offset of the non-resident name table, from the file's start |
| 50 | ne_align |
the sector shift: a sector is 1 << ne_align bytes |
| 54 | ne_exetyp |
2 for Windows, 1 for OS/2 |
| 62 | ne_expver |
the Windows version expected, 0x030A for 3.10 |
The segment table has eight bytes per segment — its offset in sectors, its length, flags, and a minimum allocation — and the resident-name table is Pascal strings, each followed by a 16-bit ordinal.
In Python¶
Verified output of ne_py.py — regenerated by tools/run_examples.py, never hand-typed.
1. MZ FIRST, THEN e_lfanew, THEN NE
------------------------------------------------------------------------
offset 0 b'MZ'
offset 0x3c 80 00 00 00 e_lfanew = 0x80
offset 0x80 4e 45 b'NE' = 0x454e, IMAGE_NE_SIGNATURE
The same door PE walks through, ten years earlier: a DOS header,
a 32-bit offset at 0x3c, and a two-letter signature there.
2. THE INFORMATION BLOCK IS 64 BYTES
------------------------------------------------------------------------
ne_ver 0x0005 5
ne_rev 0x0001 1
ne_cseg 0x0002 2
ne_segtab 0x0040 64 offset of the segment table, from the NE header
ne_restab 0x0050 80 offset of the resident-name table
ne_align 0x0004 4 a shift count: one sector is 1 << 4 = 16 bytes
ne_exetyp 0x0002 2 Windows
ne_expver 0x030a 778 expected Windows 3.10
3. SEGMENT OFFSETS ARE IN SECTORS, AND THE SECTOR SIZE IS IN THE HEADER
------------------------------------------------------------------------
segment 1 sector 0x0010 length 0x0100 flags 0x0d00 CODE file offset 16 << 4 = 0x100 b8 00 4c cd 21
segment 2 sector 0x0030 length 0x0020 flags 0x0c41 DATA file offset 48 << 4 = 0x300 63 61 66 e9 00
A 16-bit sector number times a sector size reaches 1 MB with a
shift of 4 and 32 MB with a shift of 9, out of a 16-bit field. The
unit is in the file, unlike MZ's pages, but it is a power of two
written as its exponent, which is a third way to write a number.
4. A NAME IS A LENGTH BYTE, THE BYTES, AND AN ORDINAL
------------------------------------------------------------------------
05 48 45 4c 4c 4f 00 00 len 5 'HELLO' the module name (ordinal 0)
07 57 69 6e 4d 61 69 6e 01 00 len 7 'WinMain' an exported name, ordinal 1
06 53 61 79 43 61 66 02 00 len 6 'SayCaf' an exported name, ordinal 2
00 a zero length ends the table
No NUL after the name: the length byte is the delimiter, so a name
may hold any byte and can be at most 255 long. The first entry is
the module's own name, and each one carries an ordinal -- which is
the number an import can use instead of the name.
5. THE DATA SEGMENT IS BYTES IN THE CODE PAGE OF 1990
------------------------------------------------------------------------
bytes 63 61 66 e9 cp1252 'café' as UTF-8: not valid
A Windows 3.x program's strings are single-byte, in whatever ANSI
code page the machine ran, and nothing in the NE header records
which. 'e9' is é in cp1252 and an error in UTF-8.
A unit that is an exponent¶
Section 3 is the header's answer to the problem MZ solved with 512-byte pages: a 16-bit field cannot hold a file offset past 64 KB, so the segment table stores sectors and the header stores the sector size — not as a number but as a shift count, ne_align, so that a 4 means 16-byte sectors and a 9 means 512. The file offset is sector << ne_align, and a linker that needs a bigger file raises the shift. The unit is in the file, which MZ's is not; but it is written as its logarithm, which is a third way to write a number after decimal and hex, and one that a reader who takes it as a byte count gets wrong by a factor of thousands.
Pascal strings¶
Section 4: an NE name is a length byte, that many bytes, and — in the resident-name table — a 16-bit ordinal; no NUL. The first entry is the module's own name with ordinal 0, and each export after it carries the ordinal an importer may use instead of the name; a length byte of 0 ends the table. This is the string convention of Pascal and of the classic Mac, kept by OMF and PEF on this list and abandoned by PE, whose names are NUL-terminated in the import and export tables. A Pascal string can hold any byte and is at most 255 long; a C string can hold any byte but one and has no limit. Neither convention says which characters the bytes are.
The code page of 1990¶
Section 5: the data segment holds caf and e9, one byte per character, in whatever ANSI code page the machine ran — nothing in an NE header records it, because in 1990 a Windows machine had one code page and the question did not arise. Read today, e9 is é under Windows-1252 and a decoding error under UTF-8, which is Windows-1252 vs Latin-1 met in a binary, and the reason a resource string in an old .exe shows as caf� in a modern tool.
What Ghidra checks¶
NeLoader ↗ builds a NewExecutable, which reads the DOS header, follows e_lfanew, and constructs a WindowsHeader only if the two bytes there are 0x454E; the loader then offers the file with its ne_magic as the machine name, and the language has to have a segmented, 16-bit address space, as the MZ loader's does. Its name is New Executable (NE). The MZ loader declines a file this one accepts, so the two never compete.
If you are coming from Python or ABAP¶
Python. The information block is one struct call, '<HBBHHIBBHHHIIHHHHHHHHIHHHBBHHHH', and the program keeps the field names in a list beside it; a segment is '<HHHH' and its offset sector << shift. A Pascal string is data[pos + 1:pos + 1 + data[pos]], and the loop ends on a zero length byte — the same reading as an OMF name, with an ordinal after it. Decode the data segment with cp1252 and say so, because nothing in the file will.
ABAP. (Not machine-checked — CI cannot run ABAP.) A length byte in front of a field is what a SOLIX-style byte table or a BAPI binary parameter carries in its length column, and the transferable rule is that the length is in bytes and the field is x, never c: read it into an xstring first and decode it second, naming the code page — 1252 here — as SAP code pages insists, and verify the number against the system.
Try it¶
- Find any 16-bit Windows executable —
WINHELP.EXE, a.fonfile, an installer'sSETUP.EXEfrom the 1990s — andxxd -s 60 -l 4it, thenxxd -s <that> -l 2.NE. - Read
ne_alignat offset 50 of the NE header andne_segtabat 34. Dump the first segment entry and shift its sector. - Follow
ne_restaband read the module's name: a length byte, the bytes, two bytes of zero. strings -n 4on the same file and find a word with an accent. Then find its bytes and say what code page they are in.- Take a PE and an NE and diff their first 64 bytes. The DOS header is the same header.
See also¶
- MZ — the header in front, and the units it chose five years earlier
- PE — the format that replaced this one, with NUL-terminated names and no sectors
- OMF and PEF — the other two formats here with length-prefixed strings
- Windows-1252 vs Latin-1 — the code page an NE's strings are in, and the one they are not
- Which base did you mean? — a number written as its exponent is a base question in disguise
- New Executable format, Windows 3.x ↗ — the information block and the tables, as this page checked them