Skip to content

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

  1. Find any 16-bit Windows executable — WINHELP.EXE, a .fon file, an installer's SETUP.EXE from the 1990s — and xxd -s 60 -l 4 it, then xxd -s <that> -l 2. NE.
  2. Read ne_align at offset 50 of the NE header and ne_segtab at 34. Dump the first segment entry and shift its sector.
  3. Follow ne_restab and read the module's name: a length byte, the bytes, two bytes of zero.
  4. strings -n 4 on the same file and find a word with an accent. Then find its bytes and say what code page they are in.
  5. 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